blob: fb8d642fd9c3adb5ba40fba0f9ef841f1964f4ae [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.
/// -----------------------------------------------------------------------
/// ERROR HANDLING
/// -----------------------------------------------------------------------
///
/// Consumers of the AST currently need to handle two types of error cases:
/// - The "invalid" node types (e.g. [InvalidExpression])
/// - Mismatching arguments and parameters on statically resolved invocations.
///
/// Although the frontend does not yet catch all possible errors, AST consumers
/// should assume that a member reference always points to a meaningful target.
/// For instance, a [SuperInvocation] will never target an abstract method, and
/// [StaticSet] will never have a final field as a write target. Should the
/// input contain such errors, the frontend must create an invalid node instead.
///
/// Since the frontend is not yet complete, erroneous code may slip through,
/// but this should be fixed in the frontend, not by the AST consumer.
///
/// -----------------------------------------------------------------------
/// NAMES
/// -----------------------------------------------------------------------
///
/// We distinguish two kinds of names, **binding names** and **cosmetic names**.
///
/// Binding names are:
/// - names used for dynamic dispatch
/// - external member names
/// - named parameter names
///
/// Cosmetic names are everything else, for example:
/// - local variable names
/// - positional parameter names
/// - static member names (non-external)
/// - constructor names (non-external)
/// - class names
/// - library names
///
/// Cosmetic names are only stored at the definition site of an object, e.g.
/// a static method invocation does not store the name of the target method,
/// only the method itself does.
///
/// Cosmetic names can sometimes be observed by introspection features like
/// mirrors, they can show up in stack traces, and transformers may want to rely
/// on them. Cosmetic names may in general be `null` for synthetic objects.
///
/// -----------------------------------------------------------------------
/// 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 'visitor.dart';
export 'visitor.dart';
import 'text/ast_to_text.dart';
import 'type_algebra.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).
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) & 0x7fffffff;
TreeNode parent;
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;
}
}
// ------------------------------------------------------------------------
// LIBRARIES and CLASSES
// ------------------------------------------------------------------------
class Library extends TreeNode {
/// An absolute import path to this library.
///
/// The [Uri] should have the `dart`, `package`, or `file` scheme.
//
// DESIGN TODO: Absolute `file` URIs are not ideal for serialization. We will
// revise this when we implement modular compilation.
Uri importUri;
/// If false, the library object is a placeholder for a library that has
/// not been loaded yet.
///
/// The [importUri] is always set on an unloaded library, and can be used
/// as they key to load the library.
///
/// Unloaded libraries may contain arbitrary classes and members for use by
/// the frontend until the library is loaded. Clients should not rely
/// on unloaded library objects being in any particular state.
bool isLoaded = true;
String name; // Cosmetic name.
final List<Class> classes;
final List<Procedure> procedures;
final List<Field> fields;
Library(this.importUri,
{this.name,
List<Class> classes,
List<Procedure> procedures,
List<Field> fields})
: this.classes = classes ?? <Class>[],
this.procedures = procedures ?? <Procedure>[],
this.fields = fields ?? <Field>[] {
_setParents(this.classes, this);
_setParents(this.procedures, this);
_setParents(this.fields, this);
}
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 addClass(Class class_) {
class_.parent = this;
classes.add(class_);
}
accept(TreeVisitor v) => v.visitLibrary(this);
visitChildren(Visitor v) {
_visitList(classes, v);
_visitList(procedures, v);
_visitList(fields, v);
}
transformChildren(Transformer v) {
_transformList(classes, v, this);
_transformList(procedures, v, this);
_transformList(fields, v, this);
}
/// Returns a possibly synthesized name for this library, consistent with
/// the names used in [toString] calls.
String get debugName => debugLibraryName(this);
}
/// A class declaration.
///
/// There are two kinds of classes: [MixinClass] is a mixin application and
/// a [NormalClass] is any other kind of class.
///
/// The two subclasses enforce the runtime invariant that mixin applications
/// can never declare fields or procedures. Code that relies on this invariant
/// should treat the two kinds of classes separately, but otherwise it is
/// recommended to interface against [Class].
abstract class Class extends TreeNode {
String name; // Cosmetic name.
bool isAbstract;
final List<TypeParameter> typeParameters;
/// The immediate super type, or `null` if this is the root class.
InterfaceType superType;
/// The types from the `implements` clause.
final List<InterfaceType> implementedTypes;
/// Fields declared in the class.
///
/// For mixin applications this is an immutable empty list.
final List<Field> fields;
/// Constructors declared in the class.
final List<Constructor> constructors;
/// Procedures declared in the class.
///
/// For mixin applications this is an immutable empty list.
final List<Procedure> procedures;
Class(this.name, this.isAbstract, this.typeParameters, this.superType,
this.implementedTypes, this.fields, this.constructors, this.procedures) {
_setParents(typeParameters, this);
_setParents(constructors, this);
_setParents(procedures, this);
_setParents(fields, this);
}
/// Returns the mixed-in type if this is a mixin application, otherwise null.
InterfaceType get mixedInType => null;
bool get isMixinApplication => false;
/// 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].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<InterfaceType> get supers => <Iterable<InterfaceType>>[
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 {
throw new ArgumentError(member);
}
}
accept(ClassVisitor v);
acceptReference(ClassReferenceVisitor v);
bool get isLoaded => enclosingLibrary.isLoaded;
InterfaceType _rawType;
InterfaceType get rawType => _rawType ??= new InterfaceType(this);
InterfaceType _thisType;
InterfaceType get thisType {
return _thisType ??=
new InterfaceType(this, _getAsTypeArguments(typeParameters));
}
/// Returns a possibly synthesized name for this class, consistent with
/// the names used in [toString] calls.
String get debugName => debugClassName(this);
}
/// A class that is not a mixin application.
class NormalClass extends Class {
NormalClass(InterfaceType superType,
{String name,
bool isAbstract: false,
List<TypeParameter> typeParameters,
List<InterfaceType> implementedClasses,
List<Constructor> constructors,
List<Procedure> procedures,
List<Field> fields})
: super(
name,
isAbstract,
typeParameters ?? <TypeParameter>[],
superType,
implementedClasses ?? <InterfaceType>[],
fields ?? <Field>[],
constructors ?? <Constructor>[],
procedures ?? <Procedure>[]);
accept(ClassVisitor v) => v.visitNormalClass(this);
acceptReference(ClassReferenceVisitor v) => v.visitNormalClassReference(this);
visitChildren(Visitor v) {
_visitList(typeParameters, v);
superType?.accept(v);
_visitList(implementedTypes, v);
_visitList(constructors, v);
_visitList(procedures, v);
_visitList(fields, v);
}
transformChildren(Transformer v) {
_transformList(typeParameters, v, this);
_transformList(constructors, v, this);
_transformList(procedures, v, this);
_transformList(fields, v, this);
}
}
/// The result of mixing two classes [superType] and [mixedInType].
///
/// A class declaration `class A extends B with C` is represented as a
/// normal class `A` with the mixin class `B with C` as its super class.
///
/// A mixin with multiple classes `A with B, C` is represented as a left-leaning
/// tree of mixin classes `(A with B) with C`. Each mixin will have an entry
/// in [Library.classes].
///
/// Mixin applications cannot declare any fields or procedures, as it implicitly
/// uses those from the mixed-in class.
class MixinClass extends Class {
InterfaceType mixedInType;
MixinClass(InterfaceType superType, this.mixedInType,
{String name,
bool isAbstract: false,
List<TypeParameter> typeParameters,
List<InterfaceType> implementedClasses,
List<Constructor> constructors})
: super(
name,
isAbstract,
typeParameters ?? <TypeParameter>[],
superType,
implementedClasses ?? <InterfaceType>[],
const <Field>[],
constructors ?? <Constructor>[],
const <Procedure>[]);
bool get isMixinApplication => true;
accept(ClassVisitor v) => v.visitMixinClass(this);
acceptReference(ClassReferenceVisitor v) => v.visitMixinClassReference(this);
visitChildren(Visitor v) {
_visitList(typeParameters, v);
superType?.accept(v);
mixedInType?.accept(v);
_visitList(implementedTypes, v);
_visitList(constructors, v);
}
transformChildren(Transformer v) {
_transformList(typeParameters, v, this);
_transformList(constructors, v, this);
}
}
// ------------------------------------------------------------------------
// MEMBERS
// ------------------------------------------------------------------------
abstract class Member extends TreeNode {
Name get name;
set name(Name name);
Class get enclosingClass => parent is Class ? parent : null;
Library get enclosingLibrary => parent is Class ? parent.parent : parent;
accept(MemberVisitor v);
acceptReference(MemberReferenceVisitor v);
bool get isLoaded => enclosingLibrary.isLoaded;
/// Returns a possibly synthesized name for this member, consistent with
/// the names used in [toString] calls.
String get debugName => debugMemberName(this);
}
/// A field declaration.
///
/// The implied getter and setter for the field are not represented explicitly.
class Field extends Member {
Name name;
DartType type; // Not null. Defaults to DynamicType.
int flags = 0;
Expression initializer; // May be null.
Field(this.name,
{DartType type,
this.initializer,
bool isFinal: false,
bool isConst: false,
bool isStatic: false})
: this.type = type ?? const DynamicType() {
initializer?.parent = this;
this.isFinal = isFinal;
this.isConst = isConst;
this.isStatic = isStatic;
}
static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
static const int FlagConst = 1 << 1;
static const int FlagStatic = 1 << 2;
bool get isFinal => flags & FlagFinal != 0;
bool get isConst => flags & FlagConst != 0;
bool get isStatic => flags & FlagStatic != 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 isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
/// True if the field is neither final nor const.
bool get isMutable => flags & (FlagStatic | FlagConst) == 0;
accept(MemberVisitor v) => v.visitField(this);
acceptReference(MemberReferenceVisitor v) => v.visitFieldReference(this);
visitChildren(Visitor v) {
type?.accept(v);
name?.accept(v);
initializer?.accept(v);
}
transformChildren(Transformer v) {
if (initializer != null) {
initializer = initializer.accept(v);
initializer?.parent = this;
}
}
}
/// 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.
class Constructor extends Member {
int flags = 0;
/// Cosmetic name of the constructor.
///
/// For unnamed constructors, this is the empty string (in a [Name]).
Name name;
FunctionNode function;
List<Initializer> initializers;
Constructor(this.function,
{this.name,
bool isConst: false,
bool isExternal: false,
List<Initializer> initializers})
: this.initializers = initializers ?? <Initializer>[] {
function?.parent = this;
_setParents(this.initializers, this);
this.isConst = isConst;
this.isExternal = isExternal;
}
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);
}
accept(MemberVisitor v) => v.visitConstructor(this);
acceptReference(MemberReferenceVisitor v) =>
v.visitConstructorReference(this);
visitChildren(Visitor v) {
name?.accept(v);
function?.accept(v);
_visitList(initializers, v);
}
transformChildren(Transformer v) {
if (function != null) {
function = function.accept(v);
function?.parent = this;
}
_transformList(initializers, v, this);
}
}
/// 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.
class Procedure extends Member {
ProcedureKind kind;
int flags = 0;
/// Name of the procedure.
///
/// For static non-external procedures, the name is cosmetic and may be `null`
/// but keep that the name may show up in stack traces.
///
/// 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, this is just name 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-`.
Name name;
FunctionNode function; // Null if and only if abstract or external.
Procedure(this.name, this.kind, this.function,
{bool isAbstract: false, bool isStatic: false, bool isExternal: false}) {
function?.parent = this;
this.isAbstract = isAbstract;
this.isStatic = isStatic;
this.isExternal = isExternal;
}
Procedure.abstract_(this.name, this.kind)
: function = null,
flags = FlagAbstract;
static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
static const int FlagAbstract = 1 << 1;
static const int FlagExternal = 1 << 2;
bool get isStatic => flags & FlagStatic != 0;
bool get isAbstract => flags & FlagAbstract != 0;
bool get isExternal => flags & FlagExternal != 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);
}
accept(MemberVisitor v) => v.visitProcedure(this);
acceptReference(MemberReferenceVisitor v) => v.visitProcedureReference(this);
visitChildren(Visitor v) {
name?.accept(v);
function?.accept(v);
}
transformChildren(Transformer v) {
if (function != null) {
function = function.accept(v);
function?.parent = this;
}
}
}
enum ProcedureKind {
Method,
Getter,
Setter,
IndexGetter,
IndexSetter,
Operator,
Factory,
}
// ------------------------------------------------------------------------
// CONSTRUCTOR INITIALIZERS
// ------------------------------------------------------------------------
/// Part of an initializer list in a constructor.
abstract class Initializer extends TreeNode {
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` occuring 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.
Field field;
Expression value;
FieldInitializer(this.field, this.value) {
value?.parent = this;
}
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)` occuring 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.
Constructor target;
Arguments arguments;
SuperInitializer(this.target, this.arguments) {
arguments?.parent = this;
}
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)` occuring 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.
Constructor target;
Arguments arguments;
RedirectingInitializer(this.target, this.arguments) {
arguments?.parent = this;
}
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;
}
}
}
// ------------------------------------------------------------------------
// FUNCTIONS
// ------------------------------------------------------------------------
/// A function declares parameters and has a body.
///
/// This may occur in a procedure, constructor, function expression, or local
/// function declaration.
class FunctionNode extends TreeNode {
AsyncMarker asyncMarker;
List<TypeParameter> typeParameters;
int requiredParameterCount;
List<VariableDeclaration> positionalParameters;
List<VariableDeclaration> namedParameters;
DartType returnType; // May be null. Always null for constructors.
Statement body;
FunctionNode(this.body,
{List<TypeParameter> typeParameters,
List<VariableDeclaration> positionalParameters,
List<VariableDeclaration> namedParameters,
int requiredParameterCount,
this.returnType,
this.asyncMarker: AsyncMarker.Sync})
: this.positionalParameters =
positionalParameters ?? <VariableDeclaration>[],
this.requiredParameterCount =
requiredParameterCount ?? positionalParameters?.length ?? 0,
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
this.typeParameters = typeParameters ?? <TypeParameter>[] {
_setParents(this.typeParameters, this);
_setParents(this.positionalParameters, this);
_setParents(this.namedParameters, this);
body?.parent = this;
}
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);
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
}
// ------------------------------------------------------------------------
// EXPRESSIONS
// ------------------------------------------------------------------------
abstract class Expression extends TreeNode {
accept(ExpressionVisitor v);
}
/// An expression containing compile-time errors.
///
/// Should throw a runtime error when evaluated.
class InvalidExpression extends Expression {
accept(ExpressionVisitor v) => v.visitInvalidExpression(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
/// Read a local variable, a local function, or a function parameter.
class VariableGet extends Expression {
VariableDeclaration variable;
VariableGet(this.variable);
accept(ExpressionVisitor v) => v.visitVariableGet(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
/// Assign a local variable or function parameter.
class VariableSet extends Expression {
VariableDeclaration variable;
Expression value;
VariableSet(this.variable, this.value) {
value?.parent = this;
}
accept(ExpressionVisitor v) => v.visitVariableSet(this);
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.
class PropertyGet extends Expression {
Expression receiver;
Name name;
PropertyGet(this.receiver, this.name) {
receiver?.parent = this;
}
accept(ExpressionVisitor v) => v.visitPropertyGet(this);
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.
class PropertySet extends Expression {
Expression receiver;
Name name;
Expression value;
PropertySet(this.receiver, this.name, this.value) {
receiver?.parent = this;
value?.parent = this;
}
accept(ExpressionVisitor v) => v.visitPropertySet(this);
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;
}
}
}
/// Expression of form `super.field`.
///
/// This may invoke a getter, read a field, or tear off a method.
class SuperPropertyGet extends Expression {
/// A field or a getter, or a method (for tear-off) in a super class.
///
/// Cannot be static or abstract.
Member target;
SuperPropertyGet(this.target);
accept(ExpressionVisitor v) => v.visitSuperPropertyGet(this);
visitChildren(Visitor v) {
target?.acceptReference(v);
}
transformChildren(Transformer v) {}
}
/// Expression of form `super.field = value`.
///
/// This may invoke a setter or assign a field.
class SuperPropertySet extends Expression {
/// A mutable field or a non-abstract getter in a super class.
Member target;
Expression value;
SuperPropertySet(this.target, this.value) {
value?.parent = this;
}
accept(ExpressionVisitor v) => v.visitSuperPropertySet(this);
visitChildren(Visitor v) {
target?.acceptReference(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).
Member target;
StaticGet(this.target);
accept(ExpressionVisitor v) => v.visitStaticGet(this);
visitChildren(Visitor v) {
target?.acceptReference(v);
}
transformChildren(Transformer v) {}
}
/// Assign a static field or call a static setter.
class StaticSet extends Expression {
/// A mutable static field or a static setter.
Member target;
Expression value;
StaticSet(this.target, this.value) {
value?.parent = this;
}
accept(ExpressionVisitor v) => v.visitStaticSet(this);
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.
class Arguments extends TreeNode {
final List<DartType> types;
final List<Expression> positional;
final 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>[];
accept(TreeVisitor v) => v.visitArguments(this);
visitChildren(Visitor v) {
_visitList(types, v);
_visitList(positional, v);
_visitList(named, v);
}
transformChildren(Transformer 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 [MethodInvocation], [SuperMethodInvocation],
/// [StaticInvocation], and [ConstructorInvocation].
abstract class InvocationExpression extends Expression {
Arguments get arguments;
set arguments(Arguments value);
/// The static target of the invocation, or `null` if this is a dynamic
/// dispatch invocation.
Member get target;
/// Name of the invoked method.
///
/// May be `null` if the target is synthetic static member without a name.
Name get name;
}
/// Expression of form `x.foo(y)`.
class MethodInvocation extends InvocationExpression {
Expression receiver;
Name name;
Arguments arguments;
MethodInvocation(this.receiver, this.name, this.arguments) {
receiver?.parent = this;
arguments?.parent = this;
}
Member get target => null;
accept(ExpressionVisitor v) => v.visitMethodInvocation(this);
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 {
Procedure target; // Non-abstract, non-static method in a super class.
Arguments arguments;
Name get name => target?.name;
SuperMethodInvocation(this.target, this.arguments) {
arguments?.parent = this;
}
accept(ExpressionVisitor v) => v.visitSuperMethodInvocation(this);
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 `foo(x)`.
///
/// The provided arguments might not match the parameters of the target.
class StaticInvocation extends InvocationExpression {
Procedure target; // Static method.
Arguments arguments;
Name get name => target?.name;
StaticInvocation(this.target, this.arguments) {
arguments?.parent = this;
}
accept(ExpressionVisitor v) => v.visitStaticInvocation(this);
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.
class ConstructorInvocation extends Expression {
Constructor target;
Arguments arguments;
bool isConst;
ConstructorInvocation(this.target, this.arguments, {this.isConst: false}) {
arguments?.parent = this;
}
accept(ExpressionVisitor v) => v.visitConstructorInvocation(this);
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 `!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;
}
accept(ExpressionVisitor v) => v.visitNot(this);
visitChildren(Visitor v) {
operand?.accept(v);
}
transformChildren(Transformer v) {
if (operand != null) {
operand = operand.accept(v);
operand?.parent = this;
}
}
}
/// Expression of form `x && y`, `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;
}
accept(ExpressionVisitor v) => v.visitLogicalExpression(this);
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;
ConditionalExpression(this.condition, this.then, this.otherwise) {
condition?.parent = this;
then?.parent = this;
otherwise?.parent = this;
}
accept(ExpressionVisitor v) => v.visitConditionalExpression(this);
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;
}
}
}
/// 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);
}
accept(ExpressionVisitor v) => v.visitStringConcatenation(this);
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;
}
accept(ExpressionVisitor v) => v.visitIsExpression(this);
visitChildren(Visitor v) {
operand?.accept(v);
type?.accept(v);
}
transformChildren(Transformer v) {
if (operand != null) {
operand = operand.accept(v);
operand?.parent = this;
}
}
}
/// Expression of form `x as T`.
class AsExpression extends Expression {
Expression operand;
DartType type;
AsExpression(this.operand, this.type) {
operand?.parent = this;
}
accept(ExpressionVisitor v) => v.visitAsExpression(this);
visitChildren(Visitor v) {
operand?.accept(v);
type?.accept(v);
}
transformChildren(Transformer v) {
if (operand != null) {
operand = operand.accept(v);
operand?.parent = this;
}
}
}
/// 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);
accept(ExpressionVisitor v) => v.visitStringLiteral(this);
}
class IntLiteral extends BasicLiteral {
int value;
IntLiteral(this.value);
accept(ExpressionVisitor v) => v.visitIntLiteral(this);
}
class DoubleLiteral extends BasicLiteral {
double value;
DoubleLiteral(this.value);
accept(ExpressionVisitor v) => v.visitDoubleLiteral(this);
}
class BoolLiteral extends BasicLiteral {
bool value;
BoolLiteral(this.value);
accept(ExpressionVisitor v) => v.visitBoolLiteral(this);
}
class NullLiteral extends BasicLiteral {
Object get value => null;
accept(ExpressionVisitor v) => v.visitNullLiteral(this);
}
class SymbolLiteral extends Expression {
String value; // Everything strictly after the '#'.
SymbolLiteral(this.value);
accept(ExpressionVisitor v) => v.visitSymbolLiteral(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
class TypeLiteral extends Expression {
DartType type;
TypeLiteral(this.type);
accept(ExpressionVisitor v) => v.visitTypeLiteral(this);
visitChildren(Visitor v) {
type?.accept(v);
}
transformChildren(Transformer v) {}
}
class ThisExpression extends Expression {
accept(ExpressionVisitor v) => v.visitThisExpression(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
class Rethrow extends Expression {
accept(ExpressionVisitor v) => v.visitRethrow(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
class Throw extends Expression {
Expression expression;
Throw(this.expression) {
expression?.parent = this;
}
accept(ExpressionVisitor v) => v.visitThrow(this);
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}) {
_setParents(expressions, this);
}
accept(ExpressionVisitor v) => v.visitListLiteral(this);
visitChildren(Visitor v) {
typeArgument?.accept(v);
_visitList(expressions, v);
}
transformChildren(Transformer v) {
_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}) {
_setParents(entries, this);
}
accept(ExpressionVisitor v) => v.visitMapLiteral(this);
visitChildren(Visitor v) {
keyType?.accept(v);
valueType?.accept(v);
_visitList(entries, v);
}
transformChildren(Transformer v) {
_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;
}
accept(ExpressionVisitor v) => v.visitAwaitExpression(this);
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;
}
accept(ExpressionVisitor v) => v.visitFunctionExpression(this);
visitChildren(Visitor v) {
function?.accept(v);
}
transformChildren(Transformer v) {
if (function != null) {
function = function.accept(v);
function?.parent = this;
}
}
}
/// 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;
}
accept(ExpressionVisitor v) => v.visitLet(this);
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;
}
}
}
// ------------------------------------------------------------------------
// STATEMENTS
// ------------------------------------------------------------------------
abstract class Statement extends TreeNode {
accept(StatementVisitor v);
}
/// A statement with a compile-time error.
///
/// Should throw an exception at runtime.
class InvalidStatement extends Statement {
accept(StatementVisitor v) => v.visitInvalidStatement(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
class ExpressionStatement extends Statement {
Expression expression;
ExpressionStatement(this.expression) {
expression?.parent = this;
}
accept(StatementVisitor v) => v.visitExpressionStatement(this);
visitChildren(Visitor v) {
expression?.accept(v);
}
transformChildren(Transformer v) {
if (expression != null) {
expression = expression.accept(v);
expression?.parent = this;
}
}
}
class Block extends Statement {
final List<Statement> statements;
Block(this.statements) {
_setParents(statements, this);
}
accept(StatementVisitor v) => v.visitBlock(this);
visitChildren(Visitor v) {
_visitList(statements, v);
}
transformChildren(Transformer v) {
_transformList(statements, v, this);
}
}
class EmptyStatement extends Statement {
accept(StatementVisitor v) => v.visitEmptyStatement(this);
visitChildren(Visitor v) {}
transformChildren(Transformer v) {}
}
class AssertStatement extends Statement {
Expression condition;
Expression message; // May be null.
AssertStatement(this.condition, [this.message]) {
condition?.parent = this;
message?.parent = this;
}
accept(StatementVisitor v) => v.visitAssertStatement(this);
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);
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);
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);
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);
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);
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 {
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);
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);
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;
Statement body;
bool isDefault;
SwitchCase(this.expressions, this.body, {this.isDefault: false}) {
_setParents(expressions, this);
body?.parent = this;
}
SwitchCase.defaultCase(this.body)
: isDefault = true,
expressions = <Expression>[] {
body?.parent = this;
}
SwitchCase.empty()
: expressions = <Expression>[],
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);
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);
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;
}
}
}
class ReturnStatement extends Statement {
Expression expression; // May be null.
ReturnStatement([this.expression]) {
expression?.parent = this;
}
accept(StatementVisitor v) => v.visitReturnStatement(this);
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;
TryCatch(this.body, this.catches) {
body?.parent = this;
_setParents(catches, this);
}
accept(StatementVisitor v) => v.visitTryCatch(this);
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; // May be null.
VariableDeclaration exception; // May be null. The declared type is null.
VariableDeclaration stackTrace; // May be null.
Statement body;
Catch(this.exception, this.body, {this.guard, this.stackTrace}) {
exception?.parent = this;
stackTrace?.parent = this;
body?.parent = this;
}
accept(TreeVisitor v) => v.visitCatch(this);
visitChildren(Visitor v) {
exception?.accept(v);
stackTrace?.accept(v);
body?.accept(v);
}
transformChildren(Transformer v) {
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);
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`.
class YieldStatement extends Statement {
Expression expression;
bool isYieldStar;
YieldStatement(this.expression, {this.isYieldStar: false}) {
expression?.parent = this;
}
accept(StatementVisitor v) => v.visitYieldStatement(this);
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].
//
// DESIGN TODO: Should we remove the 'final' modifier from variables?
class VariableDeclaration extends Statement {
/// 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; // May be null.
/// For locals, this is the initial value.
/// For parameters, this is the default value.
///
/// Should be null in other cases.
Expression initializer; // May be null.
VariableDeclaration(this.name,
{this.initializer, this.type, bool isFinal: false, bool isConst: false}) {
initializer?.parent = this;
this.isFinal = isFinal;
this.isConst = isConst;
}
/// Creates a synthetic variable with the given expression as initializer.
VariableDeclaration.forValue(this.initializer,
{bool isFinal: true, bool isConst: false, this.type}) {
initializer?.parent = this;
this.isFinal = isFinal;
this.isConst = isConst;
}
static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
static const int FlagConst = 1 << 1;
bool get isFinal => flags & FlagFinal != 0;
bool get isConst => flags & FlagConst != 0;
void set isFinal(bool value) {
flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
}
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
accept(StatementVisitor v) => v.visitVariableDeclaration(this);
visitChildren(Visitor v) {
type?.accept(v);
initializer?.accept(v);
}
transformChildren(Transformer v) {
if (initializer != null) {
initializer = initializer.accept(v);
initializer?.parent = this;
}
}
/// Returns a possibly synthesized name for this variable, consistent with
/// the names used in [toString] calls.
String get debugName => 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);
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.
abstract class Name implements Node {
final String name;
Library get library;
bool get isPrivate;
Name._internal(this.name);
factory Name(String name, [Library library]) {
/// Use separate subclasses for the public and private case to save memory
/// for public names.
if (name.startsWith('_')) {
return new _PrivateName(name, library);
} else {
return new _PublicName(name);
}
}
bool operator ==(other) {
return other is Name && name == other.name && library == other.library;
}
int get hashCode => 131 * name.hashCode + 17 * library.hashCode;
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 Library library;
bool get isPrivate => true;
_PrivateName(String name, this.library) : super._internal(name);
String toString() => library != null ? '${library.name}::$name' : name;
}
class _PublicName extends Name {
Library get library => null;
bool get isPrivate => false;
_PublicName(String name) : super._internal(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. The [hashCode] function throws an exception because
/// canonicalization of generic function types is too expensive for hash codes
/// to have any practical use.
//
// TODO: Maybe we should just have a really crappy hash code for generic
// function types so users can rely on hashCode if they know there will be
// very few or no generic function types.
abstract class DartType extends Node {
const DartType();
accept(DartTypeVisitor v);
bool operator ==(Object other);
int get hashCode => throw 'DartType.hashCode is not allowed.';
}
/// 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 {
const InvalidType();
accept(DartTypeVisitor v) => v.visitInvalidType(this);
visitChildren(Visitor v) {}
bool operator ==(Object other) => other is InvalidType;
}
class DynamicType extends DartType {
const DynamicType();
accept(DartTypeVisitor v) => v.visitDynamicType(this);
visitChildren(Visitor v) {}
bool operator ==(Object other) => other is DynamicType;
}
class VoidType extends DartType {
const VoidType();
accept(DartTypeVisitor v) => v.visitVoidType(this);
visitChildren(Visitor v) {}
bool operator ==(Object other) => other is VoidType;
}
class InterfaceType extends DartType {
final Class classNode;
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.classNode = classNode,
this.typeArguments = typeArguments ?? _defaultTypeArguments(classNode);
static List<DartType> _defaultTypeArguments(Class classNode) {
if (classNode.typeParameters.length == 0) {
// Avoid allocating a list in this very common case.
return const <DartType>[];
} else {
return new List<DartType>.filled(
classNode.typeParameters.length, const DynamicType());
}
}
accept(DartTypeVisitor v) => v.visitInterfaceType(this);
visitChildren(Visitor v) {
classNode.acceptReference(v);
_visitList(typeArguments, v);
}
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is InterfaceType) {
if (classNode != other.classNode) 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;
}
}
}
/// A possibly generic function type.
class FunctionType extends DartType {
final List<TypeParameter> typeParameters;
final int requiredParameterCount;
final List<DartType> positionalParameters;
final Map<String, DartType> namedParameters;
final DartType returnType;
FunctionType(List<DartType> positionalParameters, this.returnType,
{this.namedParameters: const <String, DartType>{},
this.typeParameters: const <TypeParameter>[],
int requiredParameterCount})
: this.positionalParameters = positionalParameters,
this.requiredParameterCount =
requiredParameterCount ?? positionalParameters.length;
accept(DartTypeVisitor v) => v.visitFunctionType(this);
visitChildren(Visitor v) {
_visitList(typeParameters, v);
_visitList(positionalParameters, v);
_visitIterable(namedParameters.values, 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 (var name in namedParameters.keys) {
// If the other function type declared differently named parameters,
// one side of this equality will be null and we're good.
if (namedParameters[name] != other.namedParameters[name]) {
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);
}
}
/// Reference to a type variable.
class TypeParameterType extends DartType {
TypeParameter parameter;
TypeParameterType(this.parameter);
accept(DartTypeVisitor v) => v.visitTypeParameterType(this);
visitChildren(Visitor v) {}
bool operator ==(Object other) {
return other is TypeParameterType && parameter == other.parameter;
}
}
/// 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 {
String name; // Cosmetic name.
/// The bound on the type variable, or [DynamicType] if none was given.
DartType bound;
TypeParameter([this.name, this.bound = const DynamicType()]);
accept(TreeVisitor v) => v.visitTypeParameter(this);
visitChildren(Visitor v) {
bound.accept(v);
}
transformChildren(Transformer v) {}
/// Returns a possibly synthesized name for this type parameter, consistent
/// with the names used in [toString] calls.
String get debugName => debugTypeParameterName(this);
}
// ------------------------------------------------------------------------
// PROGRAM
// ------------------------------------------------------------------------
/// A way to bundle up all the libraries in a program.
class Program extends TreeNode {
final List<Library> libraries;
/// Reference to the main method in one of the libraries.
Procedure mainMethod;
Program([List<Library> libraries]) : libraries = libraries ?? <Library>[] {
_setParents(libraries, this);
}
accept(TreeVisitor v) => v.visitProgram(this);
visitChildren(Visitor v) {
_visitList(libraries, v);
mainMethod?.acceptReference(v);
}
transformChildren(Transformer v) {
_transformList(libraries, v, this);
}
}
// ------------------------------------------------------------------------
// 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 _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);
defaultNode(TreeNode node) {
if (node == child) {
child.parent = null;
return replacement;
} else {
return node;
}
}
}