microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.0.33

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_frontend/src/incremental.rs

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