Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor the incremental compiler (#762)
The motivation for this refactoring is to be able to implement language service support for notebook cells. (You can preview how that's going to fit together at #759.) But it achieves more than that, by eliminating a lot of code duplicated between the batch compilation and incremental compilation paths, devising a coherent model for the incremental compiler, and clearly separating the evaluation parts of the `Interpreter` from the compilation parts. **1. New top-level nodes in the AST and HIR** Previously, the incremental compilation was made up of loose AST and HIR nodes stored in `stateful::Interpreter`, and an empty `CompileUnit` in the package store. This packages in this `CompileUnit` were not updated as fragments got compiled in. Every feature in the language service is implemented using an AST visitor and uses the HIR package for lookups. This imposes the requirement that notebook cells are able to be represented as AST and HIR packages. They should be kept up to date with each cell added to the compilation. Q# notebook cells were previously thought of as "fragments" (not "real" packages). This PR makes fragments syntax official, so to speak, by adding the ability for `ast::Package` and `hir::Package` to contain top-level statements. When we are able to represent each incremental unit (notebook cell) as a whole package, we can eliminate a lot of lowering/resolution/checking code that was designed to work with loose statement/item nodes (e.g. `check_stmt_fragment`).. We can now use their package counterparts (e.g. `check_package`), making for more code shared between the batch and incremental compilers. **2. Layering and `qsc::incremental`** Previously, the incremental compiler implementation was spread out between `stateful` and `qsc_frontend::incremental`. The `stateful::Interpret` struct contained too much state, making it really hard to write code that mutates logically separate parts of the struct (e.g. compiler state vs. evaluator state) without angering the Rust borrow checker. The new `qsc::incremental` module is mainly the compiler-y parts of `stateful` pulled out into their own module, bridging the gap between `stateful` and `qsc_frontend::incremental`. The layering is meant to parallel that of batch compilation: `qsi` -> `qsc::interpret::stateful` -> `qsc::incremental` -> `qsc_frontend::incremental` `qsc` -> `qsc::compiler` -> `qsc_frontend::compiler` **3. Mutable `CompileUnit` in the `PackageStore`** #675 required that the current compilation be available to be looked up in the `PackageStore`, since errors may contain the `PackageId` of the current package. But the current compilation also needs to be updated with incremental changes. Adding a `get_mut` to the `PackageStore` felt wrong since almost all use cases of the `PackageStore` require packages to remain immutable. To address the awkwardness, the concept of an `OpenPackageStore` is introduced. A `PackageStore` can be "opened" for modifications, making it possible to obtain a mutable reference to only the last `CompileUnit` in the store (the open package). The incremental compiler uses the open package store to keep updating the compilation, while the rest of the components (evaluator, error span lookups, etc) have access to the regular, immutable, `PackageStore` for lookups.
- Loading branch information