// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// -----------------------------------------------------------------------
///                          WHEN CHANGING THIS FILE:
/// -----------------------------------------------------------------------
///
/// If you are adding/removing/modifying fields/classes of the AST, you must
/// also update the following files:
///
///   - binary/ast_to_binary.dart
///   - binary/ast_from_binary.dart
///   - text/ast_to_text.dart
///   - clone.dart
///   - binary.md
///   - type_checker.dart (if relevant)
///
/// -----------------------------------------------------------------------
///                           ERROR HANDLING
/// -----------------------------------------------------------------------
///
/// As a rule of thumb, errors that can be detected statically are handled by
/// the frontend, typically by translating the erroneous code into a 'throw' or
/// a call to 'noSuchMethod'.
///
/// For example, there are no arity mismatches in static invocations, and
/// there are no direct invocations of a constructor on a abstract class.
///
/// -----------------------------------------------------------------------
///                           STATIC vs TOP-LEVEL
/// -----------------------------------------------------------------------
///
/// The term `static` includes both static class members and top-level members.
///
/// "Static class member" is the preferred term for non-top level statics.
///
/// Static class members are not lifted to the library level because mirrors
/// and stack traces can observe that they are class members.
///
/// -----------------------------------------------------------------------
///                                 PROCEDURES
/// -----------------------------------------------------------------------
///
/// "Procedure" is an umbrella term for method, getter, setter, index-getter,
/// index-setter, operator overloader, and factory constructor.
///
/// Generative constructors, field initializers, local functions are NOT
/// procedures.
///
/// -----------------------------------------------------------------------
///                               TRANSFORMATIONS
/// -----------------------------------------------------------------------
///
/// AST transformations can be performed using [TreeNode.replaceWith] or the
/// [Transformer] visitor class.
///
/// Use [Transformer] for bulk transformations that are likely to transform lots
/// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate
/// relatively few nodes.  Or use whichever is more convenient.
///
/// The AST can also be mutated by direct field manipulation, but the user then
/// has to update parent pointers manually.
///
library kernel.ast;

import 'dart:convert' show utf8;

import 'visitor.dart';
export 'visitor.dart';

import 'canonical_name.dart' show CanonicalName;
export 'canonical_name.dart' show CanonicalName;

import 'transformations/flags.dart';
import 'text/ast_to_text.dart';
import 'type_algebra.dart';
import 'type_environment.dart';
import 'coq_annot.dart';

/// Any type of node in the IR.
abstract class Node {
  const Node();

  accept(Visitor v);
  visitChildren(Visitor v);

  /// Returns the textual representation of this node for use in debugging.
  ///
  /// [toString] should only be used for debugging and short-running test tools
  /// as it can cause serious memory leaks.
  ///
  /// Synthetic names are cached globally to retain consistency across different
  /// [toString] calls (hence the memory leak).
  ///
  /// Nodes that are named, such as [Class] and [Member], return their
  /// (possibly synthesized) name, whereas other AST nodes return the complete
  /// textual representation of their subtree.
  String toString() => debugNodeToString(this);
}

/// A mutable AST node with a parent pointer.
///
/// This is anything other than [Name] and [DartType] nodes.
abstract class TreeNode extends Node {
  static int _hashCounter = 0;
  final int hashCode = _hashCounter = (_hashCounter + 1) & 0x3fffffff;
  static const int noOffset = -1;

  TreeNode parent;

  /// Offset in the source file it comes from.
  ///
  /// Valid values are from 0 and up, or -1 ([noOffset]) if the file offset is
  /// not available (this is the default if none is specifically set).
  int fileOffset = noOffset;

  accept(TreeVisitor v);
  visitChildren(Visitor v);
  transformChildren(Transformer v);

  /// Replaces [child] with [replacement].
  ///
  /// The caller is responsible for ensuring that the AST remains a tree.  In
  /// particular, [replacement] should be an orphan or be part of an orphaned
  /// subtree.
  ///
  /// Has no effect if [child] is not actually a child of this node.
  ///
  /// If [replacement] is `null`, this will [remove] the [child] node.
  void replaceChild(TreeNode child, TreeNode replacement) {
    transformChildren(new _ChildReplacer(child, replacement));
  }

  /// Inserts another node in place of this one.
  ///
  /// The caller is responsible for ensuring that the AST remains a tree.  In
  /// particular, [replacement] should be an orphan or be part of an orphaned
  /// subtree.
  ///
  /// If [replacement] is `null`, this will [remove] the node.
  void replaceWith(TreeNode replacement) {
    parent.replaceChild(this, replacement);
    parent = null;
  }

  /// Removes this node from the [List] it is currently stored in, or assigns
  /// `null` to the field on the parent currently pointing to the node.
  ///
  /// Has no effect if the node is orphaned or if the parent pointer is stale.
  void remove() {
    parent?.replaceChild(this, null);
    parent = null;
  }

  Component get enclosingComponent => parent?.enclosingComponent;

  /// Returns the best known source location of the given AST node, or `null` if
  /// the node is orphaned.
  ///
  /// This getter is intended for diagnostics and debugging, and should be
  /// avoided in production code.
  Location get location {
    if (fileOffset == noOffset) return parent?.location;
    return _getLocationInEnclosingFile(fileOffset);
  }

  Location _getLocationInEnclosingFile(int offset) {
    return parent?._getLocationInEnclosingFile(offset);
  }
}

/// An AST node that can be referenced by other nodes.
///
/// There is a single [reference] belonging to this node, providing a level of
/// indirection that is needed during serialization.
@coq
abstract class NamedNode extends TreeNode {
  @coqdef
  final Reference reference;

  NamedNode(Reference reference)
      : this.reference = reference ?? new Reference() {
    this.reference.node = this;
  }

  CanonicalName get canonicalName => reference?.canonicalName;
}

abstract class FileUriNode extends TreeNode {
  /// The URI of the source file this node was loaded from.
  Uri get fileUri;
}

/// Indirection between a reference and its definition.
///
/// There is only one reference object per [NamedNode].
@coqref
class Reference {
  CanonicalName canonicalName;

  @nocoq
  NamedNode node;

  String toString() {
    if (canonicalName != null) {
      return 'Reference to $canonicalName';
    }
    if (node != null) {
      return 'Reference to $node';
    }
    return 'Unbound reference';
  }

  Library get asLibrary {
    if (node == null) {
      throw '$this is not bound to an AST node. A library was expected';
    }
    return node as Library;
  }

  Class get asClass {
    if (node == null) {
      throw '$this is not bound to an AST node. A class was expected';
    }
    return node as Class;
  }

  Member get asMember {
    if (node == null) {
      throw '$this is not bound to an AST node. A member was expected';
    }
    return node as Member;
  }

  Field get asField {
    if (node == null) {
      throw '$this is not bound to an AST node. A field was expected';
    }
    return node as Field;
  }

  Constructor get asConstructor {
    if (node == null) {
      throw '$this is not bound to an AST node. A constructor was expected';
    }
    return node as Constructor;
  }

  Procedure get asProcedure {
    if (node == null) {
      throw '$this is not bound to an AST node. A procedure was expected';
    }
    return node as Procedure;
  }

  Typedef get asTypedef {
    if (node == null) {
      throw '$this is not bound to an AST node. A typedef was expected';
    }
    return node as Typedef;
  }
}

// ------------------------------------------------------------------------
//                      LIBRARIES and CLASSES
// ------------------------------------------------------------------------

@coq
class Library extends NamedNode implements Comparable<Library>, FileUriNode {
  /// An import path to this library.
  ///
  /// The [Uri] should have the `dart`, `package`, `app`, or `file` scheme.
  ///
  /// If the URI has the `app` scheme, it is relative to the application root.
  Uri importUri;

  /// The URI of the source file this library was loaded from.
  Uri fileUri;

  /// If true, the library is part of another build unit and its contents
  /// are only partially loaded.
  ///
  /// Classes of an external library are loaded at one of the [ClassLevel]s
  /// other than [ClassLevel.Body].  Members in an external library have no
  /// body, but have their typed interface present.
  ///
  /// If the libary is non-external, then its classes are at [ClassLevel.Body]
  /// and all members are loaded.
  bool isExternal;

  String name;

  @nocoq
  final List<Expression> annotations;

  final List<LibraryDependency> dependencies;

  /// References to nodes exported by `export` declarations that:
  /// - aren't ambiguous, or
  /// - aren't hidden by local declarations.
  @nocoq
  final List<Reference> additionalExports = <Reference>[];

  @informative
  final List<LibraryPart> parts;

  final List<Typedef> typedefs;
  final List<Class> classes;
  final List<Procedure> procedures;
  final List<Field> fields;

  Library(this.importUri,
      {this.name,
      this.isExternal: false,
      List<Expression> annotations,
      List<LibraryDependency> dependencies,
      List<LibraryPart> parts,
      List<Typedef> typedefs,
      List<Class> classes,
      List<Procedure> procedures,
      List<Field> fields,
      this.fileUri,
      Reference reference})
      : this.annotations = annotations ?? <Expression>[],
        this.dependencies = dependencies ?? <LibraryDependency>[],
        this.parts = parts ?? <LibraryPart>[],
        this.typedefs = typedefs ?? <Typedef>[],
        this.classes = classes ?? <Class>[],
        this.procedures = procedures ?? <Procedure>[],
        this.fields = fields ?? <Field>[],
        super(reference) {
    setParents(this.dependencies, this);
    setParents(this.parts, this);
    setParents(this.typedefs, this);
    setParents(this.classes, this);
    setParents(this.procedures, this);
    setParents(this.fields, this);
  }

  /// Returns the top-level fields and procedures defined in this library.
  ///
  /// This getter is for convenience, not efficiency.  Consider manually
  /// iterating the members to speed up code in production.
  Iterable<Member> get members =>
      <Iterable<Member>>[fields, procedures].expand((x) => x);

  void addMember(Member member) {
    member.parent = this;
    if (member is Procedure) {
      procedures.add(member);
    } else if (member is Field) {
      fields.add(member);
    } else {
      throw new ArgumentError(member);
    }
  }

  void addAnnotation(Expression node) {
    node.parent = this;
    annotations.add(node);
  }

  void addClass(Class class_) {
    class_.parent = this;
    classes.add(class_);
  }

  void addField(Field field) {
    field.parent = this;
    fields.add(field);
  }

  void addProcedure(Procedure procedure) {
    procedure.parent = this;
    procedures.add(procedure);
  }

  void addTypedef(Typedef typedef_) {
    typedef_.parent = this;
    typedefs.add(typedef_);
  }

