| // 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 |
| /// ----------------------------------------------------------------------- |
| /// |
| /// 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'; |
| |
| /// 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; |
| } |
| |
| Program get enclosingProgram => parent?.enclosingProgram; |
| |
| /// 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. |
| abstract class NamedNode extends TreeNode { |
| final Reference reference; |
| |
| NamedNode(Reference reference) |
| : this.reference = reference ?? new Reference() { |
| this.reference.node = this; |
| } |
| |
| CanonicalName get canonicalName => reference?.canonicalName; |
| } |
| |
| /// Indirection between a reference and its definition. |
| /// |
| /// There is only one reference object per [NamedNode]. |
| class Reference { |
| CanonicalName canonicalName; |
| 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; |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // LIBRARIES and CLASSES |
| // ------------------------------------------------------------------------ |
| |
| class Library extends NamedNode implements Comparable<Library> { |
| /// 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. |
| String 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; |
| final List<DeferredImport> deferredImports; |
| final List<Class> classes; |
| final List<Procedure> procedures; |
| final List<Field> fields; |
| |
| Library(this.importUri, |
| {this.name, |
| this.isExternal: false, |
| List<DeferredImport> imports, |
| List<Class> classes, |
| List<Procedure> procedures, |
| List<Field> fields, |
| this.fileUri, |
| Reference reference}) |
| : this.deferredImports = imports ?? <DeferredImport>[], |
| this.classes = classes ?? <Class>[], |
| this.procedures = procedures ?? <Procedure>[], |
| this.fields = fields ?? <Field>[], |
| super(reference) { |
| 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 addClass(Class class_) { |
| class_.parent = this; |
| classes.add(class_); |
| } |
| |
| void computeCanonicalNames() { |
| assert(canonicalName != null); |
| 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(); |
| } |
| } |
| |
| 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); |
| } |
| |
| 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 enclosingProgram.getLocation(fileUri, offset); |
| } |
| } |
| |
| /// An import of form: `import <url> deferred as <name>;`. |
| class DeferredImport extends TreeNode { |
| Reference importedLibraryReference; |
| String name; |
| |
| DeferredImport(Library importedLibrary, String name) |
| : this.byReference(importedLibrary.reference, name); |
| |
| DeferredImport.byReference(this.importedLibraryReference, this.name); |
| |
| Library get enclosingLibrary => parent; |
| Library get importedLibrary => importedLibraryReference.asLibrary; |
| |
| accept(TreeVisitor v) => v.visitDeferredImport(this); |
| |
| visitChildren(Visitor v) {} |
| |
| transformChildren(Transformer v) {} |
| } |
| |
| /// 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. |
| class Class extends NamedNode { |
| /// 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. |
| 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. |
| String name; |
| bool isAbstract; |
| |
| /// The uri of the source file this class was loaded from. |
| String 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 be empty. |
| final List<Procedure> procedures; |
| |
| Class( |
| {this.name, |
| this.isAbstract: false, |
| this.supertype, |
| this.mixedInType, |
| List<TypeParameter> typeParameters, |
| List<Supertype> implementedTypes, |
| List<Constructor> constructors, |
| List<Procedure> procedures, |
| List<Field> fields, |
| this.fileUri, |
| Reference reference}) |
| : this.typeParameters = typeParameters ?? <TypeParameter>[], |
| this.implementedTypes = implementedTypes ?? <Supertype>[], |
| this.fields = fields ?? <Field>[], |
| this.constructors = constructors ?? <Constructor>[], |
| this.procedures = procedures ?? <Procedure>[], |
| super(reference) { |
| setParents(this.typeParameters, this); |
| setParents(this.constructors, this); |
| setParents(this.procedures, this); |
| setParents(this.fields, this); |
| } |
| |
| 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); |
| } |
| } |
| |
| /// 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].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 { |
| 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)); |
| } |
| |
| InterfaceType _rawType; |
| InterfaceType get rawType => _rawType ??= new InterfaceType(this); |
| |
| InterfaceType _thisType; |
| InterfaceType get thisType { |
| return _thisType ??= |
| new InterfaceType(this, _getAsTypeArguments(typeParameters)); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| Location _getLocationInEnclosingFile(int offset) { |
| return enclosingProgram.getLocation(fileUri, offset); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MEMBERS |
| // ------------------------------------------------------------------------ |
| |
| abstract class Member extends NamedNode { |
| /// 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. |
| List<Expression> annotations = const <Expression>[]; |
| Name name; |
| |
| /// 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, 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. |
| |
| /// The uri of the source file this field was loaded from. |
| String fileUri; |
| |
| Field(Name name, |
| {this.type: const DynamicType(), |
| this.initializer, |
| bool isFinal: false, |
| bool isConst: false, |
| bool isStatic: false, |
| bool hasImplicitGetter, |
| bool hasImplicitSetter, |
| int transformerFlags: 0, |
| this.fileUri, |
| Reference reference}) |
| : super(name, reference) { |
| assert(type != null); |
| initializer?.parent = this; |
| 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; |
| |
| 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; |
| |
| 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); |
| } |
| |
| /// 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 enclosingProgram.getLocation(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 { |
| int flags = 0; |
| FunctionNode function; |
| List<Initializer> initializers; |
| |
| Constructor(this.function, |
| {Name name, |
| bool isConst: false, |
| bool isExternal: false, |
| List<Initializer> initializers, |
| int transformerFlags: 0, |
| Reference reference}) |
| : this.initializers = initializers ?? <Initializer>[], |
| super(name, reference) { |
| function?.parent = this; |
| setParents(this.initializers, 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; |
| |
| accept(MemberVisitor v) => v.visitConstructor(this); |
| |
| acceptReference(MemberReferenceVisitor v) => |
| v.visitConstructorReference(this); |
| |
| visitChildren(Visitor v) { |
| visitList(annotations, v); |
| name?.accept(v); |
| function?.accept(v); |
| visitList(initializers, v); |
| } |
| |
| transformChildren(Transformer v) { |
| transformList(annotations, v, this); |
| if (function != null) { |
| function = function.accept(v); |
| function?.parent = this; |
| } |
| transformList(initializers, v, this); |
| } |
| |
| DartType get getterType => const BottomType(); |
| DartType get setterType => const BottomType(); |
| } |
| |
| /// 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-`. |
| class Procedure extends Member { |
| ProcedureKind kind; |
| int flags = 0; |
| FunctionNode function; // Body is null if and only if abstract or external. |
| |
| /// The uri of the source file this procedure was loaded from. |
| String fileUri; |
| |
| Procedure(Name name, this.kind, this.function, |
| {bool isAbstract: false, |
| bool isStatic: false, |
| bool isExternal: false, |
| bool isConst: false, |
| int transformerFlags: 0, |
| this.fileUri, |
| Reference reference}) |
| : super(name, reference) { |
| function?.parent = this; |
| this.isAbstract = isAbstract; |
| this.isStatic = isStatic; |
| this.isExternal = isExternal; |
| this.isConst = isConst; |
| 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. |
| |
| 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; |
| |
| 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); |
| } |
| |
| 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; |
| |
| 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 enclosingProgram.getLocation(fileUri, offset); |
| } |
| } |
| |
| enum ProcedureKind { |
| Method, |
| Getter, |
| Setter, |
| 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` 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; |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // 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 { |
| /// 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; |
| List<VariableDeclaration> positionalParameters; |
| List<VariableDeclaration> namedParameters; |
| DartType returnType; // Not null. |
| Statement 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(); |
| return new FunctionType( |
| positionalParameters.map(_getTypeOfVariable).toList(growable: false), |
| returnType, |
| namedParameters: named, |
| typeParameters: parent is Constructor |
| ? parent.enclosingClass.typeParameters |
| : typeParameters, |
| 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 |
| // ------------------------------------------------------------------------ |
| |
| 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 futhermore 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 { |
| 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. |
| class VariableGet extends Expression { |
| VariableDeclaration variable; |
| 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. |
| class PropertyGet extends Expression { |
| Expression receiver; |
| Name name; |
| |
| 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. |
| 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) { |
| 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]. |
| 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)`. |
| class MethodInvocation extends InvocationExpression { |
| Expression receiver; |
| Name name; |
| Arguments arguments; |
| |
| Reference interfaceTargetReference; |
| |
| MethodInvocation(Expression receiver, Name name, Arguments arguments, |
| [Procedure interfaceTarget]) |
| : this.byReference( |
| receiver, name, arguments, getMemberReference(interfaceTarget)); |
| |
| MethodInvocation.byReference( |
| this.receiver, this.name, this.arguments, this.interfaceTargetReference) { |
| receiver?.parent = this; |
| arguments?.parent = this; |
| } |
| |
| Procedure get interfaceTarget => interfaceTargetReference?.asProcedure; |
| |
| void set interfaceTarget(Member target) { |
| interfaceTargetReference = getMemberReference(target); |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| if (interfaceTarget != null) { |
| if (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 returnType = Substitution |
| .fromInterfaceType(receiverType) |
| .substituteType(interfaceTarget.function.returnType); |
| return Substitution |
| .fromPairs(interfaceTarget.function.typeParameters, arguments.types) |
| .substituteType(returnType); |
| } |
| 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. |
| class ConstructorInvocation extends InvocationExpression { |
| Reference targetReference; |
| 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); |
| } |
| } |
| |
| /// Expression of form `!x`. |
| /// |
| /// The `is!` and `!=` operators are desugared into [Not] nodes with `is` and |
| /// `==` expressions inside, respectively. |
| class Not extends Expression { |
| Expression operand; |
| |
| Not(this.operand) { |
| operand?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => types.boolType; |
| |
| accept(ExpressionVisitor v) => v.visitNot(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitNot(this, arg); |
| |
| visitChildren(Visitor v) { |
| operand?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (operand != null) { |
| operand = operand.accept(v); |
| operand?.parent = this; |
| } |
| } |
| } |
| |
| /// Expression of form `x && y` or `x || y` |
| class LogicalExpression extends Expression { |
| Expression left; |
| String operator; // && or || or ?? |
| Expression right; |
| |
| LogicalExpression(this.left, this.operator, this.right) { |
| left?.parent = this; |
| right?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => types.boolType; |
| |
| accept(ExpressionVisitor v) => v.visitLogicalExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitLogicalExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| left?.accept(v); |
| right?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (left != null) { |
| left = left.accept(v); |
| left?.parent = this; |
| } |
| if (right != null) { |
| right = right.accept(v); |
| right?.parent = this; |
| } |
| } |
| } |
| |
| /// Expression of form `x ? y : z`. |
| class ConditionalExpression extends Expression { |
| Expression condition; |
| Expression then; |
| Expression otherwise; |
| |
| /// The static type of the expression. Should not be `null`. |
| DartType staticType; |
| |
| ConditionalExpression( |
| this.condition, this.then, this.otherwise, this.staticType) { |
| condition?.parent = this; |
| then?.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => staticType; |
| |
| accept(ExpressionVisitor v) => v.visitConditionalExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitConditionalExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| condition?.accept(v); |
| then?.accept(v); |
| otherwise?.accept(v); |
| staticType?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (condition != null) { |
| condition = condition.accept(v); |
| condition?.parent = this; |
| } |
| if (then != null) { |
| then = then.accept(v); |
| then?.parent = this; |
| } |
| if (otherwise != null) { |
| otherwise = otherwise.accept(v); |
| otherwise?.parent = this; |
| } |
| if (staticType != null) { |
| staticType = v.visitDartType(staticType); |
| } |
| } |
| } |
| |
| /// Convert expressions to strings and concatenate them. Semantically, calls |
| /// `toString` on every argument, checks that a string is returned, and returns |
| /// the concatenation of all the strings. |
| /// |
| /// If [expressions] is empty then an empty string is returned. |
| /// |
| /// These arise from string interpolations and adjacent string literals. |
| class StringConcatenation extends Expression { |
| final List<Expression> expressions; |
| |
| StringConcatenation(this.expressions) { |
| setParents(expressions, this); |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => types.stringType; |
| |
| accept(ExpressionVisitor v) => v.visitStringConcatenation(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitStringConcatenation(this, arg); |
| |
| visitChildren(Visitor v) { |
| visitList(expressions, v); |
| } |
| |
| transformChildren(Transformer v) { |
| transformList(expressions, v, this); |
| } |
| } |
| |
| /// Expression of form `x is T`. |
| class IsExpression extends Expression { |
| Expression operand; |
| DartType type; |
| |
| IsExpression(this.operand, this.type) { |
| operand?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => types.boolType; |
| |
| accept(ExpressionVisitor v) => v.visitIsExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitIsExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| operand?.accept(v); |
| type?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (operand != null) { |
| operand = operand.accept(v); |
| operand?.parent = this; |
| } |
| type = v.visitDartType(type); |
| } |
| } |
| |
| /// Expression of form `x as T`. |
| class AsExpression extends Expression { |
| Expression operand; |
| DartType type; |
| |
| AsExpression(this.operand, this.type) { |
| operand?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => type; |
| |
| accept(ExpressionVisitor v) => v.visitAsExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitAsExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| operand?.accept(v); |
| type?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (operand != null) { |
| operand = operand.accept(v); |
| operand?.parent = this; |
| } |
| type = v.visitDartType(type); |
| } |
| } |
| |
| /// An integer, double, boolean, string, or null constant. |
| abstract class BasicLiteral extends Expression { |
| Object get value; |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class StringLiteral extends BasicLiteral { |
| String value; |
| |
| StringLiteral(this.value); |
| |
| DartType getStaticType(TypeEnvironment types) => types.stringType; |
| |
| accept(ExpressionVisitor v) => v.visitStringLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitStringLiteral(this, arg); |
| } |
| |
| class IntLiteral extends BasicLiteral { |
| int value; |
| |
| IntLiteral(this.value); |
| |
| DartType getStaticType(TypeEnvironment types) => types.intType; |
| |
| accept(ExpressionVisitor v) => v.visitIntLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitIntLiteral(this, arg); |
| } |
| |
| class DoubleLiteral extends BasicLiteral { |
| double value; |
| |
| DoubleLiteral(this.value); |
| |
| DartType getStaticType(TypeEnvironment types) => types.doubleType; |
| |
| accept(ExpressionVisitor v) => v.visitDoubleLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitDoubleLiteral(this, arg); |
| } |
| |
| class BoolLiteral extends BasicLiteral { |
| bool value; |
| |
| BoolLiteral(this.value); |
| |
| DartType getStaticType(TypeEnvironment types) => types.boolType; |
| |
| accept(ExpressionVisitor v) => v.visitBoolLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitBoolLiteral(this, arg); |
| } |
| |
| class NullLiteral extends BasicLiteral { |
| Object get value => null; |
| |
| DartType getStaticType(TypeEnvironment types) => const BottomType(); |
| |
| accept(ExpressionVisitor v) => v.visitNullLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitNullLiteral(this, arg); |
| } |
| |
| class SymbolLiteral extends Expression { |
| String value; // Everything strictly after the '#'. |
| |
| SymbolLiteral(this.value); |
| |
| DartType getStaticType(TypeEnvironment types) => types.symbolType; |
| |
| accept(ExpressionVisitor v) => v.visitSymbolLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitSymbolLiteral(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class TypeLiteral extends Expression { |
| DartType type; |
| |
| TypeLiteral(this.type); |
| |
| DartType getStaticType(TypeEnvironment types) => types.typeType; |
| |
| accept(ExpressionVisitor v) => v.visitTypeLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitTypeLiteral(this, arg); |
| |
| visitChildren(Visitor v) { |
| type?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| type = v.visitDartType(type); |
| } |
| } |
| |
| class ThisExpression extends Expression { |
| DartType getStaticType(TypeEnvironment types) => types.thisType; |
| |
| accept(ExpressionVisitor v) => v.visitThisExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitThisExpression(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class Rethrow extends Expression { |
| DartType getStaticType(TypeEnvironment types) => const BottomType(); |
| |
| accept(ExpressionVisitor v) => v.visitRethrow(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitRethrow(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class Throw extends Expression { |
| Expression expression; |
| |
| Throw(this.expression) { |
| expression?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => const BottomType(); |
| |
| accept(ExpressionVisitor v) => v.visitThrow(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitThrow(this, arg); |
| |
| visitChildren(Visitor v) { |
| expression?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (expression != null) { |
| expression = expression.accept(v); |
| expression?.parent = this; |
| } |
| } |
| } |
| |
| class ListLiteral extends Expression { |
| bool isConst; |
| DartType typeArgument; // Not null, defaults to DynamicType. |
| final List<Expression> expressions; |
| |
| ListLiteral(this.expressions, |
| {this.typeArgument: const DynamicType(), this.isConst: false}) { |
| assert(typeArgument != null); |
| setParents(expressions, this); |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return types.literalListType(typeArgument); |
| } |
| |
| accept(ExpressionVisitor v) => v.visitListLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitListLiteral(this, arg); |
| |
| visitChildren(Visitor v) { |
| typeArgument?.accept(v); |
| visitList(expressions, v); |
| } |
| |
| transformChildren(Transformer v) { |
| typeArgument = v.visitDartType(typeArgument); |
| transformList(expressions, v, this); |
| } |
| } |
| |
| class MapLiteral extends Expression { |
| bool isConst; |
| DartType keyType; // Not null, defaults to DynamicType. |
| DartType valueType; // Not null, defaults to DynamicType. |
| final List<MapEntry> entries; |
| |
| MapLiteral(this.entries, |
| {this.keyType: const DynamicType(), |
| this.valueType: const DynamicType(), |
| this.isConst: false}) { |
| assert(keyType != null); |
| assert(valueType != null); |
| setParents(entries, this); |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return types.literalMapType(keyType, valueType); |
| } |
| |
| accept(ExpressionVisitor v) => v.visitMapLiteral(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitMapLiteral(this, arg); |
| |
| visitChildren(Visitor v) { |
| keyType?.accept(v); |
| valueType?.accept(v); |
| visitList(entries, v); |
| } |
| |
| transformChildren(Transformer v) { |
| keyType = v.visitDartType(keyType); |
| valueType = v.visitDartType(valueType); |
| transformList(entries, v, this); |
| } |
| } |
| |
| class MapEntry extends TreeNode { |
| Expression key; |
| Expression value; |
| |
| MapEntry(this.key, this.value) { |
| key?.parent = this; |
| value?.parent = this; |
| } |
| |
| accept(TreeVisitor v) => v.visitMapEntry(this); |
| |
| visitChildren(Visitor v) { |
| key?.accept(v); |
| value?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (key != null) { |
| key = key.accept(v); |
| key?.parent = this; |
| } |
| if (value != null) { |
| value = value.accept(v); |
| value?.parent = this; |
| } |
| } |
| } |
| |
| /// Expression of form `await x`. |
| class AwaitExpression extends Expression { |
| Expression operand; |
| |
| AwaitExpression(this.operand) { |
| operand?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return types.unfutureType(operand.getStaticType(types)); |
| } |
| |
| accept(ExpressionVisitor v) => v.visitAwaitExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitAwaitExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| operand?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (operand != null) { |
| operand = operand.accept(v); |
| operand?.parent = this; |
| } |
| } |
| } |
| |
| /// Expression of form `(x,y) => ...` or `(x,y) { ... }` |
| /// |
| /// The arrow-body form `=> e` is desugared into `return e;`. |
| class FunctionExpression extends Expression { |
| FunctionNode function; |
| |
| FunctionExpression(this.function) { |
| function?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => function.functionType; |
| |
| accept(ExpressionVisitor v) => v.visitFunctionExpression(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitFunctionExpression(this, arg); |
| |
| visitChildren(Visitor v) { |
| function?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (function != null) { |
| function = function.accept(v); |
| function?.parent = this; |
| } |
| } |
| } |
| |
| /// Synthetic expression of form `let v = x in y` |
| class Let extends Expression { |
| VariableDeclaration variable; // Must have an initializer. |
| Expression body; |
| |
| Let(this.variable, this.body) { |
| variable?.parent = this; |
| body?.parent = this; |
| } |
| |
| DartType getStaticType(TypeEnvironment types) => body.getStaticType(types); |
| |
| accept(ExpressionVisitor v) => v.visitLet(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitLet(this, arg); |
| |
| visitChildren(Visitor v) { |
| variable?.accept(v); |
| body?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (variable != null) { |
| variable = variable.accept(v); |
| variable?.parent = this; |
| } |
| if (body != null) { |
| body = body.accept(v); |
| body?.parent = this; |
| } |
| } |
| } |
| |
| /// Attempt to load the library referred to by a deferred import. |
| /// |
| /// This instruction is concerned with: |
| /// - keeping track whether the deferred import is marked as 'loaded' |
| /// - keeping track of whether the library code has already been downloaded |
| /// - actually downloading and linking the library |
| /// |
| /// Should return a future. The value in this future will be the same value |
| /// seen by callers of `loadLibrary` functions. |
| /// |
| /// On backends that link the entire program eagerly, this instruction needs |
| /// to mark the deferred import as 'loaded' and return a future. |
| class LoadLibrary extends Expression { |
| /// Reference to a deferred import in the enclosing library. |
| DeferredImport import; |
| |
| LoadLibrary(this.import); |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return types.futureType(const DynamicType()); |
| } |
| |
| accept(ExpressionVisitor v) => v.visitLoadLibrary(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitLoadLibrary(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| /// Checks that the given deferred import has been marked as 'loaded'. |
| class CheckLibraryIsLoaded extends Expression { |
| /// Reference to a deferred import in the enclosing library. |
| DeferredImport import; |
| |
| CheckLibraryIsLoaded(this.import); |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return types.objectType; |
| } |
| |
| accept(ExpressionVisitor v) => v.visitCheckLibraryIsLoaded(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitCheckLibraryIsLoaded(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| /// Expression of the form `MakeVector(N)` where `N` is an integer representing |
| /// the length of the vector. |
| /// |
| /// For detailed comment about Vectors see [VectorType]. |
| class VectorCreation extends Expression { |
| int length; |
| |
| VectorCreation(this.length); |
| |
| accept(ExpressionVisitor v) => v.visitVectorCreation(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitVectorCreation(this, arg); |
| |
| visitChildren(Visitor v) {} |
| |
| transformChildren(Transformer v) {} |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return const VectorType(); |
| } |
| } |
| |
| /// Expression of the form `v[i]` where `v` is a vector expression, and `i` is |
| /// an integer index. |
| class VectorGet extends Expression { |
| Expression vectorExpression; |
| int index; |
| |
| VectorGet(this.vectorExpression, this.index) { |
| vectorExpression?.parent = this; |
| } |
| |
| accept(ExpressionVisitor v) => v.visitVectorGet(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitVectorGet(this, arg); |
| |
| visitChildren(Visitor v) { |
| vectorExpression.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (vectorExpression != null) { |
| vectorExpression = vectorExpression.accept(v); |
| vectorExpression?.parent = this; |
| } |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return const DynamicType(); |
| } |
| } |
| |
| /// Expression of the form `v[i] = x` where `v` is a vector expression, `i` is |
| /// an integer index, and `x` is an arbitrary expression. |
| class VectorSet extends Expression { |
| Expression vectorExpression; |
| int index; |
| Expression value; |
| |
| VectorSet(this.vectorExpression, this.index, this.value) { |
| vectorExpression?.parent = this; |
| value?.parent = this; |
| } |
| |
| accept(ExpressionVisitor v) => v.visitVectorSet(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitVectorSet(this, arg); |
| |
| visitChildren(Visitor v) { |
| vectorExpression.accept(v); |
| value.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (vectorExpression != null) { |
| vectorExpression = vectorExpression.accept(v); |
| vectorExpression?.parent = this; |
| } |
| if (value != null) { |
| value = value.accept(v); |
| value?.parent = this; |
| } |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return value.getStaticType(types); |
| } |
| } |
| |
| /// Expression of the form `CopyVector(v)` where `v` is a vector expression. |
| class VectorCopy extends Expression { |
| Expression vectorExpression; |
| |
| VectorCopy(this.vectorExpression) { |
| vectorExpression?.parent = this; |
| } |
| |
| accept(ExpressionVisitor v) => v.visitVectorCopy(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitVectorCopy(this, arg); |
| |
| visitChildren(Visitor v) { |
| vectorExpression.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (vectorExpression != null) { |
| vectorExpression = vectorExpression.accept(v); |
| vectorExpression?.parent = this; |
| } |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return const VectorType(); |
| } |
| } |
| |
| /// Expression of the form `MakeClosure(f, c, t)` where `f` is a name of a |
| /// closed top-level function, `c` is a Vector representing closure context, and |
| /// `t` is the type of the resulting closure. |
| class ClosureCreation extends Expression { |
| Reference topLevelFunctionReference; |
| Expression contextVector; |
| FunctionType functionType; |
| |
| ClosureCreation(Member topLevelFunction, Expression contextVector, |
| FunctionType functionType) |
| : this.byReference( |
| getMemberReference(topLevelFunction), contextVector, functionType); |
| |
| ClosureCreation.byReference( |
| this.topLevelFunctionReference, this.contextVector, this.functionType) { |
| contextVector?.parent = this; |
| } |
| |
| Procedure get topLevelFunction => topLevelFunctionReference?.asProcedure; |
| |
| void set topLevelFunction(Member topLevelFunction) { |
| topLevelFunctionReference = getMemberReference(topLevelFunction); |
| } |
| |
| accept(ExpressionVisitor v) => v.visitClosureCreation(this); |
| accept1(ExpressionVisitor1 v, arg) => v.visitClosureCreation(this, arg); |
| |
| visitChildren(Visitor v) { |
| contextVector?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (contextVector != null) { |
| contextVector = contextVector.accept(v); |
| contextVector?.parent = this; |
| } |
| } |
| |
| DartType getStaticType(TypeEnvironment types) { |
| return functionType; |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // STATEMENTS |
| // ------------------------------------------------------------------------ |
| |
| abstract class Statement extends TreeNode { |
| accept(StatementVisitor v); |
| accept1(StatementVisitor1 v, arg); |
| } |
| |
| /// A statement with a compile-time error. |
| /// |
| /// Should throw an exception at runtime. |
| class InvalidStatement extends Statement { |
| accept(StatementVisitor v) => v.visitInvalidStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitInvalidStatement(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class ExpressionStatement extends Statement { |
| Expression expression; |
| |
| ExpressionStatement(this.expression) { |
| expression?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitExpressionStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitExpressionStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| expression?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (expression != null) { |
| expression = expression.accept(v); |
| expression?.parent = this; |
| } |
| } |
| } |
| |
| class Block extends Statement { |
| final List<Statement> statements; |
| |
| Block(this.statements) { |
| setParents(statements, this); |
| } |
| |
| accept(StatementVisitor v) => v.visitBlock(this); |
| accept1(StatementVisitor1 v, arg) => v.visitBlock(this, arg); |
| |
| visitChildren(Visitor v) { |
| visitList(statements, v); |
| } |
| |
| transformChildren(Transformer v) { |
| transformList(statements, v, this); |
| } |
| |
| void addStatement(Statement node) { |
| statements.add(node); |
| node.parent = this; |
| } |
| } |
| |
| class EmptyStatement extends Statement { |
| accept(StatementVisitor v) => v.visitEmptyStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitEmptyStatement(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class AssertStatement extends Statement { |
| Expression condition; |
| Expression message; // May be null. |
| |
| AssertStatement(this.condition, [this.message]) { |
| condition?.parent = this; |
| message?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitAssertStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitAssertStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| condition?.accept(v); |
| message?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (condition != null) { |
| condition = condition.accept(v); |
| condition?.parent = this; |
| } |
| if (message != null) { |
| message = message.accept(v); |
| message?.parent = this; |
| } |
| } |
| } |
| |
| /// A target of a [Break] statement. |
| /// |
| /// The label itself has no name; breaks reference the statement directly. |
| /// |
| /// The frontend does not generate labeled statements without uses. |
| class LabeledStatement extends Statement { |
| Statement body; |
| |
| LabeledStatement(this.body) { |
| body?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitLabeledStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitLabeledStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| body?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (body != null) { |
| body = body.accept(v); |
| body?.parent = this; |
| } |
| } |
| } |
| |
| /// Breaks out of an enclosing [LabeledStatement]. |
| /// |
| /// Both `break` and loop `continue` statements are translated into this node. |
| /// |
| /// For example, the following loop with a `continue` will be desugared: |
| /// |
| /// while(x) { |
| /// if (y) continue; |
| /// BODY' |
| /// } |
| /// |
| /// ==> |
| /// |
| /// while(x) { |
| /// L: { |
| /// if (y) break L; |
| /// BODY' |
| /// } |
| /// } |
| // |
| class BreakStatement extends Statement { |
| LabeledStatement target; |
| |
| BreakStatement(this.target); |
| |
| accept(StatementVisitor v) => v.visitBreakStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitBreakStatement(this, arg); |
| |
| visitChildren(Visitor v) {} |
| transformChildren(Transformer v) {} |
| } |
| |
| class WhileStatement extends Statement { |
| Expression condition; |
| Statement body; |
| |
| WhileStatement(this.condition, this.body) { |
| condition?.parent = this; |
| body?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitWhileStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitWhileStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| condition?.accept(v); |
| body?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (condition != null) { |
| condition = condition.accept(v); |
| condition?.parent = this; |
| } |
| if (body != null) { |
| body = body.accept(v); |
| body?.parent = this; |
| } |
| } |
| } |
| |
| class DoStatement extends Statement { |
| Statement body; |
| Expression condition; |
| |
| DoStatement(this.body, this.condition) { |
| body?.parent = this; |
| condition?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitDoStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitDoStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| body?.accept(v); |
| condition?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| if (body != null) { |
| body = body.accept(v); |
| body?.parent = this; |
| } |
| if (condition != null) { |
| condition = condition.accept(v); |
| condition?.parent = this; |
| } |
| } |
| } |
| |
| class ForStatement extends Statement { |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| Expression condition; // May be null. |
| final List<Expression> updates; // May be empty, but not null. |
| Statement body; |
| |
| ForStatement(this.variables, this.condition, this.updates, this.body) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body?.parent = this; |
| } |
| |
| accept(StatementVisitor v) => v.visitForStatement(this); |
| accept1(StatementVisitor1 v, arg) => v.visitForStatement(this, arg); |
| |
| visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body?.accept(v); |
| } |
| |
| transformChildren(Transformer v) { |
| transformList(variables, v, this); |
| if (condition != null) { |
| condition = condition.accept(v); |
| condition?.parent = this; |
| } |
| transformList(updates, v, this); |
| if (body != null) { |
| body = body.accept(v); |
| body?.parent = this; |
| } |
| } |
| } |
| |
| class ForInStatement extends Statement { |
| VariableDeclaration variable; // Has no initializer. |
|