blob: bd7286f75b6a7c29b0d3d86a50c91a3b5ca08f2b [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// -----------------------------------------------------------------------
/// WHEN CHANGING THIS FILE:
/// -----------------------------------------------------------------------
///
/// If you are adding/removing/modifying fields/classes of the AST, you must
/// also update the following files:
///
/// - binary/ast_to_binary.dart
/// - binary/ast_from_binary.dart
/// - text/ast_to_text.dart
/// - clone.dart
/// - binary.md
/// - type_checker.dart (if relevant)
///
/// -----------------------------------------------------------------------
/// ERROR HANDLING
/// -----------------------------------------------------------------------
///
/// As a rule of thumb, errors that can be detected statically are handled by
/// the frontend, typically by translating the erroneous code into a 'throw' or
/// a call to 'noSuchMethod'.
///
/// For example, there are no arity mismatches in static invocations, and
/// there are no direct invocations of a constructor on a abstract class.
///
/// -----------------------------------------------------------------------
/// STATIC vs TOP-LEVEL
/// -----------------------------------------------------------------------
///
/// The term `static` includes both static class members and top-level members.
///
/// "Static class member" is the preferred term for non-top level statics.
///
/// Static class members are not lifted to the library level because mirrors
/// and stack traces can observe that they are class members.
///
/// -----------------------------------------------------------------------
/// PROCEDURES
/// -----------------------------------------------------------------------
///
/// "Procedure" is an umbrella term for method, getter, setter, index-getter,
/// index-setter, operator overloader, and factory constructor.
///
/// Generative constructors, field initializers, local functions are NOT
/// procedures.
///
/// -----------------------------------------------------------------------
/// TRANSFORMATIONS
/// -----------------------------------------------------------------------
///
/// AST transformations can be performed using [TreeNode.replaceWith] or the
/// [Transformer] visitor class.
///
/// Use [Transformer] for bulk transformations that are likely to transform lots
/// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate
/// relatively few nodes. Or use whichever is more convenient.
///
/// The AST can also be mutated by direct field manipulation, but the user then
/// has to update parent pointers manually.
///
library kernel.ast;
import 'dart:convert' show utf8;
import 'visitor.dart';
export 'visitor.dart';
import 'canonical_name.dart' show CanonicalName;
export 'canonical_name.dart' show CanonicalName;
import 'transformations/flags.dart';
import 'text/ast_to_text.dart';
import 'type_algebra.dart';
import 'type_environment.dart';
import 'coq_annot.dart';
/// Any type of node in the IR.
abstract class Node {
const Node();
accept(Visitor v);
visitChildren(Visitor v);
/// Returns the textual representation of this node for use in debugging.
///
/// [toString] should only be used for debugging and short-running test tools
/// as it can cause serious memory leaks.
///
/// Synthetic names are cached globally to retain consistency across different
/// [toString] calls (hence the memory leak).
///
/// Nodes that are named, such as [Class] and [Member], return their
/// (possibly synthesized) name, whereas other AST nodes return the complete
/// textual representation of their subtree.
String toString() => debugNodeToString(this);
}
/// A mutable AST node with a parent pointer.
///
/// This is anything other than [Name] and [DartType] nodes.
abstract class TreeNode extends Node {
static int _hashCounter = 0;
final int hashCode = _hashCounter = (_hashCounter + 1) & 0x3fffffff;
static const int noOffset = -1;
TreeNode parent;
/// Offset in the source file it comes from.
///
/// Valid values are from 0 and up, or -1 ([noOffset]) if the file offset is
/// not available (this is the default if none is specifically set).
int fileOffset = noOffset;
accept(TreeVisitor v);
visitChildren(Visitor v);
transformChildren(Transformer v);
/// Replaces [child] with [replacement].
///
/// The caller is responsible for ensuring that the AST remains a tree. In
/// particular, [replacement] should be an orphan or be part of an orphaned
/// subtree.
///
/// Has no effect if [child] is not actually a child of this node.
///
/// If [replacement] is `null`, this will [remove] the [child] node.
void replaceChild(TreeNode child, TreeNode replacement) {
transformChildren(new _ChildReplacer(child, replacement));
}
/// Inserts another node in place of this one.
///
/// The caller is responsible for ensuring that the AST remains a tree. In
/// particular, [replacement] should be an orphan or be part of an orphaned
/// subtree.
///
/// If [replacement] is `null`, this will [remove] the node.
void replaceWith(TreeNode replacement) {
parent.replaceChild(this, replacement);
parent = null;
}
/// Removes this node from the [List] it is currently stored in, or assigns
/// `null` to the field on the parent currently pointing to the node.
///
/// Has no effect if the node is orphaned or if the parent pointer is stale.
void remove() {
parent?.replaceChild(this, null);
parent = null;
}
Component get enclosingComponent => parent?.enclosingComponent;
/// Returns the best known source location of the given AST node, or `null` if
/// the node is orphaned.
///
/// This getter is intended for diagnostics and debugging, and should be
/// avoided in production code.
Location get location {
if (fileOffset == noOffset) return parent?.location;
return _getLocationInEnclosingFile(fileOffset);
}
Location _getLocationInEnclosingFile(int offset) {
return parent?._getLocationInEnclosingFile(offset);
}
}
/// An AST node that can be referenced by other nodes.
///
/// There is a single [reference] belonging to this node, providing a level of
/// indirection that is needed during serialization.
@coq
abstract class NamedNode extends TreeNode {
@coqdef
final Reference reference;
NamedNode(Reference reference)
: this.reference = reference ?? new Reference() {
this.reference.node = this;
}
CanonicalName get canonicalName => reference?.canonicalName;
}
abstract class FileUriNode extends TreeNode {
/// The URI of the source file this node was loaded from.
Uri get fileUri;
}
/// Indirection between a reference and its definition.
///
/// There is only one reference object per [NamedNode].
@coqref
class Reference {
CanonicalName canonicalName;
@nocoq
NamedNode node;
String toString() {
if (canonicalName != null) {
return 'Reference to $canonicalName';
}
if (node != null) {
return 'Reference to $node';
}
return 'Unbound reference';
}
Library get asLibrary {
if (node == null) {
throw '$this is not bound to an AST node. A library was expected';
}
return node as Library;
}
Class get asClass {
if (node == null) {
throw '$this is not bound to an AST node. A class was expected';
}
return node as Class;
}
Member get asMember {
if (node == null) {
throw '$this is not bound to an AST node. A member was expected';
}
return node as Member;
}
Field get asField {
if (node == null) {
throw '$this is not bound to an AST node. A field was expected';
}
return node as Field;
}
Constructor get asConstructor {
if (node == null) {
throw '$this is not bound to an AST node. A constructor was expected';
}
return node as Constructor;
}
Procedure get asProcedure {
if (node == null) {
throw '$this is not bound to an AST node. A procedure was expected';
}
return node as Procedure;
}
Typedef get asTypedef {
if (node == null) {
throw '$this is not bound to an AST node. A typedef was expected';
}
return node as Typedef;
}
}
// ------------------------------------------------------------------------
// LIBRARIES and CLASSES
// ------------------------------------------------------------------------
@coq
class Library extends NamedNode implements Comparable<Library>, FileUriNode {
/// An import path to this library.
///
/// The [Uri] should have the `dart`, `package`, `app`, or `file` scheme.
///
/// If the URI has the `app` scheme, it is relative to the application root.
Uri importUri;
/// The URI of the source file this library was loaded from.
Uri fileUri;
/// If true, the library is part of another build unit and its contents
/// are only partially loaded.
///
/// Classes of an external library are loaded at one of the [ClassLevel]s
/// other than [ClassLevel.Body]. Members in an external library have no
/// body, but have their typed interface present.
///
/// If the libary is non-external, then its classes are at [ClassLevel.Body]
/// and all members are loaded.
bool isExternal;
String name;
@nocoq
final List<Expression> annotations;
final List<LibraryDependency> dependencies;
/// References to nodes exported by `export` declarations that:
/// - aren't ambiguous, or
/// - aren't hidden by local declarations.
@nocoq
final List<Reference> additionalExports = <Reference>[];
@informative
final List<LibraryPart> parts;
final List<Typedef> typedefs;
final List<Class> classes;
final List<Procedure> procedures;
final List<Field> fields;
Library(this.importUri,
{this.name,
this.isExternal: false,
List<Expression> annotations,
List<LibraryDependency> dependencies,
List<LibraryPart> parts,
List<Typedef> typedefs,
List<Class> classes,
List<Procedure> procedures,
List<Field> fields,
this.fileUri,
Reference reference})
: this.annotations = annotations ?? <Expression>[],
this.dependencies = dependencies ?? <LibraryDependency>[],
this.parts = parts ?? <LibraryPart>[],
this.typedefs = typedefs ?? <Typedef>[],
this.classes = classes ?? <Class>[],
this.procedures = procedures ?? <Procedure>[],
this.fields = fields ?? <Field>[],
super(reference) {
setParents(this.dependencies, this);
setParents(this.parts, this);
setParents(this.typedefs, this);
setParents(this.classes, this);
setParents(this.procedures, this);
setParents(this.fields, this);
}
/// Returns the top-level fields and procedures defined in this library.
///
/// This getter is for convenience, not efficiency. Consider manually
/// iterating the members to speed up code in production.
Iterable<Member> get members =>
<Iterable<Member>>[fields, procedures].expand((x) => x);
void addMember(Member member) {
member.parent = this;
if (member is Procedure) {
procedures.add(member);
} else if (member is Field) {
fields.add(member);
} else {
throw new ArgumentError(member);
}
}
void addAnnotation(Expression node) {
node.parent = this;
annotations.add(node);
}
void addClass(Class class_) {
class_.parent = this;
classes.add(class_);
}
void addField(Field field) {
field.parent = this;
fields.add(field);
}
void addProcedure(Procedure procedure) {
procedure.parent = this;
procedures.add(procedure);
}
void addTypedef(Typedef typedef_) {
typedef_.parent = this;
typedefs.add(typedef_);
}
void computeCanonicalNames() {
assert(canonicalName != null);
for (var typedef_ in typedefs) {
canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference);
}
for (var field in fields) {
canonicalName.getChildFromMember(field).bindTo(field.reference);
}
for (var member in procedures) {
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (var class_ in classes) {
canonicalName.getChild(class_.name).bindTo(class_.reference);
class_.computeCanonicalNames();
}
}
void addDependency(LibraryDependency node) {
dependencies.add(node..parent = this);
}
void addPart(LibraryPart node) {
parts.add(node..parent = this);
}
accept(TreeVisitor v) => v.visitLibrary(this);
visitChildren(Visitor v) {
visitList(annotations, v);
visitList(dependencies, v);
visitList(parts, v);
visitList(typedefs, v);
visitList(classes, v);
visitList(procedures, v);
visitList(fields, v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
transformList(dependencies, v, this);
transformList(parts, v, this);
transformList(typedefs, v, this);
transformList(classes, v, this);
transformList(procedures, v, this);
transformList(fields, v, this);
}
static int _libraryIdCounter = 0;
int _libraryId = ++_libraryIdCounter;
int compareTo(Library other) => _libraryId - other._libraryId;
/// Returns a possibly synthesized name for this library, consistent with
/// the names across all [toString] calls.
String toString() => debugLibraryName(this);
Location _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
/// An import or export declaration in a library.
///
/// It can represent any of the following forms,
///
/// import <url>;
/// import <url> as <name>;
/// import <url> deferred as <name>;
/// export <url>;
///
/// optionally with metadata and [Combinators].
class LibraryDependency extends TreeNode {
int flags;
final List<Expression> annotations;
Reference importedLibraryReference;
/// The name of the import prefix, if any, or `null` if this is not an import
/// with a prefix.
///
/// Must be non-null for deferred imports, and must be null for exports.
String name;
final List<Combinator> combinators;
LibraryDependency(int flags, List<Expression> annotations,
Library importedLibrary, String name, List<Combinator> combinators)
: this.byReference(
flags, annotations, importedLibrary.reference, name, combinators);
LibraryDependency.deferredImport(Library importedLibrary, String name,
{List<Combinator> combinators, List<Expression> annotations})
: this.byReference(DeferredFlag, annotations ?? <Expression>[],
importedLibrary.reference, name, combinators ?? <Combinator>[]);
LibraryDependency.import(Library importedLibrary,
{String name, List<Combinator> combinators, List<Expression> annotations})
: this.byReference(0, annotations ?? <Expression>[],
importedLibrary.reference, name, combinators ?? <Combinator>[]);
LibraryDependency.export(Library importedLibrary,
{List<Combinator> combinators, List<Expression> annotations})
: this.byReference(ExportFlag, annotations ?? <Expression>[],
importedLibrary.reference, null, combinators ?? <Combinator>[]);
LibraryDependency.byReference(this.flags, this.annotations,
this.importedLibraryReference, this.name, this.combinators) {
setParents(annotations, this);
setParents(combinators, this);
}
Library get enclosingLibrary => parent;
Library get targetLibrary => importedLibraryReference.asLibrary;
static const int ExportFlag = 1 << 0;
static const int DeferredFlag = 1 << 1;
bool get isExport => flags & ExportFlag != 0;
bool get isImport => !isExport;
bool get isDeferred => flags & DeferredFlag != 0;
void addAnnotation(Expression annotation) {
annotations.add(annotation..parent = this);
}
accept(TreeVisitor v) => v.visitLibraryDependency(this);
visitChildren(Visitor v) {
visitList(annotations, v);
visitList(combinators, v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
transformList(combinators, v, this);
}
}
/// A part declaration in a library.
///
/// part <url>;
///
/// optionally with metadata.
class LibraryPart extends TreeNode {
final List<Expression> annotations;
final String partUri;
LibraryPart(this.annotations, this.partUri) {
setParents(annotations, this);
}
void addAnnotation(Expression annotation) {
annotations.add(annotation..parent = this);
}
accept(TreeVisitor v) => v.visitLibraryPart(this);
visitChildren(Visitor v) {
visitList(annotations, v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
}
}
/// A `show` or `hide` clause for an import or export.
class Combinator extends TreeNode {
bool isShow;
final List<String> names;
LibraryDependency get dependency => parent;
Combinator(this.isShow, this.names);
Combinator.show(this.names) : isShow = true;
Combinator.hide(this.names) : isShow = false;
bool get isHide => !isShow;
@override
accept(TreeVisitor v) => v.visitCombinator(this);
@override
visitChildren(Visitor v) {}
@override
transformChildren(Transformer v) {}
}
/// Declaration of a type alias.
class Typedef extends NamedNode implements FileUriNode {
/// The URI of the source file that contains the declaration of this typedef.
Uri fileUri;
List<Expression> annotations = const <Expression>[];
String name;
final List<TypeParameter> typeParameters;
DartType type;
// The following two fields describe parameters of the underlying function
// type. They are needed to keep such attributes as names and annotations.
final List<TypeParameter> typeParametersOfFunctionType;
final List<VariableDeclaration> positionalParameters;
final List<VariableDeclaration> namedParameters;
Typedef(this.name, this.type,
{Reference reference,
this.fileUri,
List<TypeParameter> typeParameters,
List<TypeParameter> typeParametersOfFunctionType,
List<VariableDeclaration> positionalParameters,
List<VariableDeclaration> namedParameters})
: this.typeParameters = typeParameters ?? <TypeParameter>[],
this.typeParametersOfFunctionType =
typeParametersOfFunctionType ?? <TypeParameter>[],
this.positionalParameters =
positionalParameters ?? <VariableDeclaration>[],
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
super(reference) {
setParents(this.typeParameters, this);
}
Library get enclosingLibrary => parent;
accept(TreeVisitor v) {
return v.visitTypedef(this);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
transformList(typeParameters, v, this);
if (type != null) {
type = v.visitDartType(type);
}
}
visitChildren(Visitor v) {
visitList(annotations, v);
visitList(typeParameters, v);
type?.accept(v);
}
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
}
/// The degree to which the contents of a class have been loaded into memory.
///
/// Each level imply the requirements of the previous ones.
enum ClassLevel {
/// Temporary loading level for internal use by IR producers. Consumers of
/// kernel code should not expect to see classes at this level.
Temporary,
/// The class may be used as a type, and it may contain members that are
/// referenced from this build unit.
///
/// The type parameters and their bounds are present.
///
/// There is no guarantee that all members are present.
///
/// All supertypes of this class are at [Type] level or higher.
Type,
/// All instance members of the class are present.
///
/// All supertypes of this class are at [Hierarchy] level or higher.
///
/// This level exists so supertypes of a fully loaded class contain all the
/// members needed to detect override constraints.
Hierarchy,
/// All instance members of the class have their body loaded, and their
/// annotations are present.
///
/// All supertypes of this class are at [Hierarchy] level or higher.
///
/// If this class is a mixin application, then its mixin is loaded at [Mixin]
/// level or higher.
///
/// This level exists so the contents of a mixin can be cloned into a
/// mixin application.
Mixin,
/// All members of the class are fully loaded and are in the correct order.
///
/// Annotations are present on classes and members.
///
/// All supertypes of this class are at [Hierarchy] level or higher,
/// not necessarily at [Body] level.
Body,
}
/// Declaration of a regular class or a mixin application.
///
/// Mixin applications may not contain fields or procedures, as they implicitly
/// use those from its mixed-in type. However, the IR does not enforce this
/// rule directly, as doing so can obstruct transformations. It is possible to
/// transform a mixin application to become a regular class, and vice versa.
@coq
class Class extends NamedNode implements FileUriNode {
/// Start offset of the class in the source file it comes from.
///
/// Note that this includes annotations if any.
///
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
/// start offset is not available (this is the default if none is specifically
/// set).
int startFileOffset = TreeNode.noOffset;
/// End offset in the source file it comes from. Valid values are from 0 and
/// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
/// (this is the default if none is specifically set).
int fileEndOffset = TreeNode.noOffset;
/// The degree to which the contents of the class have been loaded.
ClassLevel level = ClassLevel.Body;
/// List of metadata annotations on the class.
///
/// This defaults to an immutable empty list. Use [addAnnotation] to add
/// annotations if needed.
@nocoq
List<Expression> annotations = const <Expression>[];
/// Name of the class.
///
/// Must be non-null and must be unique within the library.
///
/// The name may contain characters that are not valid in a Dart identifier,
/// in particular, the symbol '&' is used in class names generated for mixin
/// applications.
@coq
String name;
// Must match serialized bit positions.
static const int LevelMask = 0x3; // Bits 0 and 1.
static const int FlagAbstract = 1 << 2;
static const int FlagEnum = 1 << 3;
static const int FlagAnonymousMixin = 1 << 4;
static const int FlagEliminatedMixin = 1 << 5;
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 A&B extends A mixedIn B {}
/// abstract class A&B&C extends A&B mixedIn C {}
/// abstract class A&B&C&D extends A&B&C mixedIn D {}
/// class Z extends 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);
}
/// The URI of the source file this class was loaded from.
Uri fileUri;
final List<TypeParameter> typeParameters;
/// The immediate super type, or `null` if this is the root class.
Supertype supertype;
/// The mixed-in type if this is a mixin application, otherwise `null`.
Supertype mixedInType;
/// The types from the `implements` clause.
final List<Supertype> implementedTypes;
/// Fields declared in the class.
///
/// For mixin applications this should be empty.
final List<Field> fields;
/// Constructors declared in the class.
final List<Constructor> constructors;
/// Procedures declared in the class.
///
/// For mixin applications this should only contain forwarding stubs.
final List<Procedure> procedures;
/// Redirecting factory constructors declared in the class.
///
/// For mixin applications this should be empty.
final List<RedirectingFactoryConstructor> redirectingFactoryConstructors;
Class(
{this.name,
bool isAbstract: false,
bool isAnonymousMixin: false,
this.supertype,
this.mixedInType,
List<TypeParameter> typeParameters,
List<Supertype> implementedTypes,
List<Constructor> constructors,
List<Procedure> procedures,
List<Field> fields,
List<RedirectingFactoryConstructor> redirectingFactoryConstructors,
this.fileUri,
Reference reference})
: this.typeParameters = typeParameters ?? <TypeParameter>[],
this.implementedTypes = implementedTypes ?? <Supertype>[],
this.fields = fields ?? <Field>[],
this.constructors = constructors ?? <Constructor>[],
this.procedures = procedures ?? <Procedure>[],
this.redirectingFactoryConstructors =
redirectingFactoryConstructors ?? <RedirectingFactoryConstructor>[],
super(reference) {
setParents(this.typeParameters, this);
setParents(this.constructors, this);
setParents(this.procedures, this);
setParents(this.fields, this);
setParents(this.redirectingFactoryConstructors, this);
this.isAbstract = isAbstract;
this.isAnonymousMixin = isAnonymousMixin;
}
void computeCanonicalNames() {
assert(canonicalName != null);
for (var member in fields) {
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (var member in procedures) {
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (var member in constructors) {
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
for (var member in redirectingFactoryConstructors) {
canonicalName.getChildFromMember(member).bindTo(member.reference);
}
}
/// The immediate super class, or `null` if this is the root class.
Class get superclass => supertype?.classNode;
/// The mixed-in class if this is a mixin application, otherwise `null`.
///
/// Note that this may itself be a mixin application. Use [mixin] to get the
/// class that has the fields and procedures.
Class get mixedInClass => mixedInType?.classNode;
/// The class that declares the field and procedures of this class.
Class get mixin => mixedInClass?.mixin ?? this;
bool get isMixinApplication => mixedInType != null;
/// 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;
/// True if this is a field or non-setter procedure.
///
/// Note that operators and factories return `true`, even though there are
/// normally no calls to their getter.
bool get hasGetter;
/// True if this is a setter or a mutable field.
bool get hasSetter;
/// True if this is a non-static field or procedure.
bool get isInstanceMember;
/// True if the member has the `external` modifier, implying that the
/// implementation is provided by the backend, and is not necessarily written
/// in Dart.
///
/// Members can have this modifier independently of whether the enclosing
/// library is external.
bool get isExternal;
void set isExternal(bool value);
/// The body of the procedure or constructor, or `null` if this is a field.
FunctionNode get function => null;
/// Returns a possibly synthesized name for this member, consistent with
/// the names used across all [toString] calls.
String toString() => debugQualifiedMemberName(this);
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
DartType get getterType;
DartType get setterType;
bool get containsSuperCalls {
return transformerFlags & TransformerFlag.superCalls != 0;
}
}
/// A field declaration.
///
/// The implied getter and setter for the field are not represented explicitly,
/// but can be made explicit if needed.
class Field extends Member {
DartType type; // Not null. Defaults to DynamicType.
int flags = 0;
Expression initializer; // May be null.
Field(Name name,
{this.type: const DynamicType(),
this.initializer,
bool isCovariant: false,
bool isFinal: false,
bool isConst: false,
bool isStatic: false,
bool hasImplicitGetter,
bool hasImplicitSetter,
int transformerFlags: 0,
Uri fileUri,
Reference reference})
: super(name, fileUri, reference) {
assert(type != null);
initializer?.parent = this;
this.isCovariant = isCovariant;
this.isFinal = isFinal;
this.isConst = isConst;
this.isStatic = isStatic;
this.hasImplicitGetter = hasImplicitGetter ?? !isStatic;
this.hasImplicitSetter = hasImplicitSetter ?? (!isStatic && !isFinal);
this.transformerFlags = transformerFlags;
}
static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
static const int FlagConst = 1 << 1;
static const int FlagStatic = 1 << 2;
static const int FlagHasImplicitGetter = 1 << 3;
static const int FlagHasImplicitSetter = 1 << 4;
static const int FlagCovariant = 1 << 5;
static const int FlagGenericCovariantImpl = 1 << 6;
/// Whether the field is declared with the `covariant` keyword.
bool get isCovariant => flags & FlagCovariant != 0;
bool get isFinal => flags & FlagFinal != 0;
bool get isConst => flags & FlagConst != 0;
bool get isStatic => flags & FlagStatic != 0;
/// If true, a getter should be generated for this field.
///
/// If false, there may or may not exist an explicit getter in the same class
/// with the same name as the field.
///
/// By default, all non-static fields have implicit getters.
bool get hasImplicitGetter => flags & FlagHasImplicitGetter != 0;
/// If true, a setter should be generated for this field.
///
/// If false, there may or may not exist an explicit setter in the same class
/// with the same name as the field.
///
/// Final fields never have implicit setters, but a field without an implicit
/// setter is not necessarily final, as it may be mutated by direct field
/// access.
///
/// By default, all non-static, non-final fields have implicit setters.
bool get hasImplicitSetter => flags & FlagHasImplicitSetter != 0;
/// Indicates whether the implicit setter associated with this field needs to
/// contain a runtime type check to deal with generic covariance.
///
/// When `true`, runtime checks may need to be performed; see
/// [DispatchCategory] for details.
bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;
void set isCovariant(bool value) {
flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant);
}
void set isFinal(bool value) {
flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
}
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
void set hasImplicitGetter(bool value) {
flags = value
? (flags | FlagHasImplicitGetter)
: (flags & ~FlagHasImplicitGetter);
}
void set hasImplicitSetter(bool value) {
flags = value
? (flags | FlagHasImplicitSetter)
: (flags & ~FlagHasImplicitSetter);
}
void set isGenericCovariantImpl(bool value) {
flags = value
? (flags | FlagGenericCovariantImpl)
: (flags & ~FlagGenericCovariantImpl);
}
/// True if the field is neither final nor const.
bool get isMutable => flags & (FlagFinal | FlagConst) == 0;
bool get isInstanceMember => !isStatic;
bool get hasGetter => true;
bool get hasSetter => isMutable;
bool get isExternal => false;
void set isExternal(bool value) {
if (value) throw 'Fields cannot be external';
}
accept(MemberVisitor v) => v.visitField(this);
acceptReference(MemberReferenceVisitor v) => v.visitFieldReference(this);
visitChildren(Visitor v) {
visitList(annotations, v);
type?.accept(v);
name?.accept(v);
initializer?.accept(v);
}
transformChildren(Transformer v) {
type = v.visitDartType(type);
transformList(annotations, v, this);
if (initializer != null) {
initializer = initializer.accept(v);
initializer?.parent = this;
}
}
DartType get getterType => type;
DartType get setterType => isMutable ? type : const BottomType();
Location _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
/// A generative constructor, possibly redirecting.
///
/// Note that factory constructors are treated as [Procedure]s.
///
/// Constructors do not take type parameters. Type arguments from a constructor
/// invocation should be matched with the type parameters declared in the class.
///
/// For unnamed constructors, the name is an empty string (in a [Name]).
class Constructor extends Member {
/// Start offset of the constructor in the source file it comes from.
///
/// Note that this includes annotations if any.
///
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
/// start offset is not available (this is the default if none is specifically
/// set).
int startFileOffset = TreeNode.noOffset;
int flags = 0;
FunctionNode function;
List<Initializer> initializers;
Constructor(this.function,
{Name name,
bool isConst: false,
bool isExternal: false,
bool isSynthetic: false,
List<Initializer> initializers,
int transformerFlags: 0,
Uri fileUri,
Reference reference})
: this.initializers = initializers ?? <Initializer>[],
super(name, fileUri, reference) {
function?.parent = this;
setParents(this.initializers, this);
this.isConst = isConst;
this.isExternal = isExternal;
this.isSynthetic = isSynthetic;
this.transformerFlags = transformerFlags;
}
static const int FlagConst = 1 << 0; // Must match serialized bit positions.
static const int FlagExternal = 1 << 1;
static const int FlagSynthetic = 1 << 2;
bool get isConst => flags & FlagConst != 0;
bool get isExternal => flags & FlagExternal != 0;
/// True if this is a synthetic constructor inserted in a class that
/// does not otherwise declare any constructors.
bool get isSynthetic => flags & FlagSynthetic != 0;
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isSynthetic(bool value) {
flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
}
bool get isInstanceMember => false;
bool get hasGetter => false;
bool get hasSetter => false;
accept(MemberVisitor v) => v.visitConstructor(this);
acceptReference(MemberReferenceVisitor v) =>
v.visitConstructorReference(this);
visitChildren(Visitor v) {
visitList(annotations, v);
name?.accept(v);
visitList(initializers, v);
function?.accept(v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
transformList(initializers, v, this);
if (function != null) {
function = function.accept(v);
function?.parent = this;
}
}
DartType get getterType => const BottomType();
DartType get setterType => const BottomType();
Location _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
/// Residue of a redirecting factory constructor for the linking phase.
///
/// In the following example, `bar` is a redirecting factory constructor.
///
/// class A {
/// A.foo();
/// factory A.bar() = A.foo;
/// }
///
/// An invocation of `new A.bar()` has the same effect as an invocation of
/// `new A.foo()`. In Kernel, the invocations of `bar` are replaced with
/// invocations of `foo`, and after it is done, the redirecting constructor can
/// be removed from the class. However, it is needed during the linking phase,
/// because other modules can refer to that constructor.
///
/// [RedirectingFactoryConstructor]s contain the necessary information for
/// linking and are treated as non-runnable members of classes that merely serve
/// as containers for that information.
///
/// Redirecting factory constructors can be unnamed. In this case, the name is
/// an empty string (in a [Name]).
class RedirectingFactoryConstructor extends Member {
int flags = 0;
/// [RedirectingFactoryConstructor]s may redirect to constructors or factories
/// of instantiated generic types, that is, generic types with supplied type
/// arguments. The supplied type arguments are stored in this field.
final List<DartType> typeArguments;
/// Reference to the constructor or the factory that this
/// [RedirectingFactoryConstructor] redirects to.
Reference targetReference;
/// [typeParameters] are duplicates of the type parameters of the enclosing
/// class. Because [RedirectingFactoryConstructor]s aren't instance members,
/// references to the type parameters of the enclosing class in the
/// redirection target description are encoded with references to the elements
/// of [typeParameters].
List<TypeParameter> typeParameters;
/// Positional parameters of [RedirectingFactoryConstructor]s should be
/// compatible with that of the target constructor.
List<VariableDeclaration> positionalParameters;
int requiredParameterCount;
/// Named parameters of [RedirectingFactoryConstructor]s should be compatible
/// with that of the target constructor.
List<VariableDeclaration> namedParameters;
RedirectingFactoryConstructor(this.targetReference,
{Name name,
bool isConst: false,
bool isExternal: false,
int transformerFlags: 0,
List<DartType> typeArguments,
List<TypeParameter> typeParameters,
List<VariableDeclaration> positionalParameters,
List<VariableDeclaration> namedParameters,
int requiredParameterCount,
Uri fileUri,
Reference reference})
: this.typeArguments = typeArguments ?? <DartType>[],
this.typeParameters = typeParameters ?? <TypeParameter>[],
this.positionalParameters =
positionalParameters ?? <VariableDeclaration>[],
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
this.requiredParameterCount =
requiredParameterCount ?? positionalParameters?.length ?? 0,
super(name, fileUri, reference) {
setParents(this.typeParameters, this);
setParents(this.positionalParameters, this);
setParents(this.namedParameters, this);
this.isConst = isConst;
this.isExternal = isExternal;
this.transformerFlags = transformerFlags;
}
static const int FlagConst = 1 << 0; // Must match serialized bit positions.
static const int FlagExternal = 1 << 1;
bool get isConst => flags & FlagConst != 0;
bool get isExternal => flags & FlagExternal != 0;
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
bool get isInstanceMember => false;
bool get hasGetter => false;
bool get hasSetter => false;
bool get isUnresolved => targetReference == null;
Member get target => targetReference?.asMember;
void set target(Member member) {
assert(member is Constructor ||
(member is Procedure && member.kind == ProcedureKind.Factory));
targetReference = getMemberReference(member);
}
accept(MemberVisitor v) => v.visitRedirectingFactoryConstructor(this);
acceptReference(MemberReferenceVisitor v) =>
v.visitRedirectingFactoryConstructorReference(this);
visitChildren(Visitor v) {
visitList(annotations, v);
target?.acceptReference(v);
visitList(typeArguments, v);
name?.accept(v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
transformTypeList(typeArguments, v);
}
DartType get getterType => const BottomType();
DartType get setterType => const BottomType();
Location _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
/// A method, getter, setter, index-getter, index-setter, operator overloader,
/// or factory.
///
/// Procedures can have the static, abstract, and/or external modifier, although
/// only the static and external modifiers may be used together.
///
/// For non-static procedures the name is required for dynamic dispatch.
/// For external procedures the name is required for identifying the external
/// implementation.
///
/// For methods, getters, and setters the name is just as it was declared.
/// For setters this does not include a trailing `=`.
/// For index-getters/setters, this is `[]` and `[]=`.
/// For operators, this is the token for the operator, e.g. `+` or `==`,
/// except for the unary minus operator, whose name is `unary-`.
@coq
class Procedure extends Member {
/// Start offset of the function in the source file it comes from.
///
/// Note that this includes annotations if any.
///
/// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
/// start offset is not available (this is the default if none is specifically
/// set).
int startFileOffset = TreeNode.noOffset;
ProcedureKind kind;
int flags = 0;
// function is null if and only if abstract, external.
FunctionNode function;
// The function node's body might be lazily loaded, meaning that this value
// might not be set correctly yet. Make sure the body is loaded before
// returning anything.
int get transformerFlags {
function?.body;
return super.transformerFlags;
}
// The function node's body might be lazily loaded, meaning that this value
// might get overwritten later (when the body is read). To avoid that read the
// body now and only set the value afterwards.
void set transformerFlags(int newValue) {
function?.body;
super.transformerFlags = newValue;
}
// This function will set the transformer flags without loading the body.
// Used when reading the binary. For other cases one should probably use
// `transformerFlags = value;`.
void setTransformerFlagsWithoutLazyLoading(int newValue) {
super.transformerFlags = newValue;
}
Reference forwardingStubSuperTargetReference;
Reference forwardingStubInterfaceTargetReference;
Procedure(Name name, ProcedureKind kind, FunctionNode function,
{bool isAbstract: false,
bool isStatic: false,
bool isExternal: false,
bool isConst: false,
bool isForwardingStub: false,
bool isForwardingSemiStub: false,
int transformerFlags: 0,
Uri fileUri,
Reference reference,
Member forwardingStubSuperTarget,
Member forwardingStubInterfaceTarget})
: this.byReference(name, kind, function,
isAbstract: isAbstract,
isStatic: isStatic,
isExternal: isExternal,
isConst: isConst,
isForwardingStub: isForwardingStub,
isForwardingSemiStub: isForwardingSemiStub,
transformerFlags: transformerFlags,
fileUri: fileUri,
reference: reference,
forwardingStubSuperTargetReference:
getMemberReference(forwardingStubSuperTarget),
forwardingStubInterfaceTargetReference:
getMemberReference(forwardingStubInterfaceTarget));
Procedure.byReference(Name name, this.kind, this.function,
{bool isAbstract: false,
bool isStatic: false,
bool isExternal: false,
bool isConst: false,
bool isForwardingStub: false,
bool isForwardingSemiStub: false,
int transformerFlags: 0,
Uri fileUri,
Reference reference,
this.forwardingStubSuperTargetReference,
this.forwardingStubInterfaceTargetReference})
: super(name, fileUri, reference) {
function?.parent = this;
this.isAbstract = isAbstract;
this.isStatic = isStatic;
this.isExternal = isExternal;
this.isConst = isConst;
this.isForwardingStub = isForwardingStub;
this.isForwardingSemiStub = isForwardingSemiStub;
this.transformerFlags = transformerFlags;
}
static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
static const int FlagAbstract = 1 << 1;
static const int FlagExternal = 1 << 2;
static const int FlagConst = 1 << 3; // Only for external const factories.
static const int FlagForwardingStub = 1 << 4;
static const int FlagForwardingSemiStub = 1 << 5;
// TODO(29841): Remove this flag after the issue is resolved.
static const int FlagRedirectingFactoryConstructor = 1 << 6;
static const int FlagNoSuchMethodForwarder = 1 << 7;
bool get isStatic => flags & FlagStatic != 0;
bool get isAbstract => flags & FlagAbstract != 0;
bool get isExternal => flags & FlagExternal != 0;
/// True if this has the `const` modifier. This is only possible for external
/// constant factories, such as `String.fromEnvironment`.
bool get isConst => flags & FlagConst != 0;
/// If set, this flag indicates that this function's implementation exists
/// solely for the purpose of type checking arguments and forwarding to
/// [forwardingStubSuperTarget].
///
/// Note that just because this bit is set doesn't mean that the function was
/// not declared in the source; it's possible that this is a forwarding
/// semi-stub (see isForwardingSemiStub). To determine whether this function
/// was present in the source, consult [isSyntheticForwarder].
bool get isForwardingStub => flags & FlagForwardingStub != 0;
/// If set, this flag indicates that although this function is a forwarding
/// stub, it was present in the original source as an abstract method.
bool get isForwardingSemiStub => flags & FlagForwardingSemiStub != 0;
// Indicates if this [Procedure] represents a redirecting factory constructor
// and doesn't have a runnable body.
bool get isRedirectingFactoryConstructor {
return flags & FlagRedirectingFactoryConstructor != 0;
}
/// If set, this flag indicates that this function was not present in the
/// source, and it exists solely for the purpose of type checking arguments
/// and forwarding to [forwardingStubSuperTarget].
bool get isSyntheticForwarder => isForwardingStub && !isForwardingSemiStub;
bool get isNoSuchMethodForwarder => flags & FlagNoSuchMethodForwarder != 0;
void set isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
void set isAbstract(bool value) {
flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
}
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isForwardingStub(bool value) {
flags =
value ? (flags | FlagForwardingStub) : (flags & ~FlagForwardingStub);
}
void set isForwardingSemiStub(bool value) {
flags = value
? (flags | FlagForwardingSemiStub)
: (flags & ~FlagForwardingSemiStub);
}
void set isRedirectingFactoryConstructor(bool value) {
flags = value
? (flags | FlagRedirectingFactoryConstructor)
: (flags & ~FlagRedirectingFactoryConstructor);
}
void set isNoSuchMethodForwarder(bool value) {
flags = value
? (flags | FlagNoSuchMethodForwarder)
: (flags & ~FlagNoSuchMethodForwarder);
}
bool get isInstanceMember => !isStatic;
bool get isGetter => kind == ProcedureKind.Getter;
bool get isSetter => kind == ProcedureKind.Setter;
bool get isAccessor => isGetter || isSetter;
bool get hasGetter => kind != ProcedureKind.Setter;
bool get hasSetter => kind == ProcedureKind.Setter;
bool get isFactory => kind == ProcedureKind.Factory;
Member get forwardingStubSuperTarget =>
forwardingStubSuperTargetReference?.asMember;
void set forwardingStubSuperTarget(Member target) {
forwardingStubSuperTargetReference = getMemberReference(target);
}
Member get forwardingStubInterfaceTarget =>
forwardingStubInterfaceTargetReference?.asMember;
void set forwardingStubInterfaceTarget(Member target) {
forwardingStubInterfaceTargetReference = getMemberReference(target);
}
accept(MemberVisitor v) => v.visitProcedure(this);
acceptReference(MemberReferenceVisitor v) => v.visitProcedureReference(this);
visitChildren(Visitor v) {
visitList(annotations, v);
name?.accept(v);
function?.accept(v);
}
transformChildren(Transformer v) {
transformList(annotations, v, this);
if (function != null) {
function = function.accept(v);
function?.parent = this;
}
}
DartType get getterType {
return isGetter ? function.returnType : function.functionType;
}
DartType get setterType {
return isSetter
? function.positionalParameters[0].type
: const BottomType();
}
Location _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
enum ProcedureKind {
Method,
Getter,
Setter,
Operator,
Factory,
}
// ------------------------------------------------------------------------
// CONSTRUCTOR INITIALIZERS
// ------------------------------------------------------------------------
/// Part of an initializer list in a constructor.
abstract class Initializer extends TreeNode {
/// True if this is a synthetic constructor initializer.
@informative
bool isSynthetic = false;
accept(InitializerVisitor v);
}
/// An initializer with a compile-time error.
///
/// Should throw an exception at runtime.
//
// DESIGN TODO: The frontend should use this in a lot more cases to catch
// invalid cases.
class InvalidInitializer extends Initializer {
accept(InitializerVisitor v) => v.visitInvalidInitializer(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
/// A field assignment `field = value` occurring in the initializer list of
/// a constructor.
///
/// This node has nothing to do with declaration-site field initializers; those
/// are [Expression]s stored in [Field.initializer].
//
// TODO: The frontend should check that all final fields are initialized
// exactly once, and that no fields are assigned twice in the initializer list.
class FieldInitializer extends Initializer {
/// Reference to the field being initialized. Not null.
Reference fieldReference;
Expression value;
FieldInitializer(Field field, Expression value)
: this.byReference(field?.reference, value);
FieldInitializer.byReference(this.fieldReference, this.value) {
value?.parent = this;
}
Field get field => fieldReference?.node;
void set field(Field field) {
fieldReference = field?.reference;
}
accept(InitializerVisitor v) => v.visitFieldInitializer(this);
visitChildren(Visitor v) {
field?.acceptReference(v);
value?.accept(v);
}
transformChildren(Transformer v) {
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// A super call `super(x,y)` occurring in the initializer list of a
/// constructor.
///
/// There are no type arguments on this call.
//
// TODO: The frontend should check that there is no more than one super call.
//
// DESIGN TODO: Consider if the frontend should insert type arguments derived
// from the extends clause.
class SuperInitializer extends Initializer {
/// Reference to the constructor being invoked in the super class. Not null.
Reference targetReference;
Arguments arguments;
SuperInitializer(Constructor target, Arguments arguments)
: this.byReference(getMemberReference(target), arguments);
SuperInitializer.byReference(this.targetReference, this.arguments) {
arguments?.parent = this;
}
Constructor get target => targetReference?.asConstructor;
void set target(Constructor target) {
targetReference = getMemberReference(target);
}
accept(InitializerVisitor v) => v.visitSuperInitializer(this);
visitChildren(Visitor v) {
target?.acceptReference(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
}
/// A redirecting call `this(x,y)` occurring in the initializer list of
/// a constructor.
//
// TODO: The frontend should check that this is the only initializer and if the
// constructor has a body or if there is a cycle in the initializer calls.
class RedirectingInitializer extends Initializer {
/// Reference to the constructor being invoked in the same class. Not null.
Reference targetReference;
Arguments arguments;
RedirectingInitializer(Constructor target, Arguments arguments)
: this.byReference(getMemberReference(target), arguments);
RedirectingInitializer.byReference(this.targetReference, this.arguments) {
arguments?.parent = this;
}
Constructor get target => targetReference?.asConstructor;
void set target(Constructor target) {
targetReference = getMemberReference(target);
}
accept(InitializerVisitor v) => v.visitRedirectingInitializer(this);
visitChildren(Visitor v) {
target?.acceptReference(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
}
/// Binding of a temporary variable in the initializer list of a constructor.
///
/// The variable is in scope for the remainder of the initializer list, but is
/// not in scope in the constructor body.
class LocalInitializer extends Initializer {
VariableDeclaration variable;
LocalInitializer(this.variable) {
variable?.parent = this;
}
accept(InitializerVisitor v) => v.visitLocalInitializer(this);
visitChildren(Visitor v) {
variable?.accept(v);
}
transformChildren(Transformer v) {
if (variable != null) {
variable = variable.accept(v);
variable?.parent = this;
}
}
}
class AssertInitializer extends Initializer {
AssertStatement statement;
AssertInitializer(this.statement) {
statement.parent = this;
}
accept(InitializerVisitor v) => v.visitAssertInitializer(this);
visitChildren(Visitor v) {
statement.accept(v);
}
transformChildren(Transformer v) {
statement = statement.accept(v);
statement.parent = this;
}
}
// ------------------------------------------------------------------------
// FUNCTIONS
// ------------------------------------------------------------------------
/// A function declares parameters and has a body.
///
/// This may occur in a procedure, constructor, function expression, or local
/// function declaration.
@coq
class FunctionNode extends TreeNode {
/// End offset in the source file it comes from. Valid values are from 0 and
/// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
/// (this is the default if none is specifically set).
int fileEndOffset = TreeNode.noOffset;
/// Kernel async marker for the function.
///
/// See also [dartAsyncMarker].
AsyncMarker asyncMarker;
/// Dart async marker for the function.
///
/// See also [asyncMarker].
///
/// A Kernel function can represent a Dart function with a different async
/// marker.
///
/// For example, when async/await is translated away,
/// a Dart async function might be represented by a Kernel sync function.
AsyncMarker dartAsyncMarker;
List<TypeParameter> typeParameters;
int requiredParameterCount;
@coqsingledef
List<VariableDeclaration> positionalParameters;
@nocoq
List<VariableDeclaration> namedParameters;
DartType returnType; // Not null.
Statement _body;
void Function() lazyBuilder;
void _buildLazy() {
if (lazyBuilder != null) {
var lazyBuilderLocal = lazyBuilder;
lazyBuilder = null;
lazyBuilderLocal();
}
}
Statement get body {
_buildLazy();
return _body;
}
void set body(Statement body) {
_buildLazy();
_body = body;
}
FunctionNode(this._body,
{List<TypeParameter> typeParameters,
List<VariableDeclaration> positionalParameters,
List<VariableDeclaration> namedParameters,
int requiredParameterCount,
this.returnType: const DynamicType(),
this.asyncMarker: AsyncMarker.Sync,
this.dartAsyncMarker})
: this.positionalParameters =
positionalParameters ?? <VariableDeclaration>[],
this.requiredParameterCount =
requiredParameterCount ?? positionalParameters?.length ?? 0,
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
this.typeParameters = typeParameters ?? <TypeParameter>[] {
assert(returnType != null);
setParents(this.typeParameters, this);
setParents(this.positionalParameters, this);
setParents(this.namedParameters, this);
_body?.parent = this;
dartAsyncMarker ??= asyncMarker;
}
static DartType _getTypeOfVariable(VariableDeclaration node) => node.type;
static NamedType _getNamedTypeOfVariable(VariableDeclaration node) {
return new NamedType(node.name, node.type);
}
FunctionType get functionType {
TreeNode parent = this.parent;
List<NamedType> named =
namedParameters.map(_getNamedTypeOfVariable).toList(growable: false);
named.sort();
// We need create a copy of the list of type parameters, otherwise
// transformations like erasure don't work.
var typeParametersCopy = new List<TypeParameter>.from(parent is Constructor
? parent.enclosingClass.typeParameters
: typeParameters);
return new FunctionType(
positionalParameters.map(_getTypeOfVariable).toList(growable: false),
returnType,
namedParameters: named,
typeParameters: typeParametersCopy,
requiredParameterCount: requiredParameterCount);
}
accept(TreeVisitor v) => v.visitFunctionNode(this);
visitChildren(Visitor v) {
visitList(typeParameters, v);
visitList(positionalParameters, v);
visitList(namedParameters, v);
returnType?.accept(v);
body?.accept(v);
}
transformChildren(Transformer v) {
transformList(typeParameters, v, this);
transformList(positionalParameters, v, this);
transformList(namedParameters, v, this);
returnType = v.visitDartType(returnType);
if (body != null) {
body = body.accept(v);
body?.parent = this;
}
}
}
enum AsyncMarker {
// Do not change the order of these, the frontends depend on it.
Sync,
SyncStar,
Async,
AsyncStar,
// `SyncYielding` is a marker that tells Dart VM that this function is an
// artificial closure introduced by an async transformer which desugared all
// async syntax into a combination of native yields and helper method calls.
//
// Native yields (formatted as `[yield]`) are semantically close to
// `yield x` statement: they denote a yield/resume point within a function
// but are completely decoupled from the notion of iterators. When
// execution of the closure reaches `[yield] x` it stops and return the
// value of `x` to the caller. If closure is called again it continues
// to the next statement after this yield as if it was suspended and resumed.
//
// Consider this example:
//
// g() {
// var :await_jump_var = 0;
// var :await_ctx_var;
//
// f(x) yielding {
// [yield] '${x}:0';
// [yield] '${x}:1';
// [yield] '${x}:2';
// }
//
// return f;
// }
//
// print(f('a')); /* prints 'a:0', :await_jump_var = 1 */
// print(f('b')); /* prints 'b:1', :await_jump_var = 2 */
// print(f('c')); /* prints 'c:2', :await_jump_var = 3 */
//
// Note: currently Dart VM implicitly relies on async transformer to
// inject certain artificial variables into g (like `:await_jump_var`).
// As such SyncYielding and native yield are not intended to be used on their
// own, but are rather an implementation artifact of the async transformer
// itself.
SyncYielding,
}
// ------------------------------------------------------------------------
// EXPRESSIONS
// ------------------------------------------------------------------------
@coq
abstract class Expression extends TreeNode {
/// Returns the static type of the expression.
///
/// Should only be used on code compiled in strong mode, as this method
/// assumes the IR is strongly typed.
DartType getStaticType(TypeEnvironment types);
/// Returns the static type of the expression as an instantiation of
/// [superclass].
///
/// Should only be used on code compiled in strong mode, as this method
/// assumes the IR is strongly typed.
///
/// This method furthermore assumes that the type of the expression actually
/// is a subtype of (some instantiation of) the given [superclass].
/// If this is not the case, either an exception is thrown or the raw type of
/// [superclass] is returned.
InterfaceType getStaticTypeAsInstanceOf(
Class superclass, TypeEnvironment types) {
// This method assumes the program is correctly typed, so if the superclass
// is not generic, we can just return its raw type without computing the
// type of this expression. It also ensures that all types are considered
// subtypes of Object (not just interface types), and function types are
// considered subtypes of Function.
if (superclass.typeParameters.isEmpty) {
return superclass.rawType;
}
var type = getStaticType(types);
while (type is TypeParameterType) {
type = (type as TypeParameterType).parameter.bound;
}
if (type is InterfaceType) {
var upcastType = types.hierarchy.getTypeAsInstanceOf(type, superclass);
if (upcastType != null) return upcastType;
} else if (type is BottomType) {
return superclass.bottomType;
}
types.typeError(this, '$type is not a subtype of $superclass');
return superclass.rawType;
}
accept(ExpressionVisitor v);
accept1(ExpressionVisitor1 v, arg);
}
/// An expression containing compile-time errors.
///
/// Should throw a runtime error when evaluated.
class InvalidExpression extends Expression {
String message;
InvalidExpression(this.message);
DartType getStaticType(TypeEnvironment types) => const BottomType();
accept(ExpressionVisitor v) => v.visitInvalidExpression(this);
accept1(ExpressionVisitor1 v, arg) => v.visitInvalidExpression(this, arg);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
/// Read a local variable, a local function, or a function parameter.
@coq
class VariableGet extends Expression {
VariableDeclaration variable;
@nocoq
DartType promotedType; // Null if not promoted.
VariableGet(this.variable, [this.promotedType]);
DartType getStaticType(TypeEnvironment types) {
return promotedType ?? variable.type;
}
accept(ExpressionVisitor v) => v.visitVariableGet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitVariableGet(this, arg);
visitChildren(Visitor v) {
promotedType?.accept(v);
}
transformChildren(Transformer v) {
if (promotedType != null) {
promotedType = v.visitDartType(promotedType);
}
}
}
/// Assign a local variable or function parameter.
///
/// Evaluates to the value of [value].
class VariableSet extends Expression {
VariableDeclaration variable;
Expression value;
VariableSet(this.variable, this.value) {
value?.parent = this;
}
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
accept(ExpressionVisitor v) => v.visitVariableSet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitVariableSet(this, arg);
visitChildren(Visitor v) {
value?.accept(v);
}
transformChildren(Transformer v) {
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// Expression of form `x.field`.
///
/// This may invoke a getter, read a field, or tear off a method.
@coq
class PropertyGet extends Expression {
Expression receiver;
@coq
Name name;
@nocoq
Reference interfaceTargetReference;
PropertyGet(Expression receiver, Name name, [Member interfaceTarget])
: this.byReference(receiver, name, getMemberReference(interfaceTarget));
PropertyGet.byReference(
this.receiver, this.name, this.interfaceTargetReference) {
receiver?.parent = this;
}
Member get interfaceTarget => interfaceTargetReference?.asMember;
void set interfaceTarget(Member member) {
interfaceTargetReference = getMemberReference(member);
}
DartType getStaticType(TypeEnvironment types) {
var interfaceTarget = this.interfaceTarget;
if (interfaceTarget != null) {
Class superclass = interfaceTarget.enclosingClass;
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
return Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.getterType);
}
// Treat the properties of Object specially.
String nameString = name.name;
if (nameString == 'hashCode') {
return types.intType;
} else if (nameString == 'runtimeType') {
return types.typeType;
}
return const DynamicType();
}
accept(ExpressionVisitor v) => v.visitPropertyGet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitPropertyGet(this, arg);
visitChildren(Visitor v) {
receiver?.accept(v);
name?.accept(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
}
}
/// Expression of form `x.field = value`.
///
/// This may invoke a setter or assign a field.
///
/// Evaluates to the value of [value].
class PropertySet extends Expression {
Expression receiver;
Name name;
Expression value;
Reference interfaceTargetReference;
PropertySet(Expression receiver, Name name, Expression value,
[Member interfaceTarget])
: this.byReference(
receiver, name, value, getMemberReference(interfaceTarget));
PropertySet.byReference(
this.receiver, this.name, this.value, this.interfaceTargetReference) {
receiver?.parent = this;
value?.parent = this;
}
Member get interfaceTarget => interfaceTargetReference?.asMember;
void set interfaceTarget(Member member) {
interfaceTargetReference = getMemberReference(member);
}
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
accept(ExpressionVisitor v) => v.visitPropertySet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitPropertySet(this, arg);
visitChildren(Visitor v) {
receiver?.accept(v);
name?.accept(v);
value?.accept(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// Directly read a field, call a getter, or tear off a method.
class DirectPropertyGet extends Expression {
Expression receiver;
Reference targetReference;
DirectPropertyGet(Expression receiver, Member target)
: this.byReference(receiver, getMemberReference(target));
DirectPropertyGet.byReference(this.receiver, this.targetReference) {
receiver?.parent = this;
}
Member get target => targetReference?.asMember;
void set target(Member target) {
targetReference = getMemberReference(target);
}
visitChildren(Visitor v) {
receiver?.accept(v);
target?.acceptReference(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
}
accept(ExpressionVisitor v) => v.visitDirectPropertyGet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertyGet(this, arg);
DartType getStaticType(TypeEnvironment types) {
Class superclass = target.enclosingClass;
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
return Substitution.fromInterfaceType(receiverType)
.substituteType(target.getterType);
}
}
/// Directly assign a field, or call a setter.
///
/// Evaluates to the value of [value].
class DirectPropertySet extends Expression {
Expression receiver;
Reference targetReference;
Expression value;
DirectPropertySet(Expression receiver, Member target, Expression value)
: this.byReference(receiver, getMemberReference(target), value);
DirectPropertySet.byReference(
this.receiver, this.targetReference, this.value) {
receiver?.parent = this;
value?.parent = this;
}
Member get target => targetReference?.asMember;
void set target(Member target) {
targetReference = getMemberReference(target);
}
visitChildren(Visitor v) {
receiver?.accept(v);
target?.acceptReference(v);
value?.accept(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
accept(ExpressionVisitor v) => v.visitDirectPropertySet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertySet(this, arg);
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
}
/// Directly call an instance method, bypassing ordinary dispatch.
class DirectMethodInvocation extends InvocationExpression {
Expression receiver;
Reference targetReference;
Arguments arguments;
DirectMethodInvocation(
Expression receiver, Procedure target, Arguments arguments)
: this.byReference(receiver, getMemberReference(target), arguments);
DirectMethodInvocation.byReference(
this.receiver, this.targetReference, this.arguments) {
receiver?.parent = this;
arguments?.parent = this;
}
Procedure get target => targetReference?.asProcedure;
void set target(Procedure target) {
targetReference = getMemberReference(target);
}
Name get name => target?.name;
visitChildren(Visitor v) {
receiver?.accept(v);
target?.acceptReference(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
accept(ExpressionVisitor v) => v.visitDirectMethodInvocation(this);
accept1(ExpressionVisitor1 v, arg) =>
v.visitDirectMethodInvocation(this, arg);
DartType getStaticType(TypeEnvironment types) {
if (types.isOverloadedArithmeticOperator(target)) {
return types.getTypeOfOverloadedArithmetic(receiver.getStaticType(types),
arguments.positional[0].getStaticType(types));
}
Class superclass = target.enclosingClass;
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
var returnType = Substitution.fromInterfaceType(receiverType)
.substituteType(target.function.returnType);
return Substitution.fromPairs(
target.function.typeParameters, arguments.types)
.substituteType(returnType);
}
}
/// Expression of form `super.field`.
///
/// This may invoke a getter, read a field, or tear off a method.
class SuperPropertyGet extends Expression {
Name name;
Reference interfaceTargetReference;
SuperPropertyGet(Name name, [Member interfaceTarget])
: this.byReference(name, getMemberReference(interfaceTarget));
SuperPropertyGet.byReference(this.name, this.interfaceTargetReference);
Member get interfaceTarget => interfaceTargetReference?.asMember;
void set interfaceTarget(Member member) {
interfaceTargetReference = getMemberReference(member);
}
DartType getStaticType(TypeEnvironment types) {
Class declaringClass = interfaceTarget.enclosingClass;
if (declaringClass.typeParameters.isEmpty) {
return interfaceTarget.getterType;
}
var receiver =
types.hierarchy.getTypeAsInstanceOf(types.thisType, declaringClass);
return Substitution.fromInterfaceType(receiver)
.substituteType(interfaceTarget.getterType);
}
accept(ExpressionVisitor v) => v.visitSuperPropertyGet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertyGet(this, arg);
visitChildren(Visitor v) {
name?.accept(v);
}
transformChildren(Transformer v) {}
}
/// Expression of form `super.field = value`.
///
/// This may invoke a setter or assign a field.
///
/// Evaluates to the value of [value].
class SuperPropertySet extends Expression {
Name name;
Expression value;
Reference interfaceTargetReference;
SuperPropertySet(Name name, Expression value, Member interfaceTarget)
: this.byReference(name, value, getMemberReference(interfaceTarget));
SuperPropertySet.byReference(
this.name, this.value, this.interfaceTargetReference) {
value?.parent = this;
}
Member get interfaceTarget => interfaceTargetReference?.asMember;
void set interfaceTarget(Member member) {
interfaceTargetReference = getMemberReference(member);
}
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
accept(ExpressionVisitor v) => v.visitSuperPropertySet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertySet(this, arg);
visitChildren(Visitor v) {
name?.accept(v);
value?.accept(v);
}
transformChildren(Transformer v) {
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// Read a static field, call a static getter, or tear off a static method.
class StaticGet extends Expression {
/// A static field, getter, or method (for tear-off).
Reference targetReference;
StaticGet(Member target) : this.byReference(getMemberReference(target));
StaticGet.byReference(this.targetReference);
Member get target => targetReference?.asMember;
void set target(Member target) {
targetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) => target.getterType;
accept(ExpressionVisitor v) => v.visitStaticGet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitStaticGet(this, arg);
visitChildren(Visitor v) {
target?.acceptReference(v);
}
transformChildren(Transformer v) {}
}
/// Assign a static field or call a static setter.
///
/// Evaluates to the value of [value].
class StaticSet extends Expression {
/// A mutable static field or a static setter.
Reference targetReference;
Expression value;
StaticSet(Member target, Expression value)
: this.byReference(getMemberReference(target), value);
StaticSet.byReference(this.targetReference, this.value) {
value?.parent = this;
}
Member get target => targetReference?.asMember;
void set target(Member target) {
targetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
accept(ExpressionVisitor v) => v.visitStaticSet(this);
accept1(ExpressionVisitor1 v, arg) => v.visitStaticSet(this, arg);
visitChildren(Visitor v) {
target?.acceptReference(v);
value?.accept(v);
}
transformChildren(Transformer v) {
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// The arguments to a function call, divided into type arguments,
/// positional arguments, and named arguments.
@coq
class Arguments extends TreeNode {
@nocoq
final List<DartType> types;
@coqsingle
final List<Expression> positional;
List<NamedExpression> named;
Arguments(this.positional,
{List<DartType> types, List<NamedExpression> named})
: this.types = types ?? <DartType>[],
this.named = named ?? <NamedExpression>[] {
setParents(this.positional, this);
setParents(this.named, this);
}
Arguments.empty()
: types = <DartType>[],
positional = <Expression>[],
named = <NamedExpression>[];
factory Arguments.forwarded(FunctionNode function) {
return new Arguments(
function.positionalParameters.map((p) => new VariableGet(p)).toList(),
named: function.namedParameters
.map((p) => new NamedExpression(p.name, new VariableGet(p)))
.toList(),
types: function.typeParameters
.map((p) => new TypeParameterType(p))
.toList());
}
accept(TreeVisitor v) => v.visitArguments(this);
visitChildren(Visitor v) {
visitList(types, v);
visitList(positional, v);
visitList(named, v);
}
transformChildren(Transformer v) {
transformTypeList(types, v);
transformList(positional, v, this);
transformList(named, v, this);
}
}
/// A named argument, `name: value`.
class NamedExpression extends TreeNode {
String name;
Expression value;
NamedExpression(this.name, this.value) {
value?.parent = this;
}
accept(TreeVisitor v) => v.visitNamedExpression(this);
visitChildren(Visitor v) {
value?.accept(v);
}
transformChildren(Transformer v) {
if (value != null) {
value = value.accept(v);
value?.parent = this;
}
}
}
/// Common super class for [DirectMethodInvocation], [MethodInvocation],
/// [SuperMethodInvocation], [StaticInvocation], and [ConstructorInvocation].
@coq
abstract class InvocationExpression extends Expression {
Arguments get arguments;
set arguments(Arguments value);
/// Name of the invoked method.
///
/// May be `null` if the target is a synthetic static member without a name.
Name get name;
}
/// Expression of form `x.foo(y)`.
@coq
class MethodInvocation extends InvocationExpression {
Expression receiver;
Name name;
Arguments arguments;
Reference interfaceTargetReference;
MethodInvocation(Expression receiver, Name name, Arguments arguments,
[Member interfaceTarget])
: this.byReference(
receiver, name, arguments, getMemberReference(interfaceTarget));
MethodInvocation.byReference(
this.receiver, this.name, this.arguments, this.interfaceTargetReference) {
receiver?.parent = this;
arguments?.parent = this;
}
Member get interfaceTarget => interfaceTargetReference?.asMember;
void set interfaceTarget(Member target) {
interfaceTargetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) {
var interfaceTarget = this.interfaceTarget;
if (interfaceTarget != null) {
if (interfaceTarget is Procedure &&
types.isOverloadedArithmeticOperator(interfaceTarget)) {
return types.getTypeOfOverloadedArithmetic(
receiver.getStaticType(types),
arguments.positional[0].getStaticType(types));
}
Class superclass = interfaceTarget.enclosingClass;
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
var getterType = Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.getterType);
if (getterType is FunctionType) {
return Substitution.fromPairs(
getterType.typeParameters, arguments.types)
.substituteType(getterType.returnType);
} else {
return const DynamicType();
}
}
if (name.name == 'call') {
var receiverType = receiver.getStaticType(types);
if (receiverType is FunctionType) {
if (receiverType.typeParameters.length != arguments.types.length) {
return const BottomType();
}
return Substitution.fromPairs(
receiverType.typeParameters, arguments.types)
.substituteType(receiverType.returnType);
}
}
if (name.name == '==') {
// We use this special case to simplify generation of '==' checks.
return types.boolType;
}
return const DynamicType();
}
accept(ExpressionVisitor v) => v.visitMethodInvocation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitMethodInvocation(this, arg);
visitChildren(Visitor v) {
receiver?.accept(v);
name?.accept(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (receiver != null) {
receiver = receiver.accept(v);
receiver?.parent = this;
}
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
}
/// Expression of form `super.foo(x)`.
///
/// The provided arguments might not match the parameters of the target.
class SuperMethodInvocation extends InvocationExpression {
Name name;
Arguments arguments;
Reference interfaceTargetReference;
SuperMethodInvocation(Name name, Arguments arguments,
[Procedure interfaceTarget])
: this.byReference(name, arguments, getMemberReference(interfaceTarget));
SuperMethodInvocation.byReference(
this.name, this.arguments, this.interfaceTargetReference) {
arguments?.parent = this;
}
Procedure get interfaceTarget => interfaceTargetReference?.asProcedure;
void set interfaceTarget(Procedure target) {
interfaceTargetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) {
if (interfaceTarget == null) return const DynamicType();
Class superclass = interfaceTarget.enclosingClass;
var receiverType =
types.hierarchy.getTypeAsInstanceOf(types.thisType, superclass);
var returnType = Substitution.fromInterfaceType(receiverType)
.substituteType(interfaceTarget.function.returnType);
return Substitution.fromPairs(
interfaceTarget.function.typeParameters, arguments.types)
.substituteType(returnType);
}
accept(ExpressionVisitor v) => v.visitSuperMethodInvocation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitSuperMethodInvocation(this, arg);
visitChildren(Visitor v) {
name?.accept(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
}
/// Expression of form `foo(x)`, or `const foo(x)` if the target is an
/// external constant factory.
///
/// The provided arguments might not match the parameters of the target.
class StaticInvocation extends InvocationExpression {
Reference targetReference;
Arguments arguments;
/// True if this is a constant call to an external constant factory.
bool isConst;
Name get name => target?.name;
StaticInvocation(Procedure target, Arguments arguments, {bool isConst: false})
: this.byReference(getMemberReference(target), arguments,
isConst: isConst);
StaticInvocation.byReference(this.targetReference, this.arguments,
{this.isConst: false}) {
arguments?.parent = this;
}
Procedure get target => targetReference?.asProcedure;
void set target(Procedure target) {
targetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) {
return Substitution.fromPairs(
target.function.typeParameters, arguments.types)
.substituteType(target.function.returnType);
}
accept(ExpressionVisitor v) => v.visitStaticInvocation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitStaticInvocation(this, arg);
visitChildren(Visitor v) {
target?.acceptReference(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
}
/// Expression of form `new Foo(x)` or `const Foo(x)`.
///
/// The provided arguments might not match the parameters of the target.
//
// DESIGN TODO: Should we pass type arguments in a separate field
// `classTypeArguments`? They are quite different from type arguments to
// generic functions.
@coq
class ConstructorInvocation extends InvocationExpression {
Reference targetReference;
@nocoq
Arguments arguments;
bool isConst;
Name get name => target?.name;
ConstructorInvocation(Constructor target, Arguments arguments,
{bool isConst: false})
: this.byReference(getMemberReference(target), arguments,
isConst: isConst);
ConstructorInvocation.byReference(this.targetReference, this.arguments,
{this.isConst: false}) {
arguments?.parent = this;
}
Constructor get target => targetReference?.asConstructor;
void set target(Constructor target) {
targetReference = getMemberReference(target);
}
DartType getStaticType(TypeEnvironment types) {
return arguments.types.isEmpty
? target.enclosingClass.rawType
: new InterfaceType(target.enclosingClass, arguments.types);
}
accept(ExpressionVisitor v) => v.visitConstructorInvocation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitConstructorInvocation(this, arg);
visitChildren(Visitor v) {
target?.acceptReference(v);
arguments?.accept(v);
}
transformChildren(Transformer v) {
if (arguments != null) {
arguments = arguments.accept(v);
arguments?.parent = this;
}
}
InterfaceType get constructedType {
return arguments.types.isEmpty
? target.enclosingClass.rawType
: new InterfaceType(target.enclosingClass, arguments.types);
}
}
/// An explicit type instantiation of a generic function.
class Instantiation extends Expression {
Expression expression;
final List<DartType> typeArguments;
Instantiation(this.expression, this.typeArguments) {
expression?.parent = this;
}
DartType getStaticType(TypeEnvironment types) {
FunctionType type = expression.getStaticType(types);
return Substitution.fromPairs(type.typeParameters, typeArguments)
.substituteType(type.withoutTypeParameters);
}
accept(ExpressionVisitor v) => v.visitInstantiation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitInstantiation(this, arg);
visitChildren(Visitor v) {
expression?.accept(v);
visitList(typeArguments, v);
}
transformChildren(Transformer v) {
if (expression != null) {
expression = expression.accept(v);
expression?.parent = this;
}
transformTypeList(typeArguments, v);
}
}
/// Expression of form `!x`.
///
/// The `is!` and `!=` operators are desugared into [Not] nodes with `is` and
/// `==` expressions inside, respectively.
class Not extends Expression {
Expression operand;
Not(this.operand) {
operand?.parent = this;
}
DartType getStaticType(TypeEnvironment types) => types.boolType;
accept(ExpressionVisitor v) => v.visitNot(this);
accept1(ExpressionVisitor1 v, arg) => v.visitNot(this, arg);
visitChildren(Visitor v) {
operand?.accept(v);
}
transformChildren(Transformer v) {
if (operand != null) {
operand = operand.accept(v);
operand?.parent = this;
}
}
}
/// Expression of form `x && y` or `x || y`
class LogicalExpression extends Expression {
Expression left;
String operator; // && or || or ??
Expression right;
LogicalExpression(this.left, this.operator, this.right) {
left?.parent = this;
right?.parent = this;
}
DartType getStaticType(TypeEnvironment types) => types.boolType;
accept(ExpressionVisitor v) => v.visitLogicalExpression(this);
accept1(ExpressionVisitor1 v, arg) => v.visitLogicalExpression(this, arg);
visitChildren(Visitor v) {
left?.accept(v);
right?.accept(v);
}
transformChildren(Transformer v) {
if (left != null) {
left = left.accept(v);
left?.parent = this;
}
if (right != null) {
right = right.accept(v);
right?.parent = this;
}
}
}
/// Expression of form `x ? y : z`.
class ConditionalExpression extends Expression {
Expression condition;
Expression then;
Expression otherwise;
/// The static type of the expression. Should not be `null`.
DartType staticType;
ConditionalExpression(
this.condition, this.then, this.otherwise, this.staticType) {
condition?.parent = this;
then?.parent = this;
otherwise?.parent = this;
}
DartType getStaticType(TypeEnvironment types) => staticType;
accept(ExpressionVisitor v) => v.visitConditionalExpression(this);
accept1(ExpressionVisitor1 v, arg) => v.visitConditionalExpression(this, arg);
visitChildren(Visitor v) {
condition?.accept(v);
then?.accept(v);
otherwise?.accept(v);
staticType?.accept(v);
}
transformChildren(Transformer v) {
if (condition != null) {
condition = condition.accept(v);
condition?.parent = this;
}
if (then != null) {
then = then.accept(v);
then?.parent = this;
}
if (otherwise != null) {
otherwise = otherwise.accept(v);
otherwise?.parent = this;
}
if (staticType != null) {
staticType = v.visitDartType(staticType);
}
}
}
/// Convert expressions to strings and concatenate them. Semantically, calls
/// `toString` on every argument, checks that a string is returned, and returns
/// the concatenation of all the strings.
///
/// If [expressions] is empty then an empty string is returned.
///
/// These arise from string interpolations and adjacent string literals.
class StringConcatenation extends Expression {
final List<Expression> expressions;
StringConcatenation(this.expressions) {
setParents(expressions, this);
}
DartType getStaticType(TypeEnvironment types) => types.stringType;
accept(ExpressionVisitor v) => v.visitStringConcatenation(this);
accept1(ExpressionVisitor1 v, arg) => v.visitStringConcatenation(this, arg);
visitChildren(Visitor v) {
visitList(expressions, v);
}
transformChildren(Transformer v) {
transformList(expressions, v, this);
}
}
/// Expression of form `x is T`.
class IsExpression extends Expression {
Expression operand;
DartType type;
IsExpression(this.operand, this.type) {
operand?.parent = this;
}
DartType getStaticType(TypeEnvironment types) => types.boolType;
accept(ExpressionVisitor v) => v.visitIsExpression(this);
accept1(ExpressionVisitor1 v, arg) => v.visitIsExpression(this, arg);
visitChildren(Visitor v) {
operand?.accept(v);
type?.accept(v);
}