| # Summaries in Dart analyzer |
| |
| The purpose of this document is to provide a high-level overview of the summaries linking, storage format, and reading |
| in Dart analyzer. |
| |
| ## Design Considerations |
| |
| There are a couple of considerations to keep in mind when discussing the design. |
| |
| **We want the linking to be done using AST.** Default values, constructor initializers, and field initializers should be |
| stored in their resolved state, with elements and type, because we need them to perform constant evaluation. |
| |
| **We want separation between AST and resolution.** Files change rarely, usually the user changes just one file, and this |
| affects resolution of this file and a multiple other files. But only the resolution, not AST. So, we want to keep pieces |
| that are not affected by the change. |
| |
| **We want to read only as much as necessary to do resolution.** Libraries often have many classes, but when we resolve a |
| file, we don’t need all these classes, only those that are actually referenced. So, we want the format to support |
| loading individual classes, functions, etc. |
| |
| ## High level view |
| |
| When the analyzer needs to resolve a file, it works in the following way: |
| |
| 1. Find the library cycle that contains this file, and library cycles of its dependencies, down to SDK. |
| |
| 2. Link these library cycles from SDK up to the target cycle. |
| |
| 3. Add linked bundles to `LinkedElementFactory`. |
| |
| 4. When we need a specific element, we call `LinkedElementFactory.elementOfReference`. It will request parent elements, |
| and eventually load the containing library, the containing class, method, etc. |
| |
| ## Lazy loading |
| |
| We try to load only libraries that are necessary. When a `PackageBundleReader` is created, it decodes only the header of |
| the bundle - with the list of library URIs contained in the bundle, and creates `LibraryReader`s. |
| |
| When `LinkedElementFactory.createLibraryElementForReading` is invoked, the corresponding `LibraryReader` is asked to |
| load units, which are `UnitReader`s. Each `UnitReader` keeps track of the location of its AST and resolution portions |
| in `astReader` and `resolutionReader`. When `UnitReader` is created, it reads the index of top-level declarations, and |
| fills `Reference` subtree. For each `Reference` we set its `nodeAccessor`, a pointer to the `_UnitMemberReader`, which |
| can be used to load the corresponding AST node. This is a way to present the index of the unit to `LinkedElementFactory` |
| . |
| |
| To support lazy loading the package bundle has the index of libraries, the library has the index of units, the unit has |
| the index of top-level declarations, the class / extension / mixin has the index of members. |
| |
| Any time when we need the element for a `Reference`, we call `elementOfReference` of `LinkedElementFactory`, which |
| asks `Reference.nodeAccessor` for the AST node, and then creates the corresponding `Element` wrapper around it, and |
| stores into `Reference.element`. For example for `ClassDeclaration` it will create `ClassElement`. No resolution is |
| required yet. |
| |
| When `Reference.element` is already set, we just return it, we have already done loading AST node and creating the |
| element for this `Reference`. |
| |
| When we link libraries, we don’t use `LinkedElementFactory` to read nodes and create elements, because we already have |
| full ASTs, and we can create elements for all nodes in advance, and put them into corresponding `Reference` children. |
| |
| We say that we need the element for a `Reference` because resolution stores elements as such references - a pair of the |
| name, and the parent reference. So, we may have an empty `Reference` first, without `element` and `nodeAccessor`, then |
| when `elementOfReference` is invoked, it will fill both for a `Reference`. But its siblings will stay unfilled. |
| |
| When we ask an `Element` anything that requires resolution, we apply resolution to the whole AST node of the element. |
| For example, when we ask `ClassElement` for `supertype`, we apply resolution to the `ClassDeclaration` header (but not |
| to any member of the `ClassDeclaration`) - supertype, mixins, interfaces. We do this by calling `applyResolution` |
| of `AstLinkedContext`. We get `AstLinkedContext` from the node, which implements `HasAstLinkedContext`. |
| |
| `AstBinaryWriter` writes two streams of information at once - the AST itself, and resolution. In the future we might |
| split writing AST and resolution into separate writers. |
| |
| Resolution information is just a sequence of elements and types, which is stored when we visit the resolved AST |
| in `AstBinaryWriter`. We use the helper `_ResolutionSink` to encode elements and types into bytes. |
| |
| Resolution is applied to unresolved AST using `ApplyResolutionVisitor`, which visits AST nodes in the same sequence |
| as `AstBinaryWriter`, and takes either elements or types from the same (untyped, unmarked in any way) stream of |
| resolution bytes. `LinkedResolutionReader` corresponds to `_ResolutionSink` - it decodes elements and types from bytes. |
| |
| Each raw element is represented by an integer, an index in the reference table. This table is collected during writing |
| in `_BundleWriterReferences`, and stored by `BundleWriterResolution` during `BundleWriter.finish()`. During |
| loading `BundleReader` creates `_ReferenceReader`, which lazily converts names and parent references into `Reference` |
| instances in the given `LinkedElementFactory` (from which we just take `rootReference`, not actually any elements). Once |
| we have `Reference` for the element that we need to decode, we actually ask `LinkedResolutionReader` for the element, |
| see above. |
| |
| In addition to raw elements, there are “members” (which is not the best name) - which might be `Element`s which we want |
| to convert to legacy because they are declared in a null safe library, but we want their legacy types; or actually |
| members of a class with type parameters and with some `Substitution` applied to them; or both. |
| |
| Strings are encoded as integers, during writing using `StringIndexer`, and during loading using `_StringTable`. We |
| use `SummaryDataReader` to load primitive types, and also strings. |
| |
| ## Known limitations |
| |
| Currently `LibraryScope` and its basis `_LibraryImportScope` and `PrefixScope` - they all work by asking all elements |
| from the imported libraries. Which means that we load all top-level nodes of these libraries (and all libraries that |
| they export). Fortunately we don’t apply resolution to these elements until we try to access some property of these |
| elements, e.g. the return type of a getter. But still, we probably don’t actually use all the imported elements, and we |
| potentially could avoid loading all these AST nodes. A solution could be to work with `Reference`s instead. |
| |
| Similarly `InheritanceManager3` builds the whole interface of a class, and loads all members of the class and all |
| members of all its superinterfaces. But again, we might only call a few methods, and might not need any superinterfaces. |
| A solution might be to fill class interfaces on demand. |