  void computeCanonicalNames() {
    assert(canonicalName != null);
    for (var typedef_ in typedefs) {
      canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference);
    }
    for (var field in fields) {
      canonicalName.getChildFromMember(field).bindTo(field.reference);
    }
    for (var member in procedures) {
      canonicalName.getChildFromMember(member).bindTo(member.reference);
    }
    for (var class_ in classes) {
      canonicalName.getChild(class_.name).bindTo(class_.reference);
      class_.computeCanonicalNames();
    }
  }

  void addDependency(LibraryDependency node) {
    dependencies.add(node..parent = this);
  }

  void addPart(LibraryPart node) {
    parts.add(node..parent = this);
  }

  accept(TreeVisitor v) => v.visitLibrary(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    visitList(dependencies, v);
    visitList(parts, v);
    visitList(typedefs, v);
    visitList(classes, v);
    visitList(procedures, v);
    visitList(fields, v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformList(dependencies, v, this);
    transformList(parts, v, this);
    transformList(typedefs, v, this);
    transformList(classes, v, this);
    transformList(procedures, v, this);
    transformList(fields, v, this);
  }

  static int _libraryIdCounter = 0;
  int _libraryId = ++_libraryIdCounter;

  int compareTo(Library other) => _libraryId - other._libraryId;

  /// Returns a possibly synthesized name for this library, consistent with
  /// the names across all [toString] calls.
  String toString() => debugLibraryName(this);

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

/// An import or export declaration in a library.
///
/// It can represent any of the following forms,
///
///     import <url>;
///     import <url> as <name>;
///     import <url> deferred as <name>;
///     export <url>;
///
/// optionally with metadata and [Combinators].
class LibraryDependency extends TreeNode {
  int flags;

  final List<Expression> annotations;

  Reference importedLibraryReference;

  /// The name of the import prefix, if any, or `null` if this is not an import
  /// with a prefix.
  ///
  /// Must be non-null for deferred imports, and must be null for exports.
  String name;

  final List<Combinator> combinators;

  LibraryDependency(int flags, List<Expression> annotations,
      Library importedLibrary, String name, List<Combinator> combinators)
      : this.byReference(
            flags, annotations, importedLibrary.reference, name, combinators);

  LibraryDependency.deferredImport(Library importedLibrary, String name,
      {List<Combinator> combinators, List<Expression> annotations})
      : this.byReference(DeferredFlag, annotations ?? <Expression>[],
            importedLibrary.reference, name, combinators ?? <Combinator>[]);

  LibraryDependency.import(Library importedLibrary,
      {String name, List<Combinator> combinators, List<Expression> annotations})
      : this.byReference(0, annotations ?? <Expression>[],
            importedLibrary.reference, name, combinators ?? <Combinator>[]);

  LibraryDependency.export(Library importedLibrary,
      {List<Combinator> combinators, List<Expression> annotations})
      : this.byReference(ExportFlag, annotations ?? <Expression>[],
            importedLibrary.reference, null, combinators ?? <Combinator>[]);

  LibraryDependency.byReference(this.flags, this.annotations,
      this.importedLibraryReference, this.name, this.combinators) {
    setParents(annotations, this);
    setParents(combinators, this);
  }

  Library get enclosingLibrary => parent;
  Library get targetLibrary => importedLibraryReference.asLibrary;

  static const int ExportFlag = 1 << 0;
  static const int DeferredFlag = 1 << 1;

  bool get isExport => flags & ExportFlag != 0;
  bool get isImport => !isExport;
  bool get isDeferred => flags & DeferredFlag != 0;

  void addAnnotation(Expression annotation) {
    annotations.add(annotation..parent = this);
  }

  accept(TreeVisitor v) => v.visitLibraryDependency(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    visitList(combinators, v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformList(combinators, v, this);
  }
}

/// A part declaration in a library.
///
///     part <url>;
///
/// optionally with metadata.
class LibraryPart extends TreeNode {
  final List<Expression> annotations;
  final String partUri;

  LibraryPart(this.annotations, this.partUri) {
    setParents(annotations, this);
  }

  void addAnnotation(Expression annotation) {
    annotations.add(annotation..parent = this);
  }

  accept(TreeVisitor v) => v.visitLibraryPart(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
  }
}

/// A `show` or `hide` clause for an import or export.
class Combinator extends TreeNode {
  bool isShow;

  final List<String> names;

  LibraryDependency get dependency => parent;

  Combinator(this.isShow, this.names);
  Combinator.show(this.names) : isShow = true;
  Combinator.hide(this.names) : isShow = false;

  bool get isHide => !isShow;

  @override
  accept(TreeVisitor v) => v.visitCombinator(this);

  @override
  visitChildren(Visitor v) {}

  @override
  transformChildren(Transformer v) {}
}

/// Declaration of a type alias.
class Typedef extends NamedNode implements FileUriNode {
  /// The URI of the source file that contains the declaration of this typedef.
  Uri fileUri;
  List<Expression> annotations = const <Expression>[];
  String name;
  final List<TypeParameter> typeParameters;
  DartType type;

  Typedef(this.name, this.type,
      {Reference reference, this.fileUri, List<TypeParameter> typeParameters})
      : this.typeParameters = typeParameters ?? <TypeParameter>[],
        super(reference) {
    setParents(this.typeParameters, this);
  }

  Library get enclosingLibrary => parent;

  accept(TreeVisitor v) {
    return v.visitTypedef(this);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformList(typeParameters, v, this);
    if (type != null) {
      type = v.visitDartType(type);
    }
  }

  visitChildren(Visitor v) {
    visitList(annotations, v);
    visitList(typeParameters, v);
    type?.accept(v);
  }

  void addAnnotation(Expression node) {
    if (annotations.isEmpty) {
      annotations = <Expression>[];
    }
    annotations.add(node);
    node.parent = this;
  }
}

/// The degree to which the contents of a class have been loaded into memory.
///
/// Each level imply the requirements of the previous ones.
enum ClassLevel {
  /// Temporary loading level for internal use by IR producers.  Consumers of
  /// kernel code should not expect to see classes at this level.
  Temporary,

  /// The class may be used as a type, and it may contain members that are
  /// referenced from this build unit.
  ///
  /// The type parameters and their bounds are present.
  ///
  /// There is no guarantee that all members are present.
  ///
  /// All supertypes of this class are at [Type] level or higher.
  Type,

  /// All instance members of the class are present.
  ///
  /// All supertypes of this class are at [Hierarchy] level or higher.
  ///
  /// This level exists so supertypes of a fully loaded class contain all the
  /// members needed to detect override constraints.
  Hierarchy,

  /// All instance members of the class have their body loaded, and their
  /// annotations are present.
  ///
  /// All supertypes of this class are at [Hierarchy] level or higher.
  ///
  /// If this class is a mixin application, then its mixin is loaded at [Mixin]
  /// level or higher.
  ///
  /// This level exists so the contents of a mixin can be cloned into a
  /// mixin application.
  Mixin,

  /// All members of the class are fully loaded and are in the correct order.
  ///
  /// Annotations are present on classes and members.
  ///
  /// All supertypes of this class are at [Hierarchy] level or higher,
  /// not necessarily at [Body] level.
  Body,
}

/// Declaration of a regular class or a mixin application.
///
/// Mixin applications may not contain fields or procedures, as they implicitly
/// use those from its mixed-in type.  However, the IR does not enforce this
/// rule directly, as doing so can obstruct transformations.  It is possible to
/// transform a mixin application to become a regular class, and vice versa.
@coq
class Class extends NamedNode implements FileUriNode {
  /// Start offset of the class in the source file it comes from.
  ///
  /// Note that this includes annotations if any.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
  /// start offset is not available (this is the default if none is specifically
  /// set).
  int startFileOffset = TreeNode.noOffset;

  /// End offset in the source file it comes from. Valid values are from 0 and
  /// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
  /// (this is the default if none is specifically set).
  int fileEndOffset = TreeNode.noOffset;

  /// The degree to which the contents of the class have been loaded.
  ClassLevel level = ClassLevel.Body;

  /// List of metadata annotations on the class.
  ///
  /// This defaults to an immutable empty list. Use [addAnnotation] to add
  /// annotations if needed.
  @nocoq
  List<Expression> annotations = const <Expression>[];

  /// Name of the class.
  ///
  /// Must be non-null and must be unique within the library.
  ///
  /// The name may contain characters that are not valid in a Dart identifier,
  /// in particular, the symbol '&' is used in class names generated for mixin
  /// applications.
  @coq
  String name;

  // Must match serialized bit positions.
  static const int LevelMask = 0x3; // Bits 0 and 1.
  static const int FlagAbstract = 1 << 2;
  static const int FlagEnum = 1 << 3;
  static const int FlagAnonymousMixin = 1 << 4;
  static const int FlagEliminatedMixin = 1 << 5;

  int flags = 0;

  bool get isAbstract => flags & FlagAbstract != 0;

  void set isAbstract(bool value) {
    flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
  }

  /// Whether this class is an enum.
  bool get isEnum => flags & FlagEnum != 0;

  void set isEnum(bool value) {
    flags = value ? (flags | FlagEnum) : (flags & ~FlagEnum);
  }

  /// Whether this class is a synthetic implementation created for each
  /// mixed-in class. For example the following code:
  /// class Z extends A with B, C, D {}
  /// class A {}
  /// class B {}
  /// class C {}
  /// class D {}
  /// ...creates:
  /// abstract class A&B extends A mixedIn B {}
  /// abstract class A&B&C extends A&B mixedIn C {}
  /// abstract class A&B&C&D extends A&B&C mixedIn D {}
  /// class Z extends A&B&C&D {}
  /// All X&Y classes are marked as synthetic.
  bool get isAnonymousMixin => flags & FlagAnonymousMixin != 0;

  void set isAnonymousMixin(bool value) {
    flags =
        value ? (flags | FlagAnonymousMixin) : (flags & ~FlagAnonymousMixin);
  }

  /// Whether this class was transformed from a mixin application.
  /// In such case, its mixed-in type was pulled into the end of implemented
  /// types list.
  bool get isEliminatedMixin => flags & FlagEliminatedMixin != 0;

  void set isEliminatedMixin(bool value) {
    flags =
        value ? (flags | FlagEliminatedMixin) : (flags & ~FlagEliminatedMixin);
  }

  /// The URI of the source file this class was loaded from.
  Uri fileUri;

  final List<TypeParameter> typeParameters;

  /// The immediate super type, or `null` if this is the root class.
  Supertype supertype;

  /// The mixed-in type if this is a mixin application, otherwise `null`.
  Supertype mixedInType;

  /// The types from the `implements` clause.
  final List<Supertype> implementedTypes;

  /// Fields declared in the class.
  ///
  /// For mixin applications this should be empty.
  final List<Field> fields;

  /// Constructors declared in the class.
  final List<Constructor> constructors;

  /// Procedures declared in the class.
  ///
  /// For mixin applications this should only contain forwarding stubs.
  final List<Procedure> procedures;

  /// Redirecting factory constructors declared in the class.
  ///
  /// For mixin applications this should be empty.
  final List<RedirectingFactoryConstructor> redirectingFactoryConstructors;

  Class(
      {this.name,
      bool isAbstract: false,
      bool isAnonymousMixin: false,
      this.supertype,
      this.mixedInType,
      List<TypeParameter> typeParameters,
      List<Supertype> implementedTypes,
      List<Constructor> constructors,
      List<Procedure> procedures,
      List<Field> fields,
      List<RedirectingFactoryConstructor> redirectingFactoryConstructors,
      this.fileUri,
      Reference reference})
      : this.typeParameters = typeParameters ?? <TypeParameter>[],
        this.implementedTypes = implementedTypes ?? <Supertype>[],
        this.fields = fields ?? <Field>[],
        this.constructors = constructors ?? <Constructor>[],
        this.procedures = procedures ?? <Procedure>[],
        this.redirectingFactoryConstructors =
            redirectingFactoryConstructors ?? <RedirectingFactoryConstructor>[],
        super(reference) {
    setParents(this.typeParameters, this);
    setParents(this.constructors, this);
    setParents(this.procedures, this);
    setParents(this.fields, this);
    setParents(this.redirectingFactoryConstructors, this);
    this.isAbstract = isAbstract;
    this.isAnonymousMixin = isAnonymousMixin;
  }

  void computeCanonicalNames() {
    assert(canonicalName != null);
    for (var member in fields) {
      canonicalName.getChildFromMember(member).bindTo(member.reference);
    }
    for (var member in procedures) {
      canonicalName.getChildFromMember(member).bindTo(member.reference);
    }
    for (var member in constructors) {
      canonicalName.getChildFromMember(member).bindTo(member.reference);
    }
    for (var member in redirectingFactoryConstructors) {
      canonicalName.getChildFromMember(member).bindTo(member.reference);
    }
  }

  /// The immediate super class, or `null` if this is the root class.
  Class get superclass => supertype?.classNode;

  /// The mixed-in class if this is a mixin application, otherwise `null`.
  ///
  /// Note that this may itself be a mixin application.  Use [mixin] to get the
  /// class that has the fields and procedures.
  Class get mixedInClass => mixedInType?.classNode;

  /// The class that declares the field and procedures of this class.
  Class get mixin => mixedInClass?.mixin ?? this;

  bool get isMixinApplication => mixedInType != null;

  /// Members declared in this class.
  ///
  /// This getter is for convenience, not efficiency.  Consider manually
  /// iterating the members to speed up code in production.
  Iterable<Member> get members => <Iterable<Member>>[
        fields,
        constructors,
        procedures,
        redirectingFactoryConstructors
      ].expand((x) => x);

  /// The immediately extended, mixed-in, and implemented types.
  ///
  /// This getter is for convenience, not efficiency.  Consider manually
  /// iterating the super types to speed up code in production.
  Iterable<Supertype> get supers => <Iterable<Supertype>>[
        supertype == null ? const [] : [supertype],
        mixedInType == null ? const [] : [mixedInType],
        implementedTypes
      ].expand((x) => x);

  /// The library containing this class.
  Library get enclosingLibrary => parent;

  /// Adds a member to this class.
  ///
  /// Throws an error if attempting to add a field or procedure to a mixin
  /// application.
  void addMember(Member member) {
    member.parent = this;
    if (member is Constructor) {
      constructors.add(member);
    } else if (member is Procedure) {
      procedures.add(member);
    } else if (member is Field) {
      fields.add(member);
    } else if (member is RedirectingFactoryConstructor) {
      redirectingFactoryConstructors.add(member);
    } else {
      throw new ArgumentError(member);
    }
  }

  void addAnnotation(Expression node) {
    if (annotations.isEmpty) {
      annotations = <Expression>[];
    }
    annotations.add(node);
    node.parent = this;
  }

  accept(TreeVisitor v) => v.visitClass(this);
  acceptReference(Visitor v) => v.visitClassReference(this);

  /// If true, the class is part of an external library, that is, it is defined
  /// in another build unit.  Only a subset of its members are present.
  ///
  /// These classes should be loaded at either [ClassLevel.Type] or
  /// [ClassLevel.Hierarchy] level.
  bool get isInExternalLibrary => enclosingLibrary.isExternal;

  Supertype get asRawSupertype {
    return new Supertype(this,
        new List<DartType>.filled(typeParameters.length, const DynamicType()));
  }

  Supertype get asThisSupertype {
    return new Supertype(this, _getAsTypeArguments(typeParameters));
  }

  @nocoq
  InterfaceType _rawType;
  InterfaceType get rawType => _rawType ??= new InterfaceType(this);

  @nocoq
  InterfaceType _thisType;
  InterfaceType get thisType {
    return _thisType ??=
        new InterfaceType(this, _getAsTypeArguments(typeParameters));
  }

  @nocoq
  InterfaceType _bottomType;
  InterfaceType get bottomType {
    return _bottomType ??= new InterfaceType(this,
        new List<DartType>.filled(typeParameters.length, const BottomType()));
  }

  /// Returns a possibly synthesized name for this class, consistent with
  /// the names used across all [toString] calls.
  String toString() => debugQualifiedClassName(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    visitList(typeParameters, v);
    supertype?.accept(v);
    mixedInType?.accept(v);
    visitList(implementedTypes, v);
    visitList(constructors, v);
    visitList(procedures, v);
    visitList(fields, v);
    visitList(redirectingFactoryConstructors, v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformList(typeParameters, v, this);
    if (supertype != null) {
      supertype = v.visitSupertype(supertype);
    }
    if (mixedInType != null) {
      mixedInType = v.visitSupertype(mixedInType);
    }
    transformSupertypeList(implementedTypes, v);
    transformList(constructors, v, this);
    transformList(procedures, v, this);
    transformList(fields, v, this);
    transformList(redirectingFactoryConstructors, v, this);
  }

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

// ------------------------------------------------------------------------
//                            MEMBERS
// ------------------------------------------------------------------------

@coq
abstract class Member extends NamedNode implements FileUriNode {
  /// End offset in the source file it comes from.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
  /// end offset is not available (this is the default if none is specifically
  /// set).
  int fileEndOffset = TreeNode.noOffset;

  /// List of metadata annotations on the member.
  ///
  /// This defaults to an immutable empty list. Use [addAnnotation] to add
  /// annotations if needed.
  @nocoq
  List<Expression> annotations = const <Expression>[];

  Name name;

  /// The URI of the source file this member was loaded from.
  Uri fileUri;

  /// Flags summarizing the kinds of AST nodes contained in this member, for
  /// speeding up transformations that only affect certain types of nodes.
  ///
  /// See [TransformerFlag] for the meaning of each bit.
  ///
  /// These should not be used for any purpose other than skipping certain
  /// members if it can be determined that no work is needed in there.
  ///
  /// It is valid for these flags to be false positives in rare cases, so
  /// transformers must tolerate the case where a flag is spuriously set.
  ///
  /// This value is not serialized; it is populated by the frontend and the
  /// deserializer.
  //
  // TODO(asgerf): It might be worthwhile to put this on classes as well.
  int transformerFlags = 0;

  Member(this.name, this.fileUri, Reference reference) : super(reference);

  Class get enclosingClass => parent is Class ? parent : null;
  Library get enclosingLibrary => parent is Class ? parent.parent : parent;

  accept(MemberVisitor v);
  acceptReference(MemberReferenceVisitor v);

  /// If true, the member is part of an external library, that is, it is defined
  /// in another build unit.  Such members have no body or initializer present
  /// in the IR.
  bool get isInExternalLibrary => enclosingLibrary.isExternal;

  /// Returns true if this is an abstract procedure.
  bool get isAbstract => false;

  /// True if this is a field or non-setter procedure.
  ///
  /// Note that operators and factories return `true`, even though there are
  /// normally no calls to their getter.
  bool get hasGetter;

  /// True if this is a setter or a mutable field.
  bool get hasSetter;

  /// True if this is a non-static field or procedure.
  bool get isInstanceMember;

  /// True if the member has the `external` modifier, implying that the
  /// implementation is provided by the backend, and is not necessarily written
  /// in Dart.
  ///
  /// Members can have this modifier independently of whether the enclosing
  /// library is external.
  bool get isExternal;
  void set isExternal(bool value);

  /// The body of the procedure or constructor, or `null` if this is a field.
  FunctionNode get function => null;

  /// Returns a possibly synthesized name for this member, consistent with
  /// the names used across all [toString] calls.
  String toString() => debugQualifiedMemberName(this);

  void addAnnotation(Expression node) {
    if (annotations.isEmpty) {
      annotations = <Expression>[];
    }
    annotations.add(node);
    node.parent = this;
  }

  DartType get getterType;
  DartType get setterType;

  bool get containsSuperCalls {
    return transformerFlags & TransformerFlag.superCalls != 0;
  }
}

/// A field declaration.
///
/// The implied getter and setter for the field are not represented explicitly,
/// but can be made explicit if needed.
class Field extends Member {
  DartType type; // Not null. Defaults to DynamicType.
  int flags = 0;
  Expression initializer; // May be null.

  Field(Name name,
      {this.type: const DynamicType(),
      this.initializer,
      bool isCovariant: false,
      bool isFinal: false,
      bool isConst: false,
      bool isStatic: false,
      bool hasImplicitGetter,
      bool hasImplicitSetter,
      int transformerFlags: 0,
      Uri fileUri,
      Reference reference})
      : super(name, fileUri, reference) {
    assert(type != null);
    initializer?.parent = this;
    this.isCovariant = isCovariant;
    this.isFinal = isFinal;
    this.isConst = isConst;
    this.isStatic = isStatic;
    this.hasImplicitGetter = hasImplicitGetter ?? !isStatic;
    this.hasImplicitSetter = hasImplicitSetter ?? (!isStatic && !isFinal);
    this.transformerFlags = transformerFlags;
  }

  static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
  static const int FlagConst = 1 << 1;
  static const int FlagStatic = 1 << 2;
  static const int FlagHasImplicitGetter = 1 << 3;
  static const int FlagHasImplicitSetter = 1 << 4;
  static const int FlagCovariant = 1 << 5;
  static const int FlagGenericCovariantImpl = 1 << 6;

  /// Whether the field is declared with the `covariant` keyword.
  bool get isCovariant => flags & FlagCovariant != 0;

  bool get isFinal => flags & FlagFinal != 0;
  bool get isConst => flags & FlagConst != 0;
  bool get isStatic => flags & FlagStatic != 0;

  /// If true, a getter should be generated for this field.
  ///
  /// If false, there may or may not exist an explicit getter in the same class
  /// with the same name as the field.
  ///
  /// By default, all non-static fields have implicit getters.
  bool get hasImplicitGetter => flags & FlagHasImplicitGetter != 0;

  /// If true, a setter should be generated for this field.
  ///
  /// If false, there may or may not exist an explicit setter in the same class
  /// with the same name as the field.
  ///
  /// Final fields never have implicit setters, but a field without an implicit
  /// setter is not necessarily final, as it may be mutated by direct field
  /// access.
  ///
  /// By default, all non-static, non-final fields have implicit setters.
  bool get hasImplicitSetter => flags & FlagHasImplicitSetter != 0;

  /// Indicates whether the implicit setter associated with this field needs to
  /// contain a runtime type check to deal with generic covariance.
  ///
  /// When `true`, runtime checks may need to be performed; see
  /// [DispatchCategory] for details.
  bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;

  void set isCovariant(bool value) {
    flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant);
  }

  void set isFinal(bool value) {
    flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
  }

  void set isConst(bool value) {
    flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
  }

  void set isStatic(bool value) {
    flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
  }

  void set hasImplicitGetter(bool value) {
    flags = value
        ? (flags | FlagHasImplicitGetter)
        : (flags & ~FlagHasImplicitGetter);
  }

  void set hasImplicitSetter(bool value) {
    flags = value
        ? (flags | FlagHasImplicitSetter)
        : (flags & ~FlagHasImplicitSetter);
  }

  void set isGenericCovariantImpl(bool value) {
    flags = value
        ? (flags | FlagGenericCovariantImpl)
        : (flags & ~FlagGenericCovariantImpl);
  }

  /// True if the field is neither final nor const.
  bool get isMutable => flags & (FlagFinal | FlagConst) == 0;
  bool get isInstanceMember => !isStatic;
  bool get hasGetter => true;
  bool get hasSetter => isMutable;

  bool get isExternal => false;
  void set isExternal(bool value) {
    if (value) throw 'Fields cannot be external';
  }

  accept(MemberVisitor v) => v.visitField(this);

  acceptReference(MemberReferenceVisitor v) => v.visitFieldReference(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    type?.accept(v);
    name?.accept(v);
    initializer?.accept(v);
  }

  transformChildren(Transformer v) {
    type = v.visitDartType(type);
    transformList(annotations, v, this);
    if (initializer != null) {
      initializer = initializer.accept(v);
      initializer?.parent = this;
    }
  }

  DartType get getterType => type;
  DartType get setterType => isMutable ? type : const BottomType();

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

/// A generative constructor, possibly redirecting.
///
/// Note that factory constructors are treated as [Procedure]s.
///
/// Constructors do not take type parameters.  Type arguments from a constructor
/// invocation should be matched with the type parameters declared in the class.
///
/// For unnamed constructors, the name is an empty string (in a [Name]).
class Constructor extends Member {
  /// Start offset of the constructor in the source file it comes from.
  ///
  /// Note that this includes annotations if any.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
  /// start offset is not available (this is the default if none is specifically
  /// set).
  int startFileOffset = TreeNode.noOffset;

  int flags = 0;
  FunctionNode function;
  List<Initializer> initializers;

  Constructor(this.function,
      {Name name,
      bool isConst: false,
      bool isExternal: false,
      bool isSynthetic: false,
      List<Initializer> initializers,
      int transformerFlags: 0,
      Uri fileUri,
      Reference reference})
      : this.initializers = initializers ?? <Initializer>[],
        super(name, fileUri, reference) {
    function?.parent = this;
    setParents(this.initializers, this);
    this.isConst = isConst;
    this.isExternal = isExternal;
    this.isSynthetic = isSynthetic;
    this.transformerFlags = transformerFlags;
  }

  static const int FlagConst = 1 << 0; // Must match serialized bit positions.
  static const int FlagExternal = 1 << 1;
  static const int FlagSynthetic = 1 << 2;

  bool get isConst => flags & FlagConst != 0;
  bool get isExternal => flags & FlagExternal != 0;

  /// True if this is a synthetic constructor inserted in a class that
  /// does not otherwise declare any constructors.
  bool get isSynthetic => flags & FlagSynthetic != 0;

  void set isConst(bool value) {
    flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
  }

  void set isExternal(bool value) {
    flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
  }

  void set isSynthetic(bool value) {
    flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
  }

  bool get isInstanceMember => false;
  bool get hasGetter => false;
  bool get hasSetter => false;

  accept(MemberVisitor v) => v.visitConstructor(this);

  acceptReference(MemberReferenceVisitor v) =>
      v.visitConstructorReference(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    name?.accept(v);
    visitList(initializers, v);
    function?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformList(initializers, v, this);
    if (function != null) {
      function = function.accept(v);
      function?.parent = this;
    }
  }

  DartType get getterType => const BottomType();
  DartType get setterType => const BottomType();

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

/// Residue of a redirecting factory constructor for the linking phase.
///
/// In the following example, `bar` is a redirecting factory constructor.
///
///     class A {
///       A.foo();
///       factory A.bar() = A.foo;
///     }
///
/// An invocation of `new A.bar()` has the same effect as an invocation of
/// `new A.foo()`.  In Kernel, the invocations of `bar` are replaced with
/// invocations of `foo`, and after it is done, the redirecting constructor can
/// be removed from the class.  However, it is needed during the linking phase,
/// because other modules can refer to that constructor.
///
/// [RedirectingFactoryConstructor]s contain the necessary information for
/// linking and are treated as non-runnable members of classes that merely serve
/// as containers for that information.
///
/// Redirecting factory constructors can be unnamed.  In this case, the name is
/// an empty string (in a [Name]).
class RedirectingFactoryConstructor extends Member {
  int flags = 0;

  /// [RedirectingFactoryConstructor]s may redirect to constructors or factories
  /// of instantiated generic types, that is, generic types with supplied type
  /// arguments.  The supplied type arguments are stored in this field.
  final List<DartType> typeArguments;

  /// Reference to the constructor or the factory that this
  /// [RedirectingFactoryConstructor] redirects to.
  Reference targetReference;

  /// [typeParameters] are duplicates of the type parameters of the enclosing
  /// class.  Because [RedirectingFactoryConstructor]s aren't instance members,
  /// references to the type parameters of the enclosing class in the
  /// redirection target description are encoded with references to the elements
  /// of [typeParameters].
  List<TypeParameter> typeParameters;

  /// Positional parameters of [RedirectingFactoryConstructor]s should be
  /// compatible with that of the target constructor.
  List<VariableDeclaration> positionalParameters;
  int requiredParameterCount;

  /// Named parameters of [RedirectingFactoryConstructor]s should be compatible
  /// with that of the target constructor.
  List<VariableDeclaration> namedParameters;

  RedirectingFactoryConstructor(this.targetReference,
      {Name name,
      bool isConst: false,
      bool isExternal: false,
      int transformerFlags: 0,
      List<DartType> typeArguments,
      List<TypeParameter> typeParameters,
      List<VariableDeclaration> positionalParameters,
      List<VariableDeclaration> namedParameters,
      int requiredParameterCount,
      Uri fileUri,
      Reference reference})
      : this.typeArguments = typeArguments ?? <DartType>[],
        this.typeParameters = typeParameters ?? <TypeParameter>[],
        this.positionalParameters =
            positionalParameters ?? <VariableDeclaration>[],
        this.namedParameters = namedParameters ?? <VariableDeclaration>[],
        this.requiredParameterCount =
            requiredParameterCount ?? positionalParameters?.length ?? 0,
        super(name, fileUri, reference) {
    setParents(this.typeParameters, this);
    setParents(this.positionalParameters, this);
    setParents(this.namedParameters, this);
    this.isConst = isConst;
    this.isExternal = isExternal;
    this.transformerFlags = transformerFlags;
  }

  static const int FlagConst = 1 << 0; // Must match serialized bit positions.
  static const int FlagExternal = 1 << 1;

  bool get isConst => flags & FlagConst != 0;
  bool get isExternal => flags & FlagExternal != 0;

  void set isConst(bool value) {
    flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
  }

  void set isExternal(bool value) {
    flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
  }

  bool get isInstanceMember => false;
  bool get hasGetter => false;
  bool get hasSetter => false;

  bool get isUnresolved => targetReference == null;

  Member get target => targetReference?.asMember;

  void set target(Member member) {
    assert(member is Constructor ||
        (member is Procedure && member.kind == ProcedureKind.Factory));
    targetReference = getMemberReference(member);
  }

  accept(MemberVisitor v) => v.visitRedirectingFactoryConstructor(this);

  acceptReference(MemberReferenceVisitor v) =>
      v.visitRedirectingFactoryConstructorReference(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    target?.acceptReference(v);
    visitList(typeArguments, v);
    name?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    transformTypeList(typeArguments, v);
  }

  DartType get getterType => const BottomType();
  DartType get setterType => const BottomType();

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

/// A method, getter, setter, index-getter, index-setter, operator overloader,
/// or factory.
///
/// Procedures can have the static, abstract, and/or external modifier, although
/// only the static and external modifiers may be used together.
///
/// For non-static procedures the name is required for dynamic dispatch.
/// For external procedures the name is required for identifying the external
/// implementation.
///
/// For methods, getters, and setters the name is just as it was declared.
/// For setters this does not include a trailing `=`.
/// For index-getters/setters, this is `[]` and `[]=`.
/// For operators, this is the token for the operator, e.g. `+` or `==`,
/// except for the unary minus operator, whose name is `unary-`.
@coq
class Procedure extends Member {
  /// Start offset of the function in the source file it comes from.
  ///
  /// Note that this includes annotations if any.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
  /// start offset is not available (this is the default if none is specifically
  /// set).
  int startFileOffset = TreeNode.noOffset;

  ProcedureKind kind;
  int flags = 0;
  // function is null if and only if abstract, external.
  FunctionNode function;

  // The function node's body might be lazily loaded, meaning that this value
  // might not be set correctly yet. Make sure the body is loaded before
  // returning anything.
  int get transformerFlags {
    function?.body;
    return super.transformerFlags;
  }

  // The function node's body might be lazily loaded, meaning that this value
  // might get overwritten later (when the body is read). To avoid that read the
  // body now and only set the value afterwards.
  void set transformerFlags(int newValue) {
    function?.body;
    super.transformerFlags = newValue;
  }

  // This function will set the transformer flags without loading the body.
  // Used when reading the binary. For other cases one should probably use
  // `transformerFlags = value;`.
  void setTransformerFlagsWithoutLazyLoading(int newValue) {
    super.transformerFlags = newValue;
  }

  Reference forwardingStubSuperTargetReference;
  Reference forwardingStubInterfaceTargetReference;

  Procedure(Name name, ProcedureKind kind, FunctionNode function,
      {bool isAbstract: false,
      bool isStatic: false,
      bool isExternal: false,
      bool isConst: false,
      bool isForwardingStub: false,
      bool isForwardingSemiStub: false,
      int transformerFlags: 0,
      Uri fileUri,
      Reference reference,
      Member forwardingStubSuperTarget,
      Member forwardingStubInterfaceTarget})
      : this.byReference(name, kind, function,
            isAbstract: isAbstract,
            isStatic: isStatic,
            isExternal: isExternal,
            isConst: isConst,
            isForwardingStub: isForwardingStub,
            isForwardingSemiStub: isForwardingSemiStub,
            transformerFlags: transformerFlags,
            fileUri: fileUri,
            reference: reference,
            forwardingStubSuperTargetReference:
                getMemberReference(forwardingStubSuperTarget),
            forwardingStubInterfaceTargetReference:
                getMemberReference(forwardingStubInterfaceTarget));

  Procedure.byReference(Name name, this.kind, this.function,
      {bool isAbstract: false,
      bool isStatic: false,
      bool isExternal: false,
      bool isConst: false,
      bool isForwardingStub: false,
      bool isForwardingSemiStub: false,
      int transformerFlags: 0,
      Uri fileUri,
      Reference reference,
      this.forwardingStubSuperTargetReference,
      this.forwardingStubInterfaceTargetReference})
      : super(name, fileUri, reference) {
    function?.parent = this;
    this.isAbstract = isAbstract;
    this.isStatic = isStatic;
    this.isExternal = isExternal;
    this.isConst = isConst;
    this.isForwardingStub = isForwardingStub;
    this.isForwardingSemiStub = isForwardingSemiStub;
    this.transformerFlags = transformerFlags;
  }

  static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
  static const int FlagAbstract = 1 << 1;
  static const int FlagExternal = 1 << 2;
  static const int FlagConst = 1 << 3; // Only for external const factories.
  static const int FlagForwardingStub = 1 << 4;
  static const int FlagForwardingSemiStub = 1 << 5;
  // TODO(29841): Remove this flag after the issue is resolved.
  static const int FlagRedirectingFactoryConstructor = 1 << 6;
  static const int FlagNoSuchMethodForwarder = 1 << 7;

  bool get isStatic => flags & FlagStatic != 0;
  bool get isAbstract => flags & FlagAbstract != 0;
  bool get isExternal => flags & FlagExternal != 0;

  /// True if this has the `const` modifier.  This is only possible for external
  /// constant factories, such as `String.fromEnvironment`.
  bool get isConst => flags & FlagConst != 0;

  /// If set, this flag indicates that this function's implementation exists
  /// solely for the purpose of type checking arguments and forwarding to
  /// [forwardingStubSuperTarget].
  ///
  /// Note that just because this bit is set doesn't mean that the function was
  /// not declared in the source; it's possible that this is a forwarding
  /// semi-stub (see isForwardingSemiStub).  To determine whether this function
  /// was present in the source, consult [isSyntheticForwarder].
  bool get isForwardingStub => flags & FlagForwardingStub != 0;

  /// If set, this flag indicates that although this function is a forwarding
  /// stub, it was present in the original source as an abstract method.
  bool get isForwardingSemiStub => flags & FlagForwardingSemiStub != 0;

  // Indicates if this [Procedure] represents a redirecting factory constructor
  // and doesn't have a runnable body.
  bool get isRedirectingFactoryConstructor {
    return flags & FlagRedirectingFactoryConstructor != 0;
  }

  /// If set, this flag indicates that this function was not present in the
  /// source, and it exists solely for the purpose of type checking arguments
  /// and forwarding to [forwardingStubSuperTarget].
  bool get isSyntheticForwarder => isForwardingStub && !isForwardingSemiStub;

  bool get isNoSuchMethodForwarder => flags & FlagNoSuchMethodForwarder != 0;

  void set isStatic(bool value) {
    flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
  }

  void set isAbstract(bool value) {
    flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
  }

  void set isExternal(bool value) {
    flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
  }

  void set isConst(bool value) {
    flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
  }

  void set isForwardingStub(bool value) {
    flags =
        value ? (flags | FlagForwardingStub) : (flags & ~FlagForwardingStub);
  }

  void set isForwardingSemiStub(bool value) {
    flags = value
        ? (flags | FlagForwardingSemiStub)
        : (flags & ~FlagForwardingSemiStub);
  }

  void set isRedirectingFactoryConstructor(bool value) {
    flags = value
        ? (flags | FlagRedirectingFactoryConstructor)
        : (flags & ~FlagRedirectingFactoryConstructor);
  }

  void set isNoSuchMethodForwarder(bool value) {
    flags = value
        ? (flags | FlagNoSuchMethodForwarder)
        : (flags & ~FlagNoSuchMethodForwarder);
  }

  bool get isInstanceMember => !isStatic;
  bool get isGetter => kind == ProcedureKind.Getter;
  bool get isSetter => kind == ProcedureKind.Setter;
  bool get isAccessor => isGetter || isSetter;
  bool get hasGetter => kind != ProcedureKind.Setter;
  bool get hasSetter => kind == ProcedureKind.Setter;
  bool get isFactory => kind == ProcedureKind.Factory;

  Member get forwardingStubSuperTarget =>
      forwardingStubSuperTargetReference?.asMember;

  void set forwardingStubSuperTarget(Member target) {
    forwardingStubSuperTargetReference = getMemberReference(target);
  }

  Member get forwardingStubInterfaceTarget =>
      forwardingStubInterfaceTargetReference?.asMember;

  void set forwardingStubInterfaceTarget(Member target) {
    forwardingStubInterfaceTargetReference = getMemberReference(target);
  }

  accept(MemberVisitor v) => v.visitProcedure(this);

  acceptReference(MemberReferenceVisitor v) => v.visitProcedureReference(this);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    name?.accept(v);
    function?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    if (function != null) {
      function = function.accept(v);
      function?.parent = this;
    }
  }

  DartType get getterType {
    return isGetter ? function.returnType : function.functionType;
  }

  DartType get setterType {
    return isSetter
        ? function.positionalParameters[0].type
        : const BottomType();
  }

  Location _getLocationInEnclosingFile(int offset) {
    return _getLocationInComponent(enclosingComponent, fileUri, offset);
  }
}

enum ProcedureKind {
  Method,
  Getter,
  Setter,
  Operator,
  Factory,
}

// ------------------------------------------------------------------------
//                     CONSTRUCTOR INITIALIZERS
// ------------------------------------------------------------------------

/// Part of an initializer list in a constructor.
abstract class Initializer extends TreeNode {
  /// True if this is a synthetic constructor initializer.
  @informative
  bool isSynthetic = false;

  accept(InitializerVisitor v);
}

/// An initializer with a compile-time error.
///
/// Should throw an exception at runtime.
//
// DESIGN TODO: The frontend should use this in a lot more cases to catch
// invalid cases.
class InvalidInitializer extends Initializer {
  accept(InitializerVisitor v) => v.visitInvalidInitializer(this);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

/// A field assignment `field = value` occurring in the initializer list of
/// a constructor.
///
/// This node has nothing to do with declaration-site field initializers; those
/// are [Expression]s stored in [Field.initializer].
//
// TODO: The frontend should check that all final fields are initialized
//  exactly once, and that no fields are assigned twice in the initializer list.
class FieldInitializer extends Initializer {
  /// Reference to the field being initialized.  Not null.
  Reference fieldReference;
  Expression value;

  FieldInitializer(Field field, Expression value)
      : this.byReference(field?.reference, value);

  FieldInitializer.byReference(this.fieldReference, this.value) {
    value?.parent = this;
  }

  Field get field => fieldReference?.node;

  void set field(Field field) {
    fieldReference = field?.reference;
  }

  accept(InitializerVisitor v) => v.visitFieldInitializer(this);

  visitChildren(Visitor v) {
    field?.acceptReference(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// A super call `super(x,y)` occurring in the initializer list of a
/// constructor.
///
/// There are no type arguments on this call.
//
// TODO: The frontend should check that there is no more than one super call.
//
// DESIGN TODO: Consider if the frontend should insert type arguments derived
// from the extends clause.
class SuperInitializer extends Initializer {
  /// Reference to the constructor being invoked in the super class. Not null.
  Reference targetReference;
  Arguments arguments;

  SuperInitializer(Constructor target, Arguments arguments)
      : this.byReference(getMemberReference(target), arguments);

  SuperInitializer.byReference(this.targetReference, this.arguments) {
    arguments?.parent = this;
  }

  Constructor get target => targetReference?.asConstructor;

  void set target(Constructor target) {
    targetReference = getMemberReference(target);
  }

  accept(InitializerVisitor v) => v.visitSuperInitializer(this);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }
}

/// A redirecting call `this(x,y)` occurring in the initializer list of
/// a constructor.
//
// TODO: The frontend should check that this is the only initializer and if the
// constructor has a body or if there is a cycle in the initializer calls.
class RedirectingInitializer extends Initializer {
  /// Reference to the constructor being invoked in the same class. Not null.
  Reference targetReference;
  Arguments arguments;

  RedirectingInitializer(Constructor target, Arguments arguments)
      : this.byReference(getMemberReference(target), arguments);

  RedirectingInitializer.byReference(this.targetReference, this.arguments) {
    arguments?.parent = this;
  }

  Constructor get target => targetReference?.asConstructor;

  void set target(Constructor target) {
    targetReference = getMemberReference(target);
  }

  accept(InitializerVisitor v) => v.visitRedirectingInitializer(this);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }
}

/// Binding of a temporary variable in the initializer list of a constructor.
///
/// The variable is in scope for the remainder of the initializer list, but is
/// not in scope in the constructor body.
class LocalInitializer extends Initializer {
  VariableDeclaration variable;

  LocalInitializer(this.variable) {
    variable?.parent = this;
  }

  accept(InitializerVisitor v) => v.visitLocalInitializer(this);

  visitChildren(Visitor v) {
    variable?.accept(v);
  }

  transformChildren(Transformer v) {
    if (variable != null) {
      variable = variable.accept(v);
      variable?.parent = this;
    }
  }
}

class AssertInitializer extends Initializer {
  AssertStatement statement;

  AssertInitializer(this.statement) {
    statement.parent = this;
  }

  accept(InitializerVisitor v) => v.visitAssertInitializer(this);

  visitChildren(Visitor v) {
    statement.accept(v);
  }

  transformChildren(Transformer v) {
    statement = statement.accept(v);
    statement.parent = this;
  }
}

// ------------------------------------------------------------------------
//                            FUNCTIONS
// ------------------------------------------------------------------------

/// A function declares parameters and has a body.
///
/// This may occur in a procedure, constructor, function expression, or local
/// function declaration.
@coq
class FunctionNode extends TreeNode {
  /// End offset in the source file it comes from. Valid values are from 0 and
  /// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
  /// (this is the default if none is specifically set).
  int fileEndOffset = TreeNode.noOffset;

  /// Kernel async marker for the function.
  ///
  /// See also [dartAsyncMarker].
  AsyncMarker asyncMarker;

  /// Dart async marker for the function.
  ///
  /// See also [asyncMarker].
  ///
  /// A Kernel function can represent a Dart function with a different async
  /// marker.
  ///
  /// For example, when async/await is translated away,
  /// a Dart async function might be represented by a Kernel sync function.
  AsyncMarker dartAsyncMarker;

  List<TypeParameter> typeParameters;
  int requiredParameterCount;
  @coqsingledef
  List<VariableDeclaration> positionalParameters;
  @nocoq
  List<VariableDeclaration> namedParameters;
  DartType returnType; // Not null.
  Statement _body;

  void Function() lazyBuilder;

  void _buildLazy() {
    if (lazyBuilder != null) {
      var lazyBuilderLocal = lazyBuilder;
      lazyBuilder = null;
      lazyBuilderLocal();
    }
  }

  Statement get body {
    _buildLazy();
    return _body;
  }

  void set body(Statement body) {
    _buildLazy();
    _body = body;
  }

  FunctionNode(this._body,
      {List<TypeParameter> typeParameters,
      List<VariableDeclaration> positionalParameters,
      List<VariableDeclaration> namedParameters,
      int requiredParameterCount,
      this.returnType: const DynamicType(),
      this.asyncMarker: AsyncMarker.Sync,
      this.dartAsyncMarker})
      : this.positionalParameters =
            positionalParameters ?? <VariableDeclaration>[],
        this.requiredParameterCount =
            requiredParameterCount ?? positionalParameters?.length ?? 0,
        this.namedParameters = namedParameters ?? <VariableDeclaration>[],
        this.typeParameters = typeParameters ?? <TypeParameter>[] {
    assert(returnType != null);
    setParents(this.typeParameters, this);
    setParents(this.positionalParameters, this);
    setParents(this.namedParameters, this);
    _body?.parent = this;
    dartAsyncMarker ??= asyncMarker;
  }

  static DartType _getTypeOfVariable(VariableDeclaration node) => node.type;

  static NamedType _getNamedTypeOfVariable(VariableDeclaration node) {
    return new NamedType(node.name, node.type);
  }

  FunctionType get functionType {
    TreeNode parent = this.parent;
    List<NamedType> named =
        namedParameters.map(_getNamedTypeOfVariable).toList(growable: false);
    named.sort();
    // We need create a copy of the list of type parameters, otherwise
    // transformations like erasure don't work.
    var typeParametersCopy = new List<TypeParameter>.from(parent is Constructor
        ? parent.enclosingClass.typeParameters
        : typeParameters);
    return new FunctionType(
        positionalParameters.map(_getTypeOfVariable).toList(growable: false),
        returnType,
        namedParameters: named,
        typeParameters: typeParametersCopy,
        requiredParameterCount: requiredParameterCount);
  }

  accept(TreeVisitor v) => v.visitFunctionNode(this);

  visitChildren(Visitor v) {
    visitList(typeParameters, v);
    visitList(positionalParameters, v);
    visitList(namedParameters, v);
    returnType?.accept(v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(typeParameters, v, this);
    transformList(positionalParameters, v, this);
    transformList(namedParameters, v, this);
    returnType = v.visitDartType(returnType);
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

enum AsyncMarker {
  // Do not change the order of these, the frontends depend on it.
  Sync,
  SyncStar,
  Async,
  AsyncStar,

  // `SyncYielding` is a marker that tells Dart VM that this function is an
  // artificial closure introduced by an async transformer which desugared all
  // async syntax into a combination of native yields and helper method calls.
  //
  // Native yields (formatted as `[yield]`) are semantically close to
  // `yield x` statement: they denote a yield/resume point within a function
  // but are completely decoupled from the notion of iterators. When
  // execution of the closure reaches `[yield] x` it stops and return the
  // value of `x` to the caller. If closure is called again it continues
  // to the next statement after this yield as if it was suspended and resumed.
  //
  // Consider this example:
  //
  //   g() {
  //     var :await_jump_var = 0;
  //     var :await_ctx_var;
  //
  //     f(x) yielding {
  //       [yield] '${x}:0';
  //       [yield] '${x}:1';
  //       [yield] '${x}:2';
  //     }
  //
  //     return f;
  //   }
  //
  //   print(f('a'));  /* prints 'a:0', :await_jump_var = 1  */
  //   print(f('b'));  /* prints 'b:1', :await_jump_var = 2  */
  //   print(f('c'));  /* prints 'c:2', :await_jump_var = 3  */
  //
  // Note: currently Dart VM implicitly relies on async transformer to
  // inject certain artificial variables into g (like `:await_jump_var`).
  // As such SyncYielding and native yield are not intended to be used on their
  // own, but are rather an implementation artifact of the async transformer
  // itself.
  SyncYielding,
}

// ------------------------------------------------------------------------
//                                EXPRESSIONS
// ------------------------------------------------------------------------

@coq
abstract class Expression extends TreeNode {
  /// Returns the static type of the expression.
  ///
  /// Should only be used on code compiled in strong mode, as this method
  /// assumes the IR is strongly typed.
  DartType getStaticType(TypeEnvironment types);

  /// Returns the static type of the expression as an instantiation of
  /// [superclass].
  ///
  /// Should only be used on code compiled in strong mode, as this method
  /// assumes the IR is strongly typed.
  ///
  /// This method furthermore assumes that the type of the expression actually
  /// is a subtype of (some instantiation of) the given [superclass].
  /// If this is not the case, either an exception is thrown or the raw type of
  /// [superclass] is returned.
  InterfaceType getStaticTypeAsInstanceOf(
      Class superclass, TypeEnvironment types) {
    // This method assumes the program is correctly typed, so if the superclass
    // is not generic, we can just return its raw type without computing the
    // type of this expression.  It also ensures that all types are considered
    // subtypes of Object (not just interface types), and function types are
    // considered subtypes of Function.
    if (superclass.typeParameters.isEmpty) {
      return superclass.rawType;
    }
    var type = getStaticType(types);
    while (type is TypeParameterType) {
      type = (type as TypeParameterType).parameter.bound;
    }
    if (type is InterfaceType) {
      var upcastType = types.hierarchy.getTypeAsInstanceOf(type, superclass);
      if (upcastType != null) return upcastType;
    } else if (type is BottomType) {
      return superclass.bottomType;
    }
    types.typeError(this, '$type is not a subtype of $superclass');
    return superclass.rawType;
  }

  accept(ExpressionVisitor v);
  accept1(ExpressionVisitor1 v, arg);
}

/// An expression containing compile-time errors.
///
/// Should throw a runtime error when evaluated.
class InvalidExpression extends Expression {
  String message;

  InvalidExpression(this.message);

  DartType getStaticType(TypeEnvironment types) => const BottomType();

  accept(ExpressionVisitor v) => v.visitInvalidExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitInvalidExpression(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

/// Read a local variable, a local function, or a function parameter.
@coq
class VariableGet extends Expression {
  VariableDeclaration variable;
  @nocoq
  DartType promotedType; // Null if not promoted.

  VariableGet(this.variable, [this.promotedType]);

  DartType getStaticType(TypeEnvironment types) {
    return promotedType ?? variable.type;
  }

  accept(ExpressionVisitor v) => v.visitVariableGet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitVariableGet(this, arg);

  visitChildren(Visitor v) {
    promotedType?.accept(v);
  }

  transformChildren(Transformer v) {
    if (promotedType != null) {
      promotedType = v.visitDartType(promotedType);
    }
  }
}

/// Assign a local variable or function parameter.
///
/// Evaluates to the value of [value].
class VariableSet extends Expression {
  VariableDeclaration variable;
  Expression value;

  VariableSet(this.variable, this.value) {
    value?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);

  accept(ExpressionVisitor v) => v.visitVariableSet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitVariableSet(this, arg);

  visitChildren(Visitor v) {
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// Expression of form `x.field`.
///
/// This may invoke a getter, read a field, or tear off a method.
@coq
class PropertyGet extends Expression {
  Expression receiver;
  @coq
  Name name;

  @nocoq
  Reference interfaceTargetReference;

  PropertyGet(Expression receiver, Name name, [Member interfaceTarget])
      : this.byReference(receiver, name, getMemberReference(interfaceTarget));

  PropertyGet.byReference(
      this.receiver, this.name, this.interfaceTargetReference) {
    receiver?.parent = this;
  }

  Member get interfaceTarget => interfaceTargetReference?.asMember;

  void set interfaceTarget(Member member) {
    interfaceTargetReference = getMemberReference(member);
  }

  DartType getStaticType(TypeEnvironment types) {
    var interfaceTarget = this.interfaceTarget;
    if (interfaceTarget != null) {
      Class superclass = interfaceTarget.enclosingClass;
      var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
      return Substitution
          .fromInterfaceType(receiverType)
          .substituteType(interfaceTarget.getterType);
    }
    // Treat the properties of Object specially.
    String nameString = name.name;
    if (nameString == 'hashCode') {
      return types.intType;
    } else if (nameString == 'runtimeType') {
      return types.typeType;
    }
    return const DynamicType();
  }

  accept(ExpressionVisitor v) => v.visitPropertyGet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitPropertyGet(this, arg);

  visitChildren(Visitor v) {
    receiver?.accept(v);
    name?.accept(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
  }
}

/// Expression of form `x.field = value`.
///
/// This may invoke a setter or assign a field.
///
/// Evaluates to the value of [value].
class PropertySet extends Expression {
  Expression receiver;
  Name name;
  Expression value;

  Reference interfaceTargetReference;

  PropertySet(Expression receiver, Name name, Expression value,
      [Member interfaceTarget])
      : this.byReference(
            receiver, name, value, getMemberReference(interfaceTarget));

  PropertySet.byReference(
      this.receiver, this.name, this.value, this.interfaceTargetReference) {
    receiver?.parent = this;
    value?.parent = this;
  }

  Member get interfaceTarget => interfaceTargetReference?.asMember;

  void set interfaceTarget(Member member) {
    interfaceTargetReference = getMemberReference(member);
  }

  DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);

  accept(ExpressionVisitor v) => v.visitPropertySet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitPropertySet(this, arg);

  visitChildren(Visitor v) {
    receiver?.accept(v);
    name?.accept(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// Directly read a field, call a getter, or tear off a method.
class DirectPropertyGet extends Expression {
  Expression receiver;
  Reference targetReference;

  DirectPropertyGet(Expression receiver, Member target)
      : this.byReference(receiver, getMemberReference(target));

  DirectPropertyGet.byReference(this.receiver, this.targetReference) {
    receiver?.parent = this;
  }

  Member get target => targetReference?.asMember;

  void set target(Member target) {
    targetReference = getMemberReference(target);
  }

  visitChildren(Visitor v) {
    receiver?.accept(v);
    target?.acceptReference(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
  }

  accept(ExpressionVisitor v) => v.visitDirectPropertyGet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertyGet(this, arg);

  DartType getStaticType(TypeEnvironment types) {
    Class superclass = target.enclosingClass;
    var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
    return Substitution
        .fromInterfaceType(receiverType)
        .substituteType(target.getterType);
  }
}

/// Directly assign a field, or call a setter.
///
/// Evaluates to the value of [value].
class DirectPropertySet extends Expression {
  Expression receiver;
  Reference targetReference;
  Expression value;

  DirectPropertySet(Expression receiver, Member target, Expression value)
      : this.byReference(receiver, getMemberReference(target), value);

  DirectPropertySet.byReference(
      this.receiver, this.targetReference, this.value) {
    receiver?.parent = this;
    value?.parent = this;
  }

  Member get target => targetReference?.asMember;

  void set target(Member target) {
    targetReference = getMemberReference(target);
  }

  visitChildren(Visitor v) {
    receiver?.accept(v);
    target?.acceptReference(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }

  accept(ExpressionVisitor v) => v.visitDirectPropertySet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitDirectPropertySet(this, arg);

  DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);
}

/// Directly call an instance method, bypassing ordinary dispatch.
class DirectMethodInvocation extends InvocationExpression {
  Expression receiver;
  Reference targetReference;
  Arguments arguments;

  DirectMethodInvocation(
      Expression receiver, Procedure target, Arguments arguments)
      : this.byReference(receiver, getMemberReference(target), arguments);

  DirectMethodInvocation.byReference(
      this.receiver, this.targetReference, this.arguments) {
    receiver?.parent = this;
    arguments?.parent = this;
  }

  Procedure get target => targetReference?.asProcedure;

  void set target(Procedure target) {
    targetReference = getMemberReference(target);
  }

  Name get name => target?.name;

  visitChildren(Visitor v) {
    receiver?.accept(v);
    target?.acceptReference(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }

  accept(ExpressionVisitor v) => v.visitDirectMethodInvocation(this);
  accept1(ExpressionVisitor1 v, arg) =>
      v.visitDirectMethodInvocation(this, arg);

  DartType getStaticType(TypeEnvironment types) {
    if (types.isOverloadedArithmeticOperator(target)) {
      return types.getTypeOfOverloadedArithmetic(receiver.getStaticType(types),
          arguments.positional[0].getStaticType(types));
    }
    Class superclass = target.enclosingClass;
    var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
    var returnType = Substitution
        .fromInterfaceType(receiverType)
        .substituteType(target.function.returnType);
    return Substitution
        .fromPairs(target.function.typeParameters, arguments.types)
        .substituteType(returnType);
  }
}

/// Expression of form `super.field`.
///
/// This may invoke a getter, read a field, or tear off a method.
class SuperPropertyGet extends Expression {
  Name name;

  Reference interfaceTargetReference;

  SuperPropertyGet(Name name, [Member interfaceTarget])
      : this.byReference(name, getMemberReference(interfaceTarget));

  SuperPropertyGet.byReference(this.name, this.interfaceTargetReference);

  Member get interfaceTarget => interfaceTargetReference?.asMember;

  void set interfaceTarget(Member member) {
    interfaceTargetReference = getMemberReference(member);
  }

  DartType getStaticType(TypeEnvironment types) {
    Class declaringClass = interfaceTarget.enclosingClass;
    if (declaringClass.typeParameters.isEmpty) {
      return interfaceTarget.getterType;
    }
    var receiver =
        types.hierarchy.getTypeAsInstanceOf(types.thisType, declaringClass);
    return Substitution
        .fromInterfaceType(receiver)
        .substituteType(interfaceTarget.getterType);
  }

  accept(ExpressionVisitor v) => v.visitSuperPropertyGet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertyGet(this, arg);

  visitChildren(Visitor v) {
    name?.accept(v);
  }

  transformChildren(Transformer v) {}
}

/// Expression of form `super.field = value`.
///
/// This may invoke a setter or assign a field.
///
/// Evaluates to the value of [value].
class SuperPropertySet extends Expression {
  Name name;
  Expression value;

  Reference interfaceTargetReference;

  SuperPropertySet(Name name, Expression value, Member interfaceTarget)
      : this.byReference(name, value, getMemberReference(interfaceTarget));

  SuperPropertySet.byReference(
      this.name, this.value, this.interfaceTargetReference) {
    value?.parent = this;
  }

  Member get interfaceTarget => interfaceTargetReference?.asMember;

  void set interfaceTarget(Member member) {
    interfaceTargetReference = getMemberReference(member);
  }

  DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);

  accept(ExpressionVisitor v) => v.visitSuperPropertySet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitSuperPropertySet(this, arg);

  visitChildren(Visitor v) {
    name?.accept(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// Read a static field, call a static getter, or tear off a static method.
class StaticGet extends Expression {
  /// A static field, getter, or method (for tear-off).
  Reference targetReference;

  StaticGet(Member target) : this.byReference(getMemberReference(target));

  StaticGet.byReference(this.targetReference);

  Member get target => targetReference?.asMember;

  void set target(Member target) {
    targetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) => target.getterType;

  accept(ExpressionVisitor v) => v.visitStaticGet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitStaticGet(this, arg);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
  }

  transformChildren(Transformer v) {}
}

/// Assign a static field or call a static setter.
///
/// Evaluates to the value of [value].
class StaticSet extends Expression {
  /// A mutable static field or a static setter.
  Reference targetReference;
  Expression value;

  StaticSet(Member target, Expression value)
      : this.byReference(getMemberReference(target), value);

  StaticSet.byReference(this.targetReference, this.value) {
    value?.parent = this;
  }

  Member get target => targetReference?.asMember;

  void set target(Member target) {
    targetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) => value.getStaticType(types);

  accept(ExpressionVisitor v) => v.visitStaticSet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitStaticSet(this, arg);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// The arguments to a function call, divided into type arguments,
/// positional arguments, and named arguments.
@coq
class Arguments extends TreeNode {
  @nocoq
  final List<DartType> types;
  @coqsingle
  final List<Expression> positional;
  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>[];

  factory Arguments.forwarded(FunctionNode function) {
    return new Arguments(
        function.positionalParameters.map((p) => new VariableGet(p)).toList(),
        named: function.namedParameters
            .map((p) => new NamedExpression(p.name, new VariableGet(p)))
            .toList(),
        types: function.typeParameters
            .map((p) => new TypeParameterType(p))
            .toList());
  }

  accept(TreeVisitor v) => v.visitArguments(this);

  visitChildren(Visitor v) {
    visitList(types, v);
    visitList(positional, v);
    visitList(named, v);
  }

  transformChildren(Transformer v) {
    transformTypeList(types, v);
    transformList(positional, v, this);
    transformList(named, v, this);
  }
}

/// A named argument, `name: value`.
class NamedExpression extends TreeNode {
  String name;
  Expression value;

  NamedExpression(this.name, this.value) {
    value?.parent = this;
  }

  accept(TreeVisitor v) => v.visitNamedExpression(this);

  visitChildren(Visitor v) {
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// Common super class for [DirectMethodInvocation], [MethodInvocation],
/// [SuperMethodInvocation], [StaticInvocation], and [ConstructorInvocation].
@coq
abstract class InvocationExpression extends Expression {
  Arguments get arguments;
  set arguments(Arguments value);

  /// Name of the invoked method.
  ///
  /// May be `null` if the target is a synthetic static member without a name.
  Name get name;
}

/// Expression of form `x.foo(y)`.
@coq
class MethodInvocation extends InvocationExpression {
  Expression receiver;
  Name name;
  Arguments arguments;

  Reference interfaceTargetReference;

  MethodInvocation(Expression receiver, Name name, Arguments arguments,
      [Member interfaceTarget])
      : this.byReference(
            receiver, name, arguments, getMemberReference(interfaceTarget));

  MethodInvocation.byReference(
      this.receiver, this.name, this.arguments, this.interfaceTargetReference) {
    receiver?.parent = this;
    arguments?.parent = this;
  }

  Member get interfaceTarget => interfaceTargetReference?.asMember;

  void set interfaceTarget(Member target) {
    interfaceTargetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) {
    var interfaceTarget = this.interfaceTarget;
    if (interfaceTarget != null) {
      if (interfaceTarget is Procedure &&
          types.isOverloadedArithmeticOperator(interfaceTarget)) {
        return types.getTypeOfOverloadedArithmetic(
            receiver.getStaticType(types),
            arguments.positional[0].getStaticType(types));
      }
      Class superclass = interfaceTarget.enclosingClass;
      var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, types);
      var getterType = Substitution
          .fromInterfaceType(receiverType)
          .substituteType(interfaceTarget.getterType);
      if (getterType is FunctionType) {
        return Substitution
            .fromPairs(getterType.typeParameters, arguments.types)
            .substituteType(getterType.returnType);
      } else {
        return const DynamicType();
      }
    }
    if (name.name == 'call') {
      var receiverType = receiver.getStaticType(types);
      if (receiverType is FunctionType) {
        if (receiverType.typeParameters.length != arguments.types.length) {
          return const BottomType();
        }
        return Substitution
            .fromPairs(receiverType.typeParameters, arguments.types)
            .substituteType(receiverType.returnType);
      }
    }
    if (name.name == '==') {
      // We use this special case to simplify generation of '==' checks.
      return types.boolType;
    }
    return const DynamicType();
  }

  accept(ExpressionVisitor v) => v.visitMethodInvocation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitMethodInvocation(this, arg);

  visitChildren(Visitor v) {
    receiver?.accept(v);
    name?.accept(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (receiver != null) {
      receiver = receiver.accept(v);
      receiver?.parent = this;
    }
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }
}

/// Expression of form `super.foo(x)`.
///
/// The provided arguments might not match the parameters of the target.
class SuperMethodInvocation extends InvocationExpression {
  Name name;
  Arguments arguments;

  Reference interfaceTargetReference;

  SuperMethodInvocation(Name name, Arguments arguments,
      [Procedure interfaceTarget])
      : this.byReference(name, arguments, getMemberReference(interfaceTarget));

  SuperMethodInvocation.byReference(
      this.name, this.arguments, this.interfaceTargetReference) {
    arguments?.parent = this;
  }

  Procedure get interfaceTarget => interfaceTargetReference?.asProcedure;

  void set interfaceTarget(Procedure target) {
    interfaceTargetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) {
    if (interfaceTarget == null) return const DynamicType();
    Class superclass = interfaceTarget.enclosingClass;
    var receiverType =
        types.hierarchy.getTypeAsInstanceOf(types.thisType, superclass);
    var returnType = Substitution
        .fromInterfaceType(receiverType)
        .substituteType(interfaceTarget.function.returnType);
    return Substitution
        .fromPairs(interfaceTarget.function.typeParameters, arguments.types)
        .substituteType(returnType);
  }

  accept(ExpressionVisitor v) => v.visitSuperMethodInvocation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitSuperMethodInvocation(this, arg);

  visitChildren(Visitor v) {
    name?.accept(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }
}

/// Expression of form `foo(x)`, or `const foo(x)` if the target is an
/// external constant factory.
///
/// The provided arguments might not match the parameters of the target.
class StaticInvocation extends InvocationExpression {
  Reference targetReference;
  Arguments arguments;

  /// True if this is a constant call to an external constant factory.
  bool isConst;

  Name get name => target?.name;

  StaticInvocation(Procedure target, Arguments arguments, {bool isConst: false})
      : this.byReference(getMemberReference(target), arguments,
            isConst: isConst);

  StaticInvocation.byReference(this.targetReference, this.arguments,
      {this.isConst: false}) {
    arguments?.parent = this;
  }

  Procedure get target => targetReference?.asProcedure;

  void set target(Procedure target) {
    targetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) {
    return Substitution
        .fromPairs(target.function.typeParameters, arguments.types)
        .substituteType(target.function.returnType);
  }

  accept(ExpressionVisitor v) => v.visitStaticInvocation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitStaticInvocation(this, arg);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }
}

/// Expression of form `new Foo(x)` or `const Foo(x)`.
///
/// The provided arguments might not match the parameters of the target.
//
// DESIGN TODO: Should we pass type arguments in a separate field
// `classTypeArguments`? They are quite different from type arguments to
// generic functions.
@coq
class ConstructorInvocation extends InvocationExpression {
  Reference targetReference;
  @nocoq
  Arguments arguments;
  bool isConst;

  Name get name => target?.name;

  ConstructorInvocation(Constructor target, Arguments arguments,
      {bool isConst: false})
      : this.byReference(getMemberReference(target), arguments,
            isConst: isConst);

  ConstructorInvocation.byReference(this.targetReference, this.arguments,
      {this.isConst: false}) {
    arguments?.parent = this;
  }

  Constructor get target => targetReference?.asConstructor;

  void set target(Constructor target) {
    targetReference = getMemberReference(target);
  }

  DartType getStaticType(TypeEnvironment types) {
    return arguments.types.isEmpty
        ? target.enclosingClass.rawType
        : new InterfaceType(target.enclosingClass, arguments.types);
  }

  accept(ExpressionVisitor v) => v.visitConstructorInvocation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitConstructorInvocation(this, arg);

  visitChildren(Visitor v) {
    target?.acceptReference(v);
    arguments?.accept(v);
  }

  transformChildren(Transformer v) {
    if (arguments != null) {
      arguments = arguments.accept(v);
      arguments?.parent = this;
    }
  }

  InterfaceType get constructedType {
    return arguments.types.isEmpty
        ? target.enclosingClass.rawType
        : new InterfaceType(target.enclosingClass, arguments.types);
  }
}

/// An explicit type instantiation of a generic function.
class Instantiation extends Expression {
  Expression expression;
  final List<DartType> typeArguments;

  Instantiation(this.expression, this.typeArguments) {
    expression?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) {
    FunctionType type = expression.getStaticType(types);
    return Substitution
        .fromPairs(type.typeParameters, typeArguments)
        .substituteType(type.withoutTypeParameters);
  }

  accept(ExpressionVisitor v) => v.visitInstantiation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitInstantiation(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
    visitList(typeArguments, v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
    transformTypeList(typeArguments, v);
  }
}

/// Expression of form `!x`.
///
/// The `is!` and `!=` operators are desugared into [Not] nodes with `is` and
/// `==` expressions inside, respectively.
class Not extends Expression {
  Expression operand;

  Not(this.operand) {
    operand?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => types.boolType;

  accept(ExpressionVisitor v) => v.visitNot(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitNot(this, arg);

  visitChildren(Visitor v) {
    operand?.accept(v);
  }

  transformChildren(Transformer v) {
    if (operand != null) {
      operand = operand.accept(v);
      operand?.parent = this;
    }
  }
}

/// Expression of form `x && y` or `x || y`
class LogicalExpression extends Expression {
  Expression left;
  String operator; // && or || or ??
  Expression right;

  LogicalExpression(this.left, this.operator, this.right) {
    left?.parent = this;
    right?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => types.boolType;

  accept(ExpressionVisitor v) => v.visitLogicalExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitLogicalExpression(this, arg);

  visitChildren(Visitor v) {
    left?.accept(v);
    right?.accept(v);
  }

  transformChildren(Transformer v) {
    if (left != null) {
      left = left.accept(v);
      left?.parent = this;
    }
    if (right != null) {
      right = right.accept(v);
      right?.parent = this;
    }
  }
}

/// Expression of form `x ? y : z`.
class ConditionalExpression extends Expression {
  Expression condition;
  Expression then;
  Expression otherwise;

  /// The static type of the expression. Should not be `null`.
  DartType staticType;

  ConditionalExpression(
      this.condition, this.then, this.otherwise, this.staticType) {
    condition?.parent = this;
    then?.parent = this;
    otherwise?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => staticType;

  accept(ExpressionVisitor v) => v.visitConditionalExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitConditionalExpression(this, arg);

  visitChildren(Visitor v) {
    condition?.accept(v);
    then?.accept(v);
    otherwise?.accept(v);
    staticType?.accept(v);
  }

  transformChildren(Transformer v) {
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
    if (then != null) {
      then = then.accept(v);
      then?.parent = this;
    }
    if (otherwise != null) {
      otherwise = otherwise.accept(v);
      otherwise?.parent = this;
    }
    if (staticType != null) {
      staticType = v.visitDartType(staticType);
    }
  }
}

/// Convert expressions to strings and concatenate them.  Semantically, calls
/// `toString` on every argument, checks that a string is returned, and returns
/// the concatenation of all the strings.
///
/// If [expressions] is empty then an empty string is returned.
///
/// These arise from string interpolations and adjacent string literals.
class StringConcatenation extends Expression {
  final List<Expression> expressions;

  StringConcatenation(this.expressions) {
    setParents(expressions, this);
  }

  DartType getStaticType(TypeEnvironment types) => types.stringType;

  accept(ExpressionVisitor v) => v.visitStringConcatenation(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitStringConcatenation(this, arg);

  visitChildren(Visitor v) {
    visitList(expressions, v);
  }

  transformChildren(Transformer v) {
    transformList(expressions, v, this);
  }
}

/// Expression of form `x is T`.
class IsExpression extends Expression {
  Expression operand;
  DartType type;

  IsExpression(this.operand, this.type) {
    operand?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => types.boolType;

  accept(ExpressionVisitor v) => v.visitIsExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitIsExpression(this, arg);

  visitChildren(Visitor v) {
    operand?.accept(v);
    type?.accept(v);
  }

  transformChildren(Transformer v) {
    if (operand != null) {
      operand = operand.accept(v);
      operand?.parent = this;
    }
    type = v.visitDartType(type);
  }
}

/// Expression of form `x as T`.
class AsExpression extends Expression {
  int flags = 0;
  Expression operand;
  DartType type;

  AsExpression(this.operand, this.type) {
    operand?.parent = this;
  }

  // Must match serialized bit positions.
  static const int FlagTypeError = 1 << 0;

  /// Indicates the type of error that should be thrown if the check fails.
  ///
  /// `true` means that a TypeError should be thrown.  `false` means that a
  /// CastError should be thrown.
  bool get isTypeError => flags & FlagTypeError != 0;

  void set isTypeError(bool value) {
    flags = value ? (flags | FlagTypeError) : (flags & ~FlagTypeError);
  }

  DartType getStaticType(TypeEnvironment types) => type;

  accept(ExpressionVisitor v) => v.visitAsExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitAsExpression(this, arg);

  visitChildren(Visitor v) {
    operand?.accept(v);
    type?.accept(v);
  }

  transformChildren(Transformer v) {
    if (operand != null) {
      operand = operand.accept(v);
      operand?.parent = this;
    }
    type = v.visitDartType(type);
  }
}

/// An integer, double, boolean, string, or null constant.
abstract class BasicLiteral extends Expression {
  Object get value;

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class StringLiteral extends BasicLiteral {
  String value;

  StringLiteral(this.value);

  DartType getStaticType(TypeEnvironment types) => types.stringType;

  accept(ExpressionVisitor v) => v.visitStringLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitStringLiteral(this, arg);
}

class IntLiteral extends BasicLiteral {
  /// Note that this value holds a uint64 value.
  /// E.g. "0x8000000000000000" will be saved as "-9223372036854775808" despite
  /// technically (on some platforms, particularly Javascript) being positive.
  /// If the number is meant to be negative it will be wrapped in a "unary-".
  int value;

  IntLiteral(this.value);

  DartType getStaticType(TypeEnvironment types) => types.intType;

  accept(ExpressionVisitor v) => v.visitIntLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitIntLiteral(this, arg);
}

class DoubleLiteral extends BasicLiteral {
  double value;

  DoubleLiteral(this.value);

  DartType getStaticType(TypeEnvironment types) => types.doubleType;

  accept(ExpressionVisitor v) => v.visitDoubleLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitDoubleLiteral(this, arg);
}

class BoolLiteral extends BasicLiteral {
  bool value;

  BoolLiteral(this.value);

  DartType getStaticType(TypeEnvironment types) => types.boolType;

  accept(ExpressionVisitor v) => v.visitBoolLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitBoolLiteral(this, arg);
}

class NullLiteral extends BasicLiteral {
  Object get value => null;

  DartType getStaticType(TypeEnvironment types) => const BottomType();

  accept(ExpressionVisitor v) => v.visitNullLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitNullLiteral(this, arg);
}

class SymbolLiteral extends Expression {
  String value; // Everything strictly after the '#'.

  SymbolLiteral(this.value);

  DartType getStaticType(TypeEnvironment types) => types.symbolType;

  accept(ExpressionVisitor v) => v.visitSymbolLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitSymbolLiteral(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class TypeLiteral extends Expression {
  DartType type;

  TypeLiteral(this.type);

  DartType getStaticType(TypeEnvironment types) => types.typeType;

  accept(ExpressionVisitor v) => v.visitTypeLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitTypeLiteral(this, arg);

  visitChildren(Visitor v) {
    type?.accept(v);
  }

  transformChildren(Transformer v) {
    type = v.visitDartType(type);
  }
}

class ThisExpression extends Expression {
  DartType getStaticType(TypeEnvironment types) => types.thisType;

  accept(ExpressionVisitor v) => v.visitThisExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitThisExpression(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class Rethrow extends Expression {
  DartType getStaticType(TypeEnvironment types) => const BottomType();

  accept(ExpressionVisitor v) => v.visitRethrow(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitRethrow(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class Throw extends Expression {
  Expression expression;

  Throw(this.expression) {
    expression?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => const BottomType();

  accept(ExpressionVisitor v) => v.visitThrow(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitThrow(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
  }
}

class ListLiteral extends Expression {
  bool isConst;
  DartType typeArgument; // Not null, defaults to DynamicType.
  final List<Expression> expressions;

  ListLiteral(this.expressions,
      {this.typeArgument: const DynamicType(), this.isConst: false}) {
    assert(typeArgument != null);
    setParents(expressions, this);
  }

  DartType getStaticType(TypeEnvironment types) {
    return types.literalListType(typeArgument);
  }

  accept(ExpressionVisitor v) => v.visitListLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitListLiteral(this, arg);

  visitChildren(Visitor v) {
    typeArgument?.accept(v);
    visitList(expressions, v);
  }

  transformChildren(Transformer v) {
    typeArgument = v.visitDartType(typeArgument);
    transformList(expressions, v, this);
  }
}

class MapLiteral extends Expression {
  bool isConst;
  DartType keyType; // Not null, defaults to DynamicType.
  DartType valueType; // Not null, defaults to DynamicType.
  final List<MapEntry> entries;

  MapLiteral(this.entries,
      {this.keyType: const DynamicType(),
      this.valueType: const DynamicType(),
      this.isConst: false}) {
    assert(keyType != null);
    assert(valueType != null);
    setParents(entries, this);
  }

  DartType getStaticType(TypeEnvironment types) {
    return types.literalMapType(keyType, valueType);
  }

  accept(ExpressionVisitor v) => v.visitMapLiteral(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitMapLiteral(this, arg);

  visitChildren(Visitor v) {
    keyType?.accept(v);
    valueType?.accept(v);
    visitList(entries, v);
  }

  transformChildren(Transformer v) {
    keyType = v.visitDartType(keyType);
    valueType = v.visitDartType(valueType);
    transformList(entries, v, this);
  }
}

class MapEntry extends TreeNode {
  Expression key;
  Expression value;

  MapEntry(this.key, this.value) {
    key?.parent = this;
    value?.parent = this;
  }

  accept(TreeVisitor v) => v.visitMapEntry(this);

  visitChildren(Visitor v) {
    key?.accept(v);
    value?.accept(v);
  }

  transformChildren(Transformer v) {
    if (key != null) {
      key = key.accept(v);
      key?.parent = this;
    }
    if (value != null) {
      value = value.accept(v);
      value?.parent = this;
    }
  }
}

/// Expression of form `await x`.
class AwaitExpression extends Expression {
  Expression operand;

  AwaitExpression(this.operand) {
    operand?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) {
    return types.unfutureType(operand.getStaticType(types));
  }

  accept(ExpressionVisitor v) => v.visitAwaitExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitAwaitExpression(this, arg);

  visitChildren(Visitor v) {
    operand?.accept(v);
  }

  transformChildren(Transformer v) {
    if (operand != null) {
      operand = operand.accept(v);
      operand?.parent = this;
    }
  }
}

/// Expression of form `(x,y) => ...` or `(x,y) { ... }`
///
/// The arrow-body form `=> e` is desugared into `return e;`.
class FunctionExpression extends Expression {
  FunctionNode function;

  FunctionExpression(this.function) {
    function?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => function.functionType;

  accept(ExpressionVisitor v) => v.visitFunctionExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitFunctionExpression(this, arg);

  visitChildren(Visitor v) {
    function?.accept(v);
  }

  transformChildren(Transformer v) {
    if (function != null) {
      function = function.accept(v);
      function?.parent = this;
    }
  }
}

class ConstantExpression extends Expression {
  Constant constant;

  ConstantExpression(this.constant) {
    assert(constant != null);
  }

  DartType getStaticType(TypeEnvironment types) => constant.getType(types);

  accept(ExpressionVisitor v) => v.visitConstantExpression(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitConstantExpression(this, arg);

  visitChildren(Visitor v) {
    constant?.acceptReference(v);
  }

  transformChildren(Transformer v) {
    constant = v.visitConstant(constant);
  }
}

/// Synthetic expression of form `let v = x in y`
class Let extends Expression {
  VariableDeclaration variable; // Must have an initializer.
  Expression body;

  Let(this.variable, this.body) {
    variable?.parent = this;
    body?.parent = this;
  }

  DartType getStaticType(TypeEnvironment types) => body.getStaticType(types);

  accept(ExpressionVisitor v) => v.visitLet(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitLet(this, arg);

  visitChildren(Visitor v) {
    variable?.accept(v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    if (variable != null) {
      variable = variable.accept(v);
      variable?.parent = this;
    }
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

/// Attempt to load the library referred to by a deferred import.
///
/// This instruction is concerned with:
/// - keeping track whether the deferred import is marked as 'loaded'
/// - keeping track of whether the library code has already been downloaded
/// - actually downloading and linking the library
///
/// Should return a future.  The value in this future will be the same value
/// seen by callers of `loadLibrary` functions.
///
/// On backends that link the entire program eagerly, this instruction needs
/// to mark the deferred import as 'loaded' and return a future.
class LoadLibrary extends Expression {
  /// Reference to a deferred import in the enclosing library.
  LibraryDependency import;

  LoadLibrary(this.import);

  DartType getStaticType(TypeEnvironment types) {
    return types.futureType(const DynamicType());
  }

  accept(ExpressionVisitor v) => v.visitLoadLibrary(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitLoadLibrary(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

/// Checks that the given deferred import has been marked as 'loaded'.
class CheckLibraryIsLoaded extends Expression {
  /// Reference to a deferred import in the enclosing library.
  LibraryDependency import;

  CheckLibraryIsLoaded(this.import);

  DartType getStaticType(TypeEnvironment types) {
    return types.objectType;
  }

  accept(ExpressionVisitor v) => v.visitCheckLibraryIsLoaded(this);
  accept1(ExpressionVisitor1 v, arg) => v.visitCheckLibraryIsLoaded(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

/// 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<T>(f, c, t)` where `f` is a name of a
/// closed top-level function, `c` is a Vector representing closure context, `t`
/// is the type of the resulting closure and `T` is a vector of type arguments
/// to be passed to `f`.
///
/// Note these restrictions on its usage:
///
///   1. `f` must reference a statically-resolved top-level function.
///
///   2. The length of `T` must be less than or equal to the number of type
///      parameters on `f`.
///
///   3. It is disallowed to use `MakeClosure` on the same function twice with
///      different numbers of type arguments.
///
///   4. The type arguments `T` must be guaranteed to satisfy the bounds of the
///      corresponding type parameters on `f`.
class ClosureCreation extends Expression {
  Reference topLevelFunctionReference;
  Expression contextVector;
  FunctionType functionType;
  List<DartType> typeArguments;

  ClosureCreation(Member topLevelFunction, Expression contextVector,
      FunctionType functionType, List<DartType> typeArguments)
      : this.byReference(getMemberReference(topLevelFunction), contextVector,
            functionType, typeArguments);

  ClosureCreation.byReference(this.topLevelFunctionReference,
      this.contextVector, this.functionType, this.typeArguments) {
    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);
    functionType.accept(v);
    visitList(typeArguments, v);
  }

  transformChildren(Transformer v) {
    if (contextVector != null) {
      contextVector = contextVector.accept(v);
      contextVector?.parent = this;
    }
    functionType = v.visitDartType(functionType);
    transformTypeList(typeArguments, v);
  }

  DartType getStaticType(TypeEnvironment types) {
    return functionType;
  }
}

// ------------------------------------------------------------------------
//                              STATEMENTS
// ------------------------------------------------------------------------

@coq
abstract class Statement extends TreeNode {
  accept(StatementVisitor v);
  accept1(StatementVisitor1 v, arg);
}

@coq
class ExpressionStatement extends Statement {
  Expression expression;

  ExpressionStatement(this.expression) {
    expression?.parent = this;
  }

  accept(StatementVisitor v) => v.visitExpressionStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitExpressionStatement(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
  }
}

@coq
class Block extends Statement {
  final List<Statement> statements;

  Block(this.statements) {
    // Ensure statements is mutable.
    assert((statements
          ..add(null)
          ..removeLast()) !=
        null);
    setParents(statements, this);
  }

  accept(StatementVisitor v) => v.visitBlock(this);
  accept1(StatementVisitor1 v, arg) => v.visitBlock(this, arg);

  visitChildren(Visitor v) {
    visitList(statements, v);
  }

  transformChildren(Transformer v) {
    transformList(statements, v, this);
  }

  void addStatement(Statement node) {
    statements.add(node);
    node.parent = this;
  }
}

/// A block that is only executed when asserts are enabled.
///
/// Sometimes arbitrary statements must be guarded by whether asserts are
/// enabled.  For example, when a subexpression of an assert in async code is
/// linearized and named, it can produce such a block of statements.
class AssertBlock extends Statement {
  final List<Statement> statements;

  AssertBlock(this.statements) {
    // Ensure statements is mutable.
    assert((statements
          ..add(null)
          ..removeLast()) !=
        null);
    setParents(statements, this);
  }

  accept(StatementVisitor v) => v.visitAssertBlock(this);
  accept1(StatementVisitor1 v, arg) => v.visitAssertBlock(this, arg);

  transformChildren(Transformer v) {
    transformList(statements, v, this);
  }

  visitChildren(Visitor v) {
    visitList(statements, v);
  }

  void addStatement(Statement node) {
    statements.add(node);
    node.parent = this;
  }
}

class EmptyStatement extends Statement {
  accept(StatementVisitor v) => v.visitEmptyStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitEmptyStatement(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class AssertStatement extends Statement {
  Expression condition;
  Expression message; // May be null.
  int conditionStartOffset;
  int conditionEndOffset;

  AssertStatement(this.condition,
      {this.message, this.conditionStartOffset, this.conditionEndOffset}) {
    condition?.parent = this;
    message?.parent = this;
  }

  accept(StatementVisitor v) => v.visitAssertStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitAssertStatement(this, arg);

  visitChildren(Visitor v) {
    condition?.accept(v);
    message?.accept(v);
  }

  transformChildren(Transformer v) {
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
    if (message != null) {
      message = message.accept(v);
      message?.parent = this;
    }
  }
}

/// A target of a [Break] statement.
///
/// The label itself has no name; breaks reference the statement directly.
///
/// The frontend does not generate labeled statements without uses.
class LabeledStatement extends Statement {
  Statement body;

  LabeledStatement(this.body) {
    body?.parent = this;
  }

  accept(StatementVisitor v) => v.visitLabeledStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitLabeledStatement(this, arg);

  visitChildren(Visitor v) {
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

/// Breaks out of an enclosing [LabeledStatement].
///
/// Both `break` and loop `continue` statements are translated into this node.
///
/// For example, the following loop with a `continue` will be desugared:
///
///     while(x) {
///       if (y) continue;
///       BODY'
///     }
///
///     ==>
///
///     while(x) {
///       L: {
///         if (y) break L;
///         BODY'
///       }
///     }
//
class BreakStatement extends Statement {
  LabeledStatement target;

  BreakStatement(this.target);

  accept(StatementVisitor v) => v.visitBreakStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitBreakStatement(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class WhileStatement extends Statement {
  Expression condition;
  Statement body;

  WhileStatement(this.condition, this.body) {
    condition?.parent = this;
    body?.parent = this;
  }

  accept(StatementVisitor v) => v.visitWhileStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitWhileStatement(this, arg);

  visitChildren(Visitor v) {
    condition?.accept(v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

class DoStatement extends Statement {
  Statement body;
  Expression condition;

  DoStatement(this.body, this.condition) {
    body?.parent = this;
    condition?.parent = this;
  }

  accept(StatementVisitor v) => v.visitDoStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitDoStatement(this, arg);

  visitChildren(Visitor v) {
    body?.accept(v);
    condition?.accept(v);
  }

  transformChildren(Transformer v) {
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
  }
}

class ForStatement extends Statement {
  final List<VariableDeclaration> variables; // May be empty, but not null.
  Expression condition; // May be null.
  final List<Expression> updates; // May be empty, but not null.
  Statement body;

  ForStatement(this.variables, this.condition, this.updates, this.body) {
    setParents(variables, this);
    condition?.parent = this;
    setParents(updates, this);
    body?.parent = this;
  }

  accept(StatementVisitor v) => v.visitForStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitForStatement(this, arg);

  visitChildren(Visitor v) {
    visitList(variables, v);
    condition?.accept(v);
    visitList(updates, v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(variables, v, this);
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
    transformList(updates, v, this);
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

class ForInStatement extends Statement {
  /// Offset in the source file it comes from.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset]) if the file
  /// offset is not available (this is the default if none is specifically set).
  int bodyOffset = TreeNode.noOffset;

  VariableDeclaration variable; // Has no initializer.
  Expression iterable;
  Statement body;
  bool isAsync; // True if this is an 'await for' loop.

  ForInStatement(this.variable, this.iterable, this.body,
      {this.isAsync: false}) {
    variable?.parent = this;
    iterable?.parent = this;
    body?.parent = this;
  }

  accept(StatementVisitor v) => v.visitForInStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitForInStatement(this, arg);

  visitChildren(Visitor v) {
    variable?.accept(v);
    iterable?.accept(v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    if (variable != null) {
      variable = variable.accept(v);
      variable?.parent = this;
    }
    if (iterable != null) {
      iterable = iterable.accept(v);
      iterable?.parent = this;
    }
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

/// Statement of form `switch (e) { case x: ... }`.
///
/// Adjacent case clauses have been merged into a single [SwitchCase]. A runtime
/// exception must be thrown if one [SwitchCase] falls through to another case.
class SwitchStatement extends Statement {
  Expression expression;
  final List<SwitchCase> cases;

  SwitchStatement(this.expression, this.cases) {
    expression?.parent = this;
    setParents(cases, this);
  }

  accept(StatementVisitor v) => v.visitSwitchStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitSwitchStatement(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
    visitList(cases, v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
    transformList(cases, v, this);
  }
}

/// A group of `case` clauses and/or a `default` clause.
///
/// This is a potential target of [ContinueSwitchStatement].
class SwitchCase extends TreeNode {
  final List<Expression> expressions;
  final List<int> expressionOffsets;
  Statement body;
  bool isDefault;

  SwitchCase(this.expressions, this.expressionOffsets, this.body,
      {this.isDefault: false}) {
    setParents(expressions, this);
    body?.parent = this;
  }

  SwitchCase.defaultCase(this.body)
      : isDefault = true,
        expressions = <Expression>[],
        expressionOffsets = <int>[] {
    body?.parent = this;
  }

  SwitchCase.empty()
      : expressions = <Expression>[],
        expressionOffsets = <int>[],
        body = null,
        isDefault = false;

  accept(TreeVisitor v) => v.visitSwitchCase(this);

  visitChildren(Visitor v) {
    visitList(expressions, v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(expressions, v, this);
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

/// Jump to a case in an enclosing switch.
class ContinueSwitchStatement extends Statement {
  SwitchCase target;

  ContinueSwitchStatement(this.target);

  accept(StatementVisitor v) => v.visitContinueSwitchStatement(this);
  accept1(StatementVisitor1 v, arg) =>
      v.visitContinueSwitchStatement(this, arg);

  visitChildren(Visitor v) {}
  transformChildren(Transformer v) {}
}

class IfStatement extends Statement {
  Expression condition;
  Statement then;
  Statement otherwise;

  IfStatement(this.condition, this.then, this.otherwise) {
    condition?.parent = this;
    then?.parent = this;
    otherwise?.parent = this;
  }

  accept(StatementVisitor v) => v.visitIfStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitIfStatement(this, arg);

  visitChildren(Visitor v) {
    condition?.accept(v);
    then?.accept(v);
    otherwise?.accept(v);
  }

  transformChildren(Transformer v) {
    if (condition != null) {
      condition = condition.accept(v);
      condition?.parent = this;
    }
    if (then != null) {
      then = then.accept(v);
      then?.parent = this;
    }
    if (otherwise != null) {
      otherwise = otherwise.accept(v);
      otherwise?.parent = this;
    }
  }
}

@coq
class ReturnStatement extends Statement {
  Expression expression; // May be null.

  ReturnStatement([this.expression]) {
    expression?.parent = this;
  }

  accept(StatementVisitor v) => v.visitReturnStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitReturnStatement(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
  }
}

class TryCatch extends Statement {
  Statement body;
  List<Catch> catches;
  bool isSynthetic;

  TryCatch(this.body, this.catches, {this.isSynthetic: false}) {
    body?.parent = this;
    setParents(catches, this);
  }

  accept(StatementVisitor v) => v.visitTryCatch(this);
  accept1(StatementVisitor1 v, arg) => v.visitTryCatch(this, arg);

  visitChildren(Visitor v) {
    body?.accept(v);
    visitList(catches, v);
  }

  transformChildren(Transformer v) {
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
    transformList(catches, v, this);
  }
}

class Catch extends TreeNode {
  DartType guard; // Not null, defaults to dynamic.
  VariableDeclaration exception; // May be null.
  VariableDeclaration stackTrace; // May be null.
  Statement body;

  Catch(this.exception, this.body,
      {this.guard: const DynamicType(), this.stackTrace}) {
    assert(guard != null);
    exception?.parent = this;
    stackTrace?.parent = this;
    body?.parent = this;
  }

  accept(TreeVisitor v) => v.visitCatch(this);

  visitChildren(Visitor v) {
    guard?.accept(v);
    exception?.accept(v);
    stackTrace?.accept(v);
    body?.accept(v);
  }

  transformChildren(Transformer v) {
    guard = v.visitDartType(guard);
    if (exception != null) {
      exception = exception.accept(v);
      exception?.parent = this;
    }
    if (stackTrace != null) {
      stackTrace = stackTrace.accept(v);
      stackTrace?.parent = this;
    }
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
  }
}

class TryFinally extends Statement {
  Statement body;
  Statement finalizer;

  TryFinally(this.body, this.finalizer) {
    body?.parent = this;
    finalizer?.parent = this;
  }

  accept(StatementVisitor v) => v.visitTryFinally(this);
  accept1(StatementVisitor1 v, arg) => v.visitTryFinally(this, arg);

  visitChildren(Visitor v) {
    body?.accept(v);
    finalizer?.accept(v);
  }

  transformChildren(Transformer v) {
    if (body != null) {
      body = body.accept(v);
      body?.parent = this;
    }
    if (finalizer != null) {
      finalizer = finalizer.accept(v);
      finalizer?.parent = this;
    }
  }
}

/// Statement of form `yield x` or `yield* x`.
///
/// For native yield semantics see `AsyncMarker.SyncYielding`.
class YieldStatement extends Statement {
  Expression expression;
  int flags = 0;

  YieldStatement(this.expression,
      {bool isYieldStar: false, bool isNative: false}) {
    expression?.parent = this;
    this.isYieldStar = isYieldStar;
    this.isNative = isNative;
  }

  static const int FlagYieldStar = 1 << 0;
  static const int FlagNative = 1 << 1;

  bool get isYieldStar => flags & FlagYieldStar != 0;
  bool get isNative => flags & FlagNative != 0;

  void set isYieldStar(bool value) {
    flags = value ? (flags | FlagYieldStar) : (flags & ~FlagYieldStar);
  }

  void set isNative(bool value) {
    flags = value ? (flags | FlagNative) : (flags & ~FlagNative);
  }

  accept(StatementVisitor v) => v.visitYieldStatement(this);
  accept1(StatementVisitor1 v, arg) => v.visitYieldStatement(this, arg);

  visitChildren(Visitor v) {
    expression?.accept(v);
  }

  transformChildren(Transformer v) {
    if (expression != null) {
      expression = expression.accept(v);
      expression?.parent = this;
    }
  }
}

/// Declaration of a local variable.
///
/// This may occur as a statement, but is also used in several non-statement
/// contexts, such as in [ForStatement], [Catch], and [FunctionNode].
///
/// When this occurs as a statement, it must be a direct child of a [Block].
//
// DESIGN TODO: Should we remove the 'final' modifier from variables?
@coqref
class VariableDeclaration extends Statement {
  /// Offset of the equals sign in the source file it comes from.
  ///
  /// Valid values are from 0 and up, or -1 ([TreeNode.noOffset])
  /// if the equals sign offset is not available (e.g. if not initialized)
  /// (this is the default if none is specifically set).
  int fileEqualsOffset = TreeNode.noOffset;

  /// List of metadata annotations on the variable declaration.
  ///
  /// This defaults to an immutable empty list. Use [addAnnotation] to add
  /// annotations if needed.
  List<Expression> annotations = const <Expression>[];

  /// For named parameters, this is the name of the parameter. No two named
  /// parameters (in the same parameter list) can have the same name.
  ///
  /// In all other cases, the name is cosmetic, may be empty or null,
  /// and is not necessarily unique.
  String name;
  int flags = 0;
  DartType type; // Not null, defaults to dynamic.

  /// Offset of the declaration, set and used when writing the binary.
  int binaryOffsetNoTag = -1;

  /// For locals, this is the initial value.
  /// For parameters, this is the default value.
  ///
  /// Should be null in other cases.
  @coqopt
  Expression initializer; // May be null.

  VariableDeclaration(this.name,
      {this.initializer,
      this.type: const DynamicType(),
      int flags: -1,
      bool isFinal: false,
      bool isConst: false,
      bool isFieldFormal: false,
      bool isCovariant: false}) {
    assert(type != null);
    initializer?.parent = this;
    if (flags != -1) {
      this.flags = flags;
    } else {
      this.isFinal = isFinal;
      this.isConst = isConst;
      this.isFieldFormal = isFieldFormal;
      this.isCovariant = isCovariant;
    }
  }

  /// Creates a synthetic variable with the given expression as initializer.
  VariableDeclaration.forValue(this.initializer,
      {bool isFinal: true,
      bool isConst: false,
      bool isFieldFormal: false,
      this.type: const DynamicType()}) {
    assert(type != null);
    initializer?.parent = this;
    this.isFinal = isFinal;
    this.isConst = isConst;
    this.isFieldFormal = isFieldFormal;
  }

  static const int FlagFinal = 1 << 0; // Must match serialized bit positions.
  static const int FlagConst = 1 << 1;
  static const int FlagFieldFormal = 1 << 2;
  static const int FlagCovariant = 1 << 3;
  static const int FlagInScope = 1 << 4; // Temporary flag used by verifier.
  static const int FlagGenericCovariantImpl = 1 << 5;

  bool get isFinal => flags & FlagFinal != 0;
  bool get isConst => flags & FlagConst != 0;

  /// Whether the parameter is declared with the `covariant` keyword.
  bool get isCovariant => flags & FlagCovariant != 0;

  /// Whether the variable is declared as a field formal parameter of
  /// a constructor.
  @informative
  bool get isFieldFormal => flags & FlagFieldFormal != 0;

  /// If this [VariableDeclaration] is a parameter of a method, indicates
  /// whether the method implementation needs to contain a runtime type check to
  /// deal with generic covariance.
  ///
  /// When `true`, runtime checks may need to be performed; see
  /// [DispatchCategory] for details.
  bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;

  void set isFinal(bool value) {
    flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
  }

  void set isConst(bool value) {
    flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
  }

  void set isCovariant(bool value) {
    flags = value ? (flags | FlagCovariant) : (flags & ~FlagCovariant);
  }

  @informative
  void set isFieldFormal(bool value) {
    flags = value ? (flags | FlagFieldFormal) : (flags & ~FlagFieldFormal);
  }

  void set isGenericCovariantImpl(bool value) {
    flags = value
        ? (flags | FlagGenericCovariantImpl)
        : (flags & ~FlagGenericCovariantImpl);
  }

  void addAnnotation(Expression annotation) {
    if (annotations.isEmpty) {
      annotations = <Expression>[];
    }
    annotations.add(annotation..parent = this);
  }

  accept(StatementVisitor v) => v.visitVariableDeclaration(this);
  accept1(StatementVisitor1 v, arg) => v.visitVariableDeclaration(this, arg);

  visitChildren(Visitor v) {
    visitList(annotations, v);
    type?.accept(v);
    initializer?.accept(v);
  }

  transformChildren(Transformer v) {
    transformList(annotations, v, this);
    type = v.visitDartType(type);
    if (initializer != null) {
      initializer = initializer.accept(v);
      initializer?.parent = this;
    }
  }

  /// Returns a possibly synthesized name for this variable, consistent with
  /// the names used across all [toString] calls.
  String toString() => debugVariableDeclarationName(this);
}

/// Declaration a local function.
///
/// The body of the function may use [variable] as its self-reference.
class FunctionDeclaration extends Statement {
  VariableDeclaration variable; // Is final and has no initializer.
  FunctionNode function;

  FunctionDeclaration(this.variable, this.function) {
    variable?.parent = this;
    function?.parent = this;
  }

  accept(StatementVisitor v) => v.visitFunctionDeclaration(this);
  accept1(StatementVisitor1 v, arg) => v.visitFunctionDeclaration(this, arg);

  visitChildren(Visitor v) {
    variable?.accept(v);
    function?.accept(v);
  }

  transformChildren(Transformer v) {
    if (variable != null) {
      variable = variable.accept(v);
      variable?.parent = this;
    }
    if (function != null) {
      function = function.accept(v);
      function?.parent = this;
    }
  }
}

// ------------------------------------------------------------------------
//                                NAMES
// ------------------------------------------------------------------------

/// A public name, or a private name qualified by a library.
///
/// Names are only used for expressions with dynamic dispatch, as all
/// statically resolved references are represented in nameless form.
///
/// [Name]s are immutable and compare based on structural equality, and they
/// are not AST nodes.
///
/// The [toString] method returns a human-readable string that includes the
/// library name for private names; uniqueness is not guaranteed.
@coq
abstract class Name implements Node {
  final int hashCode;
  @coq
  final String name;
  @nocoq
  Reference get libraryName;
  @nocoq
  Library get library;
  bool get isPrivate;

  Name._internal(this.hashCode, this.name);

  factory Name(String name, [Library library]) =>
      new Name.byReference(name, library?.reference);

  factory Name.byReference(String name, Reference libraryName) {
    /// Use separate subclasses for the public and private case to save memory
    /// for public names.
    if (name.startsWith('_')) {
      assert(libraryName != null);
      return new _PrivateName(name, libraryName);
    } else {
      return new _PublicName(name);
    }
  }

  bool operator ==(other) {
    return other is Name && name == other.name && library == other.library;
  }

  accept(Visitor v) => v.visitName(this);

  visitChildren(Visitor v) {
    // DESIGN TODO: Should we visit the library as a library reference?
  }
}

class _PrivateName extends Name {
  final Reference libraryName;
  bool get isPrivate => true;

  _PrivateName(String name, Reference libraryName)
      : this.libraryName = libraryName,
        super._internal(_computeHashCode(name, libraryName), name);

  String toString() => library != null ? '$library::$name' : name;

  Library get library => libraryName.asLibrary;

  static int _computeHashCode(String name, Reference libraryName) {
    return 131 * name.hashCode + 17 * libraryName.hashCode;
  }
}

class _PublicName extends Name {
  Reference get libraryName => null;
  Library get library => null;
  bool get isPrivate => false;

  _PublicName(String name) : super._internal(name.hashCode, name);

  String toString() => name;
}

// ------------------------------------------------------------------------
//                             TYPES
// ------------------------------------------------------------------------

/// A syntax-independent notion of a type.
///
/// [DartType]s are not AST nodes and may be shared between different parents.
///
/// [DartType] objects should be treated as unmodifiable objects, although
/// immutability is not enforced for List fields, and [TypeParameter]s are
/// cyclic structures that are constructed by mutation.
///
/// The `==` operator on [DartType]s compare based on type equality, not
/// object identity.
@coq
abstract class DartType extends Node {
  const DartType();

  accept(DartTypeVisitor v);

  bool operator ==(Object other);

  /// If this is a typedef type, repeatedly unfolds its type definition until
  /// the root term is not a typedef type, otherwise returns the type itself.
  ///
  /// Will never return a typedef type.
  DartType get unalias => this;

  /// If this is a typedef type, unfolds its type definition once, otherwise
  /// returns the type itself.
  DartType get unaliasOnce => this;
}

/// The type arising from invalid type annotations.
///
/// Can usually be treated as 'dynamic', but should occasionally be handled
/// differently, e.g. `x is ERROR` should evaluate to false.
class InvalidType extends DartType {
  final int hashCode = 12345;

  const InvalidType();

  accept(DartTypeVisitor v) => v.visitInvalidType(this);
  visitChildren(Visitor v) {}

  bool operator ==(Object other) => other is InvalidType;
}

class DynamicType extends DartType {
  final int hashCode = 54321;

  const DynamicType();

  accept(DartTypeVisitor v) => v.visitDynamicType(this);
  visitChildren(Visitor v) {}

  bool operator ==(Object other) => other is DynamicType;
}

class VoidType extends DartType {
  final int hashCode = 123121;

  const VoidType();

  accept(DartTypeVisitor v) => v.visitVoidType(this);
  visitChildren(Visitor v) {}

  bool operator ==(Object other) => other is VoidType;
}

class BottomType extends DartType {
  final int hashCode = 514213;

  const BottomType();

  accept(DartTypeVisitor v) => v.visitBottomType(this);
  visitChildren(Visitor v) {}

  bool operator ==(Object other) => other is BottomType;
}

@coq
class InterfaceType extends DartType {
  Reference className;
  @nocoq
  final List<DartType> typeArguments;

  /// The [typeArguments] list must not be modified after this call. If the
  /// list is omitted, 'dynamic' type arguments are filled in.
  InterfaceType(Class classNode, [List<DartType> typeArguments])
      : this.byReference(getClassReference(classNode),
            typeArguments ?? _defaultTypeArguments(classNode));

  InterfaceType.byReference(this.className, this.typeArguments);

  Class get classNode => className.asClass;

  static List<DartType> _defaultTypeArguments(Class classNode) {
    if (classNode.typeParameters.length == 0) {
      // Avoid allocating a list in this very common case.
      return const <DartType>[];
    } else {
      return new List<DartType>.filled(
          classNode.typeParameters.length, const DynamicType());
    }
  }

  accept(DartTypeVisitor v) => v.visitInterfaceType(this);

  visitChildren(Visitor v) {
    classNode.acceptReference(v);
    visitList(typeArguments, v);
  }

  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is InterfaceType) {
      if (className != other.className) return false;
      if (typeArguments.length != other.typeArguments.length) return false;
      for (int i = 0; i < typeArguments.length; ++i) {
        if (typeArguments[i] != other.typeArguments[i]) return false;
      }
      return true;
    } else {
      return false;
    }
  }

  int get hashCode {
    int hash = 0x3fffffff & className.hashCode;
    for (int i = 0; i < typeArguments.length; ++i) {
      hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
    }
    return hash;
  }
}

/// [VectorType] represents Vectors, a special kind of data that is not
/// available for use by Dart programmers directly. It is used by Kernel
/// transformations as efficient index-based storage.
///
/// * Vectors aren't user-visible. For example, they are not supposed to be
/// exposed to Dart programs through variables or be visible in stack traces.
///
/// * Vectors have fixed length at runtime. The length is known at compile
/// time, and [VectorCreation] AST node stores it in a field.
///
/// * Indexes for accessing and assigning Vector items are known at compile
/// time. The corresponding [VectorGet] and [VectorSet] AST nodes store the
/// index in a field.
///
/// * For efficiency considerations, bounds checks aren't performed for Vectors.
/// If necessary, a transformer or verifier can do this checks at compile-time,
/// after adding length field to [VectorType], to make sure that previous
/// transformations didn't introduce any access errors.
///
/// * Access to Vectors is untyped.
///
/// * Vectors can be used by various transformations of Kernel components.
/// Currently they are used by Closure Conversion to represent closure contexts.
class VectorType extends DartType {
  const VectorType();

  accept(DartTypeVisitor v) => v.visitVectorType(this);
  visitChildren(Visitor v) {}
}

/// A possibly generic function type.
@coq
class FunctionType extends DartType {
  final List<TypeParameter> typeParameters;
  final int requiredParameterCount;
  @coqsingle
  final List<DartType> positionalParameters;
  final List<NamedType> namedParameters; // Must be sorted.

  /// The optional names of [positionalParameters], not `null`, but might be
  /// empty if information is not available.
  @informative
  final List<String> positionalParameterNames;

  /// The [Typedef] this function type is created for.
  @nocoq
  Reference typedefReference;

  final DartType returnType;
  int _hashCode;

  FunctionType(List<DartType> positionalParameters, this.returnType,
      {this.namedParameters: const <NamedType>[],
      this.typeParameters: const <TypeParameter>[],
      int requiredParameterCount,
      this.positionalParameterNames: const <String>[],
      this.typedefReference})
      : this.positionalParameters = positionalParameters,
        this.requiredParameterCount =
            requiredParameterCount ?? positionalParameters.length;

  /// The [Typedef] this function type is created for.
  Typedef get typedef => typedefReference?.asTypedef;

  accept(DartTypeVisitor v) => v.visitFunctionType(this);

  visitChildren(Visitor v) {
    visitList(typeParameters, v);
    visitList(positionalParameters, v);
    visitList(namedParameters, v);
    returnType.accept(v);
  }

  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is FunctionType) {
      if (typeParameters.length != other.typeParameters.length ||
          requiredParameterCount != other.requiredParameterCount ||
          positionalParameters.length != other.positionalParameters.length ||
          namedParameters.length != other.namedParameters.length) {
        return false;
      }
      if (typeParameters.isEmpty) {
        for (int i = 0; i < positionalParameters.length; ++i) {
          if (positionalParameters[i] != other.positionalParameters[i]) {
            return false;
          }
        }
        for (int i = 0; i < namedParameters.length; ++i) {
          if (namedParameters[i] != other.namedParameters[i]) {
            return false;
          }
        }
        return returnType == other.returnType;
      } else {
        // Structural equality does not tell us if two generic function types
        // are the same type.  If they are unifiable without substituting any
        // type variables, they are equal.
        return unifyTypes(this, other, new Set<TypeParameter>()) != null;
      }
    } else {
      return false;
    }
  }

  /// Returns a variant of this function type that does not declare any type
  /// parameters.
  ///
  /// Any uses of its type parameters become free variables in the returned
  /// type.
  FunctionType get withoutTypeParameters {
    if (typeParameters.isEmpty) return this;
    return new FunctionType(positionalParameters, returnType,
        requiredParameterCount: requiredParameterCount,
        namedParameters: namedParameters,
        typedefReference: typedefReference);
  }

  /// Looks up the type of the named parameter with the given name.
  ///
  /// Returns `null` if there is no named parameter with the given name.
  DartType getNamedParameter(String name) {
    int lower = 0;
    int upper = namedParameters.length - 1;
    while (lower <= upper) {
      int pivot = (lower + upper) ~/ 2;
      var namedParameter = namedParameters[pivot];
      int comparison = name.compareTo(namedParameter.name);
      if (comparison == 0) {
        return namedParameter.type;
      } else if (comparison < 0) {
        upper = pivot - 1;
      } else {
        lower = pivot + 1;
      }
    }
    return null;
  }

  int get hashCode => _hashCode ??= _computeHashCode();

  int _computeHashCode() {
    int hash = 1237;
    hash = 0x3fffffff & (hash * 31 + requiredParameterCount);
    for (int i = 0; i < typeParameters.length; ++i) {
      TypeParameter parameter = typeParameters[i];
      _temporaryHashCodeTable[parameter] = _temporaryHashCodeTable.length;
      hash = 0x3fffffff & (hash * 31 + parameter.bound.hashCode);
    }
    for (int i = 0; i < positionalParameters.length; ++i) {
      hash = 0x3fffffff & (hash * 31 + positionalParameters[i].hashCode);
    }
    for (int i = 0; i < namedParameters.length; ++i) {
      hash = 0x3fffffff & (hash * 31 + namedParameters[i].hashCode);
    }
    hash = 0x3fffffff & (hash * 31 + returnType.hashCode);
    for (int i = 0; i < typeParameters.length; ++i) {
      // Remove the type parameters from the scope again.
      _temporaryHashCodeTable.remove(typeParameters[i]);
    }
    return hash;
  }
}

/// A use of a [Typedef] as a type.
///
/// The underlying type can be extracted using [unalias].
class TypedefType extends DartType {
  final Reference typedefReference;
  final List<DartType> typeArguments;

  TypedefType(Typedef typedefNode, [List<DartType> typeArguments])
      : this.byReference(
            typedefNode.reference, typeArguments ?? const <DartType>[]);

  TypedefType.byReference(this.typedefReference, this.typeArguments);

  Typedef get typedefNode => typedefReference.asTypedef;

  accept(DartTypeVisitor v) => v.visitTypedefType(this);

  visitChildren(Visitor v) {
    visitList(typeArguments, v);
    v.visitTypedefReference(typedefNode);
  }

  DartType get unaliasOnce {
    return Substitution.fromTypedefType(this).substituteType(typedefNode.type);
  }

  DartType get unalias {
    return unaliasOnce.unalias;
  }

  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is TypedefType) {
      if (typedefReference != other.typedefReference ||
          typeArguments.length != other.typeArguments.length) {
        return false;
      }
      for (int i = 0; i < typeArguments.length; ++i) {
        if (typeArguments[i] != other.typeArguments[i]) return false;
      }
      return true;
    }
    return false;
  }

  int get hashCode {
    int hash = 0x3fffffff & typedefNode.hashCode;
    for (int i = 0; i < typeArguments.length; ++i) {
      hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
    }
    return hash;
  }
}

/// A named parameter in [FunctionType].
class NamedType extends Node implements Comparable<NamedType> {
  final String name;
  final DartType type;

  NamedType(this.name, this.type);

  bool operator ==(Object other) {
    return other is NamedType && name == other.name && type == other.type;
  }

  int get hashCode {
    return name.hashCode * 31 + type.hashCode * 37;
  }

  int compareTo(NamedType other) => name.compareTo(other.name);

  accept(Visitor v) => v.visitNamedType(this);

  void visitChildren(Visitor v) {
    type.accept(v);
  }
}

/// Stores the hash code of function type parameters while computing the hash
/// code of a [FunctionType] object.
///
/// This ensures that distinct [FunctionType] objects get the same hash code
/// if they represent the same type, even though their type parameters are
/// represented by different objects.
final Map<TypeParameter, int> _temporaryHashCodeTable = <TypeParameter, int>{};

/// Reference to a type variable.
///
/// A type variable has an optional bound because type promotion can change the
/// bound.  A bound of `null` indicates that the bound has not been promoted and
/// is the same as the [TypeParameter]'s bound.  This allows one to detect
/// whether the bound has been promoted.
class TypeParameterType extends DartType {
  TypeParameter parameter;

  /// An optional promoted bound on the type parameter.
  ///
  /// 'null' indicates that the type parameter's bound has not been promoted and
  /// is therefore the same as the bound of [parameter].
  DartType promotedBound;

  TypeParameterType(this.parameter, [this.promotedBound]);

  accept(DartTypeVisitor v) => v.visitTypeParameterType(this);

  visitChildren(Visitor v) {}

  bool operator ==(Object other) {
    return other is TypeParameterType && parameter == other.parameter;
  }

  int get hashCode => _temporaryHashCodeTable[parameter] ?? parameter.hashCode;

  /// Returns the bound of the type parameter, accounting for promotions.
  DartType get bound => promotedBound ?? parameter.bound;
}

/// Declaration of a type variable.
///
/// Type parameters declared in a [Class] or [FunctionNode] are part of the AST,
/// have a parent pointer to its declaring class or function, and will be seen
/// by tree visitors.
///
/// Type parameters declared by a [FunctionType] are orphans and have a `null`
/// parent pointer.  [TypeParameter] objects should not be shared between
/// different [FunctionType] objects.
class TypeParameter extends TreeNode {
  int flags = 0;

  /// List of metadata annotations on the type parameter.
  ///
  /// This defaults to an immutable empty list. Use [addAnnotation] to add
  /// annotations if needed.
  List<Expression> annotations = const <Expression>[];

  String name; // Cosmetic name.

  /// The bound on the type variable.
  ///
  /// Should not be null except temporarily during IR construction.  Should
  /// be set to the root class for type parameters without an explicit bound.
  DartType bound;

  /// The default value of the type variable. It is used to provide the
  /// corresponding missing type argument in type annotations and as the
  /// fall-back type value in type inference at compile time. At run time,
  /// [defaultType] is used by the backends in place of the missing type
  /// argument of a dynamic invocation of a generic function.
  DartType defaultType;

  TypeParameter([this.name, this.bound, this.defaultType]);

  // Must match serialized bit positions.
  static const int FlagGenericCovariantImpl = 1 << 0;

  /// If this [TypeParameter] is a type parameter of a generic method, indicates
  /// whether the method implementation needs to contain a runtime type check to
  /// deal with generic covariance.
  ///
  /// When `true`, runtime checks may need to be performed; see
  /// [DispatchCategory] for details.
  bool get isGenericCovariantImpl => flags & FlagGenericCovariantImpl != 0;

  void set isGenericCovariantImpl(bool value) {
    flags = value
        ? (flags | FlagGenericCovariantImpl)
        : (flags & ~FlagGenericCovariantImpl);
  }

  void addAnnotation(Expression annotation) {
    if (annotations.isEmpty) {
      annotations = <Expression>[];
    }
    annotations.add(annotation..parent = this);
  }

  accept(TreeVisitor v) => v.visitTypeParameter(this);

  visitChildren(Visitor v) {
    bound.accept(v);
    defaultType?.accept(v);
  }

  transformChildren(Transformer v) {
    bound = v.visitDartType(bound);
    if (defaultType != null) {
      defaultType = v.visitDartType(defaultType);
    }
  }

  /// Returns a possibly synthesized name for this type parameter, consistent
  /// with the names used across all [toString] calls.
  String toString() => debugQualifiedTypeParameterName(this);

  bool get isFunctionTypeTypeParameter => parent == null;
}

class Supertype extends Node {
  Reference className;
  final List<DartType> typeArguments;

  Supertype(Class classNode, List<DartType> typeArguments)
      : this.byReference(getClassReference(classNode), typeArguments);

  Supertype.byReference(this.className, this.typeArguments);

  Class get classNode => className.asClass;

  accept(Visitor v) => v.visitSupertype(this);

  visitChildren(Visitor v) {
    classNode.acceptReference(v);
    visitList(typeArguments, v);
  }

  InterfaceType get asInterfaceType {
    return new InterfaceType(classNode, typeArguments);
  }

  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is Supertype) {
      if (className != other.className) return false;
      if (typeArguments.length != other.typeArguments.length) return false;
      for (int i = 0; i < typeArguments.length; ++i) {
        if (typeArguments[i] != other.typeArguments[i]) return false;
      }
      return true;
    } else {
      return false;
    }
  }

  int get hashCode {
    int hash = 0x3fffffff & className.hashCode;
    for (int i = 0; i < typeArguments.length; ++i) {
      hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
    }
    return hash;
  }
}

// ------------------------------------------------------------------------
//                             CONSTANTS
// ------------------------------------------------------------------------

abstract class Constant extends Node {
  /// Calls the `visit*ConstantReference()` method on visitor [v] for all
  /// constants referenced in this constant.
  ///
  /// (Note that a constant can be seen as a DAG (directed acyclic graph) and
  ///  not a tree!)
  visitChildren(Visitor v);

  /// Calls the `visit*Constant()` method on the visitor [v].
  accept(ConstantVisitor v);

  /// Calls the `visit*ConstantReference()` method on the visitor [v].
  acceptReference(Visitor v);

  /// The Kernel AST will reference [Constant]s via [ConstantExpression]s.  The
  /// constants are not required to be canonicalized, but they have to be deeply
  /// comparable via hashCode/==!
  int get hashCode;
  bool operator ==(Object other);

  /// Gets the type of this constant.
  DartType getType(TypeEnvironment types);
}

abstract class PrimitiveConstant<T> extends Constant {
  final T value;

  PrimitiveConstant(this.value);

  String toString() => '$value';

  int get hashCode => value.hashCode;

  bool operator ==(Object other) =>
      other is PrimitiveConstant<T> && other.value == value;
}

class NullConstant extends PrimitiveConstant<Null> {
  NullConstant() : super(null);

  visitChildren(Visitor v) {}
  accept(ConstantVisitor v) => v.visitNullConstant(this);
  acceptReference(Visitor v) => v.visitNullConstantReference(this);

  DartType getType(TypeEnvironment types) => types.nullType;
}

class BoolConstant extends PrimitiveConstant<bool> {
  BoolConstant(bool value) : super(value);

  visitChildren(Visitor v) {}
  accept(ConstantVisitor v) => v.visitBoolConstant(this);
  acceptReference(Visitor v) => v.visitBoolConstantReference(this);

  DartType getType(TypeEnvironment types) => types.boolType;
}

class IntConstant extends PrimitiveConstant<int> {
  IntConstant(int value) : super(value);

  visitChildren(Visitor v) {}
  accept(ConstantVisitor v) => v.visitIntConstant(this);
  acceptReference(Visitor v) => v.visitIntConstantReference(this);

  DartType getType(TypeEnvironment types) => types.intType;
}

class DoubleConstant extends PrimitiveConstant<double> {
  DoubleConstant(double value) : super(value);

  visitChildren(Visitor v) {}
  accept(ConstantVisitor v) => v.visitDoubleConstant(this);
  acceptReference(Visitor v) => v.visitDoubleConstantReference(this);

  int get hashCode => value.isNaN ? 199 : super.hashCode;
  bool operator ==(Object other) =>
      other is DoubleConstant &&
      (other.value == value || identical(value, other.value) /* For NaN */);

  DartType getType(TypeEnvironment types) => types.doubleType;
}

class StringConstant extends PrimitiveConstant<String> {
  StringConstant(String value) : super(value) {
    assert(value != null);
  }

  visitChildren(Visitor v) {}
  accept(ConstantVisitor v) => v.visitStringConstant(this);
  acceptReference(Visitor v) => v.visitStringConstantReference(this);

  DartType getType(TypeEnvironment types) => types.stringType;
}

class MapConstant extends Constant {
  final DartType keyType;
  final DartType valueType;
  final List<ConstantMapEntry> entries;

  MapConstant(this.keyType, this.valueType, this.entries);

  visitChildren(Visitor v) {
    keyType.accept(v);
    valueType.accept(v);
    for (final ConstantMapEntry entry in entries) {
      entry.key.acceptReference(v);
      entry.value.acceptReference(v);
    }
  }

  accept(ConstantVisitor v) => v.visitMapConstant(this);
  acceptReference(Visitor v) => v.visitMapConstantReference(this);

  String toString() => '${this.runtimeType}<$keyType, $valueType>($entries)';

  // TODO(kustermann): Consider combining the hash codes in a better way (also
  // below and in [listHashCode]/[mapHashCode].
  int _cachedHashCode;
  int get hashCode {
    return _cachedHashCode ??=
        keyType.hashCode ^ valueType.hashCode ^ listHashCode(entries);
  }

  bool operator ==(Object other) =>
      identical(this, other) ||
      (other is MapConstant &&
          other.keyType == keyType &&
          other.valueType == valueType &&
          listEquals(other.entries, entries));

  DartType getType(TypeEnvironment types) =>
      types.literalMapType(keyType, valueType);
}

class ConstantMapEntry {
  final Constant key;
  final Constant value;
  ConstantMapEntry(this.key, this.value);

  String toString() => '$key: $value';

  int get hashCode => key.hashCode ^ value.hashCode;

  bool operator ==(Object other) =>
      other is ConstantMapEntry && other.key == key && other.value == value;
}

class ListConstant extends Constant {
  final DartType typeArgument;
  final List<Constant> entries;

  ListConstant(this.typeArgument, this.entries);

  visitChildren(Visitor v) {
    typeArgument.accept(v);
    for (final Constant constant in entries) {
      constant.acceptReference(v);
    }
  }

  accept(ConstantVisitor v) => v.visitListConstant(this);
  acceptReference(Visitor v) => v.visitListConstantReference(this);

  String toString() => '${this.runtimeType}<$typeArgument>($entries)';

  int _cachedHashCode;
  int get hashCode {
    return _cachedHashCode ??= typeArgument.hashCode ^ listHashCode(entries);
  }

  bool operator ==(Object other) =>
      identical(this, other) ||
      (other is ListConstant &&
          other.typeArgument == typeArgument &&
          listEquals(other.entries, entries));

  DartType getType(TypeEnvironment types) =>
      types.literalListType(typeArgument);
}

class InstanceConstant extends Constant {
  final Reference classReference;
  final List<DartType> typeArguments;
  final Map<Reference, Constant> fieldValues;

  InstanceConstant(this.classReference, this.typeArguments, this.fieldValues);

  Class get klass => classReference.asClass;

  visitChildren(Visitor v) {
    classReference.asClass.acceptReference(v);
    visitList(typeArguments, v);
    for (final Reference reference in fieldValues.keys) {
      reference.asField.acceptReference(v);
    }
    for (final Constant constant in fieldValues.values) {
      constant.acceptReference(v);
    }
  }

  accept(ConstantVisitor v) => v.visitInstanceConstant(this);
  acceptReference(Visitor v) => v.visitInstanceConstantReference(this);

  String toString() {
    final sb = new StringBuffer();
    sb.write('${classReference.asClass}');
    if (!classReference.asClass.typeParameters.isEmpty) {
      sb.write('<');
      sb.write(typeArguments.map((type) => type.toString()).join(', '));
      sb.write('>');
    }
    sb.write(' {');
    fieldValues.forEach((Reference fieldRef, Constant constant) {
      sb.write('${fieldRef.asField.name}: $constant, ');
    });
    sb.write('}');
    return sb.toString();
  }

  int _cachedHashCode;
  int get hashCode {
    return _cachedHashCode ??= classReference.hashCode ^
        listHashCode(typeArguments) ^
        mapHashCode(fieldValues);
  }

  bool operator ==(Object other) {
    return identical(this, other) ||
        (other is InstanceConstant &&
            other.classReference == classReference &&
            listEquals(other.typeArguments, typeArguments) &&
            mapEquals(other.fieldValues, fieldValues));
  }

  DartType getType(TypeEnvironment types) =>
      new InterfaceType(klass, typeArguments);
}

class PartialInstantiationConstant extends Constant {
  final TearOffConstant tearOffConstant;
  final List<DartType> types;

  PartialInstantiationConstant(this.tearOffConstant, this.types);

  visitChildren(Visitor v) {
    tearOffConstant.acceptReference(v);
    visitList(types, v);
  }

  accept(ConstantVisitor v) => v.visitPartialInstantiationConstant(this);
  acceptReference(Visitor v) =>
      v.visitPartialInstantiationConstantReference(this);

  String toString() {
    return '${runtimeType}(${tearOffConstant.procedure}<${types.join(', ')}>)';
  }

  int get hashCode => tearOffConstant.hashCode ^ listHashCode(types);

  bool operator ==(Object other) {
    return other is PartialInstantiationConstant &&
        other.tearOffConstant == tearOffConstant &&
        listEquals(other.types, types);
  }

  DartType getType(TypeEnvironment typeEnvironment) {
    final FunctionType type = tearOffConstant.getType(typeEnvironment);
    final mapping = <TypeParameter, DartType>{};
    for (final parameter in type.typeParameters) {
      mapping[parameter] = types[mapping.length];
    }
    return substitute(type.withoutTypeParameters, mapping);
  }
}

class TearOffConstant extends Constant {
  final Reference procedureReference;

  TearOffConstant(Procedure procedure)
      : procedureReference = procedure.reference {
    assert(procedure.isStatic);
  }

  TearOffConstant.byReference(this.procedureReference);

  Procedure get procedure => procedureReference?.asProcedure;

  visitChildren(Visitor v) {
    procedureReference.asProcedure.acceptReference(v);
  }

  accept(ConstantVisitor v) => v.visitTearOffConstant(this);
  acceptReference(Visitor v) => v.visitTearOffConstantReference(this);

  String toString() {
    return '${runtimeType}(${procedure})';
  }

  int get hashCode => procedure.hashCode;

  bool operator ==(Object other) {
    return other is TearOffConstant && other.procedure == procedure;
  }

  FunctionType getType(TypeEnvironment types) =>
      procedure.function.functionType;
}

class TypeLiteralConstant extends Constant {
  final DartType type;

  TypeLiteralConstant(this.type);

  visitChildren(Visitor v) {
    type.accept(v);
  }

  accept(ConstantVisitor v) => v.visitTypeLiteralConstant(this);
  acceptReference(Visitor v) => v.visitTypeLiteralConstantReference(this);

  String toString() => '${runtimeType}(${type})';

  int get hashCode => type.hashCode;

  bool operator ==(Object other) {
    return other is TypeLiteralConstant && other.type == type;
  }

  DartType getType(TypeEnvironment types) => types.typeType;
}

// ------------------------------------------------------------------------
//                                COMPONENT
// ------------------------------------------------------------------------

/// A way to bundle up libraries in a component.
class Component extends TreeNode {
  final CanonicalName root;

  final List<Library> libraries;

  /// Map from a source file URI to a line-starts table and source code.
  /// Given a source file URI and a offset in that file one can translate
  /// it to a line:column position in that file.
  final Map<Uri, Source> uriToSource;

  /// Mapping between string tags and [MetadataRepository] corresponding to
  /// those tags.
  final Map<String, MetadataRepository<dynamic>> metadata =
      <String, MetadataRepository<dynamic>>{};

  /// Reference to the main method in one of the libraries.
  Reference mainMethodName;

  Component(
      {CanonicalName nameRoot,
      List<Library> libraries,
      Map<Uri, Source> uriToSource})
      : root = nameRoot ?? new CanonicalName.root(),
        libraries = libraries ?? <Library>[],
        uriToSource = uriToSource ?? <Uri, Source>{} {
    if (libraries != null) {
      for (int i = 0; i < libraries.length; ++i) {
        // The libraries are owned by this component, and so are their canonical
        // names if they exist.
        Library library = libraries[i];
        library.parent = this;
        CanonicalName name = library.reference.canonicalName;
        if (name != null && name.parent != root) {
          root.adoptChild(name);
        }
      }
    }
  }

  void computeCanonicalNames() {
    for (var library in libraries) {
      root.getChildFromUri(library.importUri).bindTo(library.reference);
      library.computeCanonicalNames();
    }
  }

  void unbindCanonicalNames() {
    root.unbindAll();
  }

  Procedure get mainMethod => mainMethodName?.asProcedure;

  void set mainMethod(Procedure main) {
    mainMethodName = getMemberReference(main);
  }

  accept(TreeVisitor v) => v.visitComponent(this);

  visitChildren(Visitor v) {
    visitList(libraries, v);
    mainMethod?.acceptReference(v);
  }

  transformChildren(Transformer v) {
    transformList(libraries, v, this);
  }

  Component get enclosingComponent => this;

  /// Translates an offset to line and column numbers in the given file.
  Location getLocation(Uri file, int offset) {
    return uriToSource[file]?.getLocation(file, offset);
  }

  void addMetadataRepository(MetadataRepository repository) {
    metadata[repository.tag] = repository;
  }
}

/// A tuple with file, line, and column number, for displaying human-readable
/// locations.
class Location {
  final Uri file;
  final int line; // 1-based.
  final int column; // 1-based.

  Location(this.file, this.line, this.column);

  String toString() => '$file:$line:$column';
}

abstract class MetadataRepository<T> {
  /// Unique string tag associated with this repository.
  String get tag;

  /// Mutable mapping between nodes and their metadata.
  Map<TreeNode, T> get mapping;

  /// Write [metadata] object corresponding to the given [Node] into
  /// the given [BinarySink].
  ///
  /// Metadata is serialized immediately before serializing [node],
  /// so implementation of this method can use serialization context of
  /// [node]'s parents (such as declared type parameters and variables).
  /// In order to use scope declared by the [node] itself, implementation of
  /// this method can use [BinarySink.enterScope] and [BinarySink.leaveScope]
  /// methods.
  ///
  /// [metadata] must be an object owned by this repository.
  void writeToBinary(T metadata, Node node, BinarySink sink);

  /// Construct a metadata object from its binary payload read from the
  /// given [BinarySource].
  ///
  /// Metadata is deserialized immediately after deserializing [node],
  /// so it can use deserialization context of [node]'s parents.
  /// In order to use scope declared by the [node] itself, implementation of
  /// this method can use [BinarySource.enterScope] and
  /// [BinarySource.leaveScope] methods.
  T readFromBinary(Node node, BinarySource source);

  /// Method to check whether a node can have metadata attached to it
  /// or referenced from the metadata payload.
  ///
  /// Currently due to binary format specifics Catch and MapEntry nodes
  /// can't have metadata attached to them.
  static bool isSupported(TreeNode node) {
    return !(node is MapEntry || node is Catch);
  }
}

abstract class BinarySink {
  void writeByte(int byte);
  void writeBytes(List<int> bytes);
  void writeUInt32(int value);
  void writeUInt30(int value);

  /// Write List<Byte> into the sink.
  void writeByteList(List<int> bytes);

  void writeCanonicalNameReference(CanonicalName name);
  void writeStringReference(String str);
  void writeName(Name node);
  void writeDartType(DartType type);
  void writeNode(Node node);

  void enterScope(
      {List<TypeParameter> typeParameters,
      bool memberScope: false,
      bool variableScope: false});
  void leaveScope(
      {List<TypeParameter> typeParameters,
      bool memberScope: false,
      bool variableScope: false});
}

abstract class BinarySource {
  int get currentOffset;
  List<int> get bytes;

  int readByte();
  int readUInt();
  int readUint32();

  /// Read List<Byte> from the source.
  List<int> readByteList();

  CanonicalName readCanonicalNameReference();
  String readStringReference();
  Name readName();
  DartType readDartType();
  FunctionNode readFunctionNode();

  void enterScope({List<TypeParameter> typeParameters});
  void leaveScope({List<TypeParameter> typeParameters});
}

// ------------------------------------------------------------------------
//                             INTERNAL FUNCTIONS
// ------------------------------------------------------------------------

void setParents(List<TreeNode> nodes, TreeNode parent) {
  for (int i = 0; i < nodes.length; ++i) {
    nodes[i].parent = parent;
  }
}

void visitList(List<Node> nodes, Visitor visitor) {
  for (int i = 0; i < nodes.length; ++i) {
    nodes[i].accept(visitor);
  }
}

void visitIterable(Iterable<Node> nodes, Visitor visitor) {
  for (var node in nodes) {
    node.accept(visitor);
  }
}

void transformTypeList(List<DartType> nodes, Transformer visitor) {
  int storeIndex = 0;
  for (int i = 0; i < nodes.length; ++i) {
    var result = visitor.visitDartType(nodes[i]);
    if (result != null) {
      nodes[storeIndex] = result;
      ++storeIndex;
    }
  }
  if (storeIndex < nodes.length) {
    nodes.length = storeIndex;
  }
}

void transformSupertypeList(List<Supertype> nodes, Transformer visitor) {
  int storeIndex = 0;
  for (int i = 0; i < nodes.length; ++i) {
    var result = visitor.visitSupertype(nodes[i]);
    if (result != null) {
      nodes[storeIndex] = result;
      ++storeIndex;
    }
  }
  if (storeIndex < nodes.length) {
    nodes.length = storeIndex;
  }
}

void transformList(List<TreeNode> nodes, Transformer visitor, TreeNode parent) {
  int storeIndex = 0;
  for (int i = 0; i < nodes.length; ++i) {
    var result = nodes[i].accept(visitor);
    if (result != null) {
      nodes[storeIndex] = result;
      result.parent = parent;
      ++storeIndex;
    }
  }
  if (storeIndex < nodes.length) {
    nodes.length = storeIndex;
  }
}

List<DartType> _getAsTypeArguments(List<TypeParameter> typeParameters) {
  if (typeParameters.isEmpty) return const <DartType>[];
  return new List<DartType>.generate(
      typeParameters.length, (i) => new TypeParameterType(typeParameters[i]),
      growable: false);
}

class _ChildReplacer extends Transformer {
  final TreeNode child;
  final TreeNode replacement;

  _ChildReplacer(this.child, this.replacement);

  @override
  defaultTreeNode(TreeNode node) {
    if (node == child) {
      return replacement;
    } else {
      return node;
    }
  }
}

class Source {
  final List<int> lineStarts;

  final List<int> source;

  String cachedText;

  Source(this.lineStarts, this.source);

  /// Return the text corresponding to [line] which is a 1-based line
  /// number. The returned line contains no line separators.
  String getTextLine(int line) {
    RangeError.checkValueInInterval(line, 1, lineStarts.length, 'line');
    if (source == null) return null;

    cachedText ??= utf8.decode(source, allowMalformed: true);
    // -1 as line numbers start at 1.
    int index = line - 1;
    if (index + 1 == lineStarts.length) {
      // Last line.
      return cachedText.substring(lineStarts[index]);
    } else if (index < lineStarts.length) {
      // We subtract 1 from the next line for two reasons:
      // 1. If the file isn't terminated by a newline, that index is invalid.
      // 2. To remove the newline at the end of the line.
      int endOfLine = lineStarts[index + 1] - 1;
      if (endOfLine > index && cachedText[endOfLine - 1] == "\r") {
        --endOfLine; // Windows line endings.
      }
      return cachedText.substring(lineStarts[index], endOfLine);
    }
    // This shouldn't happen: should have been caught by the range check above.
    throw "Internal error";
  }

  /// Translates an offset to line and column numbers in the given file.
  Location getLocation(Uri file, int offset) {
    RangeError.checkValueInInterval(offset, 0, lineStarts.last, 'offset');
    int low = 0, high = lineStarts.length - 1;
    while (low < high) {
      int mid = high - ((high - low) >> 1); // Get middle, rounding up.
      int pivot = lineStarts[mid];
      if (pivot <= offset) {
        low = mid;
      } else {
        high = mid - 1;
      }
    }
    int lineIndex = low;
    int lineStart = lineStarts[lineIndex];
    int lineNumber = 1 + lineIndex;
    int columnNumber = 1 + offset - lineStart;
    return new Location(file, lineNumber, columnNumber);
  }
}

/// Returns the [Reference] object for the given member.
///
/// Returns `null` if the member is `null`.
Reference getMemberReference(Member member) {
  return member?.reference;
}

/// Returns the [Reference] object for the given class.
///
/// Returns `null` if the class is `null`.
Reference getClassReference(Class class_) {
  return class_?.reference;
}

/// Returns the canonical name of [member], or throws an exception if the
/// member has not been assigned a canonical name yet.
///
/// Returns `null` if the member is `null`.
CanonicalName getCanonicalNameOfMember(Member member) {
  if (member == null) return null;
  if (member.canonicalName == null) {
    throw '$member has no canonical name';
  }
  return member.canonicalName;
}

/// Returns the canonical name of [class_], or throws an exception if the
/// class has not been assigned a canonical name yet.
///
/// Returns `null` if the class is `null`.
CanonicalName getCanonicalNameOfClass(Class class_) {
  if (class_ == null) return null;
  if (class_.canonicalName == null) {
    throw '$class_ has no canonical name';
  }
  return class_.canonicalName;
}

/// Returns the canonical name of [library], or throws an exception if the
/// library has not been assigned a canonical name yet.
///
/// Returns `null` if the library is `null`.
CanonicalName getCanonicalNameOfLibrary(Library library) {
  if (library == null) return null;
  if (library.canonicalName == null) {
    throw '$library has no canonical name';
  }
  return library.canonicalName;
}

int listHashCode(List list) {
  return list.fold(0, (int value, Object item) => value ^ item.hashCode);
}

int mapHashCode(Map map) {
  int value = 0;
  for (final Object x in map.keys) value ^= x.hashCode;
  for (final Object x in map.values) value ^= x.hashCode;
  return value;
}

bool listEquals(List a, List b) {
  if (a.length != b.length) return false;
  for (int i = 0; i < a.length; i++) {
    if (a[i] != b[i]) return false;
  }
  return true;
}

bool mapEquals(Map a, Map b) {
  if (a.length != b.length) return false;
  for (final Object key in a.keys) {
    if (!b.containsKey(key) || a[key] != b[key]) return false;
  }
  return true;
}

/// Returns the canonical name of [typedef_], or throws an exception if the
/// typedef has not been assigned a canonical name yet.
///
/// Returns `null` if the typedef is `null`.
CanonicalName getCanonicalNameOfTypedef(Typedef typedef_) {
  if (typedef_ == null) return null;
  if (typedef_.canonicalName == null) {
    throw '$typedef_ has no canonical name';
  }
  return typedef_.canonicalName;
}

/// Annotation describing information which is not part of Dart semantics; in
/// other words, if this information (or any information it refers to) changes,
/// static analysis and runtime behavior of the library are unaffected.
const informative = null;

Location _getLocationInComponent(Component component, Uri fileUri, int offset) {
  if (component != null) {
    return component.getLocation(fileUri, offset);
  } else {
    return new Location(fileUri, TreeNode.noOffset, TreeNode.noOffset);
  }
}
