19895724dd
Change-Id: I75c4dca69a99c0129cc8ae6934d5925382228a00 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481542 Reviewed-by: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
157 lines
6.5 KiB
Markdown
157 lines
6.5 KiB
Markdown
# The Element Model
|
|
|
|
The element model, together with the [type][type] model, describes the semantic
|
|
(as opposed to syntactic) structure of Dart code. The syntactic structure of the
|
|
code is modeled by the [AST][ast].
|
|
|
|
Generally speaking, an element represents something that is declared in the
|
|
code, such as a class, method, or variable. Elements can be explicitly declared,
|
|
such as the class defined by a class declaration, or implicitly declared, such
|
|
as the default constructor defined for classes that do not have any explicit
|
|
constructor declarations. Elements that are implicitly declared are referred to
|
|
as _synthetic_ elements.
|
|
|
|
There are a few elements that represent entities that are not declared. For
|
|
example, there is an element representing a compilation unit (`.dart` file) and
|
|
another representing a library.
|
|
|
|
## The Structure of the Element Model
|
|
|
|
Elements are organized in a tree structure in which the children of an element
|
|
are the elements that are logically (and often syntactically) part of the
|
|
declaration of the parent. For example, the elements representing the methods
|
|
and fields in a class are children of the element representing the class.
|
|
|
|
Every complete element structure is rooted by an instance of the class
|
|
`LibraryElement`. A library element represents a single Dart library. Every
|
|
library is defined by one or more compilation units (the library and all of its
|
|
parts). The compilation units are represented by the class
|
|
`CompilationUnitElement` and are children of the library that is defined by
|
|
them. Each compilation unit can contain zero or more top-level declarations,
|
|
such as classes, functions, and variables. Each of these is in turn
|
|
represented as an element that is a child of the compilation unit. Classes
|
|
contain methods and fields, methods can contain local variables, etc.
|
|
|
|
The element model does not contain everything in the code, only those things
|
|
that are declared by the code. For example, it does not include any
|
|
representation of the statements in a method body, but if one of those
|
|
statements declares a local variable then the local variable will be represented
|
|
by an element.
|
|
|
|
## Getting a Compilation Unit Element
|
|
|
|
If you have followed the steps in [Performing Analysis][analysis], and you want
|
|
to get the compilation unit element for a file at a known `path`, then you can
|
|
ask the analysis session for the compilation unit representing that file.
|
|
|
|
```dart
|
|
void analyzeSingleFile(AnalysisSession session, String path) async {
|
|
var result = await session.getUnitElement(path);
|
|
if (result is UnitElementResult) {
|
|
LibraryFragment element = result.fragment;
|
|
}
|
|
}
|
|
```
|
|
|
|
(If you also need the resolved AST for the file, you can ask the session to
|
|
`getResolvedAst`, and the returned result will have the library element for the
|
|
library containing the compilation unit.)
|
|
|
|
## Traversing the Structure
|
|
|
|
There are two ways to traverse the structure of an AST: getters and visitors.
|
|
|
|
### Getters
|
|
|
|
Every element defines getters for accessing the parent and the children of that
|
|
element. Those getters can be used to traverse the structure, and are often the
|
|
most efficient way of doing so. For example, if you wanted to write a utility to
|
|
print the names of all of the members of each class in a given library,
|
|
it might look something like this:
|
|
|
|
```dart
|
|
void printMembers(LibraryElement libraryElement) {
|
|
for (ClassElement classElement in libraryElement.classes) {
|
|
print(classElement.name);
|
|
for (ConstructorElement constructorElement in classElement.constructors) {
|
|
if (constructorElement.isOriginDeclaration) {
|
|
print(' ${constructorElement.displayName}');
|
|
}
|
|
}
|
|
for (FieldElement fieldElement in classElement.fields) {
|
|
if (fieldElement.isOriginDeclaration) {
|
|
print(' ${fieldElement.name}');
|
|
}
|
|
}
|
|
for (GetterElement getterElement in classElement.getters) {
|
|
if (getterElement.isOriginDeclaration) {
|
|
print(' ${getterElement.name}');
|
|
}
|
|
}
|
|
for (SetterElement setterElement in classElement.setters) {
|
|
if (setterElement.isOriginDeclaration) {
|
|
print(' ${setterElement.name}');
|
|
}
|
|
}
|
|
for (MethodElement methodElement in classElement.methods) {
|
|
if (methodElement.isOriginDeclaration) {
|
|
print(' ${methodElement.name}');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Visitors
|
|
|
|
Getters work well for most uses, but there might be times when it is easier to
|
|
use a visitor pattern.
|
|
|
|
Getters work well for cases like the above because compilation units cannot be
|
|
nested inside other compilation units, classes cannot be nested inside other
|
|
classes, etc. But when you're dealing with a structure that can be nested inside
|
|
similar structures (such as functions), then nested loops don't work as well.
|
|
For those cases, the analyzer package provides a visitor pattern.
|
|
|
|
There is a single visitor API, defined by the abstract class `ElementVisitor`.
|
|
It defines a separate visit method for each class of element. For example, the
|
|
method `visitClassElement` is used to visit a `ClassElement`. If you ask an
|
|
element to accept a visitor, it will invoke the corresponding method on the
|
|
visitor interface.
|
|
|
|
If you want to define a visitor, you'll probably want to subclass one of the
|
|
concrete implementations of `ElementVisitor`. The concrete subclasses are
|
|
defined in `package:analyzer/dart/element/visitor.dart`. A couple of the most
|
|
useful include
|
|
- `SimpleElementVisitor` which implements every visit method by doing nothing,
|
|
- `RecursiveElementVisitor` which will cause every element in a structure to be
|
|
visited, and
|
|
- `GeneralizingElementVisitor` which makes it easy to visit kinds of nodes, such
|
|
as visiting any executable element (method, function, accessor, or
|
|
constructor).
|
|
|
|
As an example, let's assume you want to write some code to compute the largest
|
|
number of parameters defined for any function or method in a given structure.
|
|
You need to visit every element because functions can be nested inside
|
|
functions. But because methods and functions are represented by different
|
|
classes of elements, it would be useful to use a visitor that will generalize
|
|
both to allow you to just visit executable elements (those that have
|
|
parameters). Hence, you'd want to create a subclass of
|
|
`GeneralizingElementVisitor`.
|
|
|
|
```dart
|
|
class ParameterCounter extends GeneralizingElementVisitor2<void> {
|
|
int maxParameterCount = 0;
|
|
|
|
@override
|
|
void visitExecutableElement(ExecutableElement element) {
|
|
maxParameterCount = math.max(maxParameterCount, element.formalParameters.length);
|
|
super.visitExecutableElement(element);
|
|
}
|
|
}
|
|
```
|
|
|
|
[analysis]: analysis.md
|
|
[ast]: ast.md
|
|
[type]: type.md
|