microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/copilot

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/incremental.rs

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