Files
sdk/pkg/kernel/lib/ast.dart
T
Aske Simon Christensen f066c05319 Rename klass getter on InstanceConstant to classNode
This achieves consistency with similar getters in the API.

This is technically a breaking change, since it changes a published
part of the Kernel API. Since the constants API is relatively new and
so far only used internally in the AOT compiler, the change is
expected to be unproblematic.

Closes https://github.com/dart-lang/sdk/issues/35696

Change-Id: I3ca30922580d226ccbdb6f77496983c21ef2102b
Reviewed-on: https://dart-review.googlesource.com/c/90220
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Kevin Millikin <kmillikin@google.com>
2019-01-28 13:31:05 +00:00

5942 lines
175 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);
void 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;
/// Problems in this [Library] encoded as json objects.
///
/// Note that this field can be null, and by convention should be null if the
/// list is empty.
List<String> problemsAsJson;
@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 (int i = 0; i < typedefs.length; ++i) {
Typedef typedef_ = typedefs[i];
canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference);
}
for (int i = 0; i < fields.length; ++i) {
Field field = fields[i];
canonicalName.getChildFromMember(field).bindTo(field.reference);
}
for (int i = 0; i < procedures.length; ++i) {
Procedure member = procedures[i];
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (int i = 0; i < classes.length; ++i) {
Class class_ = classes[i];
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;
TypedefType get thisType {
return new TypedefType(this, _getAsTypeArguments(typeParameters));
}
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;
// Otherwise we have a left-linear binary tree (subtrees are supertype and
// mixedInType) of constraints, where all the interior nodes are anonymous
// mixin applications.
Supertype current = supertype;
while (current != null && current.classNode.isAnonymousMixin) {
Class currentClass = current.classNode;
assert(currentClass.implementedTypes.length == 2);
Substitution substitution = Substitution.fromSupertype(current);
constraints.add(
substitution.substituteSupertype(currentClass.implementedTypes[1]));
current =
substitution.substituteSupertype(currentClass.implementedTypes[0]);
}
return constraints..add(current);
}
/// 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 (int i = 0; i < fields.length; ++i) {
Field member = fields[i];
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (int i = 0; i < procedures.length; ++i) {
Procedure member = procedures[i];
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (int i = 0; i < constructors.length; ++i) {
Constructor member = constructors[i];
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (int i = 0; i < redirectingFactoryConstructors.length; ++i) {
RedirectingFactoryConstructor member = redirectingFactoryConstructors[i];
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.
///
/// Shouldn't be used on code compiled in legacy 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].
///
/// Shouldn't be used on code compiled in legacy 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.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.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.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 SetLiteral extends Expression {
bool isConst;
DartType typeArgument; // Not null, defaults to DynamicType.
final List<Expression> expressions;
SetLiteral(this.expressions,
{this.typeArgument: const DynamicType(), this.isConst: false}) {
assert(typeArgument != null);
setParents(expressions, this);
}
DartType getStaticType(TypeEnvironment types) {
return types.literalSetType(typeArgument);
}
accept(ExpressionVisitor v) => v.visitSetLiteral(this);
accept1(ExpressionVisitor1 v, arg) => v.visitSetLiteral(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);
accept1(DartTypeVisitor1 v, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitInvalidType(this, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitDynamicType(this, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitVoidType(this, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitBottomType(this, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitInterfaceType(this, arg);
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.
final TypedefType typedefType;
final DartType returnType;
int _hashCode;
FunctionType(List<DartType> positionalParameters, this.returnType,
{this.namedParameters: const <NamedType>[],
this.typeParameters: const <TypeParameter>[],
int requiredParameterCount,
this.typedefType})
: this.positionalParameters = positionalParameters,
this.requiredParameterCount =
requiredParameterCount ?? positionalParameters.length;
@nocoq
Reference get typedefReference => typedefType?.typedefReference;
Typedef get typedef => typedefReference?.asTypedef;
accept(DartTypeVisitor v) => v.visitFunctionType(this);
accept1(DartTypeVisitor1 v, arg) => v.visitFunctionType(this, arg);
visitChildren(Visitor v) {
visitList(typeParameters, v);
visitList(positionalParameters, v);
visitList(namedParameters, v);
typedefType?.accept(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,
typedefType: typedefType);
}
/// 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);
accept1(DartTypeVisitor1 v, arg) => v.visitTypedefType(this, arg);
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);
accept1(DartTypeVisitor1 v, arg) => v.visitTypeParameterType(this, arg);
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);
Expression asExpression() {
return new ConstantExpression(this);
}
}
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 classNode => 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(classNode, 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;
}
class UnevaluatedConstant extends Constant {
final Expression expression;
UnevaluatedConstant(this.expression) {
expression?.parent = null;
}
visitChildren(Visitor v) {
expression.accept(v);
}
accept(ConstantVisitor v) => v.visitUnevaluatedConstant(this);
acceptReference(Visitor v) => v.visitUnevaluatedConstantReference(this);
DartType getType(TypeEnvironment types) => expression.getStaticType(types);
@override
Expression asExpression() => expression;
}
// ------------------------------------------------------------------------
// COMPONENT
// ------------------------------------------------------------------------
/// A way to bundle up libraries in a component.
class Component extends TreeNode {
final CanonicalName root;
/// Problems in this [Component] encoded as json objects.
///
/// Note that this field can be null, and by convention should be null if the
/// list is empty.
List<String> problemsAsJson;
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 (int i = 0; i < libraries.length; ++i) {
computeCanonicalNamesForLibrary(libraries[i]);
}
}
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 {
int getBufferOffset();
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 writeNullAllowedCanonicalNameReference(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();
List<int> readBytes(int length);
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 || source.isEmpty) 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);
}