microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cesarzc/ssa-panic

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/incremental.rs

405lines · modecode

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