microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/merge-mines-changes

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc/src/incremental.rs

327lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::sync::Arc;
5
6use crate::compile::{self, compile};
7use miette::Diagnostic;
8
9use qsc_ast::ast;
10use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
11
12use qsc_frontend::{
13 compile::{Dependencies, OpenPackageStore, PackageStore, SourceMap},
14 error::WithSource,
15 incremental::Increment,
16};
17use qsc_hir::hir::PackageId;
18use qsc_passes::{PackageType, PassContext};
19
20/// An incremental Q# compiler.
21pub struct Compiler {
22 /// A package store that contains the current, mutable, `CompileUnit`
23 /// as well as all its immutable dependencies.
24 store: OpenPackageStore,
25 /// The ID of the source package. The source package
26 /// is made up of the initial sources passed in when creating the compiler.
27 source_package_id: PackageId,
28 dependencies: Vec<(PackageId, Option<Arc<str>>)>,
29 /// Context for passes that is reused across incremental compilations.
30 passes: PassContext,
31 /// The frontend incremental compiler.
32 frontend: qsc_frontend::incremental::Compiler,
33}
34
35/// An incremental compiler error.
36pub type Errors = Vec<compile::Error>;
37
38impl Compiler {
39 /// Creates a new incremental compiler, compiling the passed in sources.
40 /// # Errors
41 /// If compiling the sources fails, compiler errors are returned.
42 pub fn new(
43 sources: SourceMap,
44 package_type: PackageType,
45 capabilities: TargetCapabilityFlags,
46 language_features: LanguageFeatures,
47 mut store: PackageStore,
48 dependencies: &Dependencies,
49 ) -> Result<Self, Errors> {
50 let (mut unit, errors) = compile(
51 &store,
52 dependencies,
53 sources,
54 package_type,
55 capabilities,
56 language_features,
57 );
58 if !errors.is_empty() {
59 return Err(errors);
60 }
61
62 // make the user code fully public, so increments on top of this can access them
63 unit.expose();
64
65 let mut dependencies = dependencies.iter().map(Clone::clone).collect::<Vec<_>>();
66
67 let source_package_id = store.insert(unit);
68 dependencies.push((source_package_id, None));
69
70 let frontend = qsc_frontend::incremental::Compiler::new(
71 &store,
72 &dependencies[..],
73 capabilities,
74 language_features,
75 );
76 let store = store.open();
77
78 Ok(Self {
79 store,
80 source_package_id,
81 dependencies,
82 frontend,
83 passes: PassContext::default(),
84 })
85 }
86
87 pub fn from(
88 store: PackageStore,
89 source_package_id: PackageId,
90 dependencies: Vec<(PackageId, Option<Arc<str>>)>,
91 capabilities: TargetCapabilityFlags,
92 language_features: LanguageFeatures,
93 ) -> Result<Self, Errors> {
94 let frontend =
95 qsc_frontend::incremental::Compiler::new(&store, &[], capabilities, language_features);
96 let store = store.open();
97
98 Ok(Self {
99 store,
100 source_package_id,
101 dependencies,
102 frontend,
103 passes: PassContext::default(),
104 })
105 }
106
107 /// Compiles Q# fragments. Fragments are Q# code that can contain
108 /// top-level statements as well as namespaces. A notebook cell
109 /// or an interpreter entry is an example of fragments.
110 ///
111 /// This method returns the AST and HIR packages that were created as a result of
112 /// the compilation, however it does *not* update the current compilation.
113 ///
114 /// The caller can use the returned packages to perform passes,
115 /// get information about the newly added items, or do other modifications.
116 /// It is then the caller's responsibility to merge
117 /// these packages into the current `CompileUnit` using the `update()` method.
118 pub fn compile_fragments_fail_fast(
119 &mut self,
120 source_name: &str,
121 source_contents: &str,
122 ) -> Result<Increment, Errors> {
123 self.compile_fragments(source_name, source_contents, fail_on_error)
124 }
125
126 /// Compiles Q# ast fragments. Fragments are Q# code that can contain
127 /// top-level statements as well as namespaces. A notebook cell
128 /// or an interpreter entry is an example of fragments.
129 ///
130 /// This method returns the AST and HIR packages that were created as a result of
131 /// the compilation, however it does *not* update the current compilation.
132 ///
133 /// The caller can use the returned packages to perform passes,
134 /// get information about the newly added items, or do other modifications.
135 /// It is then the caller's responsibility to merge
136 /// these packages into the current `CompileUnit` using the `update()` method.
137 pub fn compile_ast_fragments_fail_fast(
138 &mut self,
139 source_name: &str,
140 source_contents: &str,
141 package: ast::Package,
142 ) -> Result<Increment, Errors> {
143 self.compile_ast_fragments(source_name, source_contents, package, fail_on_error)
144 }
145
146 /// Compiles Q# fragments. See [`compile_fragments_fail_fast`] for more details.
147 ///
148 /// This method calls an accumulator function with any errors returned
149 /// from each of the stages (parsing, lowering).
150 /// If the accumulator succeeds, compilation continues.
151 /// If the accumulator returns an error, compilation stops and the
152 /// error is returned to the caller.
153 pub fn compile_fragments<F>(
154 &mut self,
155 source_name: &str,
156 source_contents: &str,
157 mut accumulate_errors: F,
158 ) -> Result<Increment, Errors>
159 where
160 F: FnMut(Errors) -> Result<(), Errors>,
161 {
162 let (core, unit) = self.store.get_open_mut();
163
164 let mut errors = false;
165 let mut increment =
166 self.frontend
167 .compile_fragments(unit, source_name, source_contents, |e| {
168 errors = errors || !e.is_empty();
169 accumulate_errors(into_errors(e))
170 })?;
171
172 // Even if we don't fail fast, skip passes if there were compilation errors.
173 if !errors {
174 let pass_errors = self.passes.run_default_passes(
175 &mut increment.hir,
176 &mut unit.assigner,
177 core,
178 PackageType::Lib,
179 );
180
181 accumulate_errors(into_errors_with_source(pass_errors, &unit.sources))?;
182 }
183
184 Ok(increment)
185 }
186
187 /// Compiles Q# ast fragments. See [`compile_ast_fragments_fail_fast`] for more details.
188 ///
189 /// This method calls an accumulator function with any errors returned
190 /// from each of the stages (parsing, lowering).
191 /// If the accumulator succeeds, compilation continues.
192 /// If the accumulator returns an error, compilation stops and the
193 /// error is returned to the caller.
194 pub fn compile_ast_fragments<F>(
195 &mut self,
196 source_name: &str,
197 source_contents: &str,
198 package: ast::Package,
199 mut accumulate_errors: F,
200 ) -> Result<Increment, Errors>
201 where
202 F: FnMut(Errors) -> Result<(), Errors>,
203 {
204 let (core, unit) = self.store.get_open_mut();
205
206 let mut errors = false;
207 let mut increment = self.frontend.compile_ast_fragments(
208 unit,
209 source_name,
210 source_contents,
211 package,
212 |e| {
213 errors = errors || !e.is_empty();
214 accumulate_errors(into_errors(e))
215 },
216 )?;
217
218 // Even if we don't fail fast, skip passes if there were compilation errors.
219 if !errors {
220 let pass_errors = self.passes.run_default_passes(
221 &mut increment.hir,
222 &mut unit.assigner,
223 core,
224 PackageType::Lib,
225 );
226
227 accumulate_errors(into_errors_with_source(pass_errors, &unit.sources))?;
228 }
229
230 Ok(increment)
231 }
232
233 /// Compiles an entry expression.
234 ///
235 /// This method returns the AST and HIR packages that were created as a result of
236 /// the compilation, however it does *not* update the current compilation.
237 ///
238 /// The caller can use the returned packages to perform passes,
239 /// get information about the newly added items, or do other modifications.
240 /// It is then the caller's responsibility to merge
241 /// these packages into the current `CompileUnit` using the `update()` method.
242 pub fn compile_entry_expr(&mut self, expr: &str) -> Result<Increment, Errors> {
243 let (core, unit) = self.store.get_open_mut();
244
245 let mut increment = self
246 .frontend
247 .compile_entry_expr(unit, expr)
248 .map_err(into_errors)?;
249
250 let pass_errors = self.passes.run_default_passes(
251 &mut increment.hir,
252 &mut unit.assigner,
253 core,
254 PackageType::Lib,
255 );
256
257 if !pass_errors.is_empty() {
258 return Err(into_errors_with_source(pass_errors, &unit.sources));
259 }
260
261 Ok(increment)
262 }
263
264 /// Updates the current compilation with the AST and HIR packages,
265 /// and any associated context, returned from a previous incremental compilation.
266 /// Entry expressions are ignored.
267 pub fn update(&mut self, new: Increment) {
268 let (_, unit) = self.store.get_open_mut();
269
270 self.frontend.update(unit, new);
271 }
272
273 /// Returns a reference to the underlying package store.
274 #[must_use]
275 pub fn package_store(&self) -> &PackageStore {
276 self.store.package_store()
277 }
278
279 /// Returns ID of the current `CompileUnit`.
280 #[must_use]
281 pub fn package_id(&self) -> PackageId {
282 self.store.open_package_id()
283 }
284
285 /// Returns the ID of the source package created from the sources
286 /// passed in during inital creation.
287 #[must_use]
288 pub fn source_package_id(&self) -> PackageId {
289 self.source_package_id
290 }
291
292 /// Consumes the incremental compiler and returns an immutable package store.
293 /// This method can be used to finalize the compilation.
294 #[must_use]
295 pub fn into_package_store(self) -> (PackageStore, PackageId, Vec<(PackageId, Option<Arc<str>>)>) {
296 let t = self.store.into_package_store();
297 (t.0, t.1, self.dependencies)
298 }
299}
300
301fn into_errors_with_source<T>(errors: Vec<T>, sources: &SourceMap) -> Errors
302where
303 compile::ErrorKind: From<T>,
304{
305 errors
306 .into_iter()
307 .map(|e| WithSource::from_map(sources, e.into()))
308 .collect()
309}
310
311fn into_errors<T>(errors: Vec<WithSource<T>>) -> Errors
312where
313 compile::ErrorKind: From<T>,
314 T: Diagnostic + Send + Sync,
315{
316 errors
317 .into_iter()
318 .map(qsc_frontend::error::WithSource::into_with_source)
319 .collect()
320}
321
322fn fail_on_error(errors: Errors) -> Result<(), Errors> {
323 if !errors.is_empty() {
324 return Err(errors);
325 }
326 Ok(())
327}
328