2da0b9f4f1
Closes #34738 https://github.com/dart-lang/sdk/pull/34738 GitOrigin-RevId: d211bbacfe65355cf7304c990ffb6c79d7a229cf Change-Id: If690e6d378e543b300e1f6a353ceae73e39c29db Reviewed-on: https://dart-review.googlesource.com/c/78900 Reviewed-by: Alexander Thomas <athom@google.com>
5854 lines
172 KiB
Dart
5854 lines
172 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
/// -----------------------------------------------------------------------
|
|
/// WHEN CHANGING THIS FILE:
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// If you are adding/removing/modifying fields/classes of the AST, you must
|
|
/// also update the following files:
|
|
///
|
|
/// - binary/ast_to_binary.dart
|
|
/// - binary/ast_from_binary.dart
|
|
/// - text/ast_to_text.dart
|
|
/// - clone.dart
|
|
/// - binary.md
|
|
/// - type_checker.dart (if relevant)
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// ERROR HANDLING
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// As a rule of thumb, errors that can be detected statically are handled by
|
|
/// the frontend, typically by translating the erroneous code into a 'throw' or
|
|
/// a call to 'noSuchMethod'.
|
|
///
|
|
/// For example, there are no arity mismatches in static invocations, and
|
|
/// there are no direct invocations of a constructor on a abstract class.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// STATIC vs TOP-LEVEL
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// The term `static` includes both static class members and top-level members.
|
|
///
|
|
/// "Static class member" is the preferred term for non-top level statics.
|
|
///
|
|
/// Static class members are not lifted to the library level because mirrors
|
|
/// and stack traces can observe that they are class members.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// PROCEDURES
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// "Procedure" is an umbrella term for method, getter, setter, index-getter,
|
|
/// index-setter, operator overloader, and factory constructor.
|
|
///
|
|
/// Generative constructors, field initializers, local functions are NOT
|
|
/// procedures.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// TRANSFORMATIONS
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// AST transformations can be performed using [TreeNode.replaceWith] or the
|
|
/// [Transformer] visitor class.
|
|
///
|
|
/// Use [Transformer] for bulk transformations that are likely to transform lots
|
|
/// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate
|
|
/// relatively few nodes. Or use whichever is more convenient.
|
|
///
|
|
/// The AST can also be mutated by direct field manipulation, but the user then
|
|
/// has to update parent pointers manually.
|
|
///
|
|
library kernel.ast;
|
|
|
|
import 'dart:convert' show utf8;
|
|
|
|
import 'visitor.dart';
|
|
export 'visitor.dart';
|
|
|
|
import 'canonical_name.dart' show CanonicalName;
|
|
export 'canonical_name.dart' show CanonicalName;
|
|
|
|
import 'transformations/flags.dart';
|
|
import 'text/ast_to_text.dart';
|
|
import 'type_algebra.dart';
|
|
import 'type_environment.dart';
|
|
import 'coq_annot.dart';
|
|
|
|
/// Any type of node in the IR.
|
|
abstract class Node {
|
|
const Node();
|
|
|
|
accept(Visitor v);
|
|
visitChildren(Visitor v);
|
|
|
|
/// Returns the textual representation of this node for use in debugging.
|
|
///
|
|
/// [toString] should only be used for debugging and short-running test tools
|
|
/// as it can cause serious memory leaks.
|
|
///
|
|
/// Synthetic names are cached globally to retain consistency across different
|
|
/// [toString] calls (hence the memory leak).
|
|
///
|
|
/// Nodes that are named, such as [Class] and [Member], return their
|
|
/// (possibly synthesized) name, whereas other AST nodes return the complete
|
|
/// textual representation of their subtree.
|
|
String toString() => debugNodeToString(this);
|
|
}
|
|
|
|
/// A mutable AST node with a parent pointer.
|
|
///
|
|
/// This is anything other than [Name] and [DartType] nodes.
|
|
abstract class TreeNode extends Node {
|
|
static int _hashCounter = 0;
|
|
final int hashCode = _hashCounter = (_hashCounter + 1) & 0x3fffffff;
|
|
static const int noOffset = -1;
|
|
|
|
TreeNode parent;
|
|
|
|
/// Offset in the source file it comes from.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([noOffset]) if the file offset is
|
|
/// not available (this is the default if none is specifically set).
|
|
int fileOffset = noOffset;
|
|
|
|
accept(TreeVisitor v);
|
|
visitChildren(Visitor v);
|
|
transformChildren(Transformer v);
|
|
|
|
/// Replaces [child] with [replacement].
|
|
///
|
|
/// The caller is responsible for ensuring that the AST remains a tree. In
|
|
/// particular, [replacement] should be an orphan or be part of an orphaned
|
|
/// subtree.
|
|
///
|
|
/// Has no effect if [child] is not actually a child of this node.
|
|
///
|
|
/// If [replacement] is `null`, this will [remove] the [child] node.
|
|
void replaceChild(TreeNode child, TreeNode replacement) {
|
|
transformChildren(new _ChildReplacer(child, replacement));
|
|
}
|
|
|
|
/// Inserts another node in place of this one.
|
|
///
|
|
/// The caller is responsible for ensuring that the AST remains a tree. In
|
|
/// particular, [replacement] should be an orphan or be part of an orphaned
|
|
/// subtree.
|
|
///
|
|
/// If [replacement] is `null`, this will [remove] the node.
|
|
void replaceWith(TreeNode replacement) {
|
|
parent.replaceChild(this, replacement);
|
|
parent = null;
|
|
}
|
|
|
|
/// Removes this node from the [List] it is currently stored in, or assigns
|
|
/// `null` to the field on the parent currently pointing to the node.
|
|
///
|
|
/// Has no effect if the node is orphaned or if the parent pointer is stale.
|
|
void remove() {
|
|
parent?.replaceChild(this, null);
|
|
parent = null;
|
|
}
|
|
|
|
Component get enclosingComponent => parent?.enclosingComponent;
|
|
|
|
/// Returns the best known source location of the given AST node, or `null` if
|
|
/// the node is orphaned.
|
|
///
|
|
/// This getter is intended for diagnostics and debugging, and should be
|
|
/// avoided in production code.
|
|
Location get location {
|
|
if (fileOffset == noOffset) return parent?.location;
|
|
return _getLocationInEnclosingFile(fileOffset);
|
|
}
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return parent?._getLocationInEnclosingFile(offset);
|
|
}
|
|
}
|
|
|
|
/// An AST node that can be referenced by other nodes.
|
|
///
|
|
/// There is a single [reference] belonging to this node, providing a level of
|
|
/// indirection that is needed during serialization.
|
|
@coq
|
|
abstract class NamedNode extends TreeNode {
|
|
@coqdef
|
|
final Reference reference;
|
|
|
|
NamedNode(Reference reference)
|
|
: this.reference = reference ?? new Reference() {
|
|
this.reference.node = this;
|
|
}
|
|
|
|
CanonicalName get canonicalName => reference?.canonicalName;
|
|
}
|
|
|
|
abstract class FileUriNode extends TreeNode {
|
|
/// The URI of the source file this node was loaded from.
|
|
Uri get fileUri;
|
|
}
|
|
|
|
/// Indirection between a reference and its definition.
|
|
///
|
|
/// There is only one reference object per [NamedNode].
|
|
@coqref
|
|
class Reference {
|
|
CanonicalName canonicalName;
|
|
|
|
@nocoq
|
|
NamedNode node;
|
|
|
|
String toString() {
|
|
if (canonicalName != null) {
|
|
return 'Reference to $canonicalName';
|
|
}
|
|
if (node != null) {
|
|
return 'Reference to $node';
|
|
}
|
|
return 'Unbound reference';
|
|
}
|
|
|
|
Library get asLibrary {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A library was expected';
|
|
}
|
|
return node as Library;
|
|
}
|
|
|
|
Class get asClass {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A class was expected';
|
|
}
|
|
return node as Class;
|
|
}
|
|
|
|
Member get asMember {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A member was expected';
|
|
}
|
|
return node as Member;
|
|
}
|
|
|
|
Field get asField {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A field was expected';
|
|
}
|
|
return node as Field;
|
|
}
|
|
|
|
Constructor get asConstructor {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A constructor was expected';
|
|
}
|
|
return node as Constructor;
|
|
}
|
|
|
|
Procedure get asProcedure {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A procedure was expected';
|
|
}
|
|
return node as Procedure;
|
|
}
|
|
|
|
Typedef get asTypedef {
|
|
if (node == null) {
|
|
throw '$this is not bound to an AST node. A typedef was expected';
|
|
}
|
|
return node as Typedef;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// LIBRARIES and CLASSES
|
|
// ------------------------------------------------------------------------
|
|
|
|
@coq
|
|
class Library extends NamedNode implements Comparable<Library>, FileUriNode {
|
|
/// An import path to this library.
|
|
///
|
|
/// The [Uri] should have the `dart`, `package`, `app`, or `file` scheme.
|
|
///
|
|
/// If the URI has the `app` scheme, it is relative to the application root.
|
|
Uri importUri;
|
|
|
|
/// The URI of the source file this library was loaded from.
|
|
Uri fileUri;
|
|
|
|
/// If true, the library is part of another build unit and its contents
|
|
/// are only partially loaded.
|
|
///
|
|
/// Classes of an external library are loaded at one of the [ClassLevel]s
|
|
/// other than [ClassLevel.Body]. Members in an external library have no
|
|
/// body, but have their typed interface present.
|
|
///
|
|
/// If the library is non-external, then its classes are at [ClassLevel.Body]
|
|
/// and all members are loaded.
|
|
bool isExternal;
|
|
|
|
String name;
|
|
|
|
@nocoq
|
|
final List<Expression> annotations;
|
|
|
|
final List<LibraryDependency> dependencies;
|
|
|
|
/// References to nodes exported by `export` declarations that:
|
|
/// - aren't ambiguous, or
|
|
/// - aren't hidden by local declarations.
|
|
@nocoq
|
|
final List<Reference> additionalExports = <Reference>[];
|
|
|
|
@informative
|
|
final List<LibraryPart> parts;
|
|
|
|
final List<Typedef> typedefs;
|
|
final List<Class> classes;
|
|
final List<Procedure> procedures;
|
|
final List<Field> fields;
|
|
|
|
Library(this.importUri,
|
|
{this.name,
|
|
this.isExternal: false,
|
|
List<Expression> annotations,
|
|
List<LibraryDependency> dependencies,
|
|
List<LibraryPart> parts,
|
|
List<Typedef> typedefs,
|
|
List<Class> classes,
|
|
List<Procedure> procedures,
|
|
List<Field> fields,
|
|
this.fileUri,
|
|
Reference reference})
|
|
: this.annotations = annotations ?? <Expression>[],
|
|
this.dependencies = dependencies ?? <LibraryDependency>[],
|
|
this.parts = parts ?? <LibraryPart>[],
|
|
this.typedefs = typedefs ?? <Typedef>[],
|
|
this.classes = classes ?? <Class>[],
|
|
this.procedures = procedures ?? <Procedure>[],
|
|
this.fields = fields ?? <Field>[],
|
|
super(reference) {
|
|
setParents(this.dependencies, this);
|
|
setParents(this.parts, this);
|
|
setParents(this.typedefs, this);
|
|
setParents(this.classes, this);
|
|
setParents(this.procedures, this);
|
|
setParents(this.fields, this);
|
|
}
|
|
|
|
/// Returns the top-level fields and procedures defined in this library.
|
|
///
|
|
/// This getter is for convenience, not efficiency. Consider manually
|
|
/// iterating the members to speed up code in production.
|
|
Iterable<Member> get members =>
|
|
<Iterable<Member>>[fields, procedures].expand((x) => x);
|
|
|
|
void addMember(Member member) {
|
|
member.parent = this;
|
|
if (member is Procedure) {
|
|
procedures.add(member);
|
|
} else if (member is Field) {
|
|
fields.add(member);
|
|
} else {
|
|
throw new ArgumentError(member);
|
|
}
|
|
}
|
|
|
|
void addAnnotation(Expression node) {
|
|
node.parent = this;
|
|
annotations.add(node);
|
|
}
|
|
|
|
void addClass(Class class_) {
|
|
class_.parent = this;
|
|
classes.add(class_);
|
|
}
|
|
|
|
void addField(Field field) {
|
|
field.parent = this;
|
|
fields.add(field);
|
|
}
|
|
|
|
void addProcedure(Procedure procedure) {
|
|
procedure.parent = this;
|
|
procedures.add(procedure);
|
|
}
|
|
|
|
void addTypedef(Typedef typedef_) {
|
|
typedef_.parent = this;
|
|
typedefs.add(typedef_);
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
assert(canonicalName != null);
|
|
for (var typedef_ in typedefs) {
|
|
canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference);
|
|
}
|
|
for (var field in fields) {
|
|
canonicalName.getChildFromMember(field).bindTo(field.reference);
|
|
}
|
|
for (var member in procedures) {
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (var class_ in classes) {
|
|
canonicalName.getChild(class_.name).bindTo(class_.reference);
|
|
class_.computeCanonicalNames();
|
|
}
|
|
}
|
|
|
|
void addDependency(LibraryDependency node) {
|
|
dependencies.add(node..parent = this);
|
|
}
|
|
|
|
void addPart(LibraryPart node) {
|
|
parts.add(node..parent = this);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitLibrary(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(dependencies, v);
|
|
visitList(parts, v);
|
|
visitList(typedefs, v);
|
|
visitList(classes, v);
|
|
visitList(procedures, v);
|
|
visitList(fields, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(dependencies, v, this);
|
|
transformList(parts, v, this);
|
|
transformList(typedefs, v, this);
|
|
transformList(classes, v, this);
|
|
transformList(procedures, v, this);
|
|
transformList(fields, v, this);
|
|
}
|
|
|
|
static int _libraryIdCounter = 0;
|
|
int _libraryId = ++_libraryIdCounter;
|
|
|
|
int compareTo(Library other) => _libraryId - other._libraryId;
|
|
|
|
/// Returns a possibly synthesized name for this library, consistent with
|
|
/// the names across all [toString] calls.
|
|
String toString() => debugLibraryName(this);
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
/// An import or export declaration in a library.
|
|
///
|
|
/// It can represent any of the following forms,
|
|
///
|
|
/// import <url>;
|
|
/// import <url> as <name>;
|
|
/// import <url> deferred as <name>;
|
|
/// export <url>;
|
|
///
|
|
/// optionally with metadata and [Combinators].
|
|
class LibraryDependency extends TreeNode {
|
|
int flags;
|
|
|
|
final List<Expression> annotations;
|
|
|
|
Reference importedLibraryReference;
|
|
|
|
/// The name of the import prefix, if any, or `null` if this is not an import
|
|
/// with a prefix.
|
|
///
|
|
/// Must be non-null for deferred imports, and must be null for exports.
|
|
String name;
|
|
|
|
final List<Combinator> combinators;
|
|
|
|
LibraryDependency(int flags, List<Expression> annotations,
|
|
Library importedLibrary, String name, List<Combinator> combinators)
|
|
: this.byReference(
|
|
flags, annotations, importedLibrary.reference, name, combinators);
|
|
|
|
LibraryDependency.deferredImport(Library importedLibrary, String name,
|
|
{List<Combinator> combinators, List<Expression> annotations})
|
|
: this.byReference(DeferredFlag, annotations ?? <Expression>[],
|
|
importedLibrary.reference, name, combinators ?? <Combinator>[]);
|
|
|
|
LibraryDependency.import(Library importedLibrary,
|
|
{String name, List<Combinator> combinators, List<Expression> annotations})
|
|
: this.byReference(0, annotations ?? <Expression>[],
|
|
importedLibrary.reference, name, combinators ?? <Combinator>[]);
|
|
|
|
LibraryDependency.export(Library importedLibrary,
|
|
{List<Combinator> combinators, List<Expression> annotations})
|
|
: this.byReference(ExportFlag, annotations ?? <Expression>[],
|
|
importedLibrary.reference, null, combinators ?? <Combinator>[]);
|
|
|
|
LibraryDependency.byReference(this.flags, this.annotations,
|
|
this.importedLibraryReference, this.name, this.combinators) {
|
|
setParents(annotations, this);
|
|
setParents(combinators, this);
|
|
}
|
|
|
|
Library get enclosingLibrary => parent;
|
|
Library get targetLibrary => importedLibraryReference.asLibrary;
|
|
|
|
static const int ExportFlag = 1 << 0;
|
|
static const int DeferredFlag = 1 << 1;
|
|
|
|
bool get isExport => flags & ExportFlag != 0;
|
|
bool get isImport => !isExport;
|
|
bool get isDeferred => flags & DeferredFlag != 0;
|
|
|
|
void addAnnotation(Expression annotation) {
|
|
annotations.add(annotation..parent = this);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitLibraryDependency(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(combinators, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(combinators, v, this);
|
|
}
|
|
}
|
|
|
|
/// A part declaration in a library.
|
|
///
|
|
/// part <url>;
|
|
///
|
|
/// optionally with metadata.
|
|
class LibraryPart extends TreeNode {
|
|
final List<Expression> annotations;
|
|
final String partUri;
|
|
|
|
LibraryPart(this.annotations, this.partUri) {
|
|
setParents(annotations, this);
|
|
}
|
|
|
|
void addAnnotation(Expression annotation) {
|
|
annotations.add(annotation..parent = this);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitLibraryPart(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
}
|
|
}
|
|
|
|
/// A `show` or `hide` clause for an import or export.
|
|
class Combinator extends TreeNode {
|
|
bool isShow;
|
|
|
|
final List<String> names;
|
|
|
|
LibraryDependency get dependency => parent;
|
|
|
|
Combinator(this.isShow, this.names);
|
|
Combinator.show(this.names) : isShow = true;
|
|
Combinator.hide(this.names) : isShow = false;
|
|
|
|
bool get isHide => !isShow;
|
|
|
|
@override
|
|
accept(TreeVisitor v) => v.visitCombinator(this);
|
|
|
|
@override
|
|
visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// Declaration of a type alias.
|
|
class Typedef extends NamedNode implements FileUriNode {
|
|
/// The URI of the source file that contains the declaration of this typedef.
|
|
Uri fileUri;
|
|
List<Expression> annotations = const <Expression>[];
|
|
String name;
|
|
final List<TypeParameter> typeParameters;
|
|
DartType type;
|
|
|
|
// The following two fields describe parameters of the underlying function
|
|
// type. They are needed to keep such attributes as names and annotations.
|
|
final List<TypeParameter> typeParametersOfFunctionType;
|
|
final List<VariableDeclaration> positionalParameters;
|
|
final List<VariableDeclaration> namedParameters;
|
|
|
|
Typedef(this.name, this.type,
|
|
{Reference reference,
|
|
this.fileUri,
|
|
List<TypeParameter> typeParameters,
|
|
List<TypeParameter> typeParametersOfFunctionType,
|
|
List<VariableDeclaration> positionalParameters,
|
|
List<VariableDeclaration> namedParameters})
|
|
: this.typeParameters = typeParameters ?? <TypeParameter>[],
|
|
this.typeParametersOfFunctionType =
|
|
typeParametersOfFunctionType ?? <TypeParameter>[],
|
|
this.positionalParameters =
|
|
positionalParameters ?? <VariableDeclaration>[],
|
|
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
|
|
super(reference) {
|
|
setParents(this.typeParameters, this);
|
|
}
|
|
|
|
Library get enclosingLibrary => parent;
|
|
|
|
accept(TreeVisitor v) {
|
|
return v.visitTypedef(this);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(typeParameters, v, this);
|
|
if (type != null) {
|
|
type = v.visitDartType(type);
|
|
}
|
|
}
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(typeParameters, v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
void addAnnotation(Expression node) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(node);
|
|
node.parent = this;
|
|
}
|
|
}
|
|
|
|
/// The degree to which the contents of a class have been loaded into memory.
|
|
///
|
|
/// Each level imply the requirements of the previous ones.
|
|
enum ClassLevel {
|
|
/// Temporary loading level for internal use by IR producers. Consumers of
|
|
/// kernel code should not expect to see classes at this level.
|
|
Temporary,
|
|
|
|
/// The class may be used as a type, and it may contain members that are
|
|
/// referenced from this build unit.
|
|
///
|
|
/// The type parameters and their bounds are present.
|
|
///
|
|
/// There is no guarantee that all members are present.
|
|
///
|
|
/// All supertypes of this class are at [Type] level or higher.
|
|
Type,
|
|
|
|
/// All instance members of the class are present.
|
|
///
|
|
/// All supertypes of this class are at [Hierarchy] level or higher.
|
|
///
|
|
/// This level exists so supertypes of a fully loaded class contain all the
|
|
/// members needed to detect override constraints.
|
|
Hierarchy,
|
|
|
|
/// All instance members of the class have their body loaded, and their
|
|
/// annotations are present.
|
|
///
|
|
/// All supertypes of this class are at [Hierarchy] level or higher.
|
|
///
|
|
/// If this class is a mixin application, then its mixin is loaded at [Mixin]
|
|
/// level or higher.
|
|
///
|
|
/// This level exists so the contents of a mixin can be cloned into a
|
|
/// mixin application.
|
|
Mixin,
|
|
|
|
/// All members of the class are fully loaded and are in the correct order.
|
|
///
|
|
/// Annotations are present on classes and members.
|
|
///
|
|
/// All supertypes of this class are at [Hierarchy] level or higher,
|
|
/// not necessarily at [Body] level.
|
|
Body,
|
|
}
|
|
|
|
/// Declaration of a regular class or a mixin application.
|
|
///
|
|
/// Mixin applications may not contain fields or procedures, as they implicitly
|
|
/// use those from its mixed-in type. However, the IR does not enforce this
|
|
/// rule directly, as doing so can obstruct transformations. It is possible to
|
|
/// transform a mixin application to become a regular class, and vice versa.
|
|
@coq
|
|
class Class extends NamedNode implements FileUriNode {
|
|
/// Start offset of the class in the source file it comes from.
|
|
///
|
|
/// Note that this includes annotations if any.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
|
|
/// start offset is not available (this is the default if none is specifically
|
|
/// set).
|
|
int startFileOffset = TreeNode.noOffset;
|
|
|
|
/// End offset in the source file it comes from. Valid values are from 0 and
|
|
/// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
|
|
/// (this is the default if none is specifically set).
|
|
int fileEndOffset = TreeNode.noOffset;
|
|
|
|
/// The degree to which the contents of the class have been loaded.
|
|
ClassLevel level = ClassLevel.Body;
|
|
|
|
/// List of metadata annotations on the class.
|
|
///
|
|
/// This defaults to an immutable empty list. Use [addAnnotation] to add
|
|
/// annotations if needed.
|
|
@nocoq
|
|
List<Expression> annotations = const <Expression>[];
|
|
|
|
/// Name of the class.
|
|
///
|
|
/// Must be non-null and must be unique within the library.
|
|
///
|
|
/// The name may contain characters that are not valid in a Dart identifier,
|
|
/// in particular, the symbol '&' is used in class names generated for mixin
|
|
/// applications.
|
|
@coq
|
|
String name;
|
|
|
|
// Must match serialized bit positions.
|
|
static const int LevelMask = 0x3; // Bits 0 and 1.
|
|
static const int FlagAbstract = 1 << 2;
|
|
static const int FlagEnum = 1 << 3;
|
|
static const int FlagAnonymousMixin = 1 << 4;
|
|
static const int FlagEliminatedMixin = 1 << 5;
|
|
static const int FlagMixinDeclaration = 1 << 6;
|
|
|
|
int flags = 0;
|
|
|
|
bool get isAbstract => flags & FlagAbstract != 0;
|
|
|
|
void set isAbstract(bool value) {
|
|
flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
|
|
}
|
|
|
|
/// Whether this class is an enum.
|
|
bool get isEnum => flags & FlagEnum != 0;
|
|
|
|
void set isEnum(bool value) {
|
|
flags = value ? (flags | FlagEnum) : (flags & ~FlagEnum);
|
|
}
|
|
|
|
/// Whether this class is a synthetic implementation created for each
|
|
/// mixed-in class. For example the following code:
|
|
/// class Z extends A with B, C, D {}
|
|
/// class A {}
|
|
/// class B {}
|
|
/// class C {}
|
|
/// class D {}
|
|
/// ...creates:
|
|
/// abstract class _Z&A&B extends A mixedIn B {}
|
|
/// abstract class _Z&A&B&C extends A&B mixedIn C {}
|
|
/// abstract class _Z&A&B&C&D extends A&B&C mixedIn D {}
|
|
/// class Z extends _Z&A&B&C&D {}
|
|
/// All X&Y classes are marked as synthetic.
|
|
bool get isAnonymousMixin => flags & FlagAnonymousMixin != 0;
|
|
|
|
void set isAnonymousMixin(bool value) {
|
|
flags =
|
|
value ? (flags | FlagAnonymousMixin) : (flags & ~FlagAnonymousMixin);
|
|
}
|
|
|
|
/// Whether this class was transformed from a mixin application.
|
|
/// In such case, its mixed-in type was pulled into the end of implemented
|
|
/// types list.
|
|
bool get isEliminatedMixin => flags & FlagEliminatedMixin != 0;
|
|
|
|
void set isEliminatedMixin(bool value) {
|
|
flags =
|
|
value ? (flags | FlagEliminatedMixin) : (flags & ~FlagEliminatedMixin);
|
|
}
|
|
|
|
/// True if this class was a mixin declaration in Dart.
|
|
///
|
|
/// Mixins are declared in Dart with the `mixin` keyword. They are compiled
|
|
/// to Kernel classes.
|
|
bool get isMixinDeclaration => flags & FlagMixinDeclaration != 0;
|
|
|
|
void set isMixinDeclaration(bool value) {
|
|
flags = value
|
|
? (flags | FlagMixinDeclaration)
|
|
: (flags & ~FlagMixinDeclaration);
|
|
}
|
|
|
|
List<Supertype> superclassConstraints() {
|
|
var constraints = <Supertype>[];
|
|
|
|
// Not a mixin declaration.
|
|
if (!isMixinDeclaration) return constraints;
|
|
|
|
Class previous = this;
|
|
Class current = superclass;
|
|
|
|
// Otherwise we have a left-linear binary tree (subtrees are supertype and
|
|
// mixedInType) of constraints, where all the interior nodes are anonymous
|
|
// mixin applications.
|
|
while (current != null && current.isAnonymousMixin) {
|
|
assert(current.implementedTypes.length == 2);
|
|
constraints.add(current.implementedTypes[1]);
|
|
previous = current;
|
|
current = current.implementedTypes[0].classNode;
|
|
}
|
|
return constraints
|
|
..add(
|
|
previous == this ? previous.supertype : previous.implementedTypes[0]);
|
|
}
|
|
|
|
/// The URI of the source file this class was loaded from.
|
|
Uri fileUri;
|
|
|
|
final List<TypeParameter> typeParameters;
|
|
|
|
/// The immediate super type, or `null` if this is the root class.
|
|
Supertype supertype;
|
|
|
|
/// The mixed-in type if this is a mixin application, otherwise `null`.
|
|
Supertype mixedInType;
|
|
|
|
/// The types from the `implements` clause.
|
|
final List<Supertype> implementedTypes;
|
|
|
|
/// Fields declared in the class.
|
|
///
|
|
/// For mixin applications this should be empty.
|
|
final List<Field> fields;
|
|
|
|
/// Constructors declared in the class.
|
|
final List<Constructor> constructors;
|
|
|
|
/// Procedures declared in the class.
|
|
///
|
|
/// For mixin applications this should only contain forwarding stubs.
|
|
final List<Procedure> procedures;
|
|
|
|
/// Redirecting factory constructors declared in the class.
|
|
///
|
|
/// For mixin applications this should be empty.
|
|
final List<RedirectingFactoryConstructor> redirectingFactoryConstructors;
|
|
|
|
Class(
|
|
{this.name,
|
|
bool isAbstract: false,
|
|
bool isAnonymousMixin: false,
|
|
this.supertype,
|
|
this.mixedInType,
|
|
List<TypeParameter> typeParameters,
|
|
List<Supertype> implementedTypes,
|
|
List<Constructor> constructors,
|
|
List<Procedure> procedures,
|
|
List<Field> fields,
|
|
List<RedirectingFactoryConstructor> redirectingFactoryConstructors,
|
|
this.fileUri,
|
|
Reference reference})
|
|
: this.typeParameters = typeParameters ?? <TypeParameter>[],
|
|
this.implementedTypes = implementedTypes ?? <Supertype>[],
|
|
this.fields = fields ?? <Field>[],
|
|
this.constructors = constructors ?? <Constructor>[],
|
|
this.procedures = procedures ?? <Procedure>[],
|
|
this.redirectingFactoryConstructors =
|
|
redirectingFactoryConstructors ?? <RedirectingFactoryConstructor>[],
|
|
super(reference) {
|
|
setParents(this.typeParameters, this);
|
|
setParents(this.constructors, this);
|
|
setParents(this.procedures, this);
|
|
setParents(this.fields, this);
|
|
setParents(this.redirectingFactoryConstructors, this);
|
|
this.isAbstract = isAbstract;
|
|
this.isAnonymousMixin = isAnonymousMixin;
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
assert(canonicalName != null);
|
|
for (var member in fields) {
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (var member in procedures) {
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (var member in constructors) {
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (var member in redirectingFactoryConstructors) {
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
}
|
|
|
|
/// The immediate super class, or `null` if this is the root class.
|
|
Class get superclass => supertype?.classNode;
|
|
|
|
/// The mixed-in class if this is a mixin application, otherwise `null`.
|
|
///
|
|
/// Note that this may itself be a mixin application. Use [mixin] to get the
|
|
/// class that has the fields and procedures.
|
|
Class get mixedInClass => mixedInType?.classNode;
|
|
|
|
/// The class that declares the field and procedures of this class.
|
|
Class get mixin => mixedInClass?.mixin ?? this;
|
|
|
|
bool get isMixinApplication => mixedInType != null;
|
|
|
|
String get demangledName {
|
|
if (isAnonymousMixin) return nameAsMixinApplication;
|
|
assert(!name.contains('&'));
|
|
return name;
|
|
}
|
|
|
|
String get nameAsMixinApplication {
|
|
assert(isAnonymousMixin);
|
|
return demangleMixinApplicationName(name);
|
|
}
|
|
|
|
String get nameAsMixinApplicationSubclass {
|
|
assert(isAnonymousMixin);
|
|
return demangleMixinApplicationSubclassName(name);
|
|
}
|
|
|
|
/// Members declared in this class.
|
|
///
|
|
/// This getter is for convenience, not efficiency. Consider manually
|
|
/// iterating the members to speed up code in production.
|
|
Iterable<Member> get members => <Iterable<Member>>[
|
|
fields,
|
|
constructors,
|
|
procedures,
|
|
redirectingFactoryConstructors
|
|
].expand((x) => x);
|
|
|
|
/// The immediately extended, mixed-in, and implemented types.
|
|
///
|
|
/// This getter is for convenience, not efficiency. Consider manually
|
|
/// iterating the super types to speed up code in production.
|
|
Iterable<Supertype> get supers => <Iterable<Supertype>>[
|
|
supertype == null ? const [] : [supertype],
|
|
mixedInType == null ? const [] : [mixedInType],
|
|
implementedTypes
|
|
].expand((x) => x);
|
|
|
|
/// The library containing this class.
|
|
Library get enclosingLibrary => parent;
|
|
|
|
/// Adds a member to this class.
|
|
///
|
|
/// Throws an error if attempting to add a field or procedure to a mixin
|
|
/// application.
|
|
void addMember(Member member) {
|
|
member.parent = this;
|
|
if (member is Constructor) {
|
|
constructors.add(member);
|
|
} else if (member is Procedure) {
|
|
procedures.add(member);
|
|
} else if (member is Field) {
|
|
fields.add(member);
|
|
} else if (member is RedirectingFactoryConstructor) {
|
|
redirectingFactoryConstructors.add(member);
|
|
} else {
|
|
throw new ArgumentError(member);
|
|
}
|
|
}
|
|
|
|
void addAnnotation(Expression node) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(node);
|
|
node.parent = this;
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitClass(this);
|
|
acceptReference(Visitor v) => v.visitClassReference(this);
|
|
|
|
/// If true, the class is part of an external library, that is, it is defined
|
|
/// in another build unit. Only a subset of its members are present.
|
|
///
|
|
/// These classes should be loaded at either [ClassLevel.Type] or
|
|
/// [ClassLevel.Hierarchy] level.
|
|
bool get isInExternalLibrary => enclosingLibrary.isExternal;
|
|
|
|
Supertype get asRawSupertype {
|
|
return new Supertype(this,
|
|
new List<DartType>.filled(typeParameters.length, const DynamicType()));
|
|
}
|
|
|
|
Supertype get asThisSupertype {
|
|
return new Supertype(this, _getAsTypeArguments(typeParameters));
|
|
}
|
|
|
|
@nocoq
|
|
InterfaceType _rawType;
|
|
InterfaceType get rawType => _rawType ??= new InterfaceType(this);
|
|
|
|
@nocoq
|
|
InterfaceType _thisType;
|
|
InterfaceType get thisType {
|
|
return _thisType ??=
|
|
new InterfaceType(this, _getAsTypeArguments(typeParameters));
|
|
}
|
|
|
|
@nocoq
|
|
InterfaceType _bottomType;
|
|
InterfaceType get bottomType {
|
|
return _bottomType ??= new InterfaceType(this,
|
|
new List<DartType>.filled(typeParameters.length, const BottomType()));
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this class, consistent with
|
|
/// the names used across all [toString] calls.
|
|
String toString() => debugQualifiedClassName(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(typeParameters, v);
|
|
supertype?.accept(v);
|
|
mixedInType?.accept(v);
|
|
visitList(implementedTypes, v);
|
|
visitList(constructors, v);
|
|
visitList(procedures, v);
|
|
visitList(fields, v);
|
|
visitList(redirectingFactoryConstructors, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(typeParameters, v, this);
|
|
if (supertype != null) {
|
|
supertype = v.visitSupertype(supertype);
|
|
}
|
|
if (mixedInType != null) {
|
|
mixedInType = v.visitSupertype(mixedInType);
|
|
}
|
|
transformSupertypeList(implementedTypes, v);
|
|
transformList(constructors, v, this);
|
|
transformList(procedures, v, this);
|
|
transformList(fields, v, this);
|
|
transformList(redirectingFactoryConstructors, v, this);
|
|
}
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// MEMBERS
|
|
// ------------------------------------------------------------------------
|
|
|
|
@coq
|
|
abstract class Member extends NamedNode implements FileUriNode {
|
|
/// End offset in the source file it comes from.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
|
|
/// end offset is not available (this is the default if none is specifically
|
|
/// set).
|
|
int fileEndOffset = TreeNode.noOffset;
|
|
|
|
/// List of metadata annotations on the member.
|
|
///
|
|
/// This defaults to an immutable empty list. Use [addAnnotation] to add
|
|
/// annotations if needed.
|
|
@nocoq
|
|
List<Expression> annotations = const <Expression>[];
|
|
|
|
Name name;
|
|
|
|
/// The URI of the source file this member was loaded from.
|
|
Uri fileUri;
|
|
|
|
/// Flags summarizing the kinds of AST nodes contained in this member, for
|
|
/// speeding up transformations that only affect certain types of nodes.
|
|
///
|
|
/// See [TransformerFlag] for the meaning of each bit.
|
|
///
|
|
/// These should not be used for any purpose other than skipping certain
|
|
/// members if it can be determined that no work is needed in there.
|
|
///
|
|
/// It is valid for these flags to be false positives in rare cases, so
|
|
/// transformers must tolerate the case where a flag is spuriously set.
|
|
///
|
|
/// This value is not serialized; it is populated by the frontend and the
|
|
/// deserializer.
|
|
//
|
|
// TODO(asgerf): It might be worthwhile to put this on classes as well.
|
|
int transformerFlags = 0;
|
|
|
|
Member(this.name, this.fileUri, Reference reference) : super(reference);
|
|
|
|
Class get enclosingClass => parent is Class ? parent : null;
|
|
Library get enclosingLibrary => parent is Class ? parent.parent : parent;
|
|
|
|
accept(MemberVisitor v);
|
|
acceptReference(MemberReferenceVisitor v);
|
|
|
|
/// If true, the member is part of an external library, that is, it is defined
|
|
/// in another build unit. Such members have no body or initializer present
|
|
/// in the IR.
|
|
bool get isInExternalLibrary => enclosingLibrary.isExternal;
|
|
|
|
/// Returns true if this is an abstract procedure.
|
|
bool get isAbstract => false;
|
|
|
|
/// Returns true if the member has the 'const' modifier.
|
|
bool get isConst;
|
|
|
|
/// True if this is a field or non-setter procedure.
|
|
///
|
|
/// Note that operators and factories return `true`, even though there are
|
|
/// normally no calls to their getter.
|
|
bool get hasGetter;
|
|
|
|
/// True if this is a setter or a mutable field.
|
|
bool get hasSetter;
|
|
|
|
/// True if this is a non-static field or procedure.
|
|
bool get isInstanceMember;
|
|
|
|
/// True if the member has the `external` modifier, implying that the
|
|
/// implementation is provided by the backend, and is not necessarily written
|
|
/// in Dart.
|
|
///
|
|
/// Members can have this modifier independently of whether the enclosing
|
|
/// library is external.
|
|
bool get isExternal;
|
|
void set isExternal(bool value);
|
|
|
|
/// The body of the procedure or constructor, or `null` if this is a field.
|
|
FunctionNode get function => null;
|
|
|
|
/// Returns a possibly synthesized name for this member, consistent with
|
|
/// the names used across all [toString] calls.
|
|
String toString() => debugQualifiedMemberName(this);
|
|
|
|
void addAnnotation(Expression node) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(node);
|
|
node.parent = this;
|
|
}
|
|
|
|
DartType get getterType;
|
|
DartType get setterType;
|
|
|
|
bool get containsSuperCalls {
|
|
return transformerFlags & TransformerFlag.superCalls != 0;
|
|
}
|
|
}
|
|
|
|
/// A field declaration.
|
|
///
|
|
/// The implied getter and setter for the field are not represented explicitly,
|
|
/// but can be made explicit if needed.
|
|
class Field extends Member {
|
|
DartType type; // Not null. Defaults to DynamicType.
|
|
int flags = 0;
|
|
Expression initializer; // May be null.
|
|
|
|
Field(Name name,
|
|
{this.type: const DynamicType(),
|
|
this.initializer,
|
|
bool isCovariant: false,
|
|
bool isFinal: false,
|
|
bool isConst: false,
|
|
bool isStatic: false,
|
|
bool hasImplicitGetter,
|
|
bool hasImplicitSetter,
|
|
int transformerFlags: 0,
|
|
Uri fileUri,
|
|
Reference reference})
|
|
: super(name, fileUri, reference) {
|
|
assert(type != null);
|
|
initializer?.parent = this;
|
|
this.isCovariant = isCovariant;
|
|
this.isFinal = isFinal;
|
|
this.isConst = isConst;
|
|
this.isStatic = isStatic;
|
|
this.hasImplicitGetter = hasImplicitGetter ?? !isStatic;
|
|
this.hasImplicitSetter = hasImplicitSetter ?? (!isStatic && !isFinal);
|
|
this.transformerFlags = transformerFlags;
|
|
}
|
|
|
|
static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
|
|
static const int FlagConst = 1 << 1;
|
|
static const int FlagStatic = 1 << 2;
|
|
static const int FlagHasImplicitGetter = 1 << 3;
|
|
static const int FlagHasImplicitSetter = 1 << 4;
|
|
static const int FlagCovariant = 1 << 5;
|
|
static const int FlagGenericCovariantImpl = 1 << 6;
|
|
|
|
/// Whether the field is declared with the `covariant` keyword.
|
|
bool get isCovariant => flags & FlagCovariant != 0;
|
|
|
|
bool get isFinal => flags & FlagFinal != 0;
|
|
bool get isConst => flags & FlagConst != 0;
|
|
bool get isStatic => flags & FlagStatic != 0;
|
|
|
|
/// If true, a getter should be generated for this field.
|
|
///
|
|
/// If false, there may or may not exist an explicit getter in the same class
|
|
/// with the same name as the field.
|
|
///
|
|
/// By default, all non-static fields have implicit getters.
|
|
bool get hasImplicitGetter => flags & FlagHasImplicitGetter != 0;
|
|
|
|
/// If true, a setter should be generated for this field.
|
|
///
|
|
/// If false, there may or may not exist an explicit setter in the same class
|
|
/// with the same name as the field.
|
|
///
|
|
/// Final fields never have implicit setters, but a field without an implicit
|
|
/// setter is not necessarily final, as it may be mutated by direct field
|
|
/// access.
|
|
///
|
|
/// By default, all non-static, non-final fields have implicit setters.
|
|
bool get hasImplicitSetter => flags & FlagHasImplicitSetter != 0;
|
|
|
|
/// Indicates whether the implicit setter associated with this field needs to
|
|
/// contain a runtime type check to deal with generic covariance.
|
|
///
|
|
/// When `true`, runtime checks may need to be performed; see
|
|
/// [DispatchCategory] for details.
|
|
bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;
|
|
|
|
void set isCovariant(bool value) {
|
|
flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant);
|
|
}
|
|
|
|
void set isFinal(bool value) {
|
|
flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
|
|
}
|
|
|
|
void set isConst(bool value) {
|
|
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
|
|
}
|
|
|
|
void set isStatic(bool value) {
|
|
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
|
|
}
|
|
|
|
void set hasImplicitGetter(bool value) {
|
|
flags = value
|
|
? (flags | FlagHasImplicitGetter)
|
|
: (flags & ~FlagHasImplicitGetter);
|
|
}
|
|
|
|
void set hasImplicitSetter(bool value) {
|
|
flags = value
|
|
? (flags | FlagHasImplicitSetter)
|
|
: (flags & ~FlagHasImplicitSetter);
|
|
}
|
|
|
|
void set isGenericCovariantImpl(bool value) {
|
|
flags = value
|
|
? (flags | FlagGenericCovariantImpl)
|
|
: (flags & ~FlagGenericCovariantImpl);
|
|
}
|
|
|
|
/// True if the field is neither final nor const.
|
|
bool get isMutable => flags & (FlagFinal | FlagConst) == 0;
|
|
bool get isInstanceMember => !isStatic;
|
|
bool get hasGetter => true;
|
|
bool get hasSetter => isMutable;
|
|
|
|
bool get isExternal => false;
|
|
void set isExternal(bool value) {
|
|
if (value) throw 'Fields cannot be external';
|
|
}
|
|
|
|
accept(MemberVisitor v) => v.visitField(this);
|
|
|
|
acceptReference(MemberReferenceVisitor v) => v.visitFieldReference(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
type?.accept(v);
|
|
name?.accept(v);
|
|
initializer?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
type = v.visitDartType(type);
|
|
transformList(annotations, v, this);
|
|
if (initializer != null) {
|
|
initializer = initializer.accept(v);
|
|
initializer?.parent = this;
|
|
}
|
|
}
|
|
|
|
DartType get getterType => type;
|
|
DartType get setterType => isMutable ? type : const BottomType();
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
/// A generative constructor, possibly redirecting.
|
|
///
|
|
/// Note that factory constructors are treated as [Procedure]s.
|
|
///
|
|
/// Constructors do not take type parameters. Type arguments from a constructor
|
|
/// invocation should be matched with the type parameters declared in the class.
|
|
///
|
|
/// For unnamed constructors, the name is an empty string (in a [Name]).
|
|
class Constructor extends Member {
|
|
/// Start offset of the constructor in the source file it comes from.
|
|
///
|
|
/// Note that this includes annotations if any.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
|
|
/// start offset is not available (this is the default if none is specifically
|
|
/// set).
|
|
int startFileOffset = TreeNode.noOffset;
|
|
|
|
int flags = 0;
|
|
FunctionNode function;
|
|
List<Initializer> initializers;
|
|
|
|
Constructor(this.function,
|
|
{Name name,
|
|
bool isConst: false,
|
|
bool isExternal: false,
|
|
bool isSynthetic: false,
|
|
List<Initializer> initializers,
|
|
int transformerFlags: 0,
|
|
Uri fileUri,
|
|
Reference reference})
|
|
: this.initializers = initializers ?? <Initializer>[],
|
|
super(name, fileUri, reference) {
|
|
function?.parent = this;
|
|
setParents(this.initializers, this);
|
|
this.isConst = isConst;
|
|
this.isExternal = isExternal;
|
|
this.isSynthetic = isSynthetic;
|
|
this.transformerFlags = transformerFlags;
|
|
}
|
|
|
|
static const int FlagConst = 1 << 0; // Must match serialized bit positions.
|
|
static const int FlagExternal = 1 << 1;
|
|
static const int FlagSynthetic = 1 << 2;
|
|
|
|
bool get isConst => flags & FlagConst != 0;
|
|
bool get isExternal => flags & FlagExternal != 0;
|
|
|
|
/// True if this is a synthetic constructor inserted in a class that
|
|
/// does not otherwise declare any constructors.
|
|
bool get isSynthetic => flags & FlagSynthetic != 0;
|
|
|
|
void set isConst(bool value) {
|
|
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
|
|
}
|
|
|
|
void set isExternal(bool value) {
|
|
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
|
|
}
|
|
|
|
void set isSynthetic(bool value) {
|
|
flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
|
|
}
|
|
|
|
bool get isInstanceMember => false;
|
|
bool get hasGetter => false;
|
|
bool get hasSetter => false;
|
|
|
|
accept(MemberVisitor v) => v.visitConstructor(this);
|
|
|
|
acceptReference(MemberReferenceVisitor v) =>
|
|
v.visitConstructorReference(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
name?.accept(v);
|
|
visitList(initializers, v);
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(initializers, v, this);
|
|
if (function != null) {
|
|
function = function.accept(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
|
|
DartType get getterType => const BottomType();
|
|
DartType get setterType => const BottomType();
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
/// Residue of a redirecting factory constructor for the linking phase.
|
|
///
|
|
/// In the following example, `bar` is a redirecting factory constructor.
|
|
///
|
|
/// class A {
|
|
/// A.foo();
|
|
/// factory A.bar() = A.foo;
|
|
/// }
|
|
///
|
|
/// An invocation of `new A.bar()` has the same effect as an invocation of
|
|
/// `new A.foo()`. In Kernel, the invocations of `bar` are replaced with
|
|
/// invocations of `foo`, and after it is done, the redirecting constructor can
|
|
/// be removed from the class. However, it is needed during the linking phase,
|
|
/// because other modules can refer to that constructor.
|
|
///
|
|
/// [RedirectingFactoryConstructor]s contain the necessary information for
|
|
/// linking and are treated as non-runnable members of classes that merely serve
|
|
/// as containers for that information.
|
|
///
|
|
/// Redirecting factory constructors can be unnamed. In this case, the name is
|
|
/// an empty string (in a [Name]).
|
|
class RedirectingFactoryConstructor extends Member {
|
|
int flags = 0;
|
|
|
|
/// [RedirectingFactoryConstructor]s may redirect to constructors or factories
|
|
/// of instantiated generic types, that is, generic types with supplied type
|
|
/// arguments. The supplied type arguments are stored in this field.
|
|
final List<DartType> typeArguments;
|
|
|
|
/// Reference to the constructor or the factory that this
|
|
/// [RedirectingFactoryConstructor] redirects to.
|
|
Reference targetReference;
|
|
|
|
/// [typeParameters] are duplicates of the type parameters of the enclosing
|
|
/// class. Because [RedirectingFactoryConstructor]s aren't instance members,
|
|
/// references to the type parameters of the enclosing class in the
|
|
/// redirection target description are encoded with references to the elements
|
|
/// of [typeParameters].
|
|
List<TypeParameter> typeParameters;
|
|
|
|
/// Positional parameters of [RedirectingFactoryConstructor]s should be
|
|
/// compatible with that of the target constructor.
|
|
List<VariableDeclaration> positionalParameters;
|
|
int requiredParameterCount;
|
|
|
|
/// Named parameters of [RedirectingFactoryConstructor]s should be compatible
|
|
/// with that of the target constructor.
|
|
List<VariableDeclaration> namedParameters;
|
|
|
|
RedirectingFactoryConstructor(this.targetReference,
|
|
{Name name,
|
|
bool isConst: false,
|
|
bool isExternal: false,
|
|
int transformerFlags: 0,
|
|
List<DartType> typeArguments,
|
|
List<TypeParameter> typeParameters,
|
|
List<VariableDeclaration> positionalParameters,
|
|
List<VariableDeclaration> namedParameters,
|
|
int requiredParameterCount,
|
|
Uri fileUri,
|
|
Reference reference})
|
|
: this.typeArguments = typeArguments ?? <DartType>[],
|
|
this.typeParameters = typeParameters ?? <TypeParameter>[],
|
|
this.positionalParameters =
|
|
positionalParameters ?? <VariableDeclaration>[],
|
|
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
|
|
this.requiredParameterCount =
|
|
requiredParameterCount ?? positionalParameters?.length ?? 0,
|
|
super(name, fileUri, reference) {
|
|
setParents(this.typeParameters, this);
|
|
setParents(this.positionalParameters, this);
|
|
setParents(this.namedParameters, this);
|
|
this.isConst = isConst;
|
|
this.isExternal = isExternal;
|
|
this.transformerFlags = transformerFlags;
|
|
}
|
|
|
|
static const int FlagConst = 1 << 0; // Must match serialized bit positions.
|
|
static const int FlagExternal = 1 << 1;
|
|
|
|
bool get isConst => flags & FlagConst != 0;
|
|
bool get isExternal => flags & FlagExternal != 0;
|
|
|
|
void set isConst(bool value) {
|
|
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
|
|
}
|
|
|
|
void set isExternal(bool value) {
|
|
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
|
|
}
|
|
|
|
bool get isInstanceMember => false;
|
|
bool get hasGetter => false;
|
|
bool get hasSetter => false;
|
|
|
|
bool get isUnresolved => targetReference == null;
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member member) {
|
|
assert(member is Constructor ||
|
|
(member is Procedure && member.kind == ProcedureKind.Factory));
|
|
targetReference = getMemberReference(member);
|
|
}
|
|
|
|
accept(MemberVisitor v) => v.visitRedirectingFactoryConstructor(this);
|
|
|
|
acceptReference(MemberReferenceVisitor v) =>
|
|
v.visitRedirectingFactoryConstructorReference(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
target?.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
name?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformTypeList(typeArguments, v);
|
|
}
|
|
|
|
DartType get getterType => const BottomType();
|
|
DartType get setterType => const BottomType();
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
/// A method, getter, setter, index-getter, index-setter, operator overloader,
|
|
/// or factory.
|
|
///
|
|
/// Procedures can have the static, abstract, and/or external modifier, although
|
|
/// only the static and external modifiers may be used together.
|
|
///
|
|
/// For non-static procedures the name is required for dynamic dispatch.
|
|
/// For external procedures the name is required for identifying the external
|
|
/// implementation.
|
|
///
|
|
/// For methods, getters, and setters the name is just as it was declared.
|
|
/// For setters this does not include a trailing `=`.
|
|
/// For index-getters/setters, this is `[]` and `[]=`.
|
|
/// For operators, this is the token for the operator, e.g. `+` or `==`,
|
|
/// except for the unary minus operator, whose name is `unary-`.
|
|
@coq
|
|
class Procedure extends Member {
|
|
/// Start offset of the function in the source file it comes from.
|
|
///
|
|
/// Note that this includes annotations if any.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
|
|
/// start offset is not available (this is the default if none is specifically
|
|
/// set).
|
|
int startFileOffset = TreeNode.noOffset;
|
|
|
|
ProcedureKind kind;
|
|
int flags = 0;
|
|
// function is null if and only if abstract, external.
|
|
FunctionNode function;
|
|
|
|
// The function node's body might be lazily loaded, meaning that this value
|
|
// might not be set correctly yet. Make sure the body is loaded before
|
|
// returning anything.
|
|
int get transformerFlags {
|
|
function?.body;
|
|
return super.transformerFlags;
|
|
}
|
|
|
|
// The function node's body might be lazily loaded, meaning that this value
|
|
// might get overwritten later (when the body is read). To avoid that read the
|
|
// body now and only set the value afterwards.
|
|
void set transformerFlags(int newValue) {
|
|
function?.body;
|
|
super.transformerFlags = newValue;
|
|
}
|
|
|
|
// This function will set the transformer flags without loading the body.
|
|
// Used when reading the binary. For other cases one should probably use
|
|
// `transformerFlags = value;`.
|
|
void setTransformerFlagsWithoutLazyLoading(int newValue) {
|
|
super.transformerFlags = newValue;
|
|
}
|
|
|
|
Reference forwardingStubSuperTargetReference;
|
|
Reference forwardingStubInterfaceTargetReference;
|
|
|
|
Procedure(Name name, ProcedureKind kind, FunctionNode function,
|
|
{bool isAbstract: false,
|
|
bool isStatic: false,
|
|
bool isExternal: false,
|
|
bool isConst: false,
|
|
bool isForwardingStub: false,
|
|
bool isForwardingSemiStub: false,
|
|
int transformerFlags: 0,
|
|
Uri fileUri,
|
|
Reference reference,
|
|
Member forwardingStubSuperTarget,
|
|
Member forwardingStubInterfaceTarget})
|
|
: this.byReference(name, kind, function,
|
|
isAbstract: isAbstract,
|
|
isStatic: isStatic,
|
|
isExternal: isExternal,
|
|
isConst: isConst,
|
|
isForwardingStub: isForwardingStub,
|
|
isForwardingSemiStub: isForwardingSemiStub,
|
|
transformerFlags: transformerFlags,
|
|
fileUri: fileUri,
|
|
reference: reference,
|
|
forwardingStubSuperTargetReference:
|
|
getMemberReference(forwardingStubSuperTarget),
|
|
forwardingStubInterfaceTargetReference:
|
|
getMemberReference(forwardingStubInterfaceTarget));
|
|
|
|
Procedure.byReference(Name name, this.kind, this.function,
|
|
{bool isAbstract: false,
|
|
bool isStatic: false,
|
|
bool isExternal: false,
|
|
bool isConst: false,
|
|
bool isForwardingStub: false,
|
|
bool isForwardingSemiStub: false,
|
|
int transformerFlags: 0,
|
|
Uri fileUri,
|
|
Reference reference,
|
|
this.forwardingStubSuperTargetReference,
|
|
this.forwardingStubInterfaceTargetReference})
|
|
: super(name, fileUri, reference) {
|
|
function?.parent = this;
|
|
this.isAbstract = isAbstract;
|
|
this.isStatic = isStatic;
|
|
this.isExternal = isExternal;
|
|
this.isConst = isConst;
|
|
this.isForwardingStub = isForwardingStub;
|
|
this.isForwardingSemiStub = isForwardingSemiStub;
|
|
this.transformerFlags = transformerFlags;
|
|
}
|
|
|
|
static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
|
|
static const int FlagAbstract = 1 << 1;
|
|
static const int FlagExternal = 1 << 2;
|
|
static const int FlagConst = 1 << 3; // Only for external const factories.
|
|
static const int FlagForwardingStub = 1 << 4;
|
|
static const int FlagForwardingSemiStub = 1 << 5;
|
|
// TODO(29841): Remove this flag after the issue is resolved.
|
|
static const int FlagRedirectingFactoryConstructor = 1 << 6;
|
|
static const int FlagNoSuchMethodForwarder = 1 << 7;
|
|
|
|
bool get isStatic => flags & FlagStatic != 0;
|
|
bool get isAbstract => flags & FlagAbstract != 0;
|
|
bool get isExternal => flags & FlagExternal != 0;
|
|
|
|
/// True if this has the `const` modifier. This is only possible for external
|
|
/// constant factories, such as `String.fromEnvironment`.
|
|
bool get isConst => flags & FlagConst != 0;
|
|
|
|
/// If set, this flag indicates that this function's implementation exists
|
|
/// solely for the purpose of type checking arguments and forwarding to
|
|
/// [forwardingStubSuperTarget].
|
|
///
|
|
/// Note that just because this bit is set doesn't mean that the function was
|
|
/// not declared in the source; it's possible that this is a forwarding
|
|
/// semi-stub (see isForwardingSemiStub). To determine whether this function
|
|
/// was present in the source, consult [isSyntheticForwarder].
|
|
bool get isForwardingStub => flags & FlagForwardingStub != 0;
|
|
|
|
/// If set, this flag indicates that although this function is a forwarding
|
|
/// stub, it was present in the original source as an abstract method.
|
|
bool get isForwardingSemiStub => flags & FlagForwardingSemiStub != 0;
|
|
|
|
// Indicates if this [Procedure] represents a redirecting factory constructor
|
|
// and doesn't have a runnable body.
|
|
bool get isRedirectingFactoryConstructor {
|
|
return flags & FlagRedirectingFactoryConstructor != 0;
|
|
}
|
|
|
|
/// If set, this flag indicates that this function was not present in the
|
|
/// source, and it exists solely for the purpose of type checking arguments
|
|
/// and forwarding to [forwardingStubSuperTarget].
|
|
bool get isSyntheticForwarder => isForwardingStub && !isForwardingSemiStub;
|
|
|
|
bool get isNoSuchMethodForwarder => flags & FlagNoSuchMethodForwarder != 0;
|
|
|
|
void set isStatic(bool value) {
|
|
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
|
|
}
|
|
|
|
void set isAbstract(bool value) {
|
|
flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
|
|
}
|
|
|
|
void set isExternal(bool value) {
|
|
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
|
|
}
|
|
|
|
void set isConst(bool value) {
|
|
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
|
|
}
|
|
|
|
void set isForwardingStub(bool value) {
|
|
flags =
|
|
value ? (flags | FlagForwardingStub) : (flags & ~FlagForwardingStub);
|
|
}
|
|
|
|
void set isForwardingSemiStub(bool value) {
|
|
flags = value
|
|
? (flags | FlagForwardingSemiStub)
|
|
: (flags & ~FlagForwardingSemiStub);
|
|
}
|
|
|
|
void set isRedirectingFactoryConstructor(bool value) {
|
|
flags = value
|
|
? (flags | FlagRedirectingFactoryConstructor)
|
|
: (flags & ~FlagRedirectingFactoryConstructor);
|
|
}
|
|
|
|
void set isNoSuchMethodForwarder(bool value) {
|
|
flags = value
|
|
? (flags | FlagNoSuchMethodForwarder)
|
|
: (flags & ~FlagNoSuchMethodForwarder);
|
|
}
|
|
|
|
bool get isInstanceMember => !isStatic;
|
|
bool get isGetter => kind == ProcedureKind.Getter;
|
|
bool get isSetter => kind == ProcedureKind.Setter;
|
|
bool get isAccessor => isGetter || isSetter;
|
|
bool get hasGetter => kind != ProcedureKind.Setter;
|
|
bool get hasSetter => kind == ProcedureKind.Setter;
|
|
bool get isFactory => kind == ProcedureKind.Factory;
|
|
|
|
Member get forwardingStubSuperTarget =>
|
|
forwardingStubSuperTargetReference?.asMember;
|
|
|
|
void set forwardingStubSuperTarget(Member target) {
|
|
forwardingStubSuperTargetReference = getMemberReference(target);
|
|
}
|
|
|
|
Member get forwardingStubInterfaceTarget =>
|
|
forwardingStubInterfaceTargetReference?.asMember;
|
|
|
|
void set forwardingStubInterfaceTarget(Member target) {
|
|
forwardingStubInterfaceTargetReference = getMemberReference(target);
|
|
}
|
|
|
|
accept(MemberVisitor v) => v.visitProcedure(this);
|
|
|
|
acceptReference(MemberReferenceVisitor v) => v.visitProcedureReference(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
name?.accept(v);
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
if (function != null) {
|
|
function = function.accept(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
|
|
DartType get getterType {
|
|
return isGetter ? function.returnType : function.functionType;
|
|
}
|
|
|
|
DartType get setterType {
|
|
return isSetter
|
|
? function.positionalParameters[0].type
|
|
: const BottomType();
|
|
}
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
}
|
|
|
|
enum ProcedureKind {
|
|
Method,
|
|
Getter,
|
|
Setter,
|
|
Operator,
|
|
Factory,
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// CONSTRUCTOR INITIALIZERS
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// Part of an initializer list in a constructor.
|
|
abstract class Initializer extends TreeNode {
|
|
/// True if this is a synthetic constructor initializer.
|
|
@informative
|
|
bool isSynthetic = false;
|
|
|
|
accept(InitializerVisitor v);
|
|
}
|
|
|
|
/// An initializer with a compile-time error.
|
|
///
|
|
/// Should throw an exception at runtime.
|
|
//
|
|
// DESIGN TODO: The frontend should use this in a lot more cases to catch
|
|
// invalid cases.
|
|
class InvalidInitializer extends Initializer {
|
|
accept(InitializerVisitor v) => v.visitInvalidInitializer(this);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// A field assignment `field = value` occurring in the initializer list of
|
|
/// a constructor.
|
|
///
|
|
/// This node has nothing to do with declaration-site field initializers; those
|
|
/// are [Expression]s stored in [Field.initializer].
|
|
//
|
|
// TODO: The frontend should check that all final fields are initialized
|
|
// exactly once, and that no fields are assigned twice in the initializer list.
|
|
class FieldInitializer extends Initializer {
|
|
/// Reference to the field being initialized. Not null.
|
|
Reference fieldReference;
|
|
Expression value;
|
|
|
|
FieldInitializer(Field field, Expression value)
|
|
: this.byReference(field?.reference, value);
|
|
|
|
FieldInitializer.byReference(this.fieldReference, this.value) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
Field get field => fieldReference?.node;
|
|
|
|
void set field(Field field) {
|
|
fieldReference = field?.reference;
|
|
}
|
|
|
|
accept(InitializerVisitor v) => v.visitFieldInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
field?.acceptReference(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A super call `super(x,y)` occurring in the initializer list of a
|
|
/// constructor.
|
|
///
|
|
/// There are no type arguments on this call.
|
|
//
|
|
// TODO: The frontend should check that there is no more than one super call.
|
|
//
|
|
// DESIGN TODO: Consider if the frontend should insert type arguments derived
|
|
// from the extends clause.
|
|
class SuperInitializer extends Initializer {
|
|
/// Reference to the constructor being invoked in the super class. Not null.
|
|
Reference targetReference;
|
|
Arguments arguments;
|
|
|
|
SuperInitializer(Constructor target, Arguments arguments)
|
|
: this.byReference(getMemberReference(target), arguments);
|
|
|
|
SuperInitializer.byReference(this.targetReference, this.arguments) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Constructor get target => targetReference?.asConstructor;
|
|
|
|
void set target(Constructor target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
accept(InitializerVisitor v) => v.visitSuperInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A redirecting call `this(x,y)` occurring in the initializer list of
|
|
/// a constructor.
|
|
//
|
|
// TODO: The frontend should check that this is the only initializer and if the
|
|
// constructor has a body or if there is a cycle in the initializer calls.
|
|
class RedirectingInitializer extends Initializer {
|
|
/// Reference to the constructor being invoked in the same class. Not null.
|
|
Reference targetReference;
|
|
Arguments arguments;
|
|
|
|
RedirectingInitializer(Constructor target, Arguments arguments)
|
|
: this.byReference(getMemberReference(target), arguments);
|
|
|
|
RedirectingInitializer.byReference(this.targetReference, this.arguments) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Constructor get target => targetReference?.asConstructor;
|
|
|
|
void set target(Constructor target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
accept(InitializerVisitor v) => v.visitRedirectingInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Binding of a temporary variable in the initializer list of a constructor.
|
|
///
|
|
/// The variable is in scope for the remainder of the initializer list, but is
|
|
/// not in scope in the constructor body.
|
|
class LocalInitializer extends Initializer {
|
|
VariableDeclaration variable;
|
|
|
|
LocalInitializer(this.variable) {
|
|
variable?.parent = this;
|
|
}
|
|
|
|
accept(InitializerVisitor v) => v.visitLocalInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept(v);
|
|
variable?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class AssertInitializer extends Initializer {
|
|
AssertStatement statement;
|
|
|
|
AssertInitializer(this.statement) {
|
|
statement.parent = this;
|
|
}
|
|
|
|
accept(InitializerVisitor v) => v.visitAssertInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
statement.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
statement = statement.accept(v);
|
|
statement.parent = this;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// FUNCTIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A function declares parameters and has a body.
|
|
///
|
|
/// This may occur in a procedure, constructor, function expression, or local
|
|
/// function declaration.
|
|
@coq
|
|
class FunctionNode extends TreeNode {
|
|
/// End offset in the source file it comes from. Valid values are from 0 and
|
|
/// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
|
|
/// (this is the default if none is specifically set).
|
|
int fileEndOffset = TreeNode.noOffset;
|
|
|
|
/// Kernel async marker for the function.
|
|
///
|
|
/// See also [dartAsyncMarker].
|
|
AsyncMarker asyncMarker;
|
|
|
|
/// Dart async marker for the function.
|
|
///
|
|
/// See also [asyncMarker].
|
|
///
|
|
/// A Kernel function can represent a Dart function with a different async
|
|
/// marker.
|
|
///
|
|
/// For example, when async/await is translated away,
|
|
/// a Dart async function might be represented by a Kernel sync function.
|
|
AsyncMarker dartAsyncMarker;
|
|
|
|
List<TypeParameter> typeParameters;
|
|
int requiredParameterCount;
|
|
@coqsingledef
|
|
List<VariableDeclaration> positionalParameters;
|
|
@nocoq
|
|
List<VariableDeclaration> namedParameters;
|
|
DartType returnType; // Not null.
|
|
Statement _body;
|
|
|
|
void Function() lazyBuilder;
|
|
|
|
void _buildLazy() {
|
|
if (lazyBuilder != null) {
|
|
var lazyBuilderLocal = lazyBuilder;
|
|
lazyBuilder = null;
|
|
lazyBuilderLocal();
|
|
}
|
|
}
|
|
|
|
Statement get body {
|
|
_buildLazy();
|
|
return _body;
|
|
}
|
|
|
|
void set body(Statement body) {
|
|
_buildLazy();
|
|
_body = body;
|
|
}
|
|
|
|
FunctionNode(this._body,
|
|
{List<TypeParameter> typeParameters,
|
|
List<VariableDeclaration> positionalParameters,
|
|
List<VariableDeclaration> namedParameters,
|
|
int requiredParameterCount,
|
|
this.returnType: const DynamicType(),
|
|
this.asyncMarker: AsyncMarker.Sync,
|
|
this.dartAsyncMarker})
|
|
: this.positionalParameters =
|
|
positionalParameters ?? <VariableDeclaration>[],
|
|
this.requiredParameterCount =
|
|
requiredParameterCount ?? positionalParameters?.length ?? 0,
|
|
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
|
|
this.typeParameters = typeParameters ?? <TypeParameter>[] {
|
|
assert(returnType != null);
|
|
setParents(this.typeParameters, this);
|
|
setParents(this.positionalParameters, this);
|
|
setParents(this.namedParameters, this);
|
|
_body?.parent = this;
|
|
dartAsyncMarker ??= asyncMarker;
|
|
}
|
|
|
|
static DartType _getTypeOfVariable(VariableDeclaration node) => node.type;
|
|
|
|
static NamedType _getNamedTypeOfVariable(VariableDeclaration node) {
|
|
return new NamedType(node.name, node.type);
|
|
}
|
|
|
|
FunctionType get functionType {
|
|
TreeNode parent = this.parent;
|
|
List<NamedType> named =
|
|
namedParameters.map(_getNamedTypeOfVariable).toList(growable: false);
|
|
named.sort();
|
|
// We need create a copy of the list of type parameters, otherwise
|
|
// transformations like erasure don't work.
|
|
var typeParametersCopy = new List<TypeParameter>.from(parent is Constructor
|
|
? parent.enclosingClass.typeParameters
|
|
: typeParameters);
|
|
return new FunctionType(
|
|
positionalParameters.map(_getTypeOfVariable).toList(growable: false),
|
|
returnType,
|
|
namedParameters: named,
|
|
typeParameters: typeParametersCopy,
|
|
requiredParameterCount: requiredParameterCount);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitFunctionNode(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(typeParameters, v);
|
|
visitList(positionalParameters, v);
|
|
visitList(namedParameters, v);
|
|
returnType?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(typeParameters, v, this);
|
|
transformList(positionalParameters, v, this);
|
|
transformList(namedParameters, v, this);
|
|
returnType = v.visitDartType(returnType);
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AsyncMarker {
|
|
// Do not change the order of these, the frontends depend on it.
|
|
Sync,
|
|
SyncStar,
|
|
Async,
|
|
AsyncStar,
|
|
|
|
// `SyncYielding` is a marker that tells Dart VM that this function is an
|
|
// artificial closure introduced by an async transformer which desugared all
|
|
// async syntax into a combination of native yields and helper method calls.
|
|
//
|
|
// Native yields (formatted as `[yield]`) are semantically close to
|
|
// `yield x` statement: they denote a yield/resume point within a function
|
|
// but are completely decoupled from the notion of iterators. When
|
|
// execution of the closure reaches `[yield] x` it stops and return the
|
|
// value of `x` to the caller. If closure is called again it continues
|
|
// to the next statement after this yield as if it was suspended and resumed.
|
|
//
|
|
// Consider this example:
|
|
//
|
|
// g() {
|
|
// var :await_jump_var = 0;
|
|
// var :await_ctx_var;
|
|
//
|
|
// f(x) yielding {
|
|
// [yield] '${x}:0';
|
|
// [yield] '${x}:1';
|
|
// [yield] '${x}:2';
|
|
// }
|
|
//
|
|
// return f;
|
|
// }
|
|
//
|
|
// print(f('a')); /* prints 'a:0', :await_jump_var = 1 */
|
|
// print(f('b')); /* prints 'b:1', :await_jump_var = 2 */
|
|
// print(f('c')); /* prints 'c:2', :await_jump_var = 3 */
|
|
//
|
|
// Note: currently Dart VM implicitly relies on async transformer to
|
|
// inject certain artificial variables into g (like `:await_jump_var`).
|
|
// As such SyncYielding and native yield are not intended to be used on their
|
|
// own, but are rather an implementation artifact of the async transformer
|
|
// itself.
|
|
SyncYielding,
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// EXPRESSIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
@coq
|
|
abstract class Expression extends TreeNode {
|
|
/// Returns the static type of the expression.
|
|
///
|
|
/// Should only be used on code compiled in strong mode, as this method
|
|
/// assumes the IR is strongly typed.
|
|
DartType getStaticType(TypeEnvironment types);
|
|
|
|
/// Returns the static type of the expression as an instantiation of
|
|
/// [superclass].
|
|
///
|
|
/// Should only be used on code compiled in strong mode, as this method
|
|
/// assumes the IR is strongly typed.
|
|
///
|
|
/// This method furthermore assumes that the type of the expression actually
|
|
/// is a subtype of (some instantiation of) the given [superclass].
|
|
/// If this is not the case, either an exception is thrown or the raw type of
|
|
/// [superclass] is returned.
|
|
InterfaceType getStaticTypeAsInstanceOf(
|
|
Class superclass, TypeEnvironment types) {
|
|
// This method assumes the program is correctly typed, so if the superclass
|
|
// is not generic, we can just return its raw type without computing the
|
|
// type of this expression. It also ensures that all types are considered
|
|
// subtypes of Object (not just interface types), and function types are
|
|
// considered subtypes of Function.
|
|
if (superclass.typeParameters.isEmpty) {
|
|
return superclass.rawType;
|
|
}
|
|
var type = getStaticType(types);
|
|
while (type is TypeParameterType) {
|
|
type = (type as TypeParameterType).parameter.bound;
|
|
}
|
|
if (type is InterfaceType) {
|
|
var upcastType = types.hierarchy.getTypeAsInstanceOf(type, superclass);
|
|
if (upcastType != null) return upcastType;
|
|
} else if (type is BottomType) {
|
|
return superclass.bottomType;
|
|
}
|
|
types.typeError(this, '$type is not a subtype of $superclass');
|
|
return superclass.rawType;
|
|
}
|
|
|
|
accept(ExpressionVisitor v);
|
|
accept1(ExpressionVisitor1 v, arg);
|
|
}
|
|
|
|
/// An expression containing compile-time errors.
|
|
///
|
|
/// Should throw a runtime error when evaluated.
|
|
class InvalidExpression extends Expression {
|
|
String message;
|
|
|
|
InvalidExpression(this.message);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => const BottomType();
|
|
|
|
accept(ExpressionVisitor v) => v.visitInvalidExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitInvalidExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// Read a local variable, a local function, or a function parameter.
|
|
@coq
|
|
class VariableGet extends Expression {
|
|
VariableDeclaration variable;
|
|
@nocoq
|
|
DartType promotedType; // Null if not promoted.
|
|
|
|
VariableGet(this.variable, [this.promotedType]);
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return promotedType ?? variable.type;
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitVariableGet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitVariableGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
promotedType?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (promotedType != null) {
|
|
promotedType = v.visitDartType(promotedType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Assign a local variable or function parameter.
|
|
///
|
|
/// Evaluates to the value of [value].
|
|
class VariableSet extends Expression {
|
|
VariableDeclaration variable;
|
|
Expression value;
|
|
|
|
VariableSet(this.variable, this.value) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitVariableSet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitVariableSet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x.field`.
|
|
///
|
|
/// This may invoke a getter, read a field, or tear off a method.
|
|
@coq
|
|
class PropertyGet extends Expression {
|
|
Expression receiver;
|
|
@coq
|
|
Name name;
|
|
|
|
@nocoq
|
|
Reference interfaceTargetReference;
|
|
|
|
PropertyGet(Expression receiver, Name name, [Member interfaceTarget])
|
|
: this.byReference(receiver, name, getMemberReference(interfaceTarget));
|
|
|
|
PropertyGet.byReference(
|
|
this.receiver, this.name, this.interfaceTargetReference) {
|
|
receiver?.parent = this;
|
|
}
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member member) {
|
|
interfaceTargetReference = getMemberReference(member);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
var interfaceTarget = this.interfaceTarget;
|
|
if (interfaceTarget != null) {
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
|
|
return Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(interfaceTarget.getterType);
|
|
}
|
|
// Treat the properties of Object specially.
|
|
String nameString = name.name;
|
|
if (nameString == 'hashCode') {
|
|
return types.intType;
|
|
} else if (nameString == 'runtimeType') {
|
|
return types.typeType;
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitPropertyGet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitPropertyGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
name?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x.field = value`.
|
|
///
|
|
/// This may invoke a setter or assign a field.
|
|
///
|
|
/// Evaluates to the value of [value].
|
|
class PropertySet extends Expression {
|
|
Expression receiver;
|
|
Name name;
|
|
Expression value;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
PropertySet(Expression receiver, Name name, Expression value,
|
|
[Member interfaceTarget])
|
|
: this.byReference(
|
|
receiver, name, value, getMemberReference(interfaceTarget));
|
|
|
|
PropertySet.byReference(
|
|
this.receiver, this.name, this.value, this.interfaceTargetReference) {
|
|
receiver?.parent = this;
|
|
value?.parent = this;
|
|
}
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member member) {
|
|
interfaceTargetReference = getMemberReference(member);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitPropertySet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitPropertySet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
name?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Directly read a field, call a getter, or tear off a method.
|
|
class DirectPropertyGet extends Expression {
|
|
Expression receiver;
|
|
Reference targetReference;
|
|
|
|
DirectPropertyGet(Expression receiver, Member target)
|
|
: this.byReference(receiver, getMemberReference(target));
|
|
|
|
DirectPropertyGet.byReference(this.receiver, this.targetReference) {
|
|
receiver?.parent = this;
|
|
}
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
target?.acceptReference(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitDirectPropertyGet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertyGet(this, arg);
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
Class superclass = target.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
|
|
return Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(target.getterType);
|
|
}
|
|
}
|
|
|
|
/// Directly assign a field, or call a setter.
|
|
///
|
|
/// Evaluates to the value of [value].
|
|
class DirectPropertySet extends Expression {
|
|
Expression receiver;
|
|
Reference targetReference;
|
|
Expression value;
|
|
|
|
DirectPropertySet(Expression receiver, Member target, Expression value)
|
|
: this.byReference(receiver, getMemberReference(target), value);
|
|
|
|
DirectPropertySet.byReference(
|
|
this.receiver, this.targetReference, this.value) {
|
|
receiver?.parent = this;
|
|
value?.parent = this;
|
|
}
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
target?.acceptReference(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitDirectPropertySet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertySet(this, arg);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
|
|
}
|
|
|
|
/// Directly call an instance method, bypassing ordinary dispatch.
|
|
class DirectMethodInvocation extends InvocationExpression {
|
|
Expression receiver;
|
|
Reference targetReference;
|
|
Arguments arguments;
|
|
|
|
DirectMethodInvocation(
|
|
Expression receiver, Procedure target, Arguments arguments)
|
|
: this.byReference(receiver, getMemberReference(target), arguments);
|
|
|
|
DirectMethodInvocation.byReference(
|
|
this.receiver, this.targetReference, this.arguments) {
|
|
receiver?.parent = this;
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Procedure get target => targetReference?.asProcedure;
|
|
|
|
void set target(Procedure target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
Name get name => target?.name;
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitDirectMethodInvocation(this);
|
|
accept1(ExpressionVisitor1 v, arg) =>
|
|
v.visitDirectMethodInvocation(this, arg);
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
if (types.isOverloadedArithmeticOperator(target)) {
|
|
return types.getTypeOfOverloadedArithmetic(receiver.getStaticType(types),
|
|
arguments.positional[0].getStaticType(types));
|
|
}
|
|
Class superclass = target.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
|
|
var returnType = Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(target.function.returnType);
|
|
return Substitution.fromPairs(
|
|
target.function.typeParameters, arguments.types)
|
|
.substituteType(returnType);
|
|
}
|
|
}
|
|
|
|
/// Expression of form `super.field`.
|
|
///
|
|
/// This may invoke a getter, read a field, or tear off a method.
|
|
class SuperPropertyGet extends Expression {
|
|
Name name;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
SuperPropertyGet(Name name, [Member interfaceTarget])
|
|
: this.byReference(name, getMemberReference(interfaceTarget));
|
|
|
|
SuperPropertyGet.byReference(this.name, this.interfaceTargetReference);
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member member) {
|
|
interfaceTargetReference = getMemberReference(member);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
Class declaringClass = interfaceTarget.enclosingClass;
|
|
if (declaringClass.typeParameters.isEmpty) {
|
|
return interfaceTarget.getterType;
|
|
}
|
|
var receiver =
|
|
types.hierarchy.getTypeAsInstanceOf(types.thisType, declaringClass);
|
|
return Substitution.fromInterfaceType(receiver)
|
|
.substituteType(interfaceTarget.getterType);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitSuperPropertyGet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertyGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
name?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// Expression of form `super.field = value`.
|
|
///
|
|
/// This may invoke a setter or assign a field.
|
|
///
|
|
/// Evaluates to the value of [value].
|
|
class SuperPropertySet extends Expression {
|
|
Name name;
|
|
Expression value;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
SuperPropertySet(Name name, Expression value, Member interfaceTarget)
|
|
: this.byReference(name, value, getMemberReference(interfaceTarget));
|
|
|
|
SuperPropertySet.byReference(
|
|
this.name, this.value, this.interfaceTargetReference) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member member) {
|
|
interfaceTargetReference = getMemberReference(member);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitSuperPropertySet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertySet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
name?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Read a static field, call a static getter, or tear off a static method.
|
|
class StaticGet extends Expression {
|
|
/// A static field, getter, or method (for tear-off).
|
|
Reference targetReference;
|
|
|
|
StaticGet(Member target) : this.byReference(getMemberReference(target));
|
|
|
|
StaticGet.byReference(this.targetReference);
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => target.getterType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitStaticGet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitStaticGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// Assign a static field or call a static setter.
|
|
///
|
|
/// Evaluates to the value of [value].
|
|
class StaticSet extends Expression {
|
|
/// A mutable static field or a static setter.
|
|
Reference targetReference;
|
|
Expression value;
|
|
|
|
StaticSet(Member target, Expression value)
|
|
: this.byReference(getMemberReference(target), value);
|
|
|
|
StaticSet.byReference(this.targetReference, this.value) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitStaticSet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitStaticSet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The arguments to a function call, divided into type arguments,
|
|
/// positional arguments, and named arguments.
|
|
@coq
|
|
class Arguments extends TreeNode {
|
|
@nocoq
|
|
final List<DartType> types;
|
|
@coqsingle
|
|
final List<Expression> positional;
|
|
List<NamedExpression> named;
|
|
|
|
Arguments(this.positional,
|
|
{List<DartType> types, List<NamedExpression> named})
|
|
: this.types = types ?? <DartType>[],
|
|
this.named = named ?? <NamedExpression>[] {
|
|
setParents(this.positional, this);
|
|
setParents(this.named, this);
|
|
}
|
|
|
|
Arguments.empty()
|
|
: types = <DartType>[],
|
|
positional = <Expression>[],
|
|
named = <NamedExpression>[];
|
|
|
|
factory Arguments.forwarded(FunctionNode function) {
|
|
return new Arguments(
|
|
function.positionalParameters.map((p) => new VariableGet(p)).toList(),
|
|
named: function.namedParameters
|
|
.map((p) => new NamedExpression(p.name, new VariableGet(p)))
|
|
.toList(),
|
|
types: function.typeParameters
|
|
.map((p) => new TypeParameterType(p))
|
|
.toList());
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitArguments(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(types, v);
|
|
visitList(positional, v);
|
|
visitList(named, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformTypeList(types, v);
|
|
transformList(positional, v, this);
|
|
transformList(named, v, this);
|
|
}
|
|
}
|
|
|
|
/// A named argument, `name: value`.
|
|
class NamedExpression extends TreeNode {
|
|
String name;
|
|
Expression value;
|
|
|
|
NamedExpression(this.name, this.value) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitNamedExpression(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Common super class for [DirectMethodInvocation], [MethodInvocation],
|
|
/// [SuperMethodInvocation], [StaticInvocation], and [ConstructorInvocation].
|
|
@coq
|
|
abstract class InvocationExpression extends Expression {
|
|
Arguments get arguments;
|
|
set arguments(Arguments value);
|
|
|
|
/// Name of the invoked method.
|
|
///
|
|
/// May be `null` if the target is a synthetic static member without a name.
|
|
Name get name;
|
|
}
|
|
|
|
/// Expression of form `x.foo(y)`.
|
|
@coq
|
|
class MethodInvocation extends InvocationExpression {
|
|
Expression receiver;
|
|
Name name;
|
|
Arguments arguments;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
MethodInvocation(Expression receiver, Name name, Arguments arguments,
|
|
[Member interfaceTarget])
|
|
: this.byReference(
|
|
receiver, name, arguments, getMemberReference(interfaceTarget));
|
|
|
|
MethodInvocation.byReference(
|
|
this.receiver, this.name, this.arguments, this.interfaceTargetReference) {
|
|
receiver?.parent = this;
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member target) {
|
|
interfaceTargetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
var interfaceTarget = this.interfaceTarget;
|
|
if (interfaceTarget != null) {
|
|
if (interfaceTarget is Procedure &&
|
|
types.isOverloadedArithmeticOperator(interfaceTarget)) {
|
|
return types.getTypeOfOverloadedArithmetic(
|
|
receiver.getStaticType(types),
|
|
arguments.positional[0].getStaticType(types));
|
|
}
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
|
|
var getterType = Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(interfaceTarget.getterType);
|
|
if (getterType is FunctionType) {
|
|
return Substitution.fromPairs(
|
|
getterType.typeParameters, arguments.types)
|
|
.substituteType(getterType.returnType);
|
|
} else {
|
|
return const DynamicType();
|
|
}
|
|
}
|
|
if (name.name == 'call') {
|
|
var receiverType = receiver.getStaticType(types);
|
|
if (receiverType is FunctionType) {
|
|
if (receiverType.typeParameters.length != arguments.types.length) {
|
|
return const BottomType();
|
|
}
|
|
return Substitution.fromPairs(
|
|
receiverType.typeParameters, arguments.types)
|
|
.substituteType(receiverType.returnType);
|
|
}
|
|
}
|
|
if (name.name == '==') {
|
|
// We use this special case to simplify generation of '==' checks.
|
|
return types.boolType;
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitMethodInvocation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitMethodInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
name?.accept(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `super.foo(x)`.
|
|
///
|
|
/// The provided arguments might not match the parameters of the target.
|
|
class SuperMethodInvocation extends InvocationExpression {
|
|
Name name;
|
|
Arguments arguments;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
SuperMethodInvocation(Name name, Arguments arguments,
|
|
[Procedure interfaceTarget])
|
|
: this.byReference(name, arguments, getMemberReference(interfaceTarget));
|
|
|
|
SuperMethodInvocation.byReference(
|
|
this.name, this.arguments, this.interfaceTargetReference) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Procedure get interfaceTarget => interfaceTargetReference?.asProcedure;
|
|
|
|
void set interfaceTarget(Procedure target) {
|
|
interfaceTargetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
if (interfaceTarget == null) return const DynamicType();
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
var receiverType =
|
|
types.hierarchy.getTypeAsInstanceOf(types.thisType, superclass);
|
|
var returnType = Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(interfaceTarget.function.returnType);
|
|
return Substitution.fromPairs(
|
|
interfaceTarget.function.typeParameters, arguments.types)
|
|
.substituteType(returnType);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitSuperMethodInvocation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitSuperMethodInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
name?.accept(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `foo(x)`, or `const foo(x)` if the target is an
|
|
/// external constant factory.
|
|
///
|
|
/// The provided arguments might not match the parameters of the target.
|
|
class StaticInvocation extends InvocationExpression {
|
|
Reference targetReference;
|
|
Arguments arguments;
|
|
|
|
/// True if this is a constant call to an external constant factory.
|
|
bool isConst;
|
|
|
|
Name get name => target?.name;
|
|
|
|
StaticInvocation(Procedure target, Arguments arguments, {bool isConst: false})
|
|
: this.byReference(getMemberReference(target), arguments,
|
|
isConst: isConst);
|
|
|
|
StaticInvocation.byReference(this.targetReference, this.arguments,
|
|
{this.isConst: false}) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Procedure get target => targetReference?.asProcedure;
|
|
|
|
void set target(Procedure target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return Substitution.fromPairs(
|
|
target.function.typeParameters, arguments.types)
|
|
.substituteType(target.function.returnType);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitStaticInvocation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitStaticInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `new Foo(x)` or `const Foo(x)`.
|
|
///
|
|
/// The provided arguments might not match the parameters of the target.
|
|
//
|
|
// DESIGN TODO: Should we pass type arguments in a separate field
|
|
// `classTypeArguments`? They are quite different from type arguments to
|
|
// generic functions.
|
|
@coq
|
|
class ConstructorInvocation extends InvocationExpression {
|
|
Reference targetReference;
|
|
@nocoq
|
|
Arguments arguments;
|
|
bool isConst;
|
|
|
|
Name get name => target?.name;
|
|
|
|
ConstructorInvocation(Constructor target, Arguments arguments,
|
|
{bool isConst: false})
|
|
: this.byReference(getMemberReference(target), arguments,
|
|
isConst: isConst);
|
|
|
|
ConstructorInvocation.byReference(this.targetReference, this.arguments,
|
|
{this.isConst: false}) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Constructor get target => targetReference?.asConstructor;
|
|
|
|
void set target(Constructor target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return arguments.types.isEmpty
|
|
? target.enclosingClass.rawType
|
|
: new InterfaceType(target.enclosingClass, arguments.types);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitConstructorInvocation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitConstructorInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
InterfaceType get constructedType {
|
|
return arguments.types.isEmpty
|
|
? target.enclosingClass.rawType
|
|
: new InterfaceType(target.enclosingClass, arguments.types);
|
|
}
|
|
}
|
|
|
|
/// An explicit type instantiation of a generic function.
|
|
class Instantiation extends Expression {
|
|
Expression expression;
|
|
final List<DartType> typeArguments;
|
|
|
|
Instantiation(this.expression, this.typeArguments) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
FunctionType type = expression.getStaticType(types);
|
|
return Substitution.fromPairs(type.typeParameters, typeArguments)
|
|
.substituteType(type.withoutTypeParameters);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitInstantiation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitInstantiation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
transformTypeList(typeArguments, v);
|
|
}
|
|
}
|
|
|
|
/// Expression of form `!x`.
|
|
///
|
|
/// The `is!` and `!=` operators are desugared into [Not] nodes with `is` and
|
|
/// `==` expressions inside, respectively.
|
|
class Not extends Expression {
|
|
Expression operand;
|
|
|
|
Not(this.operand) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.boolType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitNot(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitNot(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept(v);
|
|
operand?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x && y` or `x || y`
|
|
class LogicalExpression extends Expression {
|
|
Expression left;
|
|
String operator; // && or || or ??
|
|
Expression right;
|
|
|
|
LogicalExpression(this.left, this.operator, this.right) {
|
|
left?.parent = this;
|
|
right?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.boolType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitLogicalExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitLogicalExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
left?.accept(v);
|
|
right?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (left != null) {
|
|
left = left.accept(v);
|
|
left?.parent = this;
|
|
}
|
|
if (right != null) {
|
|
right = right.accept(v);
|
|
right?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x ? y : z`.
|
|
class ConditionalExpression extends Expression {
|
|
Expression condition;
|
|
Expression then;
|
|
Expression otherwise;
|
|
|
|
/// The static type of the expression. Should not be `null`.
|
|
DartType staticType;
|
|
|
|
ConditionalExpression(
|
|
this.condition, this.then, this.otherwise, this.staticType) {
|
|
condition?.parent = this;
|
|
then?.parent = this;
|
|
otherwise?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => staticType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitConditionalExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitConditionalExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
then?.accept(v);
|
|
otherwise?.accept(v);
|
|
staticType?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (then != null) {
|
|
then = then.accept(v);
|
|
then?.parent = this;
|
|
}
|
|
if (otherwise != null) {
|
|
otherwise = otherwise.accept(v);
|
|
otherwise?.parent = this;
|
|
}
|
|
if (staticType != null) {
|
|
staticType = v.visitDartType(staticType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert expressions to strings and concatenate them. Semantically, calls
|
|
/// `toString` on every argument, checks that a string is returned, and returns
|
|
/// the concatenation of all the strings.
|
|
///
|
|
/// If [expressions] is empty then an empty string is returned.
|
|
///
|
|
/// These arise from string interpolations and adjacent string literals.
|
|
class StringConcatenation extends Expression {
|
|
final List<Expression> expressions;
|
|
|
|
StringConcatenation(this.expressions) {
|
|
setParents(expressions, this);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.stringType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitStringConcatenation(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitStringConcatenation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(expressions, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(expressions, v, this);
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x is T`.
|
|
class IsExpression extends Expression {
|
|
Expression operand;
|
|
DartType type;
|
|
|
|
IsExpression(this.operand, this.type) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.boolType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitIsExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitIsExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept(v);
|
|
operand?.parent = this;
|
|
}
|
|
type = v.visitDartType(type);
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x as T`.
|
|
class AsExpression extends Expression {
|
|
int flags = 0;
|
|
Expression operand;
|
|
DartType type;
|
|
|
|
AsExpression(this.operand, this.type) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
// Must match serialized bit positions.
|
|
static const int FlagTypeError = 1 << 0;
|
|
|
|
/// Indicates the type of error that should be thrown if the check fails.
|
|
///
|
|
/// `true` means that a TypeError should be thrown. `false` means that a
|
|
/// CastError should be thrown.
|
|
bool get isTypeError => flags & FlagTypeError != 0;
|
|
|
|
void set isTypeError(bool value) {
|
|
flags = value ? (flags | FlagTypeError) : (flags & ~FlagTypeError);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => type;
|
|
|
|
accept(ExpressionVisitor v) => v.visitAsExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitAsExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept(v);
|
|
operand?.parent = this;
|
|
}
|
|
type = v.visitDartType(type);
|
|
}
|
|
}
|
|
|
|
/// An integer, double, boolean, string, or null constant.
|
|
abstract class BasicLiteral extends Expression {
|
|
Object get value;
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class StringLiteral extends BasicLiteral {
|
|
String value;
|
|
|
|
StringLiteral(this.value);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.stringType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitStringLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitStringLiteral(this, arg);
|
|
}
|
|
|
|
class IntLiteral extends BasicLiteral {
|
|
/// Note that this value holds a uint64 value.
|
|
/// E.g. "0x8000000000000000" will be saved as "-9223372036854775808" despite
|
|
/// technically (on some platforms, particularly Javascript) being positive.
|
|
/// If the number is meant to be negative it will be wrapped in a "unary-".
|
|
int value;
|
|
|
|
IntLiteral(this.value);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.intType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitIntLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitIntLiteral(this, arg);
|
|
}
|
|
|
|
class DoubleLiteral extends BasicLiteral {
|
|
double value;
|
|
|
|
DoubleLiteral(this.value);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.doubleType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitDoubleLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitDoubleLiteral(this, arg);
|
|
}
|
|
|
|
class BoolLiteral extends BasicLiteral {
|
|
bool value;
|
|
|
|
BoolLiteral(this.value);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.boolType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitBoolLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitBoolLiteral(this, arg);
|
|
}
|
|
|
|
class NullLiteral extends BasicLiteral {
|
|
Object get value => null;
|
|
|
|
DartType getStaticType(TypeEnvironment types) => const BottomType();
|
|
|
|
accept(ExpressionVisitor v) => v.visitNullLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitNullLiteral(this, arg);
|
|
}
|
|
|
|
class SymbolLiteral extends Expression {
|
|
String value; // Everything strictly after the '#'.
|
|
|
|
SymbolLiteral(this.value);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.symbolType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitSymbolLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitSymbolLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class TypeLiteral extends Expression {
|
|
DartType type;
|
|
|
|
TypeLiteral(this.type);
|
|
|
|
DartType getStaticType(TypeEnvironment types) => types.typeType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitTypeLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitTypeLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
type = v.visitDartType(type);
|
|
}
|
|
}
|
|
|
|
class ThisExpression extends Expression {
|
|
DartType getStaticType(TypeEnvironment types) => types.thisType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitThisExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitThisExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class Rethrow extends Expression {
|
|
DartType getStaticType(TypeEnvironment types) => const BottomType();
|
|
|
|
accept(ExpressionVisitor v) => v.visitRethrow(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitRethrow(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class Throw extends Expression {
|
|
Expression expression;
|
|
|
|
Throw(this.expression) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => const BottomType();
|
|
|
|
accept(ExpressionVisitor v) => v.visitThrow(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitThrow(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ListLiteral extends Expression {
|
|
bool isConst;
|
|
DartType typeArgument; // Not null, defaults to DynamicType.
|
|
final List<Expression> expressions;
|
|
|
|
ListLiteral(this.expressions,
|
|
{this.typeArgument: const DynamicType(), this.isConst: false}) {
|
|
assert(typeArgument != null);
|
|
setParents(expressions, this);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return types.literalListType(typeArgument);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitListLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitListLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument?.accept(v);
|
|
visitList(expressions, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
typeArgument = v.visitDartType(typeArgument);
|
|
transformList(expressions, v, this);
|
|
}
|
|
}
|
|
|
|
class MapLiteral extends Expression {
|
|
bool isConst;
|
|
DartType keyType; // Not null, defaults to DynamicType.
|
|
DartType valueType; // Not null, defaults to DynamicType.
|
|
final List<MapEntry> entries;
|
|
|
|
MapLiteral(this.entries,
|
|
{this.keyType: const DynamicType(),
|
|
this.valueType: const DynamicType(),
|
|
this.isConst: false}) {
|
|
assert(keyType != null);
|
|
assert(valueType != null);
|
|
setParents(entries, this);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return types.literalMapType(keyType, valueType);
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitMapLiteral(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitMapLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
keyType?.accept(v);
|
|
valueType?.accept(v);
|
|
visitList(entries, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
keyType = v.visitDartType(keyType);
|
|
valueType = v.visitDartType(valueType);
|
|
transformList(entries, v, this);
|
|
}
|
|
}
|
|
|
|
class MapEntry extends TreeNode {
|
|
Expression key;
|
|
Expression value;
|
|
|
|
MapEntry(this.key, this.value) {
|
|
key?.parent = this;
|
|
value?.parent = this;
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitMapEntry(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
key?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (key != null) {
|
|
key = key.accept(v);
|
|
key?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `await x`.
|
|
class AwaitExpression extends Expression {
|
|
Expression operand;
|
|
|
|
AwaitExpression(this.operand) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return types.unfutureType(operand.getStaticType(types));
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitAwaitExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitAwaitExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept(v);
|
|
operand?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expression of form `(x,y) => ...` or `(x,y) { ... }`
|
|
///
|
|
/// The arrow-body form `=> e` is desugared into `return e;`.
|
|
class FunctionExpression extends Expression {
|
|
FunctionNode function;
|
|
|
|
FunctionExpression(this.function) {
|
|
function?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => function.functionType;
|
|
|
|
accept(ExpressionVisitor v) => v.visitFunctionExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitFunctionExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (function != null) {
|
|
function = function.accept(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ConstantExpression extends Expression {
|
|
Constant constant;
|
|
|
|
ConstantExpression(this.constant) {
|
|
assert(constant != null);
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => constant.getType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitConstantExpression(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitConstantExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
constant?.acceptReference(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
constant = v.visitConstant(constant);
|
|
}
|
|
}
|
|
|
|
/// Synthetic expression of form `let v = x in y`
|
|
class Let extends Expression {
|
|
VariableDeclaration variable; // Must have an initializer.
|
|
Expression body;
|
|
|
|
Let(this.variable, this.body) {
|
|
variable?.parent = this;
|
|
body?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(TypeEnvironment types) => body.getStaticType(types);
|
|
|
|
accept(ExpressionVisitor v) => v.visitLet(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitLet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Attempt to load the library referred to by a deferred import.
|
|
///
|
|
/// This instruction is concerned with:
|
|
/// - keeping track whether the deferred import is marked as 'loaded'
|
|
/// - keeping track of whether the library code has already been downloaded
|
|
/// - actually downloading and linking the library
|
|
///
|
|
/// Should return a future. The value in this future will be the same value
|
|
/// seen by callers of `loadLibrary` functions.
|
|
///
|
|
/// On backends that link the entire program eagerly, this instruction needs
|
|
/// to mark the deferred import as 'loaded' and return a future.
|
|
class LoadLibrary extends Expression {
|
|
/// Reference to a deferred import in the enclosing library.
|
|
LibraryDependency import;
|
|
|
|
LoadLibrary(this.import);
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return types.futureType(const DynamicType());
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitLoadLibrary(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitLoadLibrary(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
/// Checks that the given deferred import has been marked as 'loaded'.
|
|
class CheckLibraryIsLoaded extends Expression {
|
|
/// Reference to a deferred import in the enclosing library.
|
|
LibraryDependency import;
|
|
|
|
CheckLibraryIsLoaded(this.import);
|
|
|
|
DartType getStaticType(TypeEnvironment types) {
|
|
return types.objectType;
|
|
}
|
|
|
|
accept(ExpressionVisitor v) => v.visitCheckLibraryIsLoaded(this);
|
|
accept1(ExpressionVisitor1 v, arg) => v.visitCheckLibraryIsLoaded(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// STATEMENTS
|
|
// ------------------------------------------------------------------------
|
|
|
|
@coq
|
|
abstract class Statement extends TreeNode {
|
|
accept(StatementVisitor v);
|
|
accept1(StatementVisitor1 v, arg);
|
|
}
|
|
|
|
@coq
|
|
class ExpressionStatement extends Statement {
|
|
Expression expression;
|
|
|
|
ExpressionStatement(this.expression) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitExpressionStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitExpressionStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
@coq
|
|
class Block extends Statement {
|
|
final List<Statement> statements;
|
|
|
|
Block(this.statements) {
|
|
// Ensure statements is mutable.
|
|
assert((statements
|
|
..add(null)
|
|
..removeLast()) !=
|
|
null);
|
|
setParents(statements, this);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitBlock(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitBlock(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(statements, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(statements, v, this);
|
|
}
|
|
|
|
void addStatement(Statement node) {
|
|
statements.add(node);
|
|
node.parent = this;
|
|
}
|
|
}
|
|
|
|
/// A block that is only executed when asserts are enabled.
|
|
///
|
|
/// Sometimes arbitrary statements must be guarded by whether asserts are
|
|
/// enabled. For example, when a subexpression of an assert in async code is
|
|
/// linearized and named, it can produce such a block of statements.
|
|
class AssertBlock extends Statement {
|
|
final List<Statement> statements;
|
|
|
|
AssertBlock(this.statements) {
|
|
// Ensure statements is mutable.
|
|
assert((statements
|
|
..add(null)
|
|
..removeLast()) !=
|
|
null);
|
|
setParents(statements, this);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitAssertBlock(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitAssertBlock(this, arg);
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(statements, v, this);
|
|
}
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(statements, v);
|
|
}
|
|
|
|
void addStatement(Statement node) {
|
|
statements.add(node);
|
|
node.parent = this;
|
|
}
|
|
}
|
|
|
|
class EmptyStatement extends Statement {
|
|
accept(StatementVisitor v) => v.visitEmptyStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitEmptyStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class AssertStatement extends Statement {
|
|
Expression condition;
|
|
Expression message; // May be null.
|
|
int conditionStartOffset;
|
|
int conditionEndOffset;
|
|
|
|
AssertStatement(this.condition,
|
|
{this.message, this.conditionStartOffset, this.conditionEndOffset}) {
|
|
condition?.parent = this;
|
|
message?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitAssertStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitAssertStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
message?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (message != null) {
|
|
message = message.accept(v);
|
|
message?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A target of a [Break] statement.
|
|
///
|
|
/// The label itself has no name; breaks reference the statement directly.
|
|
///
|
|
/// The frontend does not generate labeled statements without uses.
|
|
class LabeledStatement extends Statement {
|
|
Statement body;
|
|
|
|
LabeledStatement(this.body) {
|
|
body?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitLabeledStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitLabeledStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Breaks out of an enclosing [LabeledStatement].
|
|
///
|
|
/// Both `break` and loop `continue` statements are translated into this node.
|
|
///
|
|
/// For example, the following loop with a `continue` will be desugared:
|
|
///
|
|
/// while(x) {
|
|
/// if (y) continue;
|
|
/// BODY'
|
|
/// }
|
|
///
|
|
/// ==>
|
|
///
|
|
/// while(x) {
|
|
/// L: {
|
|
/// if (y) break L;
|
|
/// BODY'
|
|
/// }
|
|
/// }
|
|
//
|
|
class BreakStatement extends Statement {
|
|
LabeledStatement target;
|
|
|
|
BreakStatement(this.target);
|
|
|
|
accept(StatementVisitor v) => v.visitBreakStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitBreakStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class WhileStatement extends Statement {
|
|
Expression condition;
|
|
Statement body;
|
|
|
|
WhileStatement(this.condition, this.body) {
|
|
condition?.parent = this;
|
|
body?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitWhileStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitWhileStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class DoStatement extends Statement {
|
|
Statement body;
|
|
Expression condition;
|
|
|
|
DoStatement(this.body, this.condition) {
|
|
body?.parent = this;
|
|
condition?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitDoStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitDoStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
condition?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ForStatement extends Statement {
|
|
final List<VariableDeclaration> variables; // May be empty, but not null.
|
|
Expression condition; // May be null.
|
|
final List<Expression> updates; // May be empty, but not null.
|
|
Statement body;
|
|
|
|
ForStatement(this.variables, this.condition, this.updates, this.body) {
|
|
setParents(variables, this);
|
|
condition?.parent = this;
|
|
setParents(updates, this);
|
|
body?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitForStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitForStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(variables, v);
|
|
condition?.accept(v);
|
|
visitList(updates, v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(variables, v, this);
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
transformList(updates, v, this);
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ForInStatement extends Statement {
|
|
/// Offset in the source file it comes from.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
|
|
/// offset is not available (this is the default if none is specifically set).
|
|
int bodyOffset = TreeNode.noOffset;
|
|
|
|
VariableDeclaration variable; // Has no initializer.
|
|
Expression iterable;
|
|
Statement body;
|
|
bool isAsync; // True if this is an 'await for' loop.
|
|
|
|
ForInStatement(this.variable, this.iterable, this.body,
|
|
{this.isAsync: false}) {
|
|
variable?.parent = this;
|
|
iterable?.parent = this;
|
|
body?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitForInStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitForInStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
iterable?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (iterable != null) {
|
|
iterable = iterable.accept(v);
|
|
iterable?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Statement of form `switch (e) { case x: ... }`.
|
|
///
|
|
/// Adjacent case clauses have been merged into a single [SwitchCase]. A runtime
|
|
/// exception must be thrown if one [SwitchCase] falls through to another case.
|
|
class SwitchStatement extends Statement {
|
|
Expression expression;
|
|
final List<SwitchCase> cases;
|
|
|
|
SwitchStatement(this.expression, this.cases) {
|
|
expression?.parent = this;
|
|
setParents(cases, this);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitSwitchStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitSwitchStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
visitList(cases, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
transformList(cases, v, this);
|
|
}
|
|
}
|
|
|
|
/// A group of `case` clauses and/or a `default` clause.
|
|
///
|
|
/// This is a potential target of [ContinueSwitchStatement].
|
|
class SwitchCase extends TreeNode {
|
|
final List<Expression> expressions;
|
|
final List<int> expressionOffsets;
|
|
Statement body;
|
|
bool isDefault;
|
|
|
|
SwitchCase(this.expressions, this.expressionOffsets, this.body,
|
|
{this.isDefault: false}) {
|
|
setParents(expressions, this);
|
|
body?.parent = this;
|
|
}
|
|
|
|
SwitchCase.defaultCase(this.body)
|
|
: isDefault = true,
|
|
expressions = <Expression>[],
|
|
expressionOffsets = <int>[] {
|
|
body?.parent = this;
|
|
}
|
|
|
|
SwitchCase.empty()
|
|
: expressions = <Expression>[],
|
|
expressionOffsets = <int>[],
|
|
body = null,
|
|
isDefault = false;
|
|
|
|
accept(TreeVisitor v) => v.visitSwitchCase(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(expressions, v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(expressions, v, this);
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Jump to a case in an enclosing switch.
|
|
class ContinueSwitchStatement extends Statement {
|
|
SwitchCase target;
|
|
|
|
ContinueSwitchStatement(this.target);
|
|
|
|
accept(StatementVisitor v) => v.visitContinueSwitchStatement(this);
|
|
accept1(StatementVisitor1 v, arg) =>
|
|
v.visitContinueSwitchStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
}
|
|
|
|
class IfStatement extends Statement {
|
|
Expression condition;
|
|
Statement then;
|
|
Statement otherwise;
|
|
|
|
IfStatement(this.condition, this.then, this.otherwise) {
|
|
condition?.parent = this;
|
|
then?.parent = this;
|
|
otherwise?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitIfStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitIfStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
then?.accept(v);
|
|
otherwise?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (then != null) {
|
|
then = then.accept(v);
|
|
then?.parent = this;
|
|
}
|
|
if (otherwise != null) {
|
|
otherwise = otherwise.accept(v);
|
|
otherwise?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
@coq
|
|
class ReturnStatement extends Statement {
|
|
Expression expression; // May be null.
|
|
|
|
ReturnStatement([this.expression]) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitReturnStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitReturnStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class TryCatch extends Statement {
|
|
Statement body;
|
|
List<Catch> catches;
|
|
bool isSynthetic;
|
|
|
|
TryCatch(this.body, this.catches, {this.isSynthetic: false}) {
|
|
body?.parent = this;
|
|
setParents(catches, this);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitTryCatch(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitTryCatch(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
visitList(catches, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
transformList(catches, v, this);
|
|
}
|
|
}
|
|
|
|
class Catch extends TreeNode {
|
|
DartType guard; // Not null, defaults to dynamic.
|
|
VariableDeclaration exception; // May be null.
|
|
VariableDeclaration stackTrace; // May be null.
|
|
Statement body;
|
|
|
|
Catch(this.exception, this.body,
|
|
{this.guard: const DynamicType(), this.stackTrace}) {
|
|
assert(guard != null);
|
|
exception?.parent = this;
|
|
stackTrace?.parent = this;
|
|
body?.parent = this;
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitCatch(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
guard?.accept(v);
|
|
exception?.accept(v);
|
|
stackTrace?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
guard = v.visitDartType(guard);
|
|
if (exception != null) {
|
|
exception = exception.accept(v);
|
|
exception?.parent = this;
|
|
}
|
|
if (stackTrace != null) {
|
|
stackTrace = stackTrace.accept(v);
|
|
stackTrace?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
class TryFinally extends Statement {
|
|
Statement body;
|
|
Statement finalizer;
|
|
|
|
TryFinally(this.body, this.finalizer) {
|
|
body?.parent = this;
|
|
finalizer?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitTryFinally(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitTryFinally(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
finalizer?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept(v);
|
|
body?.parent = this;
|
|
}
|
|
if (finalizer != null) {
|
|
finalizer = finalizer.accept(v);
|
|
finalizer?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Statement of form `yield x` or `yield* x`.
|
|
///
|
|
/// For native yield semantics see `AsyncMarker.SyncYielding`.
|
|
class YieldStatement extends Statement {
|
|
Expression expression;
|
|
int flags = 0;
|
|
|
|
YieldStatement(this.expression,
|
|
{bool isYieldStar: false, bool isNative: false}) {
|
|
expression?.parent = this;
|
|
this.isYieldStar = isYieldStar;
|
|
this.isNative = isNative;
|
|
}
|
|
|
|
static const int FlagYieldStar = 1 << 0;
|
|
static const int FlagNative = 1 << 1;
|
|
|
|
bool get isYieldStar => flags & FlagYieldStar != 0;
|
|
bool get isNative => flags & FlagNative != 0;
|
|
|
|
void set isYieldStar(bool value) {
|
|
flags = value ? (flags | FlagYieldStar) : (flags & ~FlagYieldStar);
|
|
}
|
|
|
|
void set isNative(bool value) {
|
|
flags = value ? (flags | FlagNative) : (flags & ~FlagNative);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitYieldStatement(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitYieldStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Declaration of a local variable.
|
|
///
|
|
/// This may occur as a statement, but is also used in several non-statement
|
|
/// contexts, such as in [ForStatement], [Catch], and [FunctionNode].
|
|
///
|
|
/// When this occurs as a statement, it must be a direct child of a [Block].
|
|
//
|
|
// DESIGN TODO: Should we remove the 'final' modifier from variables?
|
|
@coqref
|
|
class VariableDeclaration extends Statement {
|
|
/// Offset of the equals sign in the source file it comes from.
|
|
///
|
|
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset])
|
|
/// if the equals sign offset is not available (e.g. if not initialized)
|
|
/// (this is the default if none is specifically set).
|
|
int fileEqualsOffset = TreeNode.noOffset;
|
|
|
|
/// List of metadata annotations on the variable declaration.
|
|
///
|
|
/// This defaults to an immutable empty list. Use [addAnnotation] to add
|
|
/// annotations if needed.
|
|
List<Expression> annotations = const <Expression>[];
|
|
|
|
/// For named parameters, this is the name of the parameter. No two named
|
|
/// parameters (in the same parameter list) can have the same name.
|
|
///
|
|
/// In all other cases, the name is cosmetic, may be empty or null,
|
|
/// and is not necessarily unique.
|
|
String name;
|
|
int flags = 0;
|
|
DartType type; // Not null, defaults to dynamic.
|
|
|
|
/// Offset of the declaration, set and used when writing the binary.
|
|
int binaryOffsetNoTag = -1;
|
|
|
|
/// For locals, this is the initial value.
|
|
/// For parameters, this is the default value.
|
|
///
|
|
/// Should be null in other cases.
|
|
@coqopt
|
|
Expression initializer; // May be null.
|
|
|
|
VariableDeclaration(this.name,
|
|
{this.initializer,
|
|
this.type: const DynamicType(),
|
|
int flags: -1,
|
|
bool isFinal: false,
|
|
bool isConst: false,
|
|
bool isFieldFormal: false,
|
|
bool isCovariant: false}) {
|
|
assert(type != null);
|
|
initializer?.parent = this;
|
|
if (flags != -1) {
|
|
this.flags = flags;
|
|
} else {
|
|
this.isFinal = isFinal;
|
|
this.isConst = isConst;
|
|
this.isFieldFormal = isFieldFormal;
|
|
this.isCovariant = isCovariant;
|
|
}
|
|
}
|
|
|
|
/// Creates a synthetic variable with the given expression as initializer.
|
|
VariableDeclaration.forValue(this.initializer,
|
|
{bool isFinal: true,
|
|
bool isConst: false,
|
|
bool isFieldFormal: false,
|
|
this.type: const DynamicType()}) {
|
|
assert(type != null);
|
|
initializer?.parent = this;
|
|
this.isFinal = isFinal;
|
|
this.isConst = isConst;
|
|
this.isFieldFormal = isFieldFormal;
|
|
}
|
|
|
|
static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
|
|
static const int FlagConst = 1 << 1;
|
|
static const int FlagFieldFormal = 1 << 2;
|
|
static const int FlagCovariant = 1 << 3;
|
|
static const int FlagInScope = 1 << 4; // Temporary flag used by verifier.
|
|
static const int FlagGenericCovariantImpl = 1 << 5;
|
|
|
|
bool get isFinal => flags & FlagFinal != 0;
|
|
bool get isConst => flags & FlagConst != 0;
|
|
|
|
/// Whether the parameter is declared with the `covariant` keyword.
|
|
bool get isCovariant => flags & FlagCovariant != 0;
|
|
|
|
/// Whether the variable is declared as a field formal parameter of
|
|
/// a constructor.
|
|
@informative
|
|
bool get isFieldFormal => flags & FlagFieldFormal != 0;
|
|
|
|
/// If this [VariableDeclaration] is a parameter of a method, indicates
|
|
/// whether the method implementation needs to contain a runtime type check to
|
|
/// deal with generic covariance.
|
|
///
|
|
/// When `true`, runtime checks may need to be performed; see
|
|
/// [DispatchCategory] for details.
|
|
bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;
|
|
|
|
void set isFinal(bool value) {
|
|
flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
|
|
}
|
|
|
|
void set isConst(bool value) {
|
|
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
|
|
}
|
|
|
|
void set isCovariant(bool value) {
|
|
flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant);
|
|
}
|
|
|
|
@informative
|
|
void set isFieldFormal(bool value) {
|
|
flags = value ? (flags | FlagFieldFormal) : (flags & ~FlagFieldFormal);
|
|
}
|
|
|
|
void set isGenericCovariantImpl(bool value) {
|
|
flags = value
|
|
? (flags | FlagGenericCovariantImpl)
|
|
: (flags & ~FlagGenericCovariantImpl);
|
|
}
|
|
|
|
void addAnnotation(Expression annotation) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(annotation..parent = this);
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitVariableDeclaration(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitVariableDeclaration(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
type?.accept(v);
|
|
initializer?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
type = v.visitDartType(type);
|
|
if (initializer != null) {
|
|
initializer = initializer.accept(v);
|
|
initializer?.parent = this;
|
|
}
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this variable, consistent with
|
|
/// the names used across all [toString] calls.
|
|
String toString() => debugVariableDeclarationName(this);
|
|
}
|
|
|
|
/// Declaration a local function.
|
|
///
|
|
/// The body of the function may use [variable] as its self-reference.
|
|
class FunctionDeclaration extends Statement {
|
|
VariableDeclaration variable; // Is final and has no initializer.
|
|
FunctionNode function;
|
|
|
|
FunctionDeclaration(this.variable, this.function) {
|
|
variable?.parent = this;
|
|
function?.parent = this;
|
|
}
|
|
|
|
accept(StatementVisitor v) => v.visitFunctionDeclaration(this);
|
|
accept1(StatementVisitor1 v, arg) => v.visitFunctionDeclaration(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (function != null) {
|
|
function = function.accept(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// NAMES
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A public name, or a private name qualified by a library.
|
|
///
|
|
/// Names are only used for expressions with dynamic dispatch, as all
|
|
/// statically resolved references are represented in nameless form.
|
|
///
|
|
/// [Name]s are immutable and compare based on structural equality, and they
|
|
/// are not AST nodes.
|
|
///
|
|
/// The [toString] method returns a human-readable string that includes the
|
|
/// library name for private names; uniqueness is not guaranteed.
|
|
@coq
|
|
abstract class Name implements Node {
|
|
final int hashCode;
|
|
@coq
|
|
final String name;
|
|
@nocoq
|
|
Reference get libraryName;
|
|
@nocoq
|
|
Library get library;
|
|
bool get isPrivate;
|
|
|
|
Name._internal(this.hashCode, this.name);
|
|
|
|
factory Name(String name, [Library library]) =>
|
|
new Name.byReference(name, library?.reference);
|
|
|
|
factory Name.byReference(String name, Reference libraryName) {
|
|
/// Use separate subclasses for the public and private case to save memory
|
|
/// for public names.
|
|
if (name.startsWith('_')) {
|
|
assert(libraryName != null);
|
|
return new _PrivateName(name, libraryName);
|
|
} else {
|
|
return new _PublicName(name);
|
|
}
|
|
}
|
|
|
|
bool operator ==(other) {
|
|
return other is Name && name == other.name && library == other.library;
|
|
}
|
|
|
|
accept(Visitor v) => v.visitName(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
// DESIGN TODO: Should we visit the library as a library reference?
|
|
}
|
|
}
|
|
|
|
class _PrivateName extends Name {
|
|
final Reference libraryName;
|
|
bool get isPrivate => true;
|
|
|
|
_PrivateName(String name, Reference libraryName)
|
|
: this.libraryName = libraryName,
|
|
super._internal(_computeHashCode(name, libraryName), name);
|
|
|
|
String toString() => library != null ? '$library::$name' : name;
|
|
|
|
Library get library => libraryName.asLibrary;
|
|
|
|
static int _computeHashCode(String name, Reference libraryName) {
|
|
// TODO(dmitryas): Factor in [libraryName] in a non-deterministic way into
|
|
// the result. Note, the previous code here was the following:
|
|
// return 131 * name.hashCode + 17 * libraryName.asLibrary._libraryId;
|
|
return name.hashCode;
|
|
}
|
|
}
|
|
|
|
class _PublicName extends Name {
|
|
Reference get libraryName => null;
|
|
Library get library => null;
|
|
bool get isPrivate => false;
|
|
|
|
_PublicName(String name) : super._internal(name.hashCode, name);
|
|
|
|
String toString() => name;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// TYPES
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A syntax-independent notion of a type.
|
|
///
|
|
/// [DartType]s are not AST nodes and may be shared between different parents.
|
|
///
|
|
/// [DartType] objects should be treated as unmodifiable objects, although
|
|
/// immutability is not enforced for List fields, and [TypeParameter]s are
|
|
/// cyclic structures that are constructed by mutation.
|
|
///
|
|
/// The `==` operator on [DartType]s compare based on type equality, not
|
|
/// object identity.
|
|
@coq
|
|
abstract class DartType extends Node {
|
|
const DartType();
|
|
|
|
accept(DartTypeVisitor v);
|
|
|
|
bool operator ==(Object other);
|
|
|
|
/// If this is a typedef type, repeatedly unfolds its type definition until
|
|
/// the root term is not a typedef type, otherwise returns the type itself.
|
|
///
|
|
/// Will never return a typedef type.
|
|
DartType get unalias => this;
|
|
|
|
/// If this is a typedef type, unfolds its type definition once, otherwise
|
|
/// returns the type itself.
|
|
DartType get unaliasOnce => this;
|
|
}
|
|
|
|
/// The type arising from invalid type annotations.
|
|
///
|
|
/// Can usually be treated as 'dynamic', but should occasionally be handled
|
|
/// differently, e.g. `x is ERROR` should evaluate to false.
|
|
class InvalidType extends DartType {
|
|
final int hashCode = 12345;
|
|
|
|
const InvalidType();
|
|
|
|
accept(DartTypeVisitor v) => v.visitInvalidType(this);
|
|
visitChildren(Visitor v) {}
|
|
|
|
bool operator ==(Object other) => other is InvalidType;
|
|
}
|
|
|
|
class DynamicType extends DartType {
|
|
final int hashCode = 54321;
|
|
|
|
const DynamicType();
|
|
|
|
accept(DartTypeVisitor v) => v.visitDynamicType(this);
|
|
visitChildren(Visitor v) {}
|
|
|
|
bool operator ==(Object other) => other is DynamicType;
|
|
}
|
|
|
|
class VoidType extends DartType {
|
|
final int hashCode = 123121;
|
|
|
|
const VoidType();
|
|
|
|
accept(DartTypeVisitor v) => v.visitVoidType(this);
|
|
visitChildren(Visitor v) {}
|
|
|
|
bool operator ==(Object other) => other is VoidType;
|
|
}
|
|
|
|
class BottomType extends DartType {
|
|
final int hashCode = 514213;
|
|
|
|
const BottomType();
|
|
|
|
accept(DartTypeVisitor v) => v.visitBottomType(this);
|
|
visitChildren(Visitor v) {}
|
|
|
|
bool operator ==(Object other) => other is BottomType;
|
|
}
|
|
|
|
@coq
|
|
class InterfaceType extends DartType {
|
|
Reference className;
|
|
@nocoq
|
|
final List<DartType> typeArguments;
|
|
|
|
/// The [typeArguments] list must not be modified after this call. If the
|
|
/// list is omitted, 'dynamic' type arguments are filled in.
|
|
InterfaceType(Class classNode, [List<DartType> typeArguments])
|
|
: this.byReference(getClassReference(classNode),
|
|
typeArguments ?? _defaultTypeArguments(classNode));
|
|
|
|
InterfaceType.byReference(this.className, this.typeArguments);
|
|
|
|
Class get classNode => className.asClass;
|
|
|
|
static List<DartType> _defaultTypeArguments(Class classNode) {
|
|
if (classNode.typeParameters.length == 0) {
|
|
// Avoid allocating a list in this very common case.
|
|
return const <DartType>[];
|
|
} else {
|
|
return new List<DartType>.filled(
|
|
classNode.typeParameters.length, const DynamicType());
|
|
}
|
|
}
|
|
|
|
accept(DartTypeVisitor v) => v.visitInterfaceType(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
classNode.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
if (other is InterfaceType) {
|
|
if (className != other.className) return false;
|
|
if (typeArguments.length != other.typeArguments.length) return false;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
if (typeArguments[i] != other.typeArguments[i]) return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int get hashCode {
|
|
int hash = 0x3fffffff & className.hashCode;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
/// A possibly generic function type.
|
|
@coq
|
|
class FunctionType extends DartType {
|
|
final List<TypeParameter> typeParameters;
|
|
final int requiredParameterCount;
|
|
@coqsingle
|
|
final List<DartType> positionalParameters;
|
|
final List<NamedType> namedParameters; // Must be sorted.
|
|
|
|
/// The [Typedef] this function type is created for.
|
|
@nocoq
|
|
Reference typedefReference;
|
|
|
|
final DartType returnType;
|
|
int _hashCode;
|
|
|
|
FunctionType(List<DartType> positionalParameters, this.returnType,
|
|
{this.namedParameters: const <NamedType>[],
|
|
this.typeParameters: const <TypeParameter>[],
|
|
int requiredParameterCount,
|
|
this.typedefReference})
|
|
: this.positionalParameters = positionalParameters,
|
|
this.requiredParameterCount =
|
|
requiredParameterCount ?? positionalParameters.length;
|
|
|
|
/// The [Typedef] this function type is created for.
|
|
Typedef get typedef => typedefReference?.asTypedef;
|
|
|
|
accept(DartTypeVisitor v) => v.visitFunctionType(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(typeParameters, v);
|
|
visitList(positionalParameters, v);
|
|
visitList(namedParameters, v);
|
|
returnType.accept(v);
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
if (other is FunctionType) {
|
|
if (typeParameters.length != other.typeParameters.length ||
|
|
requiredParameterCount != other.requiredParameterCount ||
|
|
positionalParameters.length != other.positionalParameters.length ||
|
|
namedParameters.length != other.namedParameters.length) {
|
|
return false;
|
|
}
|
|
if (typeParameters.isEmpty) {
|
|
for (int i = 0; i < positionalParameters.length; ++i) {
|
|
if (positionalParameters[i] != other.positionalParameters[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
for (int i = 0; i < namedParameters.length; ++i) {
|
|
if (namedParameters[i] != other.namedParameters[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return returnType == other.returnType;
|
|
} else {
|
|
// Structural equality does not tell us if two generic function types
|
|
// are the same type. If they are unifiable without substituting any
|
|
// type variables, they are equal.
|
|
return unifyTypes(this, other, new Set<TypeParameter>()) != null;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Returns a variant of this function type that does not declare any type
|
|
/// parameters.
|
|
///
|
|
/// Any uses of its type parameters become free variables in the returned
|
|
/// type.
|
|
FunctionType get withoutTypeParameters {
|
|
if (typeParameters.isEmpty) return this;
|
|
return new FunctionType(positionalParameters, returnType,
|
|
requiredParameterCount: requiredParameterCount,
|
|
namedParameters: namedParameters,
|
|
typedefReference: typedefReference);
|
|
}
|
|
|
|
/// Looks up the type of the named parameter with the given name.
|
|
///
|
|
/// Returns `null` if there is no named parameter with the given name.
|
|
DartType getNamedParameter(String name) {
|
|
int lower = 0;
|
|
int upper = namedParameters.length - 1;
|
|
while (lower <= upper) {
|
|
int pivot = (lower + upper) ~/ 2;
|
|
var namedParameter = namedParameters[pivot];
|
|
int comparison = name.compareTo(namedParameter.name);
|
|
if (comparison == 0) {
|
|
return namedParameter.type;
|
|
} else if (comparison < 0) {
|
|
upper = pivot - 1;
|
|
} else {
|
|
lower = pivot + 1;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
int get hashCode => _hashCode ??= _computeHashCode();
|
|
|
|
int _computeHashCode() {
|
|
int hash = 1237;
|
|
hash = 0x3fffffff & (hash * 31 + requiredParameterCount);
|
|
for (int i = 0; i < typeParameters.length; ++i) {
|
|
TypeParameter parameter = typeParameters[i];
|
|
_temporaryHashCodeTable[parameter] = _temporaryHashCodeTable.length;
|
|
hash = 0x3fffffff & (hash * 31 + parameter.bound.hashCode);
|
|
}
|
|
for (int i = 0; i < positionalParameters.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + positionalParameters[i].hashCode);
|
|
}
|
|
for (int i = 0; i < namedParameters.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + namedParameters[i].hashCode);
|
|
}
|
|
hash = 0x3fffffff & (hash * 31 + returnType.hashCode);
|
|
for (int i = 0; i < typeParameters.length; ++i) {
|
|
// Remove the type parameters from the scope again.
|
|
_temporaryHashCodeTable.remove(typeParameters[i]);
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
/// A use of a [Typedef] as a type.
|
|
///
|
|
/// The underlying type can be extracted using [unalias].
|
|
class TypedefType extends DartType {
|
|
final Reference typedefReference;
|
|
final List<DartType> typeArguments;
|
|
|
|
TypedefType(Typedef typedefNode, [List<DartType> typeArguments])
|
|
: this.byReference(
|
|
typedefNode.reference, typeArguments ?? const <DartType>[]);
|
|
|
|
TypedefType.byReference(this.typedefReference, this.typeArguments);
|
|
|
|
Typedef get typedefNode => typedefReference.asTypedef;
|
|
|
|
accept(DartTypeVisitor v) => v.visitTypedefType(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(typeArguments, v);
|
|
v.visitTypedefReference(typedefNode);
|
|
}
|
|
|
|
DartType get unaliasOnce {
|
|
return Substitution.fromTypedefType(this).substituteType(typedefNode.type);
|
|
}
|
|
|
|
DartType get unalias {
|
|
return unaliasOnce.unalias;
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
if (other is TypedefType) {
|
|
if (typedefReference != other.typedefReference ||
|
|
typeArguments.length != other.typeArguments.length) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
if (typeArguments[i] != other.typeArguments[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int get hashCode {
|
|
int hash = 0x3fffffff & typedefNode.hashCode;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
/// A named parameter in [FunctionType].
|
|
class NamedType extends Node implements Comparable<NamedType> {
|
|
final String name;
|
|
final DartType type;
|
|
|
|
NamedType(this.name, this.type);
|
|
|
|
bool operator ==(Object other) {
|
|
return other is NamedType && name == other.name && type == other.type;
|
|
}
|
|
|
|
int get hashCode {
|
|
return name.hashCode * 31 + type.hashCode * 37;
|
|
}
|
|
|
|
int compareTo(NamedType other) => name.compareTo(other.name);
|
|
|
|
accept(Visitor v) => v.visitNamedType(this);
|
|
|
|
void visitChildren(Visitor v) {
|
|
type.accept(v);
|
|
}
|
|
}
|
|
|
|
/// Stores the hash code of function type parameters while computing the hash
|
|
/// code of a [FunctionType] object.
|
|
///
|
|
/// This ensures that distinct [FunctionType] objects get the same hash code
|
|
/// if they represent the same type, even though their type parameters are
|
|
/// represented by different objects.
|
|
final Map<TypeParameter, int> _temporaryHashCodeTable = <TypeParameter, int>{};
|
|
|
|
/// Reference to a type variable.
|
|
///
|
|
/// A type variable has an optional bound because type promotion can change the
|
|
/// bound. A bound of `null` indicates that the bound has not been promoted and
|
|
/// is the same as the [TypeParameter]'s bound. This allows one to detect
|
|
/// whether the bound has been promoted.
|
|
class TypeParameterType extends DartType {
|
|
TypeParameter parameter;
|
|
|
|
/// An optional promoted bound on the type parameter.
|
|
///
|
|
/// 'null' indicates that the type parameter's bound has not been promoted and
|
|
/// is therefore the same as the bound of [parameter].
|
|
DartType promotedBound;
|
|
|
|
TypeParameterType(this.parameter, [this.promotedBound]);
|
|
|
|
accept(DartTypeVisitor v) => v.visitTypeParameterType(this);
|
|
|
|
visitChildren(Visitor v) {}
|
|
|
|
bool operator ==(Object other) {
|
|
return other is TypeParameterType && parameter == other.parameter;
|
|
}
|
|
|
|
int get hashCode => _temporaryHashCodeTable[parameter] ?? parameter.hashCode;
|
|
|
|
/// Returns the bound of the type parameter, accounting for promotions.
|
|
DartType get bound => promotedBound ?? parameter.bound;
|
|
}
|
|
|
|
/// Declaration of a type variable.
|
|
///
|
|
/// Type parameters declared in a [Class] or [FunctionNode] are part of the AST,
|
|
/// have a parent pointer to its declaring class or function, and will be seen
|
|
/// by tree visitors.
|
|
///
|
|
/// Type parameters declared by a [FunctionType] are orphans and have a `null`
|
|
/// parent pointer. [TypeParameter] objects should not be shared between
|
|
/// different [FunctionType] objects.
|
|
class TypeParameter extends TreeNode {
|
|
int flags = 0;
|
|
|
|
/// List of metadata annotations on the type parameter.
|
|
///
|
|
/// This defaults to an immutable empty list. Use [addAnnotation] to add
|
|
/// annotations if needed.
|
|
List<Expression> annotations = const <Expression>[];
|
|
|
|
String name; // Cosmetic name.
|
|
|
|
/// The bound on the type variable.
|
|
///
|
|
/// Should not be null except temporarily during IR construction. Should
|
|
/// be set to the root class for type parameters without an explicit bound.
|
|
DartType bound;
|
|
|
|
/// The default value of the type variable. It is used to provide the
|
|
/// corresponding missing type argument in type annotations and as the
|
|
/// fall-back type value in type inference at compile time. At run time,
|
|
/// [defaultType] is used by the backends in place of the missing type
|
|
/// argument of a dynamic invocation of a generic function.
|
|
DartType defaultType;
|
|
|
|
TypeParameter([this.name, this.bound, this.defaultType]);
|
|
|
|
// Must match serialized bit positions.
|
|
static const int FlagGenericCovariantImpl = 1 << 0;
|
|
|
|
/// If this [TypeParameter] is a type parameter of a generic method, indicates
|
|
/// whether the method implementation needs to contain a runtime type check to
|
|
/// deal with generic covariance.
|
|
///
|
|
/// When `true`, runtime checks may need to be performed; see
|
|
/// [DispatchCategory] for details.
|
|
bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;
|
|
|
|
void set isGenericCovariantImpl(bool value) {
|
|
flags = value
|
|
? (flags | FlagGenericCovariantImpl)
|
|
: (flags & ~FlagGenericCovariantImpl);
|
|
}
|
|
|
|
void addAnnotation(Expression annotation) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(annotation..parent = this);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitTypeParameter(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
bound.accept(v);
|
|
defaultType?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
bound = v.visitDartType(bound);
|
|
if (defaultType != null) {
|
|
defaultType = v.visitDartType(defaultType);
|
|
}
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this type parameter, consistent
|
|
/// with the names used across all [toString] calls.
|
|
String toString() => debugQualifiedTypeParameterName(this);
|
|
|
|
bool get isFunctionTypeTypeParameter => parent == null;
|
|
}
|
|
|
|
class Supertype extends Node {
|
|
Reference className;
|
|
final List<DartType> typeArguments;
|
|
|
|
Supertype(Class classNode, List<DartType> typeArguments)
|
|
: this.byReference(getClassReference(classNode), typeArguments);
|
|
|
|
Supertype.byReference(this.className, this.typeArguments);
|
|
|
|
Class get classNode => className.asClass;
|
|
|
|
accept(Visitor v) => v.visitSupertype(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
classNode.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
InterfaceType get asInterfaceType {
|
|
return new InterfaceType(classNode, typeArguments);
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
if (other is Supertype) {
|
|
if (className != other.className) return false;
|
|
if (typeArguments.length != other.typeArguments.length) return false;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
if (typeArguments[i] != other.typeArguments[i]) return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int get hashCode {
|
|
int hash = 0x3fffffff & className.hashCode;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// CONSTANTS
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class Constant extends Node {
|
|
/// Calls the `visit*ConstantReference()` method on visitor [v] for all
|
|
/// constants referenced in this constant.
|
|
///
|
|
/// (Note that a constant can be seen as a DAG (directed acyclic graph) and
|
|
/// not a tree!)
|
|
visitChildren(Visitor v);
|
|
|
|
/// Calls the `visit*Constant()` method on the visitor [v].
|
|
accept(ConstantVisitor v);
|
|
|
|
/// Calls the `visit*ConstantReference()` method on the visitor [v].
|
|
acceptReference(Visitor v);
|
|
|
|
/// The Kernel AST will reference [Constant]s via [ConstantExpression]s. The
|
|
/// constants are not required to be canonicalized, but they have to be deeply
|
|
/// comparable via hashCode/==!
|
|
int get hashCode;
|
|
bool operator ==(Object other);
|
|
|
|
/// Gets the type of this constant.
|
|
DartType getType(TypeEnvironment types);
|
|
}
|
|
|
|
abstract class PrimitiveConstant<T> extends Constant {
|
|
final T value;
|
|
|
|
PrimitiveConstant(this.value);
|
|
|
|
String toString() => '$value';
|
|
|
|
int get hashCode => value.hashCode;
|
|
|
|
bool operator ==(Object other) =>
|
|
other is PrimitiveConstant<T> && other.value == value;
|
|
}
|
|
|
|
class NullConstant extends PrimitiveConstant<Null> {
|
|
NullConstant() : super(null);
|
|
|
|
visitChildren(Visitor v) {}
|
|
accept(ConstantVisitor v) => v.visitNullConstant(this);
|
|
acceptReference(Visitor v) => v.visitNullConstantReference(this);
|
|
|
|
DartType getType(TypeEnvironment types) => types.nullType;
|
|
}
|
|
|
|
class BoolConstant extends PrimitiveConstant<bool> {
|
|
BoolConstant(bool value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
accept(ConstantVisitor v) => v.visitBoolConstant(this);
|
|
acceptReference(Visitor v) => v.visitBoolConstantReference(this);
|
|
|
|
DartType getType(TypeEnvironment types) => types.boolType;
|
|
}
|
|
|
|
class IntConstant extends PrimitiveConstant<int> {
|
|
IntConstant(int value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
accept(ConstantVisitor v) => v.visitIntConstant(this);
|
|
acceptReference(Visitor v) => v.visitIntConstantReference(this);
|
|
|
|
DartType getType(TypeEnvironment types) => types.intType;
|
|
}
|
|
|
|
class DoubleConstant extends PrimitiveConstant<double> {
|
|
DoubleConstant(double value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
accept(ConstantVisitor v) => v.visitDoubleConstant(this);
|
|
acceptReference(Visitor v) => v.visitDoubleConstantReference(this);
|
|
|
|
int get hashCode => value.isNaN ? 199 : super.hashCode;
|
|
bool operator ==(Object other) =>
|
|
other is DoubleConstant && identical(value, other.value);
|
|
|
|
DartType getType(TypeEnvironment types) => types.doubleType;
|
|
}
|
|
|
|
class StringConstant extends PrimitiveConstant<String> {
|
|
StringConstant(String value) : super(value) {
|
|
assert(value != null);
|
|
}
|
|
|
|
visitChildren(Visitor v) {}
|
|
accept(ConstantVisitor v) => v.visitStringConstant(this);
|
|
acceptReference(Visitor v) => v.visitStringConstantReference(this);
|
|
|
|
DartType getType(TypeEnvironment types) => types.stringType;
|
|
}
|
|
|
|
class SymbolConstant extends Constant {
|
|
final String name;
|
|
final Reference libraryReference;
|
|
|
|
SymbolConstant(this.name, this.libraryReference);
|
|
|
|
visitChildren(Visitor v) {}
|
|
|
|
accept(ConstantVisitor v) => v.visitSymbolConstant(this);
|
|
acceptReference(Visitor v) => v.visitSymbolConstantReference(this);
|
|
|
|
String toString() {
|
|
return libraryReference != null
|
|
? '#${libraryReference.asLibrary.importUri}::$name'
|
|
: '#$name';
|
|
}
|
|
|
|
int get hashCode => name.hashCode ^ libraryReference.hashCode;
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is SymbolConstant &&
|
|
other.name == name &&
|
|
other.libraryReference == libraryReference);
|
|
|
|
DartType getType(TypeEnvironment types) => types.symbolType;
|
|
}
|
|
|
|
class MapConstant extends Constant {
|
|
final DartType keyType;
|
|
final DartType valueType;
|
|
final List<ConstantMapEntry> entries;
|
|
|
|
MapConstant(this.keyType, this.valueType, this.entries);
|
|
|
|
visitChildren(Visitor v) {
|
|
keyType.accept(v);
|
|
valueType.accept(v);
|
|
for (final ConstantMapEntry entry in entries) {
|
|
entry.key.acceptReference(v);
|
|
entry.value.acceptReference(v);
|
|
}
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitMapConstant(this);
|
|
acceptReference(Visitor v) => v.visitMapConstantReference(this);
|
|
|
|
String toString() => '${this.runtimeType}<$keyType, $valueType>($entries)';
|
|
|
|
// TODO(kustermann): Consider combining the hash codes in a better way (also
|
|
// below and in [listHashCode]/[mapHashCode].
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??=
|
|
keyType.hashCode ^ valueType.hashCode ^ listHashCode(entries);
|
|
}
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is MapConstant &&
|
|
other.keyType == keyType &&
|
|
other.valueType == valueType &&
|
|
listEquals(other.entries, entries));
|
|
|
|
DartType getType(TypeEnvironment types) =>
|
|
types.literalMapType(keyType, valueType);
|
|
}
|
|
|
|
class ConstantMapEntry {
|
|
final Constant key;
|
|
final Constant value;
|
|
ConstantMapEntry(this.key, this.value);
|
|
|
|
String toString() => '$key: $value';
|
|
|
|
int get hashCode => key.hashCode ^ value.hashCode;
|
|
|
|
bool operator ==(Object other) =>
|
|
other is ConstantMapEntry && other.key == key && other.value == value;
|
|
}
|
|
|
|
class ListConstant extends Constant {
|
|
final DartType typeArgument;
|
|
final List<Constant> entries;
|
|
|
|
ListConstant(this.typeArgument, this.entries);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument.accept(v);
|
|
for (final Constant constant in entries) {
|
|
constant.acceptReference(v);
|
|
}
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitListConstant(this);
|
|
acceptReference(Visitor v) => v.visitListConstantReference(this);
|
|
|
|
String toString() => '${this.runtimeType}<$typeArgument>($entries)';
|
|
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??= typeArgument.hashCode ^ listHashCode(entries);
|
|
}
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is ListConstant &&
|
|
other.typeArgument == typeArgument &&
|
|
listEquals(other.entries, entries));
|
|
|
|
DartType getType(TypeEnvironment types) =>
|
|
types.literalListType(typeArgument);
|
|
}
|
|
|
|
class InstanceConstant extends Constant {
|
|
final Reference classReference;
|
|
final List<DartType> typeArguments;
|
|
final Map<Reference, Constant> fieldValues;
|
|
|
|
InstanceConstant(this.classReference, this.typeArguments, this.fieldValues);
|
|
|
|
Class get klass => classReference.asClass;
|
|
|
|
visitChildren(Visitor v) {
|
|
classReference.asClass.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
for (final Reference reference in fieldValues.keys) {
|
|
reference.asField.acceptReference(v);
|
|
}
|
|
for (final Constant constant in fieldValues.values) {
|
|
constant.acceptReference(v);
|
|
}
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitInstanceConstant(this);
|
|
acceptReference(Visitor v) => v.visitInstanceConstantReference(this);
|
|
|
|
String toString() {
|
|
final sb = new StringBuffer();
|
|
sb.write('${classReference.asClass}');
|
|
if (!classReference.asClass.typeParameters.isEmpty) {
|
|
sb.write('<');
|
|
sb.write(typeArguments.map((type) => type.toString()).join(', '));
|
|
sb.write('>');
|
|
}
|
|
sb.write(' {');
|
|
fieldValues.forEach((Reference fieldRef, Constant constant) {
|
|
sb.write('${fieldRef.asField.name}: $constant, ');
|
|
});
|
|
sb.write('}');
|
|
return sb.toString();
|
|
}
|
|
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??= classReference.hashCode ^
|
|
listHashCode(typeArguments) ^
|
|
mapHashCode(fieldValues);
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
return identical(this, other) ||
|
|
(other is InstanceConstant &&
|
|
other.classReference == classReference &&
|
|
listEquals(other.typeArguments, typeArguments) &&
|
|
mapEquals(other.fieldValues, fieldValues));
|
|
}
|
|
|
|
DartType getType(TypeEnvironment types) =>
|
|
new InterfaceType(klass, typeArguments);
|
|
}
|
|
|
|
class PartialInstantiationConstant extends Constant {
|
|
final TearOffConstant tearOffConstant;
|
|
final List<DartType> types;
|
|
|
|
PartialInstantiationConstant(this.tearOffConstant, this.types);
|
|
|
|
visitChildren(Visitor v) {
|
|
tearOffConstant.acceptReference(v);
|
|
visitList(types, v);
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitPartialInstantiationConstant(this);
|
|
acceptReference(Visitor v) =>
|
|
v.visitPartialInstantiationConstantReference(this);
|
|
|
|
String toString() {
|
|
return '${runtimeType}(${tearOffConstant.procedure}<${types.join(', ')}>)';
|
|
}
|
|
|
|
int get hashCode => tearOffConstant.hashCode ^ listHashCode(types);
|
|
|
|
bool operator ==(Object other) {
|
|
return other is PartialInstantiationConstant &&
|
|
other.tearOffConstant == tearOffConstant &&
|
|
listEquals(other.types, types);
|
|
}
|
|
|
|
DartType getType(TypeEnvironment typeEnvironment) {
|
|
final FunctionType type = tearOffConstant.getType(typeEnvironment);
|
|
final mapping = <TypeParameter, DartType>{};
|
|
for (final parameter in type.typeParameters) {
|
|
mapping[parameter] = types[mapping.length];
|
|
}
|
|
return substitute(type.withoutTypeParameters, mapping);
|
|
}
|
|
}
|
|
|
|
class TearOffConstant extends Constant {
|
|
final Reference procedureReference;
|
|
|
|
TearOffConstant(Procedure procedure)
|
|
: procedureReference = procedure.reference {
|
|
assert(procedure.isStatic);
|
|
}
|
|
|
|
TearOffConstant.byReference(this.procedureReference);
|
|
|
|
Procedure get procedure => procedureReference?.asProcedure;
|
|
|
|
visitChildren(Visitor v) {
|
|
procedureReference.asProcedure.acceptReference(v);
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitTearOffConstant(this);
|
|
acceptReference(Visitor v) => v.visitTearOffConstantReference(this);
|
|
|
|
String toString() {
|
|
return '${runtimeType}(${procedure})';
|
|
}
|
|
|
|
int get hashCode => procedure.hashCode;
|
|
|
|
bool operator ==(Object other) {
|
|
return other is TearOffConstant && other.procedure == procedure;
|
|
}
|
|
|
|
FunctionType getType(TypeEnvironment types) =>
|
|
procedure.function.functionType;
|
|
}
|
|
|
|
class TypeLiteralConstant extends Constant {
|
|
final DartType type;
|
|
|
|
TypeLiteralConstant(this.type);
|
|
|
|
visitChildren(Visitor v) {
|
|
type.accept(v);
|
|
}
|
|
|
|
accept(ConstantVisitor v) => v.visitTypeLiteralConstant(this);
|
|
acceptReference(Visitor v) => v.visitTypeLiteralConstantReference(this);
|
|
|
|
String toString() => '${runtimeType}(${type})';
|
|
|
|
int get hashCode => type.hashCode;
|
|
|
|
bool operator ==(Object other) {
|
|
return other is TypeLiteralConstant && other.type == type;
|
|
}
|
|
|
|
DartType getType(TypeEnvironment types) => types.typeType;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// COMPONENT
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A way to bundle up libraries in a component.
|
|
class Component extends TreeNode {
|
|
final CanonicalName root;
|
|
|
|
final List<Library> libraries;
|
|
|
|
/// Map from a source file URI to a line-starts table and source code.
|
|
/// Given a source file URI and a offset in that file one can translate
|
|
/// it to a line:column position in that file.
|
|
final Map<Uri, Source> uriToSource;
|
|
|
|
/// Mapping between string tags and [MetadataRepository] corresponding to
|
|
/// those tags.
|
|
final Map<String, MetadataRepository<dynamic>> metadata =
|
|
<String, MetadataRepository<dynamic>>{};
|
|
|
|
/// Reference to the main method in one of the libraries.
|
|
Reference mainMethodName;
|
|
|
|
Component(
|
|
{CanonicalName nameRoot,
|
|
List<Library> libraries,
|
|
Map<Uri, Source> uriToSource})
|
|
: root = nameRoot ?? new CanonicalName.root(),
|
|
libraries = libraries ?? <Library>[],
|
|
uriToSource = uriToSource ?? <Uri, Source>{} {
|
|
if (libraries != null) {
|
|
for (int i = 0; i < libraries.length; ++i) {
|
|
// The libraries are owned by this component, and so are their canonical
|
|
// names if they exist.
|
|
Library library = libraries[i];
|
|
library.parent = this;
|
|
CanonicalName name = library.reference.canonicalName;
|
|
if (name != null && name.parent != root) {
|
|
root.adoptChild(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
for (var library in libraries) {
|
|
computeCanonicalNamesForLibrary(library);
|
|
}
|
|
}
|
|
|
|
void computeCanonicalNamesForLibrary(Library library) {
|
|
root.getChildFromUri(library.importUri).bindTo(library.reference);
|
|
library.computeCanonicalNames();
|
|
}
|
|
|
|
void unbindCanonicalNames() {
|
|
root.unbindAll();
|
|
}
|
|
|
|
Procedure get mainMethod => mainMethodName?.asProcedure;
|
|
|
|
void set mainMethod(Procedure main) {
|
|
mainMethodName = getMemberReference(main);
|
|
}
|
|
|
|
accept(TreeVisitor v) => v.visitComponent(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(libraries, v);
|
|
mainMethod?.acceptReference(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(libraries, v, this);
|
|
}
|
|
|
|
Component get enclosingComponent => this;
|
|
|
|
/// Translates an offset to line and column numbers in the given file.
|
|
Location getLocation(Uri file, int offset) {
|
|
return uriToSource[file]?.getLocation(file, offset);
|
|
}
|
|
|
|
void addMetadataRepository(MetadataRepository repository) {
|
|
metadata[repository.tag] = repository;
|
|
}
|
|
}
|
|
|
|
/// A tuple with file, line, and column number, for displaying human-readable
|
|
/// locations.
|
|
class Location {
|
|
final Uri file;
|
|
final int line; // 1-based.
|
|
final int column; // 1-based.
|
|
|
|
Location(this.file, this.line, this.column);
|
|
|
|
String toString() => '$file:$line:$column';
|
|
}
|
|
|
|
abstract class MetadataRepository<T> {
|
|
/// Unique string tag associated with this repository.
|
|
String get tag;
|
|
|
|
/// Mutable mapping between nodes and their metadata.
|
|
Map<TreeNode, T> get mapping;
|
|
|
|
/// Write [metadata] object corresponding to the given [Node] into
|
|
/// the given [BinarySink].
|
|
///
|
|
/// Metadata is serialized immediately before serializing [node],
|
|
/// so implementation of this method can use serialization context of
|
|
/// [node]'s parents (such as declared type parameters and variables).
|
|
/// In order to use scope declared by the [node] itself, implementation of
|
|
/// this method can use [BinarySink.enterScope] and [BinarySink.leaveScope]
|
|
/// methods.
|
|
///
|
|
/// [metadata] must be an object owned by this repository.
|
|
void writeToBinary(T metadata, Node node, BinarySink sink);
|
|
|
|
/// Construct a metadata object from its binary payload read from the
|
|
/// given [BinarySource].
|
|
///
|
|
/// Metadata is deserialized immediately after deserializing [node],
|
|
/// so it can use deserialization context of [node]'s parents.
|
|
/// In order to use scope declared by the [node] itself, implementation of
|
|
/// this method can use [BinarySource.enterScope] and
|
|
/// [BinarySource.leaveScope] methods.
|
|
T readFromBinary(Node node, BinarySource source);
|
|
|
|
/// Method to check whether a node can have metadata attached to it
|
|
/// or referenced from the metadata payload.
|
|
///
|
|
/// Currently due to binary format specifics Catch and MapEntry nodes
|
|
/// can't have metadata attached to them.
|
|
static bool isSupported(TreeNode node) {
|
|
return !(node is MapEntry || node is Catch);
|
|
}
|
|
}
|
|
|
|
abstract class BinarySink {
|
|
void writeByte(int byte);
|
|
void writeBytes(List<int> bytes);
|
|
void writeUInt32(int value);
|
|
void writeUInt30(int value);
|
|
|
|
/// Write List<Byte> into the sink.
|
|
void writeByteList(List<int> bytes);
|
|
|
|
void writeCanonicalNameReference(CanonicalName name);
|
|
void writeStringReference(String str);
|
|
void writeName(Name node);
|
|
void writeDartType(DartType type);
|
|
void writeNode(Node node);
|
|
|
|
void enterScope(
|
|
{List<TypeParameter> typeParameters,
|
|
bool memberScope: false,
|
|
bool variableScope: false});
|
|
void leaveScope(
|
|
{List<TypeParameter> typeParameters,
|
|
bool memberScope: false,
|
|
bool variableScope: false});
|
|
}
|
|
|
|
abstract class BinarySource {
|
|
int get currentOffset;
|
|
List<int> get bytes;
|
|
|
|
int readByte();
|
|
int readUInt();
|
|
int readUint32();
|
|
|
|
/// Read List<Byte> from the source.
|
|
List<int> readByteList();
|
|
|
|
CanonicalName readCanonicalNameReference();
|
|
String readStringReference();
|
|
Name readName();
|
|
DartType readDartType();
|
|
FunctionNode readFunctionNode();
|
|
|
|
void enterScope({List<TypeParameter> typeParameters});
|
|
void leaveScope({List<TypeParameter> typeParameters});
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// INTERNAL FUNCTIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
void setParents(List<TreeNode> nodes, TreeNode parent) {
|
|
for (int i = 0; i < nodes.length; ++i) {
|
|
nodes[i].parent = parent;
|
|
}
|
|
}
|
|
|
|
void visitList(List<Node> nodes, Visitor visitor) {
|
|
for (int i = 0; i < nodes.length; ++i) {
|
|
nodes[i].accept(visitor);
|
|
}
|
|
}
|
|
|
|
void visitIterable(Iterable<Node> nodes, Visitor visitor) {
|
|
for (var node in nodes) {
|
|
node.accept(visitor);
|
|
}
|
|
}
|
|
|
|
void transformTypeList(List<DartType> nodes, Transformer visitor) {
|
|
int storeIndex = 0;
|
|
for (int i = 0; i < nodes.length; ++i) {
|
|
var result = visitor.visitDartType(nodes[i]);
|
|
if (result != null) {
|
|
nodes[storeIndex] = result;
|
|
++storeIndex;
|
|
}
|
|
}
|
|
if (storeIndex < nodes.length) {
|
|
nodes.length = storeIndex;
|
|
}
|
|
}
|
|
|
|
void transformSupertypeList(List<Supertype> nodes, Transformer visitor) {
|
|
int storeIndex = 0;
|
|
for (int i = 0; i < nodes.length; ++i) {
|
|
var result = visitor.visitSupertype(nodes[i]);
|
|
if (result != null) {
|
|
nodes[storeIndex] = result;
|
|
++storeIndex;
|
|
}
|
|
}
|
|
if (storeIndex < nodes.length) {
|
|
nodes.length = storeIndex;
|
|
}
|
|
}
|
|
|
|
void transformList(List<TreeNode> nodes, Transformer visitor, TreeNode parent) {
|
|
int storeIndex = 0;
|
|
for (int i = 0; i < nodes.length; ++i) {
|
|
var result = nodes[i].accept(visitor);
|
|
if (result != null) {
|
|
nodes[storeIndex] = result;
|
|
result.parent = parent;
|
|
++storeIndex;
|
|
}
|
|
}
|
|
if (storeIndex < nodes.length) {
|
|
nodes.length = storeIndex;
|
|
}
|
|
}
|
|
|
|
List<DartType> _getAsTypeArguments(List<TypeParameter> typeParameters) {
|
|
if (typeParameters.isEmpty) return const <DartType>[];
|
|
return new List<DartType>.generate(
|
|
typeParameters.length, (i) => new TypeParameterType(typeParameters[i]),
|
|
growable: false);
|
|
}
|
|
|
|
class _ChildReplacer extends Transformer {
|
|
final TreeNode child;
|
|
final TreeNode replacement;
|
|
|
|
_ChildReplacer(this.child, this.replacement);
|
|
|
|
@override
|
|
defaultTreeNode(TreeNode node) {
|
|
if (node == child) {
|
|
return replacement;
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Source {
|
|
final List<int> lineStarts;
|
|
|
|
final List<int> source;
|
|
|
|
String cachedText;
|
|
|
|
Source(this.lineStarts, this.source);
|
|
|
|
/// Return the text corresponding to [line] which is a 1-based line
|
|
/// number. The returned line contains no line separators.
|
|
String getTextLine(int line) {
|
|
RangeError.checkValueInInterval(line, 1, lineStarts.length, 'line');
|
|
if (source == null) return null;
|
|
|
|
cachedText ??= utf8.decode(source, allowMalformed: true);
|
|
// -1 as line numbers start at 1.
|
|
int index = line - 1;
|
|
if (index + 1 == lineStarts.length) {
|
|
// Last line.
|
|
return cachedText.substring(lineStarts[index]);
|
|
} else if (index < lineStarts.length) {
|
|
// We subtract 1 from the next line for two reasons:
|
|
// 1. If the file isn't terminated by a newline, that index is invalid.
|
|
// 2. To remove the newline at the end of the line.
|
|
int endOfLine = lineStarts[index + 1] - 1;
|
|
if (endOfLine > index && cachedText[endOfLine - 1] == "\r") {
|
|
--endOfLine; // Windows line endings.
|
|
}
|
|
return cachedText.substring(lineStarts[index], endOfLine);
|
|
}
|
|
// This shouldn't happen: should have been caught by the range check above.
|
|
throw "Internal error";
|
|
}
|
|
|
|
/// Translates an offset to line and column numbers in the given file.
|
|
Location getLocation(Uri file, int offset) {
|
|
RangeError.checkValueInInterval(offset, 0, lineStarts.last, 'offset');
|
|
int low = 0, high = lineStarts.length - 1;
|
|
while (low < high) {
|
|
int mid = high - ((high - low) >> 1); // Get middle, rounding up.
|
|
int pivot = lineStarts[mid];
|
|
if (pivot <= offset) {
|
|
low = mid;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
}
|
|
int lineIndex = low;
|
|
int lineStart = lineStarts[lineIndex];
|
|
int lineNumber = 1 + lineIndex;
|
|
int columnNumber = 1 + offset - lineStart;
|
|
return new Location(file, lineNumber, columnNumber);
|
|
}
|
|
}
|
|
|
|
/// Returns the [Reference] object for the given member.
|
|
///
|
|
/// Returns `null` if the member is `null`.
|
|
Reference getMemberReference(Member member) {
|
|
return member?.reference;
|
|
}
|
|
|
|
/// Returns the [Reference] object for the given class.
|
|
///
|
|
/// Returns `null` if the class is `null`.
|
|
Reference getClassReference(Class class_) {
|
|
return class_?.reference;
|
|
}
|
|
|
|
/// Returns the canonical name of [member], or throws an exception if the
|
|
/// member has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the member is `null`.
|
|
CanonicalName getCanonicalNameOfMember(Member member) {
|
|
if (member == null) return null;
|
|
if (member.canonicalName == null) {
|
|
throw '$member has no canonical name';
|
|
}
|
|
return member.canonicalName;
|
|
}
|
|
|
|
/// Returns the canonical name of [class_], or throws an exception if the
|
|
/// class has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the class is `null`.
|
|
CanonicalName getCanonicalNameOfClass(Class class_) {
|
|
if (class_ == null) return null;
|
|
if (class_.canonicalName == null) {
|
|
throw '$class_ has no canonical name';
|
|
}
|
|
return class_.canonicalName;
|
|
}
|
|
|
|
/// Returns the canonical name of [library], or throws an exception if the
|
|
/// library has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the library is `null`.
|
|
CanonicalName getCanonicalNameOfLibrary(Library library) {
|
|
if (library == null) return null;
|
|
if (library.canonicalName == null) {
|
|
throw '$library has no canonical name';
|
|
}
|
|
return library.canonicalName;
|
|
}
|
|
|
|
int listHashCode(List list) {
|
|
return list.fold(0, (int value, Object item) => value ^ item.hashCode);
|
|
}
|
|
|
|
int mapHashCode(Map map) {
|
|
int value = 0;
|
|
for (final Object x in map.keys) value ^= x.hashCode;
|
|
for (final Object x in map.values) value ^= x.hashCode;
|
|
return value;
|
|
}
|
|
|
|
bool listEquals(List a, List b) {
|
|
if (a.length != b.length) return false;
|
|
for (int i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mapEquals(Map a, Map b) {
|
|
if (a.length != b.length) return false;
|
|
for (final Object key in a.keys) {
|
|
if (!b.containsKey(key) || a[key] != b[key]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns the canonical name of [typedef_], or throws an exception if the
|
|
/// typedef has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the typedef is `null`.
|
|
CanonicalName getCanonicalNameOfTypedef(Typedef typedef_) {
|
|
if (typedef_ == null) return null;
|
|
if (typedef_.canonicalName == null) {
|
|
throw '$typedef_ has no canonical name';
|
|
}
|
|
return typedef_.canonicalName;
|
|
}
|
|
|
|
/// Annotation describing information which is not part of Dart semantics; in
|
|
/// other words, if this information (or any information it refers to) changes,
|
|
/// static analysis and runtime behavior of the library are unaffected.
|
|
const informative = null;
|
|
|
|
Location _getLocationInComponent(Component component, Uri fileUri, int offset) {
|
|
if (component != null) {
|
|
return component.getLocation(fileUri, offset);
|
|
} else {
|
|
return new Location(fileUri, TreeNode.noOffset, TreeNode.noOffset);
|
|
}
|
|
}
|
|
|
|
/// Convert the synthetic name of an implicit mixin application class
|
|
/// into a name suitable for user-faced strings.
|
|
///
|
|
/// For example, when compiling "class A extends S with M1, M2", the
|
|
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
|
|
/// This function will return "S with M1" and "S with M1, M2", respectively.
|
|
String demangleMixinApplicationName(String name) {
|
|
List<String> nameParts = name.split('&');
|
|
if (nameParts.length < 2) return name;
|
|
String demangledName = nameParts[1];
|
|
for (int i = 2; i < nameParts.length; i++) {
|
|
demangledName += (i == 2 ? " with " : ", ") + nameParts[i];
|
|
}
|
|
return demangledName;
|
|
}
|
|
|
|
/// Extract from the synthetic name of an implicit mixin application class
|
|
/// the name of the final subclass of the mixin application.
|
|
///
|
|
/// For example, when compiling "class A extends S with M1, M2", the
|
|
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
|
|
/// This function will return "A" for both classes.
|
|
String demangleMixinApplicationSubclassName(String name) {
|
|
List<String> nameParts = name.split('&');
|
|
if (nameParts.length < 2) return name;
|
|
assert(nameParts[0].startsWith('_'));
|
|
return nameParts[0].substring(1);
|
|
}
|