19652ae5ad
Change-Id: Ibf0221a3c233c958e5065b64207faf9dfae33083 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/168260 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
104 lines
6.8 KiB
Markdown
104 lines
6.8 KiB
Markdown
# 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.
|