microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/pythontelem

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/incremental.rs

433lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::{
8 compile::{
9 self, preprocess, AstPackage, CompileUnit, Dependencies, Offsetter, PackageStore, SourceMap,
10 },
11 error::WithSource,
12 lower::Lowerer,
13 resolve::{self, Resolver},
14 typeck::{self, Checker},
15};
16use qsc_ast::{
17 assigner::Assigner as AstAssigner,
18 ast::{self},
19 mut_visit::MutVisitor,
20 validate::Validator as AstValidator,
21 visit::Visitor as AstVisitor,
22};
23use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
24use qsc_hir::{
25 assigner::Assigner as HirAssigner,
26 hir::{self, PackageId},
27 validate::Validator as HirValidator,
28 visit::Visitor as HirVisitor,
29};
30use std::mem::take;
31
32/// The frontend for an incremental compiler.
33/// It is used to update a single `CompileUnit`
34/// with additional sources.
35pub struct Compiler {
36 ast_assigner: AstAssigner,
37 resolver: Resolver,
38 checker: Checker,
39 lowerer: Lowerer,
40 capabilities: TargetCapabilityFlags,
41 language_features: LanguageFeatures,
42}
43
44pub type Error = WithSource<compile::Error>;
45
46/// The result of an incremental compilation.
47/// These packages can be merged into the original
48/// `CompileUnit` that was used for the incremental compilation.
49#[derive(Debug)]
50pub struct Increment {
51 pub ast: AstPackage,
52 pub hir: hir::Package,
53}
54
55impl Increment {
56 pub fn clear_entry(&mut self) {
57 self.hir.entry = None;
58 }
59}
60
61impl Compiler {
62 /// Creates a new compiler.
63 pub fn new(
64 store: &PackageStore,
65 dependencies: &Dependencies,
66 capabilities: TargetCapabilityFlags,
67 language_features: LanguageFeatures,
68 ) -> Self {
69 let mut resolve_globals = resolve::GlobalTable::new();
70 let mut typeck_globals = typeck::GlobalTable::new();
71 let mut dropped_names = Vec::new();
72 if let Some(unit) = store.get(PackageId::CORE) {
73 resolve_globals
74 .add_external_package(PackageId::CORE, &unit.package, store, &None)
75 .expect("CORE is added before all other packages and can not clobber anything");
76 typeck_globals.add_external_package(PackageId::CORE, &unit.package, store);
77 dropped_names.extend(unit.dropped_names.iter().cloned());
78 }
79
80 let mut errors: Vec<resolve::Error> = Vec::new();
81 for (id, alias) in dependencies {
82 let unit = store
83 .get(*id)
84 .expect("dependency should be added to package store before compilation");
85 if let Err(mut errs) =
86 resolve_globals.add_external_package(*id, &unit.package, store, alias)
87 {
88 errors.append(&mut errs);
89 };
90 typeck_globals.add_external_package(*id, &unit.package, store);
91 dropped_names.extend(unit.dropped_names.iter().cloned());
92 }
93
94 Self {
95 ast_assigner: AstAssigner::new(),
96 resolver: Resolver::with_persistent_local_scope(resolve_globals, dropped_names)
97 .with_errors(errors),
98 checker: Checker::new(typeck_globals),
99 lowerer: Lowerer::new(),
100 capabilities,
101 language_features,
102 }
103 }
104
105 /// Compiles Q# fragments.
106 ///
107 /// Uses the assigners and other mutable state from the passed in
108 /// `CompileUnit` to guarantee uniqueness, however does not
109 /// update the `CompileUnit` with the resulting AST and HIR packages.
110 ///
111 /// The caller can use the returned packages to perform passes,
112 /// get information about the newly added items, or do other modifications.
113 /// It is then the caller's responsibility to merge
114 /// these packages into the current `CompileUnit`.
115 ///
116 /// This method calls an accumulator function with any errors returned
117 /// from each of the stages (parsing, lowering), instead of failing.
118 /// If the accumulator succeeds, compilation continues.
119 /// If the accumulator returns an error, compilation stops and the
120 /// error is returned to the caller.
121 pub fn compile_fragments<F, E>(
122 &mut self,
123 unit: &mut CompileUnit,
124 source_name: &str,
125 source_contents: &str,
126 accumulate_errors: F,
127 ) -> Result<Increment, E>
128 where
129 F: FnMut(Vec<Error>) -> Result<(), E>,
130 {
131 let (ast, parse_errors) = Self::parse_fragments(
132 &mut unit.sources,
133 source_name,
134 source_contents,
135 self.language_features,
136 );
137
138 self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors)
139 }
140
141 /// Compiles Q# AST fragments.
142 ///
143 /// Uses the assigners and other mutable state from the passed in
144 /// `CompileUnit` to guarantee uniqueness, however does not
145 /// update the `CompileUnit` with the resulting AST and HIR packages.
146 ///
147 /// The caller can use the returned packages to perform passes,
148 /// get information about the newly added items, or do other modifications.
149 /// It is then the caller's responsibility to merge
150 /// these packages into the current `CompileUnit`.
151 ///
152 /// This method calls an accumulator function with any errors returned
153 /// from each of the stages instead of failing.
154 /// If the accumulator succeeds, compilation continues.
155 /// If the accumulator returns an error, compilation stops and the
156 /// error is returned to the caller.
157 pub fn compile_ast_fragments<F, E>(
158 &mut self,
159 unit: &mut CompileUnit,
160 source_name: &str,
161 source_contents: &str,
162 package: ast::Package,
163 accumulate_errors: F,
164 ) -> Result<Increment, E>
165 where
166 F: FnMut(Vec<Error>) -> Result<(), E>,
167 {
168 // Update the AST with source information offset from the current source map.
169 let (ast, parse_errors) = Self::offset_ast_fragments(
170 &mut unit.sources,
171 source_name,
172 source_contents,
173 package,
174 vec![],
175 );
176
177 self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors)
178 }
179
180 fn compile_fragments_internal<F, E>(
181 &mut self,
182 unit: &mut CompileUnit,
183 mut ast: ast::Package,
184 parse_errors: Vec<Error>,
185 mut accumulate_errors: F,
186 ) -> Result<Increment, E>
187 where
188 F: FnMut(Vec<Error>) -> Result<(), E>,
189 {
190 accumulate_errors(parse_errors)?;
191
192 let (hir, errors) = self.resolve_check_lower(unit, &mut ast);
193
194 accumulate_errors(errors)?;
195
196 Ok(Increment {
197 ast: AstPackage {
198 package: ast,
199 names: self.resolver.names().clone(),
200 locals: self.resolver.locals().clone(),
201 tys: self.checker.table().clone(),
202 },
203 hir,
204 })
205 }
206
207 /// Compiles an entry expression.
208 ///
209 /// Uses the assigners and other mutable state from the passed in
210 /// `CompileUnit` to guarantee uniqueness, however does not
211 /// update the `CompileUnit` with the resulting AST and HIR packages.
212 ///
213 /// The caller can use the returned packages to perform passes,
214 /// get information about the newly added items, or do other modifications.
215 /// It is then the caller's responsibility to merge
216 /// these packages into the current `CompileUnit`.
217 pub fn compile_entry_expr(
218 &mut self,
219 unit: &mut CompileUnit,
220 source_contents: &str,
221 ) -> Result<Increment, Vec<Error>> {
222 let (mut ast, parse_errors) =
223 Self::parse_entry_expr(&mut unit.sources, source_contents, self.language_features);
224
225 if !parse_errors.is_empty() {
226 return Err(parse_errors);
227 }
228
229 let (hir, errors) = self.resolve_check_lower(unit, &mut ast);
230
231 if !errors.is_empty() {
232 return Err(errors);
233 }
234
235 Ok(Increment {
236 ast: AstPackage {
237 package: ast,
238 names: self.resolver.names().clone(),
239 locals: self.resolver.locals().clone(),
240 tys: self.checker.table().clone(),
241 },
242 hir,
243 })
244 }
245
246 pub fn update(&mut self, unit: &mut CompileUnit, new: Increment) {
247 // Update the AST
248 unit.ast.package = self.concat_ast(take(&mut unit.ast.package), new.ast.package);
249
250 // The new `Increment` will contain the names and tys
251 // from the original package as well, so just
252 // replace the current tables instead of extending.
253 unit.ast.names = new.ast.names;
254 unit.ast.tys = new.ast.tys;
255 unit.ast.locals = new.ast.locals;
256
257 // Update the HIR
258 extend_hir(&mut unit.package, new.hir);
259 }
260
261 fn resolve_check_lower(
262 &mut self,
263 unit: &mut CompileUnit,
264 ast: &mut ast::Package,
265 ) -> (hir::Package, Vec<Error>) {
266 let mut cond_compile = preprocess::Conditional::new(self.capabilities);
267 cond_compile.visit_package(ast);
268
269 self.ast_assigner.visit_package(ast);
270
271 self.resolver
272 .extend_dropped_names(cond_compile.into_names());
273 self.resolver.bind_fragments(ast, &mut unit.assigner);
274 self.resolver.bind_and_resolve_imports_and_exports(ast);
275 self.resolver.with(&mut unit.assigner).visit_package(ast);
276
277 self.checker.check_package(self.resolver.names(), ast);
278 self.checker.solve(self.resolver.names());
279
280 let package = self.lower(
281 &mut unit.assigner,
282 &*ast,
283 // not an ideal clone, but it is once per fragment, and the namespace tree is
284 // relatively lightweight
285 self.resolver.namespaces().clone(),
286 );
287
288 let errors = self
289 .resolver
290 .drain_errors()
291 .map(|e| compile::Error(e.into()))
292 .chain(
293 self.checker
294 .drain_errors()
295 .map(|e| compile::Error(e.into())),
296 )
297 .chain(
298 self.lowerer
299 .drain_errors()
300 .map(|e| compile::Error(e.into())),
301 )
302 .map(|e| WithSource::from_map(&unit.sources, e))
303 .collect::<Vec<_>>();
304
305 if !errors.is_empty() {
306 self.lowerer.clear_items();
307 }
308
309 (package, errors)
310 }
311
312 /// Creates a new `Package` by combining two packages.
313 /// The two packages should not contain any conflicting `NodeId`s.
314 /// Entry expressions are ignored.
315 #[must_use]
316 fn concat_ast(&mut self, mut left: ast::Package, right: ast::Package) -> ast::Package {
317 let mut nodes = Vec::with_capacity(left.nodes.len() + right.nodes.len());
318 nodes.extend(left.nodes.into_vec());
319 nodes.extend(right.nodes.into_vec());
320 left.id = self.ast_assigner.next_id();
321 left.nodes = nodes.into_boxed_slice();
322
323 AstValidator::default().visit_package(&left);
324 left
325 }
326
327 fn parse_entry_expr(
328 sources: &mut SourceMap,
329 source_contents: &str,
330 language_features: LanguageFeatures,
331 ) -> (ast::Package, Vec<Error>) {
332 let offset = sources.push("<entry>".into(), source_contents.into());
333
334 let (mut expr, errors) = qsc_parse::expr(source_contents, language_features);
335
336 let mut offsetter = Offsetter(offset);
337 offsetter.visit_expr(&mut expr);
338
339 let package = ast::Package {
340 id: ast::NodeId::default(),
341 nodes: Box::default(),
342 entry: Some(expr),
343 };
344
345 (package, with_source(errors, sources, offset))
346 }
347
348 fn parse_fragments(
349 sources: &mut SourceMap,
350 source_name: &str,
351 source_contents: &str,
352 features: LanguageFeatures,
353 ) -> (ast::Package, Vec<Error>) {
354 let offset = sources.push(source_name.into(), source_contents.into());
355 let (mut top_level_nodes, errors) = qsc_parse::top_level_nodes(source_contents, features);
356 let mut offsetter = Offsetter(offset);
357 for node in &mut top_level_nodes {
358 match node {
359 ast::TopLevelNode::Namespace(ns) => offsetter.visit_namespace(ns),
360 ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt),
361 }
362 }
363 let package = ast::Package {
364 id: ast::NodeId::default(),
365 nodes: top_level_nodes.into_boxed_slice(),
366 entry: None,
367 };
368 (package, with_source(errors, sources, offset))
369 }
370
371 /// offset all top level nodes based on the source input
372 /// and return the updated package and errors
373 fn offset_ast_fragments(
374 sources: &mut SourceMap,
375 source_name: &str,
376 source_contents: &str,
377 mut package: ast::Package,
378 errors: Vec<qsc_parse::Error>,
379 ) -> (ast::Package, Vec<Error>) {
380 let offset = sources.push(source_name.into(), source_contents.into());
381
382 let mut offsetter = Offsetter(offset);
383 for node in &mut *package.nodes {
384 match node {
385 ast::TopLevelNode::Namespace(ns) => offsetter.visit_namespace(ns),
386 ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt),
387 }
388 }
389
390 (package, with_source(errors, sources, offset))
391 }
392
393 fn lower(
394 &mut self,
395 hir_assigner: &mut HirAssigner,
396 package: &ast::Package,
397 namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot,
398 ) -> hir::Package {
399 self.lowerer
400 .with(hir_assigner, self.resolver.names(), self.checker.table())
401 .lower_package(package, namespaces)
402 }
403}
404
405/// Extends the `Package` with the contents of another `Package`.
406/// `other` should not contain any `LocalItemId`s
407/// that conflict with the current `Package`.
408/// The entry expression from `other` will be ignored.
409fn extend_hir(this: &mut hir::Package, mut other: hir::Package) {
410 for (k, v) in other.items.drain() {
411 this.items.insert(k, v);
412 }
413
414 this.stmts.extend(other.stmts);
415
416 HirValidator::default().visit_package(this);
417}
418
419fn with_source(
420 errors: Vec<qsc_parse::Error>,
421 sources: &SourceMap,
422 offset: u32,
423) -> Vec<WithSource<compile::Error>> {
424 errors
425 .into_iter()
426 .map(|e| {
427 WithSource::from_map(
428 sources,
429 compile::Error(compile::ErrorKind::Parse(e.with_offset(offset))),
430 )
431 })
432 .collect()
433}
434