microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_frontend/src/incremental.rs

423lines · modecode

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