microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.2.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/incremental.rs

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