eb18db2116
This bit is required to support synthesized field used for the late lowering. These fields should not be seen as part of the interface, that is, classes that implement the class declaring the late field should not be required to override these fields. Closes #41436 Change-Id: I9a98322bdd1842b46bde34fff1176a7577672e0f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/142998 Reviewed-by: Jens Johansen <jensj@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
9110 lines
265 KiB
Dart
9110 lines
265 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
/// -----------------------------------------------------------------------
|
|
/// WHEN CHANGING THIS FILE:
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// If you are adding/removing/modifying fields/classes of the AST, you must
|
|
/// also update the following files:
|
|
///
|
|
/// - binary/ast_to_binary.dart
|
|
/// - binary/ast_from_binary.dart
|
|
/// - text/ast_to_text.dart
|
|
/// - clone.dart
|
|
/// - binary.md
|
|
/// - type_checker.dart (if relevant)
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// ERROR HANDLING
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// As a rule of thumb, errors that can be detected statically are handled by
|
|
/// the frontend, typically by translating the erroneous code into a 'throw' or
|
|
/// a call to 'noSuchMethod'.
|
|
///
|
|
/// For example, there are no arity mismatches in static invocations, and
|
|
/// there are no direct invocations of a constructor on a abstract class.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// STATIC vs TOP-LEVEL
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// The term `static` includes both static class members and top-level members.
|
|
///
|
|
/// "Static class member" is the preferred term for non-top level statics.
|
|
///
|
|
/// Static class members are not lifted to the library level because mirrors
|
|
/// and stack traces can observe that they are class members.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// PROCEDURES
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// "Procedure" is an umbrella term for method, getter, setter, index-getter,
|
|
/// index-setter, operator overloader, and factory constructor.
|
|
///
|
|
/// Generative constructors, field initializers, local functions are NOT
|
|
/// procedures.
|
|
///
|
|
/// -----------------------------------------------------------------------
|
|
/// TRANSFORMATIONS
|
|
/// -----------------------------------------------------------------------
|
|
///
|
|
/// AST transformations can be performed using [TreeNode.replaceWith] or the
|
|
/// [Transformer] visitor class.
|
|
///
|
|
/// Use [Transformer] for bulk transformations that are likely to transform lots
|
|
/// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate
|
|
/// relatively few nodes. Or use whichever is more convenient.
|
|
///
|
|
/// The AST can also be mutated by direct field manipulation, but the user then
|
|
/// has to update parent pointers manually.
|
|
///
|
|
library kernel.ast;
|
|
|
|
import 'dart:collection' show ListBase;
|
|
import 'dart:convert' show utf8;
|
|
|
|
import 'visitor.dart';
|
|
export 'visitor.dart';
|
|
|
|
import 'canonical_name.dart' show CanonicalName;
|
|
export 'canonical_name.dart' show CanonicalName;
|
|
|
|
import 'default_language_version.dart';
|
|
|
|
import 'transformations/flags.dart';
|
|
import 'text/ast_to_text.dart' as astToText;
|
|
import 'core_types.dart';
|
|
import 'type_algebra.dart';
|
|
import 'type_environment.dart';
|
|
import 'src/assumptions.dart';
|
|
import 'src/text_util.dart';
|
|
|
|
/// Set this `true` to use fully qualified names in types for debugging.
|
|
const bool _verboseTypeToString = false;
|
|
|
|
/// Set this `true` to use fully qualified names in classes, extensions,
|
|
/// typedefs and members for debugging.
|
|
const bool _verboseMemberToString = false;
|
|
|
|
/// Any type of node in the IR.
|
|
abstract class Node {
|
|
const Node();
|
|
|
|
R accept<R>(Visitor<R> v);
|
|
void visitChildren(Visitor v);
|
|
|
|
/// Returns the textual representation of this node for use in debugging.
|
|
///
|
|
/// [toString] should only be used for debugging, but should not leak.
|
|
///
|
|
/// The data is generally bare-bones, but can easily be updated for your
|
|
/// specific debugging needs.
|
|
String toString();
|
|
|
|
/// Returns the textual representation of this node for use in debugging.
|
|
///
|
|
/// [toStringInternal] should only be used for debugging, but should not leak.
|
|
///
|
|
/// The data is generally bare-bones, but can easily be updated for your
|
|
/// specific debugging needs.
|
|
///
|
|
/// This method is called internally by toString methods to create conciser
|
|
/// textual representations.
|
|
String toStringInternal();
|
|
|
|
/// Returns the textual representation of this node for use in debugging.
|
|
///
|
|
/// Note that this adds some nodes to a static map to ensure consistent
|
|
/// naming, but that it thus also leaks memory. [leakingDebugToString] should
|
|
/// thus only be used for debugging and short-running test tools.
|
|
///
|
|
/// Synthetic names are cached globally to retain consistency across different
|
|
/// [leakingDebugToString] calls (hence the memory leak).
|
|
String leakingDebugToString() => astToText.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;
|
|
|
|
R accept<R>(TreeVisitor<R> v);
|
|
void visitChildren(Visitor v);
|
|
void 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.
|
|
abstract class NamedNode extends TreeNode {
|
|
final Reference reference;
|
|
|
|
NamedNode(Reference reference)
|
|
: this.reference = reference ?? new Reference() {
|
|
this.reference.node = this;
|
|
}
|
|
|
|
CanonicalName get canonicalName => reference?.canonicalName;
|
|
|
|
/// This is an advanced feature.
|
|
///
|
|
/// See [Component.relink] for a comprehensive description.
|
|
///
|
|
/// Makes sure the reference in this named node points to itself.
|
|
void _relinkNode() {
|
|
this.reference.node = this;
|
|
}
|
|
}
|
|
|
|
abstract class FileUriNode extends TreeNode {
|
|
/// The URI of the source file this node was loaded from.
|
|
Uri get fileUri;
|
|
}
|
|
|
|
abstract class Annotatable extends TreeNode {
|
|
List<Expression> get annotations;
|
|
void addAnnotation(Expression node);
|
|
}
|
|
|
|
/// Indirection between a reference and its definition.
|
|
///
|
|
/// There is only one reference object per [NamedNode].
|
|
class Reference {
|
|
CanonicalName canonicalName;
|
|
|
|
NamedNode _node;
|
|
|
|
NamedNode get node {
|
|
if (_node == null) {
|
|
// Either this is an unbound reference or it belongs to a lazy-loaded
|
|
// (and not yet loaded) class. If it belongs to a lazy-loaded class,
|
|
// load the class.
|
|
|
|
CanonicalName canonicalNameParent = canonicalName?.parent;
|
|
while (canonicalNameParent != null) {
|
|
if (canonicalNameParent.name.startsWith("@")) {
|
|
break;
|
|
}
|
|
canonicalNameParent = canonicalNameParent.parent;
|
|
}
|
|
if (canonicalNameParent != null) {
|
|
NamedNode parentNamedNode =
|
|
canonicalNameParent?.parent?.reference?._node;
|
|
if (parentNamedNode is Class) {
|
|
Class parentClass = parentNamedNode;
|
|
if (parentClass.lazyBuilder != null) {
|
|
parentClass.ensureLoaded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return _node;
|
|
}
|
|
|
|
void set node(NamedNode node) {
|
|
_node = node;
|
|
}
|
|
|
|
String toString() {
|
|
return "Reference to ${toStringInternal()}";
|
|
}
|
|
|
|
String toStringInternal() {
|
|
if (canonicalName != null) {
|
|
return '${canonicalName.toStringInternal()}';
|
|
}
|
|
if (node != null) {
|
|
return node.toStringInternal();
|
|
}
|
|
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
|
|
// ------------------------------------------------------------------------
|
|
|
|
enum NonNullableByDefaultCompiledMode { Disabled, Weak, Strong, Agnostic }
|
|
|
|
class Library extends NamedNode
|
|
implements Annotatable, 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;
|
|
|
|
int _languageVersionMajor;
|
|
int _languageVersionMinor;
|
|
int get languageVersionMajor =>
|
|
_languageVersionMajor ?? defaultLanguageVersionMajor;
|
|
int get languageVersionMinor =>
|
|
_languageVersionMinor ?? defaultLanguageVersionMinor;
|
|
void setLanguageVersion(int languageVersionMajor, int languageVersionMinor) {
|
|
if (languageVersionMajor == null || languageVersionMinor == null) {
|
|
throw new StateError("Trying to set language version 'null'");
|
|
}
|
|
_languageVersionMajor = languageVersionMajor;
|
|
_languageVersionMinor = languageVersionMinor;
|
|
}
|
|
|
|
static const int SyntheticFlag = 1 << 1;
|
|
static const int NonNullableByDefaultFlag = 1 << 2;
|
|
static const int NonNullableByDefaultModeBit1Weak = 1 << 3;
|
|
static const int NonNullableByDefaultModeBit2Strong = 1 << 4;
|
|
|
|
int flags = 0;
|
|
|
|
/// If true, the library is synthetic, for instance library that doesn't
|
|
/// represents an actual file and is created as the result of error recovery.
|
|
bool get isSynthetic => flags & SyntheticFlag != 0;
|
|
void set isSynthetic(bool value) {
|
|
flags = value ? (flags | SyntheticFlag) : (flags & ~SyntheticFlag);
|
|
}
|
|
|
|
bool get isNonNullableByDefault => (flags & NonNullableByDefaultFlag) != 0;
|
|
void set isNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | NonNullableByDefaultFlag)
|
|
: (flags & ~NonNullableByDefaultFlag);
|
|
}
|
|
|
|
NonNullableByDefaultCompiledMode get nonNullableByDefaultCompiledMode {
|
|
bool weak = (flags & NonNullableByDefaultModeBit1Weak) != 0;
|
|
bool strong = (flags & NonNullableByDefaultModeBit2Strong) != 0;
|
|
|
|
if (weak && strong) return NonNullableByDefaultCompiledMode.Agnostic;
|
|
if (strong) return NonNullableByDefaultCompiledMode.Strong;
|
|
if (weak) return NonNullableByDefaultCompiledMode.Weak;
|
|
return NonNullableByDefaultCompiledMode.Disabled;
|
|
}
|
|
|
|
void set nonNullableByDefaultCompiledMode(
|
|
NonNullableByDefaultCompiledMode mode) {
|
|
switch (mode) {
|
|
case NonNullableByDefaultCompiledMode.Disabled:
|
|
flags = (flags & ~NonNullableByDefaultModeBit1Weak) &
|
|
~NonNullableByDefaultModeBit2Strong;
|
|
break;
|
|
case NonNullableByDefaultCompiledMode.Weak:
|
|
flags = (flags | NonNullableByDefaultModeBit1Weak) &
|
|
~NonNullableByDefaultModeBit2Strong;
|
|
break;
|
|
case NonNullableByDefaultCompiledMode.Strong:
|
|
flags = (flags & ~NonNullableByDefaultModeBit1Weak) |
|
|
NonNullableByDefaultModeBit2Strong;
|
|
break;
|
|
case NonNullableByDefaultCompiledMode.Agnostic:
|
|
flags = (flags | NonNullableByDefaultModeBit1Weak) |
|
|
NonNullableByDefaultModeBit2Strong;
|
|
break;
|
|
}
|
|
}
|
|
|
|
String name;
|
|
|
|
/// Problems in this [Library] encoded as json objects.
|
|
///
|
|
/// Note that this field can be null, and by convention should be null if the
|
|
/// list is empty.
|
|
List<String> problemsAsJson;
|
|
|
|
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.
|
|
final List<Reference> additionalExports = <Reference>[];
|
|
|
|
@informative
|
|
final List<LibraryPart> parts;
|
|
|
|
final List<Typedef> typedefs;
|
|
final List<Class> classes;
|
|
final List<Extension> extensions;
|
|
final List<Procedure> procedures;
|
|
final List<Field> fields;
|
|
|
|
Library(this.importUri,
|
|
{this.name,
|
|
List<Expression> annotations,
|
|
List<LibraryDependency> dependencies,
|
|
List<LibraryPart> parts,
|
|
List<Typedef> typedefs,
|
|
List<Class> classes,
|
|
List<Extension> extensions,
|
|
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.extensions = extensions ?? <Extension>[],
|
|
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.extensions, this);
|
|
setParents(this.procedures, this);
|
|
setParents(this.fields, this);
|
|
}
|
|
|
|
Nullability get nullable {
|
|
return isNonNullableByDefault ? Nullability.nullable : Nullability.legacy;
|
|
}
|
|
|
|
Nullability get nonNullable {
|
|
return isNonNullableByDefault
|
|
? Nullability.nonNullable
|
|
: Nullability.legacy;
|
|
}
|
|
|
|
Nullability nullableIfTrue(bool isNullable) {
|
|
if (isNonNullableByDefault) {
|
|
return isNullable ? Nullability.nullable : Nullability.nonNullable;
|
|
}
|
|
return Nullability.legacy;
|
|
}
|
|
|
|
/// 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 addExtension(Extension extension) {
|
|
extension.parent = this;
|
|
extensions.add(extension);
|
|
}
|
|
|
|
void addField(Field field) {
|
|
field.parent = this;
|
|
fields.add(field);
|
|
}
|
|
|
|
void addProcedure(Procedure procedure) {
|
|
procedure.parent = this;
|
|
procedures.add(procedure);
|
|
}
|
|
|
|
void addTypedef(Typedef typedef_) {
|
|
typedef_.parent = this;
|
|
typedefs.add(typedef_);
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
assert(canonicalName != null);
|
|
for (int i = 0; i < typedefs.length; ++i) {
|
|
Typedef typedef_ = typedefs[i];
|
|
canonicalName.getChildFromTypedef(typedef_).bindTo(typedef_.reference);
|
|
}
|
|
for (int i = 0; i < fields.length; ++i) {
|
|
Field field = fields[i];
|
|
canonicalName.getChildFromMember(field).bindTo(field.reference);
|
|
}
|
|
for (int i = 0; i < procedures.length; ++i) {
|
|
Procedure member = procedures[i];
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (int i = 0; i < classes.length; ++i) {
|
|
Class class_ = classes[i];
|
|
canonicalName.getChild(class_.name).bindTo(class_.reference);
|
|
class_.computeCanonicalNames();
|
|
}
|
|
for (int i = 0; i < extensions.length; ++i) {
|
|
Extension extension = extensions[i];
|
|
canonicalName.getChild(extension.name).bindTo(extension.reference);
|
|
}
|
|
}
|
|
|
|
/// This is an advanced feature. Use of this method should be coordinated
|
|
/// with the kernel team.
|
|
///
|
|
/// See [Component.relink] for a comprehensive description.
|
|
///
|
|
/// Makes sure all references in named nodes in this library points to said
|
|
/// named node.
|
|
void relink() {
|
|
_relinkNode();
|
|
assert(canonicalName != null);
|
|
for (int i = 0; i < typedefs.length; ++i) {
|
|
Typedef typedef_ = typedefs[i];
|
|
typedef_._relinkNode();
|
|
}
|
|
for (int i = 0; i < fields.length; ++i) {
|
|
Field field = fields[i];
|
|
field._relinkNode();
|
|
}
|
|
for (int i = 0; i < procedures.length; ++i) {
|
|
Procedure member = procedures[i];
|
|
member._relinkNode();
|
|
}
|
|
for (int i = 0; i < classes.length; ++i) {
|
|
Class class_ = classes[i];
|
|
class_.relink();
|
|
}
|
|
for (int i = 0; i < extensions.length; ++i) {
|
|
Extension extension = extensions[i];
|
|
extension._relinkNode();
|
|
}
|
|
}
|
|
|
|
void addDependency(LibraryDependency node) {
|
|
dependencies.add(node..parent = this);
|
|
}
|
|
|
|
void addPart(LibraryPart node) {
|
|
parts.add(node..parent = this);
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitLibrary(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(dependencies, v);
|
|
visitList(parts, v);
|
|
visitList(typedefs, v);
|
|
visitList(classes, v);
|
|
visitList(extensions, 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(extensions, 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() => libraryNameToString(this);
|
|
String toStringInternal() => libraryNameToString(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);
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitLibraryDependency(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
visitList(combinators, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
transformList(combinators, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LibraryDependency(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitLibraryPart(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LibraryPart(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
R accept<R>(TreeVisitor<R> v) => v.visitCombinator(this);
|
|
|
|
@override
|
|
visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Combinator(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Declaration of a type alias.
|
|
class Typedef extends NamedNode implements FileUriNode {
|
|
/// The URI of the source file that contains the declaration of this typedef.
|
|
Uri fileUri;
|
|
List<Expression> annotations = const <Expression>[];
|
|
String name;
|
|
final List<TypeParameter> typeParameters;
|
|
DartType type;
|
|
|
|
// The following two fields describe parameters of the underlying type when
|
|
// that is a function type. They are needed to keep such attributes as names
|
|
// and annotations. When the underlying type is not a function type, they are
|
|
// empty.
|
|
final List<TypeParameter> typeParametersOfFunctionType;
|
|
final List<VariableDeclaration> positionalParameters;
|
|
final List<VariableDeclaration> namedParameters;
|
|
|
|
Typedef(this.name, this.type,
|
|
{Reference reference,
|
|
this.fileUri,
|
|
List<TypeParameter> typeParameters,
|
|
List<TypeParameter> typeParametersOfFunctionType,
|
|
List<VariableDeclaration> positionalParameters,
|
|
List<VariableDeclaration> namedParameters})
|
|
: this.typeParameters = typeParameters ?? <TypeParameter>[],
|
|
this.typeParametersOfFunctionType =
|
|
typeParametersOfFunctionType ?? <TypeParameter>[],
|
|
this.positionalParameters =
|
|
positionalParameters ?? <VariableDeclaration>[],
|
|
this.namedParameters = namedParameters ?? <VariableDeclaration>[],
|
|
super(reference) {
|
|
setParents(this.typeParameters, this);
|
|
}
|
|
|
|
Library get enclosingLibrary => parent;
|
|
|
|
R accept<R>(TreeVisitor<R> 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;
|
|
}
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Typedef(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return qualifiedTypedefNameToString(this,
|
|
includeLibraryName: _verboseMemberToString);
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
|
|
/// List-wrapper that marks the parent-class as dirty if the list is modified.
|
|
///
|
|
/// The idea being, that for non-dirty classes (classes just loaded from dill)
|
|
/// the canonical names has already been calculated, and recalculating them is
|
|
/// not needed. If, however, we change anything, recalculation of the canonical
|
|
/// names can be needed.
|
|
class DirtifyingList<E> extends ListBase<E> {
|
|
final Class dirtifyClass;
|
|
final List<E> wrapped;
|
|
|
|
DirtifyingList(this.dirtifyClass, this.wrapped);
|
|
|
|
@override
|
|
int get length {
|
|
return wrapped.length;
|
|
}
|
|
|
|
@override
|
|
void set length(int length) {
|
|
dirtifyClass.dirty = true;
|
|
wrapped.length = length;
|
|
}
|
|
|
|
@override
|
|
E operator [](int index) {
|
|
return wrapped[index];
|
|
}
|
|
|
|
@override
|
|
void operator []=(int index, E value) {
|
|
dirtifyClass.dirty = true;
|
|
wrapped[index] = value;
|
|
}
|
|
}
|
|
|
|
/// Declaration of a regular class or a mixin application.
|
|
///
|
|
/// Mixin applications may not contain fields or procedures, as they implicitly
|
|
/// use those from its mixed-in type. However, the IR does not enforce this
|
|
/// rule directly, as doing so can obstruct transformations. It is possible to
|
|
/// transform a mixin application to become a regular class, and vice versa.
|
|
class Class extends NamedNode implements Annotatable, 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.
|
|
List<Expression> annotations = const <Expression>[];
|
|
|
|
/// Name of the class.
|
|
///
|
|
/// Must be non-null and must be unique within the library.
|
|
///
|
|
/// The name may contain characters that are not valid in a Dart identifier,
|
|
/// in particular, the symbol '&' is used in class names generated for mixin
|
|
/// applications.
|
|
String name;
|
|
|
|
// Must match serialized bit positions.
|
|
static const int LevelMask = 0x3; // Bits 0 and 1.
|
|
static const int FlagAbstract = 1 << 2;
|
|
static const int FlagEnum = 1 << 3;
|
|
static const int FlagAnonymousMixin = 1 << 4;
|
|
static const int FlagEliminatedMixin = 1 << 5;
|
|
static const int FlagMixinDeclaration = 1 << 6;
|
|
static const int FlagHasConstConstructor = 1 << 7;
|
|
|
|
int flags = 0;
|
|
|
|
bool get isAbstract => flags & FlagAbstract != 0;
|
|
|
|
void set isAbstract(bool value) {
|
|
flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
|
|
}
|
|
|
|
/// Whether this class is an enum.
|
|
bool get isEnum => flags & FlagEnum != 0;
|
|
|
|
void set isEnum(bool value) {
|
|
flags = value ? (flags | FlagEnum) : (flags & ~FlagEnum);
|
|
}
|
|
|
|
/// Whether this class is a synthetic implementation created for each
|
|
/// mixed-in class. For example the following code:
|
|
/// class Z extends A with B, C, D {}
|
|
/// class A {}
|
|
/// class B {}
|
|
/// class C {}
|
|
/// class D {}
|
|
/// ...creates:
|
|
/// abstract class _Z&A&B extends A mixedIn B {}
|
|
/// abstract class _Z&A&B&C extends A&B mixedIn C {}
|
|
/// abstract class _Z&A&B&C&D extends A&B&C mixedIn D {}
|
|
/// class Z extends _Z&A&B&C&D {}
|
|
/// All X&Y classes are marked as synthetic.
|
|
bool get isAnonymousMixin => flags & FlagAnonymousMixin != 0;
|
|
|
|
void set isAnonymousMixin(bool value) {
|
|
flags =
|
|
value ? (flags | FlagAnonymousMixin) : (flags & ~FlagAnonymousMixin);
|
|
}
|
|
|
|
/// Whether this class was transformed from a mixin application.
|
|
/// In such case, its mixed-in type was pulled into the end of implemented
|
|
/// types list.
|
|
bool get isEliminatedMixin => flags & FlagEliminatedMixin != 0;
|
|
|
|
void set isEliminatedMixin(bool value) {
|
|
flags =
|
|
value ? (flags | FlagEliminatedMixin) : (flags & ~FlagEliminatedMixin);
|
|
}
|
|
|
|
/// True if this class was a mixin declaration in Dart.
|
|
///
|
|
/// Mixins are declared in Dart with the `mixin` keyword. They are compiled
|
|
/// to Kernel classes.
|
|
bool get isMixinDeclaration => flags & FlagMixinDeclaration != 0;
|
|
|
|
void set isMixinDeclaration(bool value) {
|
|
flags = value
|
|
? (flags | FlagMixinDeclaration)
|
|
: (flags & ~FlagMixinDeclaration);
|
|
}
|
|
|
|
/// True if this class declares one or more constant constructors.
|
|
bool get hasConstConstructor => flags & FlagHasConstConstructor != 0;
|
|
|
|
void set hasConstConstructor(bool value) {
|
|
flags = value
|
|
? (flags | FlagHasConstConstructor)
|
|
: (flags & ~FlagHasConstConstructor);
|
|
}
|
|
|
|
List<Supertype> superclassConstraints() {
|
|
var constraints = <Supertype>[];
|
|
|
|
// Not a mixin declaration.
|
|
if (!isMixinDeclaration) return constraints;
|
|
|
|
// Otherwise we have a left-linear binary tree (subtrees are supertype and
|
|
// mixedInType) of constraints, where all the interior nodes are anonymous
|
|
// mixin applications.
|
|
Supertype current = supertype;
|
|
while (current != null && current.classNode.isAnonymousMixin) {
|
|
Class currentClass = current.classNode;
|
|
assert(currentClass.implementedTypes.length == 2);
|
|
Substitution substitution = Substitution.fromSupertype(current);
|
|
constraints.add(
|
|
substitution.substituteSupertype(currentClass.implementedTypes[1]));
|
|
current =
|
|
substitution.substituteSupertype(currentClass.implementedTypes[0]);
|
|
}
|
|
return constraints..add(current);
|
|
}
|
|
|
|
/// The URI of the source file this class was loaded from.
|
|
Uri fileUri;
|
|
|
|
final List<TypeParameter> typeParameters;
|
|
|
|
/// The immediate super type, or `null` if this is the root class.
|
|
Supertype supertype;
|
|
|
|
/// The mixed-in type if this is a mixin application, otherwise `null`.
|
|
Supertype mixedInType;
|
|
|
|
/// The types from the `implements` clause.
|
|
final List<Supertype> implementedTypes;
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// If non-null, the function that will have to be called to fill-out the
|
|
/// content of this class. Note that this should not be called directly
|
|
/// though.
|
|
void Function() lazyBuilder;
|
|
|
|
/// Makes sure the class is loaded, i.e. the fields, procedures etc have been
|
|
/// loaded from the dill. Generally, one should not need to call this as it is
|
|
/// done automatically when accessing the lists.
|
|
void ensureLoaded() {
|
|
if (lazyBuilder != null) {
|
|
var lazyBuilderLocal = lazyBuilder;
|
|
lazyBuilder = null;
|
|
lazyBuilderLocal();
|
|
}
|
|
}
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// Used for adding fields when reading the dill file.
|
|
final List<Field> fieldsInternal;
|
|
DirtifyingList<Field> _fieldsView;
|
|
|
|
/// Fields declared in the class.
|
|
///
|
|
/// For mixin applications this should be empty.
|
|
List<Field> get fields {
|
|
ensureLoaded();
|
|
// If already dirty the caller just might as well add stuff directly too.
|
|
if (dirty) return fieldsInternal;
|
|
_fieldsView ??= new DirtifyingList(this, fieldsInternal);
|
|
return _fieldsView;
|
|
}
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// Used for adding constructors when reading the dill file.
|
|
final List<Constructor> constructorsInternal;
|
|
DirtifyingList<Constructor> _constructorsView;
|
|
|
|
/// Constructors declared in the class.
|
|
List<Constructor> get constructors {
|
|
ensureLoaded();
|
|
// If already dirty the caller just might as well add stuff directly too.
|
|
if (dirty) return constructorsInternal;
|
|
_constructorsView ??= new DirtifyingList(this, constructorsInternal);
|
|
return _constructorsView;
|
|
}
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// Used for adding procedures when reading the dill file.
|
|
final List<Procedure> proceduresInternal;
|
|
DirtifyingList<Procedure> _proceduresView;
|
|
|
|
/// Procedures declared in the class.
|
|
///
|
|
/// For mixin applications this should only contain forwarding stubs.
|
|
List<Procedure> get procedures {
|
|
ensureLoaded();
|
|
// If already dirty the caller just might as well add stuff directly too.
|
|
if (dirty) return proceduresInternal;
|
|
_proceduresView ??= new DirtifyingList(this, proceduresInternal);
|
|
return _proceduresView;
|
|
}
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// Used for adding redirecting factory constructor when reading the dill
|
|
/// file.
|
|
final List<RedirectingFactoryConstructor>
|
|
redirectingFactoryConstructorsInternal;
|
|
DirtifyingList<RedirectingFactoryConstructor>
|
|
_redirectingFactoryConstructorsView;
|
|
|
|
/// Redirecting factory constructors declared in the class.
|
|
///
|
|
/// For mixin applications this should be empty.
|
|
List<RedirectingFactoryConstructor> get redirectingFactoryConstructors {
|
|
ensureLoaded();
|
|
// If already dirty the caller just might as well add stuff directly too.
|
|
if (dirty) return redirectingFactoryConstructorsInternal;
|
|
_redirectingFactoryConstructorsView ??=
|
|
new DirtifyingList(this, redirectingFactoryConstructorsInternal);
|
|
return _redirectingFactoryConstructorsView;
|
|
}
|
|
|
|
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.fieldsInternal = fields ?? <Field>[],
|
|
this.constructorsInternal = constructors ?? <Constructor>[],
|
|
this.proceduresInternal = procedures ?? <Procedure>[],
|
|
this.redirectingFactoryConstructorsInternal =
|
|
redirectingFactoryConstructors ?? <RedirectingFactoryConstructor>[],
|
|
super(reference) {
|
|
setParents(this.typeParameters, this);
|
|
setParents(this.constructorsInternal, this);
|
|
setParents(this.proceduresInternal, this);
|
|
setParents(this.fieldsInternal, this);
|
|
setParents(this.redirectingFactoryConstructorsInternal, this);
|
|
this.isAbstract = isAbstract;
|
|
this.isAnonymousMixin = isAnonymousMixin;
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
assert(canonicalName != null);
|
|
if (!dirty) return;
|
|
for (int i = 0; i < fields.length; ++i) {
|
|
Field member = fields[i];
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (int i = 0; i < procedures.length; ++i) {
|
|
Procedure member = procedures[i];
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (int i = 0; i < constructors.length; ++i) {
|
|
Constructor member = constructors[i];
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
for (int i = 0; i < redirectingFactoryConstructors.length; ++i) {
|
|
RedirectingFactoryConstructor member = redirectingFactoryConstructors[i];
|
|
canonicalName.getChildFromMember(member).bindTo(member.reference);
|
|
}
|
|
dirty = false;
|
|
}
|
|
|
|
/// This is an advanced feature. Use of this method should be coordinated
|
|
/// with the kernel team.
|
|
///
|
|
/// See [Component.relink] for a comprehensive description.
|
|
///
|
|
/// Makes sure all references in named nodes in this class points to said
|
|
/// named node.
|
|
void relink() {
|
|
this.reference.node = this;
|
|
for (int i = 0; i < fields.length; ++i) {
|
|
Field member = fields[i];
|
|
member._relinkNode();
|
|
}
|
|
for (int i = 0; i < procedures.length; ++i) {
|
|
Procedure member = procedures[i];
|
|
member._relinkNode();
|
|
}
|
|
for (int i = 0; i < constructors.length; ++i) {
|
|
Constructor member = constructors[i];
|
|
member._relinkNode();
|
|
}
|
|
for (int i = 0; i < redirectingFactoryConstructors.length; ++i) {
|
|
RedirectingFactoryConstructor member = redirectingFactoryConstructors[i];
|
|
member._relinkNode();
|
|
}
|
|
dirty = false;
|
|
}
|
|
|
|
/// The immediate super class, or `null` if this is the root class.
|
|
Class get superclass => supertype?.classNode;
|
|
|
|
/// The mixed-in class if this is a mixin application, otherwise `null`.
|
|
///
|
|
/// Note that this may itself be a mixin application. Use [mixin] to get the
|
|
/// class that has the fields and procedures.
|
|
Class get mixedInClass => mixedInType?.classNode;
|
|
|
|
/// The class that declares the field and procedures of this class.
|
|
Class get mixin => mixedInClass?.mixin ?? this;
|
|
|
|
bool get isMixinApplication => mixedInType != null;
|
|
|
|
String get demangledName {
|
|
if (isAnonymousMixin) return nameAsMixinApplication;
|
|
assert(!name.contains('&'));
|
|
return name;
|
|
}
|
|
|
|
String get nameAsMixinApplication {
|
|
assert(isAnonymousMixin);
|
|
return demangleMixinApplicationName(name);
|
|
}
|
|
|
|
String get nameAsMixinApplicationSubclass {
|
|
assert(isAnonymousMixin);
|
|
return demangleMixinApplicationSubclassName(name);
|
|
}
|
|
|
|
/// Members declared in this class.
|
|
///
|
|
/// This getter is for convenience, not efficiency. Consider manually
|
|
/// iterating the members to speed up code in production.
|
|
Iterable<Member> get members => <Iterable<Member>>[
|
|
fields,
|
|
constructors,
|
|
procedures,
|
|
redirectingFactoryConstructors
|
|
].expand((x) => x);
|
|
|
|
/// The immediately extended, mixed-in, and implemented types.
|
|
///
|
|
/// This getter is for convenience, not efficiency. Consider manually
|
|
/// iterating the super types to speed up code in production.
|
|
Iterable<Supertype> get supers => <Iterable<Supertype>>[
|
|
supertype == null ? const [] : [supertype],
|
|
mixedInType == null ? const [] : [mixedInType],
|
|
implementedTypes
|
|
].expand((x) => x);
|
|
|
|
/// The library containing this class.
|
|
Library get enclosingLibrary => parent;
|
|
|
|
/// Internal. Should *ONLY* be used from within kernel.
|
|
///
|
|
/// If true we have to compute canonical names for all children of this class.
|
|
/// if false we can skip it.
|
|
bool dirty = true;
|
|
|
|
/// 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) {
|
|
dirty = true;
|
|
member.parent = this;
|
|
if (member is Constructor) {
|
|
constructorsInternal.add(member);
|
|
} else if (member is Procedure) {
|
|
proceduresInternal.add(member);
|
|
} else if (member is Field) {
|
|
fieldsInternal.add(member);
|
|
} else if (member is RedirectingFactoryConstructor) {
|
|
redirectingFactoryConstructorsInternal.add(member);
|
|
} else {
|
|
throw new ArgumentError(member);
|
|
}
|
|
}
|
|
|
|
void addAnnotation(Expression node) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(node);
|
|
node.parent = this;
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitClass(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitClassReference(this);
|
|
|
|
Supertype get asRawSupertype {
|
|
return new Supertype(this,
|
|
new List<DartType>.filled(typeParameters.length, const DynamicType()));
|
|
}
|
|
|
|
Supertype get asThisSupertype {
|
|
return new Supertype(
|
|
this, getAsTypeArguments(typeParameters, this.enclosingLibrary));
|
|
}
|
|
|
|
/// Returns the type of `this` for the class using [coreTypes] for caching.
|
|
InterfaceType getThisType(CoreTypes coreTypes, Nullability nullability) {
|
|
return coreTypes.thisInterfaceType(this, nullability);
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this class, consistent with
|
|
/// the names used across all [toString] calls.
|
|
// TODO(johnniwinther): Remove test dependencies on Class.toString();
|
|
@override
|
|
String toString() =>
|
|
qualifiedClassNameToString(this, includeLibraryName: true);
|
|
|
|
@override
|
|
String toStringInternal() => qualifiedClassNameToString(this,
|
|
includeLibraryName: _verboseMemberToString);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// Declaration of an extension.
|
|
///
|
|
/// The members are converted into top-level procedures and only accessible
|
|
/// by reference in the [Extension] node.
|
|
class Extension extends NamedNode implements FileUriNode {
|
|
/// Name of the extension.
|
|
///
|
|
/// If unnamed, the extension will be given a synthesized name by the
|
|
/// front end.
|
|
String name;
|
|
|
|
/// The URI of the source file this class was loaded from.
|
|
Uri fileUri;
|
|
|
|
/// Type parameters declared on the extension.
|
|
final List<TypeParameter> typeParameters;
|
|
|
|
/// The type in the 'on clause' of the extension declaration.
|
|
///
|
|
/// For instance A in:
|
|
///
|
|
/// class A {}
|
|
/// extension B on A {}
|
|
///
|
|
DartType onType;
|
|
|
|
/// The members declared by the extension.
|
|
///
|
|
/// The members are converted into top-level members and only accessible
|
|
/// by reference through [ExtensionMemberDescriptor].
|
|
final List<ExtensionMemberDescriptor> members;
|
|
|
|
Extension(
|
|
{this.name,
|
|
List<TypeParameter> typeParameters,
|
|
this.onType,
|
|
List<Reference> members,
|
|
this.fileUri,
|
|
Reference reference})
|
|
: this.typeParameters = typeParameters ?? <TypeParameter>[],
|
|
this.members = members ?? <ExtensionMemberDescriptor>[],
|
|
super(reference) {
|
|
setParents(this.typeParameters, this);
|
|
}
|
|
|
|
Library get enclosingLibrary => parent;
|
|
|
|
@override
|
|
R accept<R>(TreeVisitor<R> v) => v.visitExtension(this);
|
|
|
|
@override
|
|
visitChildren(Visitor v) {
|
|
visitList(typeParameters, v);
|
|
onType?.accept(v);
|
|
}
|
|
|
|
@override
|
|
transformChildren(Transformer v) {
|
|
transformList(typeParameters, v, this);
|
|
if (onType != null) {
|
|
onType = v.visitDartType(onType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Extension(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return qualifiedExtensionNameToString(this,
|
|
includeLibraryName: _verboseMemberToString);
|
|
}
|
|
}
|
|
|
|
enum ExtensionMemberKind {
|
|
Field,
|
|
Method,
|
|
Getter,
|
|
Setter,
|
|
Operator,
|
|
TearOff,
|
|
}
|
|
|
|
/// Information about an member declaration in an extension.
|
|
class ExtensionMemberDescriptor {
|
|
static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
|
|
|
|
/// The name of the extension member.
|
|
///
|
|
/// The name of the generated top-level member is mangled to ensure
|
|
/// uniqueness. This name is used to lookup an extension method the
|
|
/// extension itself.
|
|
Name name;
|
|
|
|
/// [ExtensionMemberKind] kind of the original member.
|
|
///
|
|
/// An extension method is converted into a regular top-level method. For
|
|
/// instance:
|
|
///
|
|
/// class A {
|
|
/// var foo;
|
|
/// }
|
|
/// extension B on A {
|
|
/// get bar => this.foo;
|
|
/// }
|
|
///
|
|
/// will be converted into
|
|
///
|
|
/// class A {}
|
|
/// B|get#bar(A #this) => #this.foo;
|
|
///
|
|
/// where `B|get#bar` is the synthesized name of the top-level method and
|
|
/// `#this` is the synthesized parameter that holds represents `this`.
|
|
///
|
|
ExtensionMemberKind kind;
|
|
|
|
int flags = 0;
|
|
|
|
/// Reference to the top-level member created for the extension method.
|
|
Reference member;
|
|
|
|
ExtensionMemberDescriptor(
|
|
{this.name, this.kind, bool isStatic: false, this.member}) {
|
|
this.isStatic = isStatic;
|
|
}
|
|
|
|
/// Return `true` if the extension method was declared as `static`.
|
|
bool get isStatic => flags & FlagStatic != 0;
|
|
|
|
void set isStatic(bool value) {
|
|
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// MEMBERS
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class Member extends NamedNode implements Annotatable, 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.
|
|
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;
|
|
|
|
R accept<R>(MemberVisitor<R> v);
|
|
acceptReference(MemberReferenceVisitor v);
|
|
|
|
/// Returns true if this is an abstract procedure.
|
|
bool get isAbstract => false;
|
|
|
|
/// Returns true if the member has the 'const' modifier.
|
|
bool get isConst;
|
|
|
|
/// True if this is a field or non-setter procedure.
|
|
///
|
|
/// Note that operators and factories return `true`, even though there are
|
|
/// normally no calls to their getter.
|
|
bool get hasGetter;
|
|
|
|
/// True if this is a setter or a mutable field.
|
|
bool get hasSetter;
|
|
|
|
/// True if this is a non-static field or procedure.
|
|
bool get isInstanceMember;
|
|
|
|
/// True if the member has the `external` modifier, implying that the
|
|
/// implementation is provided by the backend, and is not necessarily written
|
|
/// in Dart.
|
|
///
|
|
/// Members can have this modifier independently of whether the enclosing
|
|
/// library is external.
|
|
bool get isExternal;
|
|
void set isExternal(bool value);
|
|
|
|
/// If `true` this member is compiled from a member declared in an extension
|
|
/// declaration.
|
|
///
|
|
/// For instance `field`, `method1` and `method2` in:
|
|
///
|
|
/// extension A on B {
|
|
/// static var field;
|
|
/// B method1() => this;
|
|
/// static B method2() => new B();
|
|
/// }
|
|
///
|
|
bool get isExtensionMember;
|
|
|
|
/// If `true` this member is defined in a library for which non-nullable by
|
|
/// default is enabled.
|
|
bool get isNonNullableByDefault;
|
|
void set isNonNullableByDefault(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.
|
|
@override
|
|
String toString() => toStringInternal();
|
|
|
|
@override
|
|
String toStringInternal() => qualifiedMemberNameToString(this,
|
|
includeLibraryName: _verboseMemberToString);
|
|
|
|
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,
|
|
bool isLate: false,
|
|
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.isLate = isLate;
|
|
this.hasImplicitGetter = hasImplicitGetter ?? !isStatic;
|
|
this.hasImplicitSetter = hasImplicitSetter ??
|
|
(!isStatic &&
|
|
!isConst &&
|
|
(!isFinal || (isLate && initializer == null)));
|
|
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;
|
|
static const int FlagLate = 1 << 7;
|
|
static const int FlagExtensionMember = 1 << 8;
|
|
static const int FlagNonNullableByDefault = 1 << 9;
|
|
static const int FlagInternalImplementation = 1 << 10;
|
|
|
|
/// 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;
|
|
|
|
@override
|
|
bool get isExtensionMember => flags & FlagExtensionMember != 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;
|
|
|
|
/// Whether the field is declared with the `late` keyword.
|
|
bool get isLate => flags & FlagLate != 0;
|
|
|
|
// If `true` this field is not part of the interface but only part of the
|
|
// class members.
|
|
//
|
|
// This is `true` for instance for synthesized fields added for the late
|
|
// lowering.
|
|
bool get isInternalImplementation => flags & FlagInternalImplementation != 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 isExtensionMember(bool value) {
|
|
flags =
|
|
value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void set isLate(bool value) {
|
|
flags = value ? (flags | FlagLate) : (flags & ~FlagLate);
|
|
}
|
|
|
|
void set isInternalImplementation(bool value) {
|
|
flags = value
|
|
? (flags | FlagInternalImplementation)
|
|
: (flags & ~FlagInternalImplementation);
|
|
}
|
|
|
|
/// 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 || isLate && initializer == null;
|
|
|
|
bool get isExternal => false;
|
|
void set isExternal(bool value) {
|
|
if (value) throw 'Fields cannot be external';
|
|
}
|
|
|
|
@override
|
|
bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0;
|
|
|
|
@override
|
|
void set isNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagNonNullableByDefault)
|
|
: (flags & ~FlagNonNullableByDefault);
|
|
}
|
|
|
|
R accept<R>(MemberVisitor<R> 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<TreeNode>(v);
|
|
initializer?.parent = this;
|
|
}
|
|
}
|
|
|
|
DartType get getterType => type;
|
|
DartType get setterType => hasSetter ? 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;
|
|
static const int FlagNonNullableByDefault = 1 << 3;
|
|
|
|
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;
|
|
|
|
@override
|
|
bool get isExtensionMember => false;
|
|
|
|
@override
|
|
bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0;
|
|
|
|
@override
|
|
void set isNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagNonNullableByDefault)
|
|
: (flags & ~FlagNonNullableByDefault);
|
|
}
|
|
|
|
R accept<R>(MemberVisitor<R> 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<TreeNode>(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;
|
|
static const int FlagNonNullableByDefault = 1 << 2;
|
|
|
|
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;
|
|
|
|
@override
|
|
bool get isExtensionMember => false;
|
|
|
|
bool get isUnresolved => targetReference == null;
|
|
|
|
@override
|
|
bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0;
|
|
|
|
@override
|
|
void set isNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagNonNullableByDefault)
|
|
: (flags & ~FlagNonNullableByDefault);
|
|
}
|
|
|
|
Member get target => targetReference?.asMember;
|
|
|
|
void set target(Member member) {
|
|
assert(member is Constructor ||
|
|
(member is Procedure && member.kind == ProcedureKind.Factory));
|
|
targetReference = getMemberReference(member);
|
|
}
|
|
|
|
R accept<R>(MemberVisitor<R> 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-`.
|
|
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,
|
|
bool isMemberSignature: false,
|
|
bool isExtensionMember: false,
|
|
int transformerFlags: 0,
|
|
Uri fileUri,
|
|
Reference reference,
|
|
Member forwardingStubSuperTarget,
|
|
Member forwardingStubInterfaceTarget})
|
|
: this._byReferenceRenamed(name, kind, function,
|
|
isAbstract: isAbstract,
|
|
isStatic: isStatic,
|
|
isExternal: isExternal,
|
|
isConst: isConst,
|
|
isForwardingStub: isForwardingStub,
|
|
isMemberSignature: isMemberSignature,
|
|
isForwardingSemiStub: isForwardingSemiStub,
|
|
isExtensionMember: isExtensionMember,
|
|
transformerFlags: transformerFlags,
|
|
fileUri: fileUri,
|
|
reference: reference,
|
|
forwardingStubSuperTargetReference:
|
|
getMemberReference(forwardingStubSuperTarget),
|
|
forwardingStubInterfaceTargetReference:
|
|
getMemberReference(forwardingStubInterfaceTarget));
|
|
|
|
Procedure._byReferenceRenamed(Name name, this.kind, this.function,
|
|
{bool isAbstract: false,
|
|
bool isStatic: false,
|
|
bool isExternal: false,
|
|
bool isConst: false,
|
|
bool isForwardingStub: false,
|
|
bool isForwardingSemiStub: false,
|
|
bool isMemberSignature: false,
|
|
bool isExtensionMember: 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.isMemberSignature = isMemberSignature;
|
|
this.isExtensionMember = isExtensionMember;
|
|
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;
|
|
static const int FlagExtensionMember = 1 << 8;
|
|
static const int FlagMemberSignature = 1 << 9;
|
|
static const int FlagNonNullableByDefault = 1 << 10;
|
|
|
|
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;
|
|
|
|
/// If set, this method is a class member added to show the type of an
|
|
/// inherited member.
|
|
///
|
|
/// This is used when the type of the inherited member cannot be computed
|
|
/// directly from the member(s) in the supertypes. For instance in case of
|
|
/// an nnbd opt-out class inheriting from an nnbd opt-in class; here all nnbd-
|
|
/// aware types are replaced with legacy types in the inherited signature.
|
|
bool get isMemberSignature => flags & FlagMemberSignature != 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;
|
|
|
|
@override
|
|
bool get isExtensionMember => flags & FlagExtensionMember != 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 isMemberSignature(bool value) {
|
|
flags =
|
|
value ? (flags | FlagMemberSignature) : (flags & ~FlagMemberSignature);
|
|
}
|
|
|
|
void set isRedirectingFactoryConstructor(bool value) {
|
|
flags = value
|
|
? (flags | FlagRedirectingFactoryConstructor)
|
|
: (flags & ~FlagRedirectingFactoryConstructor);
|
|
}
|
|
|
|
void set isNoSuchMethodForwarder(bool value) {
|
|
flags = value
|
|
? (flags | FlagNoSuchMethodForwarder)
|
|
: (flags & ~FlagNoSuchMethodForwarder);
|
|
}
|
|
|
|
void set isExtensionMember(bool value) {
|
|
flags =
|
|
value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember);
|
|
}
|
|
|
|
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;
|
|
|
|
@override
|
|
bool get isNonNullableByDefault => flags & FlagNonNullableByDefault != 0;
|
|
|
|
@override
|
|
void set isNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagNonNullableByDefault)
|
|
: (flags & ~FlagNonNullableByDefault);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
R accept<R>(MemberVisitor<R> 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<TreeNode>(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
|
|
DartType get getterType {
|
|
return isGetter
|
|
? function.returnType
|
|
: function.computeFunctionType(enclosingLibrary.nonNullable);
|
|
}
|
|
|
|
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;
|
|
|
|
R accept<R>(InitializerVisitor<R> 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 {
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitInvalidInitializer(this);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "InvalidInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitFieldInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
field?.acceptReference(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FieldInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitSuperInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SuperInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitRedirectingInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "RedirectingInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitLocalInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept<TreeNode>(v);
|
|
variable?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LocalInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class AssertInitializer extends Initializer {
|
|
AssertStatement statement;
|
|
|
|
AssertInitializer(this.statement) {
|
|
statement.parent = this;
|
|
}
|
|
|
|
R accept<R>(InitializerVisitor<R> v) => v.visitAssertInitializer(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
statement.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
statement = statement.accept<TreeNode>(v);
|
|
statement.parent = this;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "AssertInitializer(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// FUNCTIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A function declares parameters and has a body.
|
|
///
|
|
/// This may occur in a procedure, constructor, function expression, or local
|
|
/// function declaration.
|
|
class FunctionNode extends TreeNode {
|
|
/// End offset in the source file it comes from. Valid values are from 0 and
|
|
/// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available
|
|
/// (this is the default if none is specifically set).
|
|
int fileEndOffset = TreeNode.noOffset;
|
|
|
|
/// Kernel async marker for the function.
|
|
///
|
|
/// See also [dartAsyncMarker].
|
|
AsyncMarker asyncMarker;
|
|
|
|
/// Dart async marker for the function.
|
|
///
|
|
/// See also [asyncMarker].
|
|
///
|
|
/// A Kernel function can represent a Dart function with a different async
|
|
/// marker.
|
|
///
|
|
/// For example, when async/await is translated away,
|
|
/// a Dart async function might be represented by a Kernel sync function.
|
|
AsyncMarker dartAsyncMarker;
|
|
|
|
List<TypeParameter> typeParameters;
|
|
int requiredParameterCount;
|
|
List<VariableDeclaration> positionalParameters;
|
|
List<VariableDeclaration> namedParameters;
|
|
DartType returnType; // Not null.
|
|
Statement _body;
|
|
|
|
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, isRequired: node.isRequired);
|
|
}
|
|
|
|
/// Returns the function type of the node reusing its type parameters.
|
|
///
|
|
/// This getter works similarly to [functionType], but reuses type parameters
|
|
/// of the function node (or the class enclosing it -- see the comment on
|
|
/// [functionType] about constructors of generic classes) in the result. It
|
|
/// is useful in some contexts, especially when reasoning about the function
|
|
/// type of the enclosing generic function and in combination with
|
|
/// [FunctionType.withoutTypeParameters].
|
|
FunctionType computeThisFunctionType(Nullability nullability) {
|
|
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,
|
|
nullability,
|
|
namedParameters: named,
|
|
typeParameters: typeParametersCopy,
|
|
requiredParameterCount: requiredParameterCount);
|
|
}
|
|
|
|
/// Returns the function type of the function node.
|
|
///
|
|
/// If the function node describes a generic function, the resulting function
|
|
/// type will be generic. If the function node describes a constructor of a
|
|
/// generic class, the resulting function type will be generic with its type
|
|
/// parameters constructed after those of the class. In both cases, if the
|
|
/// resulting function type is generic, a fresh set of type parameters is used
|
|
/// in it.
|
|
FunctionType computeFunctionType(Nullability nullability) {
|
|
return typeParameters.isEmpty
|
|
? computeThisFunctionType(nullability)
|
|
: getFreshTypeParameters(typeParameters)
|
|
.applyToFunctionType(computeThisFunctionType(nullability));
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> 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<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FunctionNode(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
enum AsyncMarker {
|
|
// Do not change the order of these, the frontends depend on it.
|
|
Sync,
|
|
SyncStar,
|
|
Async,
|
|
AsyncStar,
|
|
|
|
// `SyncYielding` is a marker that tells Dart VM that this function is an
|
|
// artificial closure introduced by an async transformer which desugared all
|
|
// async syntax into a combination of native yields and helper method calls.
|
|
//
|
|
// Native yields (formatted as `[yield]`) are semantically close to
|
|
// `yield x` statement: they denote a yield/resume point within a function
|
|
// but are completely decoupled from the notion of iterators. When
|
|
// execution of the closure reaches `[yield] x` it stops and return the
|
|
// value of `x` to the caller. If closure is called again it continues
|
|
// to the next statement after this yield as if it was suspended and resumed.
|
|
//
|
|
// Consider this example:
|
|
//
|
|
// g() {
|
|
// var :await_jump_var = 0;
|
|
// var :await_ctx_var;
|
|
//
|
|
// f(x) yielding {
|
|
// [yield] '${x}:0';
|
|
// [yield] '${x}:1';
|
|
// [yield] '${x}:2';
|
|
// }
|
|
//
|
|
// return f;
|
|
// }
|
|
//
|
|
// print(f('a')); /* prints 'a:0', :await_jump_var = 1 */
|
|
// print(f('b')); /* prints 'b:1', :await_jump_var = 2 */
|
|
// print(f('c')); /* prints 'c:2', :await_jump_var = 3 */
|
|
//
|
|
// Note: currently Dart VM implicitly relies on async transformer to
|
|
// inject certain artificial variables into g (like `:await_jump_var`).
|
|
// As such SyncYielding and native yield are not intended to be used on their
|
|
// own, but are rather an implementation artifact of the async transformer
|
|
// itself.
|
|
SyncYielding,
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// EXPRESSIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class Expression extends TreeNode {
|
|
/// Returns the static type of the expression.
|
|
DartType getStaticType(StaticTypeContext context);
|
|
|
|
/// Returns the static type of the expression as an instantiation of
|
|
/// [superclass].
|
|
///
|
|
/// Shouldn't be used on code compiled in legacy mode, as this method assumes
|
|
/// the IR is strongly typed.
|
|
///
|
|
/// This method furthermore assumes that the type of the expression actually
|
|
/// is a subtype of (some instantiation of) the given [superclass].
|
|
/// If this is not the case, either an exception is thrown or the raw type of
|
|
/// [superclass] is returned.
|
|
InterfaceType getStaticTypeAsInstanceOf(
|
|
Class superclass, StaticTypeContext context) {
|
|
// 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 context.typeEnvironment.coreTypes
|
|
.rawType(superclass, context.nonNullable);
|
|
}
|
|
var type = getStaticType(context);
|
|
while (type is TypeParameterType) {
|
|
TypeParameterType typeParameterType = type;
|
|
type =
|
|
typeParameterType.promotedBound ?? typeParameterType.parameter.bound;
|
|
}
|
|
if (type == context.typeEnvironment.nullType) {
|
|
return context.typeEnvironment.coreTypes
|
|
.bottomInterfaceType(superclass, context.nullable);
|
|
}
|
|
if (type is InterfaceType) {
|
|
List<DartType> upcastTypeArguments = context.typeEnvironment
|
|
.getTypeArgumentsAsInstanceOf(type, superclass);
|
|
if (upcastTypeArguments != null) {
|
|
return new InterfaceType(
|
|
superclass, type.nullability, upcastTypeArguments);
|
|
}
|
|
} else if (type is BottomType) {
|
|
return context.typeEnvironment.coreTypes
|
|
.bottomInterfaceType(superclass, context.nonNullable);
|
|
}
|
|
context.typeEnvironment
|
|
.typeError(this, '$type is not a subtype of $superclass');
|
|
return context.typeEnvironment.coreTypes
|
|
.rawType(superclass, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg);
|
|
}
|
|
|
|
/// An expression containing compile-time errors.
|
|
///
|
|
/// Should throw a runtime error when evaluated.
|
|
///
|
|
/// The [fileOffset] of an [InvalidExpression] indicates the location in the
|
|
/// tree where the expression occurs, rather than the location of the error.
|
|
class InvalidExpression extends Expression {
|
|
String message;
|
|
|
|
InvalidExpression(this.message);
|
|
|
|
DartType getStaticType(StaticTypeContext context) => const BottomType();
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitInvalidExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitInvalidExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "InvalidExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Read a local variable, a local function, or a function parameter.
|
|
class VariableGet extends Expression {
|
|
VariableDeclaration variable;
|
|
DartType promotedType; // Null if not promoted.
|
|
|
|
VariableGet(this.variable, [this.promotedType]) : assert(variable != null);
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return promotedType ?? variable.type;
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitVariableGet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitVariableGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
promotedType?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (promotedType != null) {
|
|
promotedType = v.visitDartType(promotedType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "VariableGet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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) : assert(variable != null) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitVariableSet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitVariableSet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "VariableSet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x.field`.
|
|
///
|
|
/// This may invoke a getter, read a field, or tear off a method.
|
|
class PropertyGet extends Expression {
|
|
Expression receiver;
|
|
Name name;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
PropertyGet(Expression receiver, Name name, [Member interfaceTarget])
|
|
: this.byReference(receiver, name, getMemberReference(interfaceTarget));
|
|
|
|
PropertyGet.byReference(
|
|
this.receiver, this.name, this.interfaceTargetReference) {
|
|
receiver?.parent = this;
|
|
}
|
|
|
|
Member get interfaceTarget => interfaceTargetReference?.asMember;
|
|
|
|
void set interfaceTarget(Member member) {
|
|
interfaceTargetReference = getMemberReference(member);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
var interfaceTarget = this.interfaceTarget;
|
|
if (interfaceTarget != null) {
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
var receiverType =
|
|
receiver.getStaticTypeAsInstanceOf(superclass, context);
|
|
return Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(interfaceTarget.getterType);
|
|
}
|
|
// Treat the properties of Object specially.
|
|
String nameString = name.name;
|
|
if (nameString == 'hashCode') {
|
|
return context.typeEnvironment.coreTypes.intRawType(context.nonNullable);
|
|
} else if (nameString == 'runtimeType') {
|
|
return context.typeEnvironment.coreTypes.typeRawType(context.nonNullable);
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitPropertyGet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitPropertyGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "PropertyGet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "${receiver.toStringInternal()}.${name.toStringInternal()}";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitPropertySet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitPropertySet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "PropertySet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitDirectPropertyGet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitDirectPropertyGet(this, arg);
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
Class superclass = target.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, context);
|
|
return Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(target.getterType);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "DirectPropertyGet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitDirectPropertySet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitDirectPropertySet(this, arg);
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
@override
|
|
String toString() {
|
|
return "DirectPropertySet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitDirectMethodInvocation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitDirectMethodInvocation(this, arg);
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
if (context.typeEnvironment.isOverloadedArithmeticOperator(target)) {
|
|
return context.typeEnvironment.getTypeOfOverloadedArithmetic(
|
|
receiver.getStaticType(context),
|
|
arguments.positional[0].getStaticType(context));
|
|
}
|
|
Class superclass = target.enclosingClass;
|
|
var receiverType = receiver.getStaticTypeAsInstanceOf(superclass, context);
|
|
var returnType = Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(target.function.returnType);
|
|
return Substitution.fromPairs(
|
|
target.function.typeParameters, arguments.types)
|
|
.substituteType(returnType);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "DirectMethodInvocation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
if (interfaceTarget == null) {
|
|
// TODO(johnniwinther): SuperPropertyGet without a target should be
|
|
// replaced by invalid expressions.
|
|
return const DynamicType();
|
|
}
|
|
Class declaringClass = interfaceTarget.enclosingClass;
|
|
if (declaringClass.typeParameters.isEmpty) {
|
|
return interfaceTarget.getterType;
|
|
}
|
|
List<DartType> receiverArguments = context.typeEnvironment
|
|
.getTypeArgumentsAsInstanceOf(context.thisType, declaringClass);
|
|
return Substitution.fromPairs(
|
|
declaringClass.typeParameters, receiverArguments)
|
|
.substituteType(interfaceTarget.getterType);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSuperPropertyGet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSuperPropertyGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SuperPropertyGet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSuperPropertySet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSuperPropertySet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SuperPropertySet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) => target.getterType;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitStaticGet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitStaticGet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "StaticGet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitStaticSet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitStaticSet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "StaticSet(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// The arguments to a function call, divided into type arguments,
|
|
/// positional arguments, and named arguments.
|
|
class Arguments extends TreeNode {
|
|
final List<DartType> types;
|
|
final List<Expression> positional;
|
|
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, Library library) {
|
|
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.withDefaultNullabilityForLibrary(
|
|
p, library))
|
|
.toList());
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> 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);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Arguments(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
// TODO(jensj): Make (much) better.
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// A named argument, `name: value`.
|
|
class NamedExpression extends TreeNode {
|
|
String name;
|
|
Expression value;
|
|
|
|
NamedExpression(this.name, this.value) {
|
|
value?.parent = this;
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitNamedExpression(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "NamedExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Common super class for [DirectMethodInvocation], [MethodInvocation],
|
|
/// [SuperMethodInvocation], [StaticInvocation], and [ConstructorInvocation].
|
|
abstract class InvocationExpression extends Expression {
|
|
Arguments get arguments;
|
|
set arguments(Arguments value);
|
|
|
|
/// Name of the invoked method.
|
|
///
|
|
/// May be `null` if the target is a synthetic static member without a name.
|
|
Name get name;
|
|
}
|
|
|
|
/// Expression of form `x.foo(y)`.
|
|
class MethodInvocation extends InvocationExpression {
|
|
Expression receiver;
|
|
Name name;
|
|
Arguments arguments;
|
|
|
|
Reference interfaceTargetReference;
|
|
|
|
MethodInvocation(Expression receiver, Name name, Arguments arguments,
|
|
[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(StaticTypeContext context) {
|
|
var interfaceTarget = this.interfaceTarget;
|
|
if (interfaceTarget != null) {
|
|
if (interfaceTarget is Procedure &&
|
|
context.typeEnvironment
|
|
.isOverloadedArithmeticOperator(interfaceTarget)) {
|
|
return context.typeEnvironment.getTypeOfOverloadedArithmetic(
|
|
receiver.getStaticType(context),
|
|
arguments.positional[0].getStaticType(context));
|
|
}
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
var receiverType =
|
|
receiver.getStaticTypeAsInstanceOf(superclass, context);
|
|
var getterType = Substitution.fromInterfaceType(receiverType)
|
|
.substituteType(interfaceTarget.getterType);
|
|
if (getterType is FunctionType) {
|
|
Substitution substitution;
|
|
if (getterType.typeParameters.length == arguments.types.length) {
|
|
substitution = Substitution.fromPairs(
|
|
getterType.typeParameters, arguments.types);
|
|
} else {
|
|
// TODO(johnniwinther): The front end should normalize the type
|
|
// argument count or create an invalid expression in case of method
|
|
// invocations with invalid type argument count.
|
|
substitution = Substitution.fromPairs(
|
|
getterType.typeParameters,
|
|
getterType.typeParameters
|
|
.map((TypeParameter typeParameter) =>
|
|
typeParameter.defaultType)
|
|
.toList());
|
|
}
|
|
return substitution.substituteType(getterType.returnType);
|
|
}
|
|
// The front end currently do not replace a property call `o.foo()`, where
|
|
// `foo` is a field or getter, with a function call on the property,
|
|
// `o.foo.call()`, so we look up the call method explicitly here.
|
|
// TODO(johnniwinther): Remove this when the front end performs the
|
|
// correct replacement.
|
|
if (getterType is InterfaceType) {
|
|
Member member = context.typeEnvironment
|
|
.getInterfaceMember(getterType.classNode, new Name('call'));
|
|
if (member != null) {
|
|
DartType callType = member.getterType;
|
|
if (callType is FunctionType) {
|
|
return Substitution.fromInterfaceType(getterType)
|
|
.substituteType(callType.returnType);
|
|
}
|
|
}
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
if (name.name == 'call') {
|
|
var receiverType = receiver.getStaticType(context);
|
|
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 context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitMethodInvocation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitMethodInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
receiver?.accept(v);
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (receiver != null) {
|
|
receiver = receiver.accept<TreeNode>(v);
|
|
receiver?.parent = this;
|
|
}
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "MethodInvocation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
if (interfaceTarget == null) return const DynamicType();
|
|
Class superclass = interfaceTarget.enclosingClass;
|
|
List<DartType> receiverTypeArguments = context.typeEnvironment
|
|
.getTypeArgumentsAsInstanceOf(context.thisType, superclass);
|
|
DartType returnType =
|
|
Substitution.fromPairs(superclass.typeParameters, receiverTypeArguments)
|
|
.substituteType(interfaceTarget.function.returnType);
|
|
return Substitution.fromPairs(
|
|
interfaceTarget.function.typeParameters, arguments.types)
|
|
.substituteType(returnType);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSuperMethodInvocation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSuperMethodInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
interfaceTarget?.acceptReference(v);
|
|
name?.accept(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SuperMethodInvocation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
return Substitution.fromPairs(
|
|
target.function.typeParameters, arguments.types)
|
|
.substituteType(target.function.returnType);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitStaticInvocation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitStaticInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "StaticInvocation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "${targetReference.toStringInternal()}, "
|
|
"${arguments.toStringInternal()}";
|
|
}
|
|
}
|
|
|
|
/// Expression of form `new Foo(x)` or `const Foo(x)`.
|
|
///
|
|
/// The provided arguments might not match the parameters of the target.
|
|
//
|
|
// DESIGN TODO: Should we pass type arguments in a separate field
|
|
// `classTypeArguments`? They are quite different from type arguments to
|
|
// generic functions.
|
|
class ConstructorInvocation extends InvocationExpression {
|
|
Reference targetReference;
|
|
Arguments arguments;
|
|
bool isConst;
|
|
|
|
Name get name => target?.name;
|
|
|
|
ConstructorInvocation(Constructor target, Arguments arguments,
|
|
{bool isConst: false})
|
|
: this.byReference(getMemberReference(target), arguments,
|
|
isConst: isConst);
|
|
|
|
ConstructorInvocation.byReference(this.targetReference, this.arguments,
|
|
{this.isConst: false}) {
|
|
arguments?.parent = this;
|
|
}
|
|
|
|
Constructor get target => targetReference?.asConstructor;
|
|
|
|
void set target(Constructor target) {
|
|
targetReference = getMemberReference(target);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return arguments.types.isEmpty
|
|
? context.typeEnvironment.coreTypes
|
|
.rawType(target.enclosingClass, context.nonNullable)
|
|
: new InterfaceType(
|
|
target.enclosingClass, context.nonNullable, arguments.types);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitConstructorInvocation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitConstructorInvocation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
target?.acceptReference(v);
|
|
arguments?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (arguments != null) {
|
|
arguments = arguments.accept<TreeNode>(v);
|
|
arguments?.parent = this;
|
|
}
|
|
}
|
|
|
|
// TODO(dmitryas): Change the getter into a method that accepts a CoreTypes.
|
|
InterfaceType get constructedType {
|
|
Class enclosingClass = target.enclosingClass;
|
|
// TODO(dmitryas): Get raw type from a CoreTypes object if arguments is
|
|
// empty.
|
|
return arguments.types.isEmpty
|
|
? new InterfaceType(
|
|
enclosingClass, Nullability.legacy, const <DartType>[])
|
|
: new InterfaceType(
|
|
enclosingClass, Nullability.legacy, arguments.types);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ConstructorInvocation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
FunctionType type = expression.getStaticType(context);
|
|
return Substitution.fromPairs(type.typeParameters, typeArguments)
|
|
.substituteType(type.withoutTypeParameters);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitInstantiation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitInstantiation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
transformTypeList(typeArguments, v);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Instantiation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitNot(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) => v.visitNot(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept<TreeNode>(v);
|
|
operand?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Not(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitLogicalExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitLogicalExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
left?.accept(v);
|
|
right?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (left != null) {
|
|
left = left.accept<TreeNode>(v);
|
|
left?.parent = this;
|
|
}
|
|
if (right != null) {
|
|
right = right.accept<TreeNode>(v);
|
|
right?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LogicalExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) => staticType;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitConditionalExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A 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<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (then != null) {
|
|
then = then.accept<TreeNode>(v);
|
|
then?.parent = this;
|
|
}
|
|
if (otherwise != null) {
|
|
otherwise = otherwise.accept<TreeNode>(v);
|
|
otherwise?.parent = this;
|
|
}
|
|
if (staticType != null) {
|
|
staticType = v.visitDartType(staticType);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ConditionalExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "${condition.toStringInternal()} ? "
|
|
"${then.toStringInternal()} : "
|
|
"${otherwise.toStringInternal()}";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.stringRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitStringConcatenation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitStringConcatenation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(expressions, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(expressions, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "StringConcatenation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Concatenate lists into a single list.
|
|
///
|
|
/// If [lists] is empty then an empty list is returned.
|
|
///
|
|
/// These arise from spread and control-flow elements in const list literals.
|
|
/// They are only present before constant evaluation, or within unevaluated
|
|
/// constants in constant expressions.
|
|
class ListConcatenation extends Expression {
|
|
DartType typeArgument;
|
|
final List<Expression> lists;
|
|
|
|
ListConcatenation(this.lists, {this.typeArgument: const DynamicType()}) {
|
|
setParents(lists, this);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return context.typeEnvironment.listType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitListConcatenation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitListConcatenation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument?.accept(v);
|
|
visitList(lists, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
typeArgument = v.visitDartType(typeArgument);
|
|
transformList(lists, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ListConcatenation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Concatenate sets into a single set.
|
|
///
|
|
/// If [sets] is empty then an empty set is returned.
|
|
///
|
|
/// These arise from spread and control-flow elements in const set literals.
|
|
/// They are only present before constant evaluation, or within unevaluated
|
|
/// constants in constant expressions.
|
|
///
|
|
/// Duplicated values in or across the sets will result in a compile-time error
|
|
/// during constant evaluation.
|
|
class SetConcatenation extends Expression {
|
|
DartType typeArgument;
|
|
final List<Expression> sets;
|
|
|
|
SetConcatenation(this.sets, {this.typeArgument: const DynamicType()}) {
|
|
setParents(sets, this);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return context.typeEnvironment.setType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSetConcatenation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSetConcatenation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument?.accept(v);
|
|
visitList(sets, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
typeArgument = v.visitDartType(typeArgument);
|
|
transformList(sets, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SetConcatenation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Concatenate maps into a single map.
|
|
///
|
|
/// If [maps] is empty then an empty map is returned.
|
|
///
|
|
/// These arise from spread and control-flow elements in const map literals.
|
|
/// They are only present before constant evaluation, or within unevaluated
|
|
/// constants in constant expressions.
|
|
///
|
|
/// Duplicated keys in or across the maps will result in a compile-time error
|
|
/// during constant evaluation.
|
|
class MapConcatenation extends Expression {
|
|
DartType keyType;
|
|
DartType valueType;
|
|
final List<Expression> maps;
|
|
|
|
MapConcatenation(this.maps,
|
|
{this.keyType: const DynamicType(),
|
|
this.valueType: const DynamicType()}) {
|
|
setParents(maps, this);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return context.typeEnvironment
|
|
.mapType(keyType, valueType, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitMapConcatenation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitMapConcatenation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
keyType?.accept(v);
|
|
valueType?.accept(v);
|
|
visitList(maps, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
keyType = v.visitDartType(keyType);
|
|
valueType = v.visitDartType(valueType);
|
|
transformList(maps, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "MapConcatenation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Create an instance directly from the field values.
|
|
///
|
|
/// These expressions arise from const constructor calls when one or more field
|
|
/// initializing expressions, field initializers, assert initializers or unused
|
|
/// arguments contain unevaluated expressions. They only ever occur within
|
|
/// unevaluated constants in constant expressions.
|
|
class InstanceCreation extends Expression {
|
|
final Reference classReference;
|
|
final List<DartType> typeArguments;
|
|
final Map<Reference, Expression> fieldValues;
|
|
final List<AssertStatement> asserts;
|
|
final List<Expression> unusedArguments;
|
|
|
|
InstanceCreation(this.classReference, this.typeArguments, this.fieldValues,
|
|
this.asserts, this.unusedArguments) {
|
|
setParents(fieldValues.values.toList(), this);
|
|
setParents(asserts, this);
|
|
setParents(unusedArguments, this);
|
|
}
|
|
|
|
Class get classNode => classReference.asClass;
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return typeArguments.isEmpty
|
|
? context.typeEnvironment.coreTypes
|
|
.rawType(classNode, context.nonNullable)
|
|
: new InterfaceType(classNode, context.nonNullable, typeArguments);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitInstanceCreation(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitInstanceCreation(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
classReference.asClass.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
for (final Reference reference in fieldValues.keys) {
|
|
reference.asField.acceptReference(v);
|
|
}
|
|
for (final Expression value in fieldValues.values) {
|
|
value.accept(v);
|
|
}
|
|
visitList(asserts, v);
|
|
visitList(unusedArguments, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
fieldValues.forEach((Reference fieldRef, Expression value) {
|
|
Expression transformed = value.accept<TreeNode>(v);
|
|
if (transformed != null && !identical(value, transformed)) {
|
|
fieldValues[fieldRef] = transformed;
|
|
transformed.parent = this;
|
|
}
|
|
});
|
|
transformList(asserts, v, this);
|
|
transformList(unusedArguments, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "InstanceCreation(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// A marker indicating that a subexpression originates in a different source
|
|
/// file than the surrounding context.
|
|
///
|
|
/// These expressions arise from inlining of const variables during constant
|
|
/// evaluation. They only ever occur within unevaluated constants in constant
|
|
/// expressions.
|
|
class FileUriExpression extends Expression implements FileUriNode {
|
|
/// The URI of the source file in which the subexpression is located.
|
|
/// Can be different from the file containing the [FileUriExpression].
|
|
Uri fileUri;
|
|
|
|
Expression expression;
|
|
|
|
FileUriExpression(this.expression, this.fileUri) {
|
|
expression.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
expression.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitFileUriExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitFileUriExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
expression = expression.accept<TreeNode>(v)..parent = this;
|
|
}
|
|
|
|
Location _getLocationInEnclosingFile(int offset) {
|
|
return _getLocationInComponent(enclosingComponent, fileUri, offset);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FileUriExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Expression of form `x is T`.
|
|
class IsExpression extends Expression {
|
|
int flags = 0;
|
|
Expression operand;
|
|
DartType type;
|
|
|
|
IsExpression(this.operand, this.type) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
// Must match serialized bit positions.
|
|
static const int FlagForNonNullableByDefault = 1 << 0;
|
|
|
|
/// If `true`, this test take the nullability of [type] into account.
|
|
///
|
|
/// This is the case for is-tests written in libraries that are opted in to
|
|
/// the non nullable by default feature.
|
|
bool get isForNonNullableByDefault =>
|
|
flags & FlagForNonNullableByDefault != 0;
|
|
|
|
void set isForNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagForNonNullableByDefault)
|
|
: (flags & ~FlagForNonNullableByDefault);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitIsExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitIsExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept<TreeNode>(v);
|
|
operand?.parent = this;
|
|
}
|
|
type = v.visitDartType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "IsExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
static const int FlagCovarianceCheck = 1 << 1;
|
|
static const int FlagForDynamic = 1 << 2;
|
|
static const int FlagForNonNullableByDefault = 1 << 3;
|
|
|
|
/// If `true`, this test is an implicit down cast.
|
|
///
|
|
/// If `true` a TypeError should be thrown. If `false` a CastError should be
|
|
/// thrown.
|
|
bool get isTypeError => flags & FlagTypeError != 0;
|
|
|
|
void set isTypeError(bool value) {
|
|
flags = value ? (flags | FlagTypeError) : (flags & ~FlagTypeError);
|
|
}
|
|
|
|
/// If `true`, this test is needed to ensure soundness of covariant type
|
|
/// variables using in contravariant positions.
|
|
///
|
|
/// For instance
|
|
///
|
|
/// class Class<T> {
|
|
/// void Function(T) field;
|
|
/// Class(this.field);
|
|
/// }
|
|
/// main() {
|
|
/// Class<num> c = new Class<int>((int i) {});
|
|
/// void Function<num> field = c.field; // Check needed on `c.field`
|
|
/// field(0.5);
|
|
/// }
|
|
///
|
|
/// Here a covariant check `c.field as void Function(num)` is needed because
|
|
/// the field could be (and indeed is) not a subtype of the static type of
|
|
/// the expression.
|
|
bool get isCovarianceCheck => flags & FlagCovarianceCheck != 0;
|
|
|
|
void set isCovarianceCheck(bool value) {
|
|
flags =
|
|
value ? (flags | FlagCovarianceCheck) : (flags & ~FlagCovarianceCheck);
|
|
}
|
|
|
|
/// If `true`, this is an implicit down cast from an expression of type
|
|
/// `dynamic`.
|
|
bool get isForDynamic => flags & FlagForDynamic != 0;
|
|
|
|
void set isForDynamic(bool value) {
|
|
flags = value ? (flags | FlagForDynamic) : (flags & ~FlagForDynamic);
|
|
}
|
|
|
|
/// If `true`, this test take the nullability of [type] into account.
|
|
///
|
|
/// This is the case for is-tests written in libraries that are opted in to
|
|
/// the non nullable by default feature.
|
|
bool get isForNonNullableByDefault =>
|
|
flags & FlagForNonNullableByDefault != 0;
|
|
|
|
void set isForNonNullableByDefault(bool value) {
|
|
flags = value
|
|
? (flags | FlagForNonNullableByDefault)
|
|
: (flags & ~FlagForNonNullableByDefault);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) => type;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitAsExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitAsExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept<TreeNode>(v);
|
|
operand?.parent = this;
|
|
}
|
|
type = v.visitDartType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "AsExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "${operand.toStringInternal()} as ${type.toStringInternal()}";
|
|
}
|
|
}
|
|
|
|
/// Null check expression of form `x!`.
|
|
///
|
|
/// This expression was added as part of NNBD and is currently only created when
|
|
/// the 'non-nullable' experimental feature is enabled.
|
|
class NullCheck extends Expression {
|
|
Expression operand;
|
|
|
|
NullCheck(this.operand) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
DartType operandType = operand.getStaticType(context);
|
|
return operandType == context.typeEnvironment.nullType
|
|
? const NeverType(Nullability.nonNullable)
|
|
: operandType.withNullability(Nullability.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitNullCheck(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitNullCheck(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept<TreeNode>(v);
|
|
operand?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "NullCheck(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.stringRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitStringLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitStringLiteral(this, arg);
|
|
|
|
@override
|
|
String toString() {
|
|
return "StringLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "$value";
|
|
}
|
|
}
|
|
|
|
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(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.intRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitIntLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitIntLiteral(this, arg);
|
|
|
|
@override
|
|
String toString() {
|
|
return "IntLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "$value";
|
|
}
|
|
}
|
|
|
|
class DoubleLiteral extends BasicLiteral {
|
|
double value;
|
|
|
|
DoubleLiteral(this.value);
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.doubleRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitDoubleLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitDoubleLiteral(this, arg);
|
|
|
|
@override
|
|
String toString() {
|
|
return "DoubleLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "$value";
|
|
}
|
|
}
|
|
|
|
class BoolLiteral extends BasicLiteral {
|
|
bool value;
|
|
|
|
BoolLiteral(this.value);
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitBoolLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitBoolLiteral(this, arg);
|
|
|
|
@override
|
|
String toString() {
|
|
return "BoolLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "$value";
|
|
}
|
|
}
|
|
|
|
class NullLiteral extends BasicLiteral {
|
|
Object get value => null;
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.nullType;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitNullLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitNullLiteral(this, arg);
|
|
|
|
@override
|
|
String toString() {
|
|
return "NullLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "null";
|
|
}
|
|
}
|
|
|
|
class SymbolLiteral extends Expression {
|
|
String value; // Everything strictly after the '#'.
|
|
|
|
SymbolLiteral(this.value);
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.symbolRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSymbolLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSymbolLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SymbolLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "#$value";
|
|
}
|
|
}
|
|
|
|
class TypeLiteral extends Expression {
|
|
DartType type;
|
|
|
|
TypeLiteral(this.type);
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.typeRawType(context.nonNullable);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitTypeLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitTypeLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
type = v.visitDartType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "TypeLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "${type.toStringInternal()}";
|
|
}
|
|
}
|
|
|
|
class ThisExpression extends Expression {
|
|
DartType getStaticType(StaticTypeContext context) => context.thisType;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitThisExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitThisExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ThisExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class Rethrow extends Expression {
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.isNonNullableByDefault
|
|
? const NeverType(Nullability.nonNullable)
|
|
: const BottomType();
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitRethrow(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitRethrow(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Rethrow(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class Throw extends Expression {
|
|
Expression expression;
|
|
|
|
Throw(this.expression) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
context.isNonNullableByDefault
|
|
? const NeverType(Nullability.nonNullable)
|
|
: const BottomType();
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitThrow(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) => v.visitThrow(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Throw(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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(StaticTypeContext context) {
|
|
return context.typeEnvironment.listType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitListLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A 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);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ListLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class SetLiteral extends Expression {
|
|
bool isConst;
|
|
DartType typeArgument; // Not null, defaults to DynamicType.
|
|
final List<Expression> expressions;
|
|
|
|
SetLiteral(this.expressions,
|
|
{this.typeArgument: const DynamicType(), this.isConst: false}) {
|
|
assert(typeArgument != null);
|
|
setParents(expressions, this);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return context.typeEnvironment.setType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitSetLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitSetLiteral(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument?.accept(v);
|
|
visitList(expressions, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
typeArgument = v.visitDartType(typeArgument);
|
|
transformList(expressions, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SetLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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(StaticTypeContext context) {
|
|
return context.typeEnvironment
|
|
.mapType(keyType, valueType, context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitMapLiteral(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A 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);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "MapLiteral(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class MapEntry extends TreeNode {
|
|
Expression key;
|
|
Expression value;
|
|
|
|
MapEntry(this.key, this.value) {
|
|
key?.parent = this;
|
|
value?.parent = this;
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitMapEntry(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
key?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (key != null) {
|
|
key = key.accept<TreeNode>(v);
|
|
key?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "MapEntry(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Expression of form `await x`.
|
|
class AwaitExpression extends Expression {
|
|
Expression operand;
|
|
|
|
AwaitExpression(this.operand) {
|
|
operand?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return context.typeEnvironment.unfutureType(operand.getStaticType(context));
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitAwaitExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitAwaitExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
operand?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (operand != null) {
|
|
operand = operand.accept<TreeNode>(v);
|
|
operand?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "AwaitExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Common super-interface for [FunctionExpression] and [FunctionDeclaration].
|
|
abstract class LocalFunction implements TreeNode {
|
|
FunctionNode get function;
|
|
}
|
|
|
|
/// Expression of form `(x,y) => ...` or `(x,y) { ... }`
|
|
///
|
|
/// The arrow-body form `=> e` is desugared into `return e;`.
|
|
class FunctionExpression extends Expression implements LocalFunction {
|
|
FunctionNode function;
|
|
|
|
FunctionExpression(this.function) {
|
|
function?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) {
|
|
return function.computeFunctionType(context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitFunctionExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitFunctionExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (function != null) {
|
|
function = function.accept<TreeNode>(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FunctionExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class ConstantExpression extends Expression {
|
|
Constant constant;
|
|
DartType type;
|
|
|
|
ConstantExpression(this.constant, [this.type = const DynamicType()]) {
|
|
assert(constant != null);
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) => type;
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitConstantExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitConstantExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
constant?.acceptReference(v);
|
|
type?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
constant = v.visitConstant(constant);
|
|
type = v.visitDartType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ConstantExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) =>
|
|
body.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitLet(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) => v.visitLet(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept<TreeNode>(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Let(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class BlockExpression extends Expression {
|
|
Block body;
|
|
Expression value;
|
|
|
|
BlockExpression(this.body, this.value) {
|
|
body?.parent = this;
|
|
value?.parent = this;
|
|
}
|
|
|
|
DartType getStaticType(StaticTypeContext context) =>
|
|
value.getStaticType(context);
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitBlockExpression(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitBlockExpression(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
value?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
if (value != null) {
|
|
value = value.accept<TreeNode>(v);
|
|
value?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "BlockExpression(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
return context.typeEnvironment
|
|
.futureType(const DynamicType(), context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitLoadLibrary(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitLoadLibrary(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LoadLibrary(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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(StaticTypeContext context) {
|
|
return context.typeEnvironment.coreTypes.objectRawType(context.nonNullable);
|
|
}
|
|
|
|
R accept<R>(ExpressionVisitor<R> v) => v.visitCheckLibraryIsLoaded(this);
|
|
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
|
|
v.visitCheckLibraryIsLoaded(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "CheckLibraryIsLoaded(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// STATEMENTS
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class Statement extends TreeNode {
|
|
R accept<R>(StatementVisitor<R> v);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg);
|
|
}
|
|
|
|
class ExpressionStatement extends Statement {
|
|
Expression expression;
|
|
|
|
ExpressionStatement(this.expression) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitExpressionStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitExpressionStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ExpressionStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class Block extends Statement {
|
|
final List<Statement> statements;
|
|
|
|
Block(this.statements) {
|
|
// Ensure statements is mutable.
|
|
assert((statements
|
|
..add(null)
|
|
..removeLast()) !=
|
|
null);
|
|
setParents(statements, this);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitBlock(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A 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;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Block(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitAssertBlock(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A 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;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "AssertBlock(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class EmptyStatement extends Statement {
|
|
R accept<R>(StatementVisitor<R> v) => v.visitEmptyStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitEmptyStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "EmptyStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class AssertStatement extends Statement {
|
|
Expression condition;
|
|
Expression message; // May be null.
|
|
|
|
/// Character offset in the source where the assertion condition begins.
|
|
///
|
|
/// Note: This is not the offset into the UTF8 encoded `List<int>` source.
|
|
int conditionStartOffset;
|
|
|
|
/// Character offset in the source where the assertion condition ends.
|
|
///
|
|
/// Note: This is not the offset into the UTF8 encoded `List<int>` source.
|
|
int conditionEndOffset;
|
|
|
|
AssertStatement(this.condition,
|
|
{this.message, this.conditionStartOffset, this.conditionEndOffset}) {
|
|
condition?.parent = this;
|
|
message?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitAssertStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitAssertStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
message?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (message != null) {
|
|
message = message.accept<TreeNode>(v);
|
|
message?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "AssertStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitLabeledStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitLabeledStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "LabeledStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitBreakStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitBreakStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "BreakStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class WhileStatement extends Statement {
|
|
Expression condition;
|
|
Statement body;
|
|
|
|
WhileStatement(this.condition, this.body) {
|
|
condition?.parent = this;
|
|
body?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitWhileStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitWhileStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
condition?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (condition != null) {
|
|
condition = condition.accept<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "WhileStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class DoStatement extends Statement {
|
|
Statement body;
|
|
Expression condition;
|
|
|
|
DoStatement(this.body, this.condition) {
|
|
body?.parent = this;
|
|
condition?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitDoStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitDoStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
condition?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
if (condition != null) {
|
|
condition = condition.accept<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "DoStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitForStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A 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<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
transformList(updates, v, this);
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ForStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitForInStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitForInStatement(this, arg);
|
|
|
|
void visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
iterable?.accept(v);
|
|
body?.accept(v);
|
|
}
|
|
|
|
void transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept<TreeNode>(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (iterable != null) {
|
|
iterable = iterable.accept<TreeNode>(v);
|
|
iterable?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ForInStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitSwitchStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitSwitchStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
visitList(cases, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
transformList(cases, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SwitchStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
|
|
R accept<R>(TreeVisitor<R> 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<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "SwitchCase(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Jump to a case in an enclosing switch.
|
|
class ContinueSwitchStatement extends Statement {
|
|
SwitchCase target;
|
|
|
|
ContinueSwitchStatement(this.target);
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitContinueSwitchStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitContinueSwitchStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {}
|
|
transformChildren(Transformer v) {}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ContinueSwitchStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitIfStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A 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<TreeNode>(v);
|
|
condition?.parent = this;
|
|
}
|
|
if (then != null) {
|
|
then = then.accept<TreeNode>(v);
|
|
then?.parent = this;
|
|
}
|
|
if (otherwise != null) {
|
|
otherwise = otherwise.accept<TreeNode>(v);
|
|
otherwise?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "IfStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class ReturnStatement extends Statement {
|
|
Expression expression; // May be null.
|
|
|
|
ReturnStatement([this.expression]) {
|
|
expression?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitReturnStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitReturnStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "ReturnStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitTryCatch(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitTryCatch(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
visitList(catches, v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
transformList(catches, v, this);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "TryCatch(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> 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<TreeNode>(v);
|
|
exception?.parent = this;
|
|
}
|
|
if (stackTrace != null) {
|
|
stackTrace = stackTrace.accept<TreeNode>(v);
|
|
stackTrace?.parent = this;
|
|
}
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Catch(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class TryFinally extends Statement {
|
|
Statement body;
|
|
Statement finalizer;
|
|
|
|
TryFinally(this.body, this.finalizer) {
|
|
body?.parent = this;
|
|
finalizer?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitTryFinally(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitTryFinally(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
body?.accept(v);
|
|
finalizer?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (body != null) {
|
|
body = body.accept<TreeNode>(v);
|
|
body?.parent = this;
|
|
}
|
|
if (finalizer != null) {
|
|
finalizer = finalizer.accept<TreeNode>(v);
|
|
finalizer?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "TryFinally(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitYieldStatement(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitYieldStatement(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
expression?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (expression != null) {
|
|
expression = expression.accept<TreeNode>(v);
|
|
expression?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "YieldStatement(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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?
|
|
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.
|
|
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,
|
|
bool isLate: false,
|
|
bool isRequired: 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;
|
|
this.isLate = isLate;
|
|
this.isRequired = isRequired;
|
|
}
|
|
}
|
|
|
|
/// Creates a synthetic variable with the given expression as initializer.
|
|
VariableDeclaration.forValue(this.initializer,
|
|
{bool isFinal: true,
|
|
bool isConst: false,
|
|
bool isFieldFormal: false,
|
|
bool isLate: false,
|
|
bool isRequired: false,
|
|
this.type: const DynamicType()}) {
|
|
assert(type != null);
|
|
initializer?.parent = this;
|
|
this.isFinal = isFinal;
|
|
this.isConst = isConst;
|
|
this.isFieldFormal = isFieldFormal;
|
|
this.isLate = isLate;
|
|
this.isRequired = isRequired;
|
|
}
|
|
|
|
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;
|
|
static const int FlagLate = 1 << 6;
|
|
static const int FlagRequired = 1 << 7;
|
|
|
|
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;
|
|
|
|
/// Whether the variable is declared with the `late` keyword.
|
|
///
|
|
/// The `late` modifier is only supported on local variables and not on
|
|
/// parameters.
|
|
bool get isLate => flags & FlagLate != 0;
|
|
|
|
/// Whether the parameter is declared with the `required` keyword.
|
|
///
|
|
/// The `required` modifier is only supported on named parameters and not on
|
|
/// positional parameters and local variables.
|
|
bool get isRequired => flags & FlagRequired != 0;
|
|
|
|
/// Whether the variable is assignable.
|
|
///
|
|
/// This is `true` if the variable is neither constant nor final, or if it
|
|
/// is late final without an initializer.
|
|
bool get isAssignable {
|
|
if (isConst) return false;
|
|
if (isFinal) {
|
|
if (isLate) return initializer == null;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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 set isLate(bool value) {
|
|
flags = value ? (flags | FlagLate) : (flags & ~FlagLate);
|
|
}
|
|
|
|
void set isRequired(bool value) {
|
|
flags = value ? (flags | FlagRequired) : (flags & ~FlagRequired);
|
|
}
|
|
|
|
void clearAnnotations() {
|
|
annotations = const <Expression>[];
|
|
}
|
|
|
|
void addAnnotation(Expression annotation) {
|
|
if (annotations.isEmpty) {
|
|
annotations = <Expression>[];
|
|
}
|
|
annotations.add(annotation..parent = this);
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitVariableDeclaration(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A 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<TreeNode>(v);
|
|
initializer?.parent = this;
|
|
}
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this variable, consistent with
|
|
/// the names used across all [toString] calls.
|
|
String toString() {
|
|
return "VariableDeclaration(${toStringInternal()})";
|
|
}
|
|
|
|
String toStringInternal() {
|
|
return name ?? "null-named VariableDeclaration (${hashCode})";
|
|
}
|
|
}
|
|
|
|
/// Declaration a local function.
|
|
///
|
|
/// The body of the function may use [variable] as its self-reference.
|
|
class FunctionDeclaration extends Statement implements LocalFunction {
|
|
VariableDeclaration variable; // Is final and has no initializer.
|
|
FunctionNode function;
|
|
|
|
FunctionDeclaration(this.variable, this.function) {
|
|
variable?.parent = this;
|
|
function?.parent = this;
|
|
}
|
|
|
|
R accept<R>(StatementVisitor<R> v) => v.visitFunctionDeclaration(this);
|
|
R accept1<R, A>(StatementVisitor1<R, A> v, A arg) =>
|
|
v.visitFunctionDeclaration(this, arg);
|
|
|
|
visitChildren(Visitor v) {
|
|
variable?.accept(v);
|
|
function?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
if (variable != null) {
|
|
variable = variable.accept<TreeNode>(v);
|
|
variable?.parent = this;
|
|
}
|
|
if (function != null) {
|
|
function = function.accept<TreeNode>(v);
|
|
function?.parent = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FunctionDeclaration(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// NAMES
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A public name, or a private name qualified by a library.
|
|
///
|
|
/// Names are only used for expressions with dynamic dispatch, as all
|
|
/// statically resolved references are represented in nameless form.
|
|
///
|
|
/// [Name]s are immutable and compare based on structural equality, and they
|
|
/// are not AST nodes.
|
|
///
|
|
/// The [toString] method returns a human-readable string that includes the
|
|
/// library name for private names; uniqueness is not guaranteed.
|
|
abstract class Name implements Node {
|
|
final int hashCode;
|
|
final String name;
|
|
Reference get libraryName;
|
|
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;
|
|
}
|
|
|
|
R accept<R>(Visitor<R> v) => v.visitName(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
// DESIGN TODO: Should we visit the library as a library reference?
|
|
}
|
|
|
|
/// Returns the textual representation of this node for use in debugging.
|
|
///
|
|
/// Note that this adds some nodes to a static map to ensure consistent
|
|
/// naming, but that it thus also leaks memory.
|
|
String leakingDebugToString() => astToText.debugNodeToString(this);
|
|
}
|
|
|
|
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() => toStringInternal();
|
|
String toStringInternal() => library != null ? '$library::$name' : name;
|
|
|
|
Library get library => libraryName.asLibrary;
|
|
|
|
static int _computeHashCode(String name, Reference libraryName) {
|
|
// TODO(dmitryas): Factor in [libraryName] in a non-deterministic way into
|
|
// the result. Note, the previous code here was the following:
|
|
// return 131 * name.hashCode + 17 * libraryName.asLibrary._libraryId;
|
|
return name.hashCode;
|
|
}
|
|
}
|
|
|
|
class _PublicName extends Name {
|
|
Reference get libraryName => null;
|
|
Library get library => null;
|
|
bool get isPrivate => false;
|
|
|
|
_PublicName(String name) : super._internal(name.hashCode, name);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() => name;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// TYPES
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// Represents nullability of a type.
|
|
enum Nullability {
|
|
/// Non-legacy types not known to be nullable or non-nullable statically.
|
|
///
|
|
/// An example of such type is type T in the example below. Note that both
|
|
/// int and int? can be passed in for T, so an attempt to assign null to x is
|
|
/// a compile-time error as well as assigning x to y.
|
|
///
|
|
/// class A<T extends Object?> {
|
|
/// foo(T x) {
|
|
/// x = null; // Compile-time error.
|
|
/// Object y = x; // Compile-time error.
|
|
/// }
|
|
/// }
|
|
undetermined,
|
|
|
|
/// Nullable types are marked with the '?' modifier.
|
|
///
|
|
/// Null, dynamic, and void are nullable by default.
|
|
nullable,
|
|
|
|
/// Non-nullable types are types that aren't marked with the '?' modifier.
|
|
///
|
|
/// Note that Null, dynamic, and void that are nullable by default. Note also
|
|
/// that some types denoted by a type parameter without the '?' modifier can
|
|
/// be something else rather than non-nullable.
|
|
nonNullable,
|
|
|
|
/// Types in opt-out libraries are 'legacy' types.
|
|
///
|
|
/// They are both subtypes and supertypes of the nullable and non-nullable
|
|
/// versions of the type.
|
|
legacy
|
|
}
|
|
|
|
/// 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.
|
|
abstract class DartType extends Node {
|
|
const DartType();
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v);
|
|
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg);
|
|
|
|
@override
|
|
bool operator ==(Object other);
|
|
|
|
Nullability get nullability;
|
|
|
|
/// 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;
|
|
|
|
/// Creates a copy of the type with the given [nullability] if possible.
|
|
///
|
|
/// Some types have fixed nullabilities, such as `dynamic`, `invalid-type`,
|
|
/// `void`, or `bottom`.
|
|
DartType withNullability(Nullability nullability);
|
|
|
|
/// Checks if the type is potentially nullable.
|
|
///
|
|
/// A type is potentially nullable if it's nullable or if it's nullability is
|
|
/// undetermined at compile time.
|
|
bool get isPotentiallyNullable {
|
|
return nullability == Nullability.nullable ||
|
|
nullability == Nullability.undetermined;
|
|
}
|
|
|
|
/// Checks if the type is potentially non-nullable.
|
|
///
|
|
/// A type is potentially non-nullable if it's nullable or if it's nullability
|
|
/// is undetermined at compile time.
|
|
bool get isPotentiallyNonNullable {
|
|
return nullability == Nullability.nonNullable ||
|
|
nullability == Nullability.undetermined;
|
|
}
|
|
|
|
bool equals(Object other, Assumptions assumptions);
|
|
}
|
|
|
|
/// 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 {
|
|
@override
|
|
final int hashCode = 12345;
|
|
|
|
const InvalidType();
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitInvalidType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitInvalidType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) => other is InvalidType;
|
|
|
|
@override
|
|
Nullability get nullability => throw "InvalidType doesn't have nullability";
|
|
|
|
@override
|
|
InvalidType withNullability(Nullability nullability) => this;
|
|
|
|
@override
|
|
String toString() {
|
|
return "InvalidType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "invalid-type";
|
|
}
|
|
}
|
|
|
|
class DynamicType extends DartType {
|
|
@override
|
|
final int hashCode = 54321;
|
|
|
|
const DynamicType();
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitDynamicType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitDynamicType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) => other is DynamicType;
|
|
|
|
@override
|
|
Nullability get nullability => Nullability.nullable;
|
|
|
|
@override
|
|
DynamicType withNullability(Nullability nullability) => this;
|
|
|
|
@override
|
|
String toString() {
|
|
return "DynamicType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "dynamic";
|
|
}
|
|
}
|
|
|
|
class VoidType extends DartType {
|
|
@override
|
|
final int hashCode = 123121;
|
|
|
|
const VoidType();
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitVoidType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitVoidType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) => other is VoidType;
|
|
|
|
@override
|
|
Nullability get nullability => Nullability.nullable;
|
|
|
|
@override
|
|
VoidType withNullability(Nullability nullability) => this;
|
|
|
|
@override
|
|
String toString() {
|
|
return "VoidType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "void";
|
|
}
|
|
}
|
|
|
|
class NeverType extends DartType {
|
|
@override
|
|
final Nullability nullability;
|
|
|
|
const NeverType(this.nullability);
|
|
|
|
@override
|
|
int get hashCode {
|
|
return 485786 ^ ((0x33333333 >> nullability.index) ^ 0x33333333);
|
|
}
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitNeverType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitNeverType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) =>
|
|
other is NeverType && nullability == other.nullability;
|
|
|
|
@override
|
|
NeverType withNullability(Nullability nullability) {
|
|
return this.nullability == nullability ? this : new NeverType(nullability);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "NeverType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class BottomType extends DartType {
|
|
@override
|
|
final int hashCode = 514213;
|
|
|
|
const BottomType();
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitBottomType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitBottomType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) => other is BottomType;
|
|
|
|
@override
|
|
Nullability get nullability => Nullability.nonNullable;
|
|
|
|
@override
|
|
BottomType withNullability(Nullability nullability) => this;
|
|
|
|
@override
|
|
String toString() {
|
|
return "BottomType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "<BottomType>";
|
|
}
|
|
}
|
|
|
|
class InterfaceType extends DartType {
|
|
Reference className;
|
|
|
|
@override
|
|
final Nullability nullability;
|
|
|
|
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, Nullability nullability,
|
|
[List<DartType> typeArguments])
|
|
: this.byReference(getClassReference(classNode), nullability,
|
|
typeArguments ?? _defaultTypeArguments(classNode));
|
|
|
|
InterfaceType.byReference(
|
|
this.className, this.nullability, this.typeArguments)
|
|
: assert(nullability != null);
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitInterfaceType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitInterfaceType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {
|
|
classNode.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) {
|
|
if (identical(this, other)) return true;
|
|
if (other is InterfaceType) {
|
|
if (nullability != other.nullability) return false;
|
|
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].equals(other.typeArguments[i], assumptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
int hash = 0x3fffffff & className.hashCode;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
|
|
}
|
|
int nullabilityHash = (0x33333333 >> nullability.index) ^ 0x33333333;
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ nullabilityHash));
|
|
return hash;
|
|
}
|
|
|
|
@override
|
|
InterfaceType withNullability(Nullability nullability) {
|
|
return nullability == this.nullability
|
|
? this
|
|
: new InterfaceType.byReference(className, nullability, typeArguments);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "InterfaceType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
if (_verboseTypeToString) {
|
|
sb.write(className.toStringInternal());
|
|
} else {
|
|
sb.write(classNode.name);
|
|
}
|
|
if (typeArguments.isNotEmpty) {
|
|
sb.write("<");
|
|
String comma = "";
|
|
for (DartType typeArgument in typeArguments) {
|
|
sb.write(comma);
|
|
sb.write(typeArgument.toStringInternal());
|
|
comma = ", ";
|
|
}
|
|
sb.write(">");
|
|
}
|
|
sb.write(nullabilityToString(nullability));
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// A possibly generic function type.
|
|
class FunctionType extends DartType {
|
|
final List<TypeParameter> typeParameters;
|
|
final int requiredParameterCount;
|
|
final List<DartType> positionalParameters;
|
|
final List<NamedType> namedParameters; // Must be sorted.
|
|
final Nullability nullability;
|
|
|
|
/// The [Typedef] this function type is created for.
|
|
final TypedefType typedefType;
|
|
|
|
final DartType returnType;
|
|
int _hashCode;
|
|
|
|
FunctionType(
|
|
List<DartType> positionalParameters, this.returnType, this.nullability,
|
|
{this.namedParameters: const <NamedType>[],
|
|
this.typeParameters: const <TypeParameter>[],
|
|
int requiredParameterCount,
|
|
this.typedefType})
|
|
: this.positionalParameters = positionalParameters,
|
|
this.requiredParameterCount =
|
|
requiredParameterCount ?? positionalParameters.length;
|
|
|
|
Reference get typedefReference => typedefType?.typedefReference;
|
|
|
|
Typedef get typedef => typedefReference?.asTypedef;
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitFunctionType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitFunctionType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {
|
|
visitList(typeParameters, v);
|
|
visitList(positionalParameters, v);
|
|
visitList(namedParameters, v);
|
|
typedefType?.accept(v);
|
|
returnType.accept(v);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
} else if (other is FunctionType) {
|
|
if (nullability != other.nullability) return false;
|
|
if (typeParameters.length != other.typeParameters.length ||
|
|
requiredParameterCount != other.requiredParameterCount ||
|
|
positionalParameters.length != other.positionalParameters.length ||
|
|
namedParameters.length != other.namedParameters.length) {
|
|
return false;
|
|
}
|
|
if (typeParameters.isNotEmpty) {
|
|
assumptions ??= new Assumptions();
|
|
for (int index = 0; index < typeParameters.length; index++) {
|
|
assumptions.assume(
|
|
typeParameters[index], other.typeParameters[index]);
|
|
}
|
|
for (int index = 0; index < typeParameters.length; index++) {
|
|
if (!typeParameters[index]
|
|
.bound
|
|
.equals(other.typeParameters[index].bound, assumptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!returnType.equals(other.returnType, assumptions)) {
|
|
return false;
|
|
}
|
|
|
|
for (int index = 0; index < positionalParameters.length; index++) {
|
|
if (!positionalParameters[index]
|
|
.equals(other.positionalParameters[index], assumptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (int index = 0; index < namedParameters.length; index++) {
|
|
if (!namedParameters[index]
|
|
.equals(other.namedParameters[index], assumptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (typeParameters.isNotEmpty) {
|
|
for (int index = 0; index < typeParameters.length; index++) {
|
|
assumptions.forget(
|
|
typeParameters[index], other.typeParameters[index]);
|
|
}
|
|
}
|
|
return true;
|
|
} 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, nullability,
|
|
requiredParameterCount: requiredParameterCount,
|
|
namedParameters: namedParameters,
|
|
typedefType: null);
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
@override
|
|
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];
|
|
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);
|
|
hash = 0x3fffffff & (hash * 31 + nullability.index);
|
|
return hash;
|
|
}
|
|
|
|
@override
|
|
FunctionType withNullability(Nullability nullability) {
|
|
if (nullability == this.nullability) return this;
|
|
FunctionType result = FunctionType(
|
|
positionalParameters, returnType, nullability,
|
|
namedParameters: namedParameters,
|
|
typeParameters: typeParameters,
|
|
requiredParameterCount: requiredParameterCount,
|
|
typedefType: typedefType?.withNullability(nullability));
|
|
if (typeParameters.isEmpty) return result;
|
|
return getFreshTypeParameters(typeParameters).applyToFunctionType(result);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "FunctionType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.write(returnType.toStringInternal());
|
|
sb.write(" Function");
|
|
if (typeParameters.isNotEmpty) {
|
|
sb.write("<");
|
|
String comma = "";
|
|
for (TypeParameter typeParameter in typeParameters) {
|
|
sb.write(comma);
|
|
sb.write(typeParameter.toStringInternal());
|
|
comma = ", ";
|
|
}
|
|
sb.write(">");
|
|
}
|
|
|
|
sb.write("(");
|
|
for (int i = 0; i < positionalParameters.length; i++) {
|
|
if (i > 0) sb.write(", ");
|
|
if (i == requiredParameterCount) sb.write("[");
|
|
sb.write(positionalParameters[i].toStringInternal());
|
|
}
|
|
if (requiredParameterCount < positionalParameters.length) sb.write("]");
|
|
|
|
if (namedParameters.isNotEmpty) {
|
|
if (positionalParameters.isNotEmpty) {
|
|
sb.write(", ");
|
|
}
|
|
sb.write("{");
|
|
for (int i = 0; i < namedParameters.length; i++) {
|
|
if (i > 0) sb.write(", ");
|
|
sb.write(namedParameters[i].toStringInternal());
|
|
}
|
|
sb.write("}");
|
|
}
|
|
sb.write(")");
|
|
sb.write(nullabilityToString(nullability));
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// A use of a [Typedef] as a type.
|
|
///
|
|
/// The underlying type can be extracted using [unalias].
|
|
class TypedefType extends DartType {
|
|
final Nullability nullability;
|
|
final Reference typedefReference;
|
|
final List<DartType> typeArguments;
|
|
|
|
TypedefType(Typedef typedefNode, Nullability nullability,
|
|
[List<DartType> typeArguments])
|
|
: this.byReference(typedefNode.reference, nullability,
|
|
typeArguments ?? const <DartType>[]);
|
|
|
|
TypedefType.byReference(
|
|
this.typedefReference, this.nullability, this.typeArguments);
|
|
|
|
Typedef get typedefNode => typedefReference.asTypedef;
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitTypedefType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitTypedefType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {
|
|
visitList(typeArguments, v);
|
|
v.visitTypedefReference(typedefNode);
|
|
}
|
|
|
|
@override
|
|
DartType get unaliasOnce {
|
|
DartType result =
|
|
Substitution.fromTypedefType(this).substituteType(typedefNode.type);
|
|
return result.withNullability(
|
|
combineNullabilitiesForSubstitution(result.nullability, nullability));
|
|
}
|
|
|
|
@override
|
|
DartType get unalias {
|
|
return unaliasOnce.unalias;
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
} else if (other is TypedefType) {
|
|
if (nullability != other.nullability) return false;
|
|
if (typedefReference != other.typedefReference ||
|
|
typeArguments.length != other.typeArguments.length) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
if (!typeArguments[i].equals(other.typeArguments[i], assumptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
int hash = 0x3fffffff & typedefNode.hashCode;
|
|
for (int i = 0; i < typeArguments.length; ++i) {
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ typeArguments[i].hashCode));
|
|
}
|
|
int nullabilityHash = (0x33333333 >> nullability.index) ^ 0x33333333;
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ nullabilityHash));
|
|
return hash;
|
|
}
|
|
|
|
@override
|
|
TypedefType withNullability(Nullability nullability) {
|
|
return nullability == this.nullability
|
|
? this
|
|
: new TypedefType.byReference(
|
|
typedefReference, nullability, typeArguments);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "TypedefType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.write(typedefNode.toStringInternal());
|
|
if (typeArguments.isNotEmpty) {
|
|
sb.write("<");
|
|
String comma = "";
|
|
for (DartType typeArgument in typeArguments) {
|
|
sb.write(comma);
|
|
sb.write(typeArgument.toStringInternal());
|
|
comma = ", ";
|
|
}
|
|
sb.write(">");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// A named parameter in [FunctionType].
|
|
class NamedType extends Node implements Comparable<NamedType> {
|
|
// Flag used for serialization if [isRequired].
|
|
static const int FlagRequiredNamedType = 1 << 0;
|
|
|
|
final String name;
|
|
final DartType type;
|
|
final bool isRequired;
|
|
|
|
NamedType(this.name, this.type, {this.isRequired: false});
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
bool equals(Object other, Assumptions assumptions) {
|
|
return other is NamedType &&
|
|
name == other.name &&
|
|
isRequired == other.isRequired &&
|
|
type.equals(other.type, assumptions);
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return name.hashCode * 31 + type.hashCode * 37 + isRequired.hashCode * 41;
|
|
}
|
|
|
|
@override
|
|
int compareTo(NamedType other) => name.compareTo(other.name);
|
|
|
|
@override
|
|
R accept<R>(Visitor<R> v) => v.visitNamedType(this);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {
|
|
type.accept(v);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "NamedType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
if (isRequired) sb.write("required ");
|
|
sb.write("$name: ${type.toStringInternal()}");
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// 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. The case of promoted bound can be
|
|
/// viewed as representing an intersection type between the type-parameter type
|
|
/// and the promoted bound.
|
|
class TypeParameterType extends DartType {
|
|
/// Nullability of the type-parameter type or of its part of the intersection.
|
|
///
|
|
/// Declarations of type-parameter types can set the nullability of a
|
|
/// type-parameter type to [Nullability.nullable] (if the `?` marker is used)
|
|
/// or [Nullability.legacy] (if the type comes from a library opted out from
|
|
/// NNBD). Otherwise, it's defined indirectly via the nullability of the
|
|
/// bound of [parameter]. In cases when the [TypeParameterType] represents an
|
|
/// intersection between a type-parameter type and [promotedBound],
|
|
/// [typeParameterTypeNullability] represents the nullability of the left-hand
|
|
/// side of the intersection.
|
|
Nullability typeParameterTypeNullability;
|
|
|
|
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.typeParameterTypeNullability,
|
|
[this.promotedBound]);
|
|
|
|
/// Creates an intersection type between a type parameter and [promotedBound].
|
|
TypeParameterType.intersection(
|
|
this.parameter, this.typeParameterTypeNullability, this.promotedBound);
|
|
|
|
/// Creates a type-parameter type to be used in alpha-renaming.
|
|
///
|
|
/// The constructed type object is supposed to be used as a value in a
|
|
/// substitution map created to perform an alpha-renaming from parameter
|
|
/// [from] to parameter [to] on a generic type. The resulting type-parameter
|
|
/// type is an occurrence of [to] as a type, but the nullability property is
|
|
/// derived from the bound of [from]. It allows to assign the bound to [to]
|
|
/// after the desired alpha-renaming is performed, which is often the case.
|
|
TypeParameterType.forAlphaRenaming(TypeParameter from, TypeParameter to)
|
|
: this(to, computeNullabilityFromBound(from));
|
|
|
|
/// Creates a type-parameter type with default nullability for the library.
|
|
///
|
|
/// The nullability is computed as if the programmer omitted the modifier. It
|
|
/// means that in the opt-out libraries `Nullability.legacy` will be used, and
|
|
/// in opt-in libraries either `Nullability.nonNullable` or
|
|
/// `Nullability.undetermined` will be used, depending on the nullability of
|
|
/// the bound of [parameter].
|
|
TypeParameterType.withDefaultNullabilityForLibrary(
|
|
this.parameter, Library library) {
|
|
typeParameterTypeNullability = library.isNonNullableByDefault
|
|
? computeNullabilityFromBound(parameter)
|
|
: Nullability.legacy;
|
|
}
|
|
|
|
@override
|
|
R accept<R>(DartTypeVisitor<R> v) => v.visitTypeParameterType(this);
|
|
|
|
@override
|
|
R accept1<R, A>(DartTypeVisitor1<R, A> v, A arg) =>
|
|
v.visitTypeParameterType(this, arg);
|
|
|
|
@override
|
|
void visitChildren(Visitor v) {}
|
|
|
|
@override
|
|
bool operator ==(Object other) => equals(other, null);
|
|
|
|
@override
|
|
bool equals(Object other, Assumptions assumptions) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
} else if (other is TypeParameterType) {
|
|
if (nullability != other.nullability) return false;
|
|
if (parameter != other.parameter) {
|
|
if (parameter.parent == null) {
|
|
// Function type parameters are also equal by assumption.
|
|
if (assumptions == null) {
|
|
return false;
|
|
}
|
|
if (!assumptions.isAssumed(parameter, other.parameter)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
if (promotedBound != null) {
|
|
if (other.promotedBound == null) return false;
|
|
if (!promotedBound.equals(other.promotedBound, assumptions)) {
|
|
return false;
|
|
}
|
|
} else if (other.promotedBound != null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
// TODO(johnniwinther): Since we use a unification strategy for function
|
|
// type type parameter equality, we have to assume they can end up being
|
|
// equal. Maybe we should change the equality strategy.
|
|
int hash = parameter.isFunctionTypeTypeParameter ? 0 : parameter.hashCode;
|
|
int nullabilityHash = (0x33333333 >> nullability.index) ^ 0x33333333;
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ nullabilityHash));
|
|
hash = 0x3fffffff & (hash * 31 + (hash ^ promotedBound.hashCode));
|
|
return hash;
|
|
}
|
|
|
|
/// Returns the bound of the type parameter, accounting for promotions.
|
|
DartType get bound => promotedBound ?? parameter.bound;
|
|
|
|
/// Nullability of the type, calculated from its parts.
|
|
///
|
|
/// [nullability] is calculated from [typeParameterTypeNullability] and the
|
|
/// nullability of [promotedBound] if it's present.
|
|
///
|
|
/// For example, in the following program [typeParameterTypeNullability] of
|
|
/// both `x` and `y` is [Nullability.undetermined], because it's copied from
|
|
/// that of `bar` and T has a nullable type as its bound. However, despite
|
|
/// [nullability] of `x` is [Nullability.undetermined], [nullability] of `y`
|
|
/// is [Nullability.nonNullable] because of its [promotedBound].
|
|
///
|
|
/// class A<T extends Object?> {
|
|
/// foo(T bar) {
|
|
/// var x = bar;
|
|
/// if (bar is int) {
|
|
/// var y = bar;
|
|
/// }
|
|
/// }
|
|
/// }
|
|
@override
|
|
Nullability get nullability {
|
|
return getNullability(
|
|
typeParameterTypeNullability ?? computeNullabilityFromBound(parameter),
|
|
promotedBound);
|
|
}
|
|
|
|
/// Gets a new [TypeParameterType] with given [typeParameterTypeNullability].
|
|
///
|
|
/// In contrast with other types, [TypeParameterType.withNullability] doesn't
|
|
/// set the overall nullability of the returned type but sets that of the
|
|
/// left-hand side of the intersection type. In case [promotedBound] is null,
|
|
/// it is an equivalent of setting the overall nullability.
|
|
@override
|
|
TypeParameterType withNullability(Nullability typeParameterTypeNullability) {
|
|
assert(promotedBound == null,
|
|
"Can't change the nullability attribute of an intersection type.");
|
|
return typeParameterTypeNullability == this.typeParameterTypeNullability
|
|
? this
|
|
: new TypeParameterType(
|
|
parameter, typeParameterTypeNullability, promotedBound);
|
|
}
|
|
|
|
/// Gets the nullability of a type-parameter type based on the bound.
|
|
///
|
|
/// This is a helper function to be used when the bound of the type parameter
|
|
/// is changing or is being set for the first time, and the update on some
|
|
/// type-parameter types is required.
|
|
static Nullability computeNullabilityFromBound(TypeParameter typeParameter) {
|
|
// If the bound is nullable or 'undetermined', both nullable and
|
|
// non-nullable types can be passed in for the type parameter, making the
|
|
// corresponding type parameter types 'undetermined.' Otherwise, the
|
|
// nullability matches that of the bound.
|
|
DartType bound = typeParameter.bound;
|
|
if (bound == null) {
|
|
throw new StateError("Can't compute nullability from an absent bound.");
|
|
}
|
|
Nullability boundNullability =
|
|
bound is InvalidType ? Nullability.undetermined : bound.nullability;
|
|
return boundNullability == Nullability.nullable ||
|
|
boundNullability == Nullability.undetermined
|
|
? Nullability.undetermined
|
|
: boundNullability;
|
|
}
|
|
|
|
/// Gets nullability of [TypeParameterType] from arguments to its constructor.
|
|
///
|
|
/// The method combines [typeParameterTypeNullability] and the nullability of
|
|
/// [promotedBound] to yield the nullability of the intersection type. If the
|
|
/// right-hand side of the intersection is absent (that is, if [promotedBound]
|
|
/// is null), the nullability of the intersection type is simply
|
|
/// [typeParameterTypeNullability].
|
|
static Nullability getNullability(
|
|
Nullability typeParameterTypeNullability, DartType promotedBound) {
|
|
// If promotedBound is null, getNullability simply returns the nullability
|
|
// of the type parameter type.
|
|
Nullability lhsNullability = typeParameterTypeNullability;
|
|
if (promotedBound == null) {
|
|
return lhsNullability;
|
|
}
|
|
|
|
// If promotedBound isn't null, getNullability returns the nullability of an
|
|
// intersection of the left-hand side (referred to as LHS below) and the
|
|
// right-hand side (referred to as RHS below). Note that RHS is always a
|
|
// subtype of the bound of the type parameter.
|
|
|
|
// The code below implements the rule for the nullability of an intersection
|
|
// type as per the following table:
|
|
//
|
|
// | LHS \ RHS | ! | ? | * | % |
|
|
// |-----------|-----|-----|-----|-----|
|
|
// | ! | ! | N/A | N/A | ! |
|
|
// | ? | N/A | N/A | N/A | N/A |
|
|
// | * | N/A | N/A | * | N/A |
|
|
// | % | ! | % | N/A | % |
|
|
//
|
|
// In the table, LHS corresponds to lhsNullability in the code below; RHS
|
|
// corresponds to promotedBound.nullability; !, ?, *, and % correspond to
|
|
// nonNullable, nullable, legacy, and undetermined values of the Nullability
|
|
// enum.
|
|
//
|
|
// Whenever there's N/A in the table, it means that the corresponding
|
|
// combination of the LHS and RHS nullability is not possible when compiling
|
|
// from Dart source files, so we can define it to be whatever is faster and
|
|
// more convenient to implement. The verifier should check that the cases
|
|
// marked as N/A never occur in the output of the CFE.
|
|
//
|
|
// The code below uses the following extension of the table function:
|
|
//
|
|
// | LHS \ RHS | ! | ? | * | % |
|
|
// |-----------|-----|-----|-----|-----|
|
|
// | ! | ! | ! | ! | ! |
|
|
// | ? | ! | * | * | % |
|
|
// | * | ! | * | * | % |
|
|
// | % | ! | % | % | % |
|
|
|
|
// Intersection with a non-nullable type always yields a non-nullable type,
|
|
// as it's the most restrictive kind of types.
|
|
if (lhsNullability == Nullability.nonNullable ||
|
|
promotedBound.nullability == Nullability.nonNullable) {
|
|
return Nullability.nonNullable;
|
|
}
|
|
|
|
// If the nullability of LHS is 'undetermined', the nullability of the
|
|
// intersection is also 'undetermined' if RHS is 'undetermined' or nullable.
|
|
//
|
|
// Consider the following example:
|
|
//
|
|
// class A<X extends Object?, Y extends X> {
|
|
// foo(X x) {
|
|
// if (x is Y) {
|
|
// x = null; // Compile-time error. Consider X = Y = int.
|
|
// Object a = x; // Compile-time error. Consider X = Y = int?.
|
|
// }
|
|
// if (x is int?) {
|
|
// x = null; // Compile-time error. Consider X = int.
|
|
// Object b = x; // Compile-time error. Consider X = int?.
|
|
// }
|
|
// }
|
|
// }
|
|
if (lhsNullability == Nullability.undetermined ||
|
|
promotedBound.nullability == Nullability.undetermined) {
|
|
return Nullability.undetermined;
|
|
}
|
|
|
|
return Nullability.legacy;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "TypeParameterType(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.write(qualifiedTypeParameterNameToString(parameter,
|
|
includeLibraryName: _verboseTypeToString));
|
|
sb.write(nullabilityToString(typeParameterTypeNullability));
|
|
if (promotedBound != null) {
|
|
sb.write(" & ");
|
|
sb.write(promotedBound.toStringInternal());
|
|
sb.write(" /* '");
|
|
sb.write(nullabilityToString(typeParameterTypeNullability));
|
|
sb.write("' & '");
|
|
if (promotedBound is InvalidType) {
|
|
sb.write(nullabilityToString(Nullability.undetermined));
|
|
} else {
|
|
sb.write(nullabilityToString(promotedBound.nullability));
|
|
}
|
|
sb.write("' = '");
|
|
sb.write(nullabilityToString(nullability));
|
|
sb.write("' */");
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// Value set for variance of a type parameter X in a type term T.
|
|
class Variance {
|
|
/// Used when X does not occur free in T.
|
|
static const int unrelated = 0;
|
|
|
|
/// Used when X occurs free in T, and U <: V implies [U/X]T <: [V/X]T.
|
|
static const int covariant = 1;
|
|
|
|
/// Used when X occurs free in T, and U <: V implies [V/X]T <: [U/X]T.
|
|
static const int contravariant = 2;
|
|
|
|
/// Used when there exists a pair U and V such that U <: V, but [U/X]T and
|
|
/// [V/X]T are incomparable.
|
|
static const int invariant = 3;
|
|
|
|
/// Variance values form a lattice where [unrelated] is the top, [invariant]
|
|
/// is the bottom, and [covariant] and [contravariant] are incomparable.
|
|
/// [meet] calculates the meet of two elements of such lattice. It can be
|
|
/// used, for example, to calculate the variance of a typedef type parameter
|
|
/// if it's encountered on the r.h.s. of the typedef multiple times.
|
|
static int meet(int a, int b) => a | b;
|
|
|
|
/// Combines variances of X in T and Y in S into variance of X in [Y/T]S.
|
|
///
|
|
/// Consider the following examples:
|
|
///
|
|
/// * variance of X in Function(X) is [contravariant], variance of Y in
|
|
/// List<Y> is [covariant], so variance of X in List<Function(X)> is
|
|
/// [contravariant];
|
|
///
|
|
/// * variance of X in List<X> is [covariant], variance of Y in Function(Y) is
|
|
/// [contravariant], so variance of X in Function(List<X>) is [contravariant];
|
|
///
|
|
/// * variance of X in Function(X) is [contravariant], variance of Y in
|
|
/// Function(Y) is [contravariant], so variance of X in Function(Function(X))
|
|
/// is [covariant];
|
|
///
|
|
/// * let the following be declared:
|
|
///
|
|
/// typedef F<Z> = Function();
|
|
///
|
|
/// then variance of X in F<X> is [unrelated], variance of Y in List<Y> is
|
|
/// [covariant], so variance of X in List<F<X>> is [unrelated];
|
|
///
|
|
/// * let the following be declared:
|
|
///
|
|
/// typedef G<Z> = Z Function(Z);
|
|
///
|
|
/// then variance of X in List<X> is [covariant], variance of Y in G<Y> is
|
|
/// [invariant], so variance of `X` in `G<List<X>>` is [invariant].
|
|
static int combine(int a, int b) {
|
|
if (a == unrelated || b == unrelated) return unrelated;
|
|
if (a == invariant || b == invariant) return invariant;
|
|
return a == b ? covariant : contravariant;
|
|
}
|
|
|
|
/// Returns true if [a] is greater than (above) [b] in the partial order
|
|
/// induced by the variance lattice.
|
|
static bool greaterThan(int a, int b) {
|
|
return greaterThanOrEqual(a, b) && a != b;
|
|
}
|
|
|
|
/// Returns true if [a] is greater than (above) or equal to [b] in the
|
|
/// partial order induced by the variance lattice.
|
|
static bool greaterThanOrEqual(int a, int b) {
|
|
return meet(a, b) == b;
|
|
}
|
|
|
|
/// Returns true if [a] is less than (below) [b] in the partial order
|
|
/// induced by the variance lattice.
|
|
static bool lessThan(int a, int b) {
|
|
return lessThanOrEqual(a, b) && a != b;
|
|
}
|
|
|
|
/// Returns true if [a] is less than (below) or equal to [b] in the
|
|
/// partial order induced by the variance lattice.
|
|
static bool lessThanOrEqual(int a, int b) {
|
|
return meet(a, b) == a;
|
|
}
|
|
|
|
static int fromString(String variance) {
|
|
if (variance == "in") {
|
|
return contravariant;
|
|
} else if (variance == "inout") {
|
|
return invariant;
|
|
} else if (variance == "out") {
|
|
return covariant;
|
|
} else {
|
|
return unrelated;
|
|
}
|
|
}
|
|
|
|
// Returns the keyword lexeme associated with the variance given.
|
|
static String keywordString(int variance) {
|
|
switch (variance) {
|
|
case Variance.contravariant:
|
|
return 'in';
|
|
case Variance.invariant:
|
|
return 'inout';
|
|
case Variance.covariant:
|
|
default:
|
|
return 'out';
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
|
|
/// Describes variance of the type parameter w.r.t. declaration on which it is
|
|
/// defined. For classes, if variance is not explicitly set, the type
|
|
/// parameter has legacy covariance defined by [isLegacyCovariant] which
|
|
/// on the lattice is equivalent to [Variance.covariant]. For typedefs, it's
|
|
/// the variance of the type parameters in the type term on the r.h.s. of the
|
|
/// typedef.
|
|
int _variance;
|
|
|
|
int get variance => _variance ?? Variance.covariant;
|
|
|
|
void set variance(int newVariance) => _variance = newVariance;
|
|
|
|
bool get isLegacyCovariant => _variance == null;
|
|
|
|
static const int legacyCovariantSerializationMarker = 4;
|
|
|
|
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);
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> v) => v.visitTypeParameter(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
visitList(annotations, v);
|
|
bound.accept(v);
|
|
defaultType?.accept(v);
|
|
}
|
|
|
|
transformChildren(Transformer v) {
|
|
transformList(annotations, v, this);
|
|
bound = v.visitDartType(bound);
|
|
if (defaultType != null) {
|
|
defaultType = v.visitDartType(defaultType);
|
|
}
|
|
}
|
|
|
|
/// Returns a possibly synthesized name for this type parameter, consistent
|
|
/// with the names used across all [toString] calls.
|
|
String toString() {
|
|
return "TypeParameter(${toStringInternal()})";
|
|
}
|
|
|
|
String toStringInternal() {
|
|
return qualifiedTypeParameterNameToString(this,
|
|
includeLibraryName: _verboseMemberToString);
|
|
}
|
|
|
|
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;
|
|
|
|
R accept<R>(Visitor<R> v) => v.visitSupertype(this);
|
|
|
|
visitChildren(Visitor v) {
|
|
classNode.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
}
|
|
|
|
InterfaceType get asInterfaceType {
|
|
return new InterfaceType(classNode, Nullability.legacy, 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;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Supertype(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
StringBuffer sb = new StringBuffer();
|
|
if (_verboseTypeToString) {
|
|
sb.write(className.toStringInternal());
|
|
} else {
|
|
sb.write(classNode.name);
|
|
}
|
|
if (typeArguments.isNotEmpty) {
|
|
sb.write("<");
|
|
String comma = "";
|
|
for (DartType typeArgument in typeArguments) {
|
|
sb.write(comma);
|
|
sb.write(typeArgument.toStringInternal());
|
|
comma = ", ";
|
|
}
|
|
sb.write(">");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 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].
|
|
R accept<R>(ConstantVisitor<R> v);
|
|
|
|
/// Calls the `visit*ConstantReference()` method on the visitor [v].
|
|
R acceptReference<R>(Visitor<R> 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(StaticTypeContext context);
|
|
|
|
Expression asExpression() {
|
|
return new ConstantExpression(this);
|
|
}
|
|
}
|
|
|
|
abstract class PrimitiveConstant<T> extends Constant {
|
|
final T value;
|
|
|
|
PrimitiveConstant(this.value);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() => '$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) {}
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitNullConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitNullConstantReference(this);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.nullType;
|
|
}
|
|
|
|
class BoolConstant extends PrimitiveConstant<bool> {
|
|
BoolConstant(bool value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitBoolConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitBoolConstantReference(this);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
|
|
}
|
|
|
|
/// An integer constant on a non-JS target.
|
|
class IntConstant extends PrimitiveConstant<int> {
|
|
IntConstant(int value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitIntConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitIntConstantReference(this);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.intRawType(context.nonNullable);
|
|
}
|
|
|
|
/// A double constant on a non-JS target or any numeric constant on a JS target.
|
|
class DoubleConstant extends PrimitiveConstant<double> {
|
|
DoubleConstant(double value) : super(value);
|
|
|
|
visitChildren(Visitor v) {}
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitDoubleConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitDoubleConstantReference(this);
|
|
|
|
int get hashCode => value.isNaN ? 199 : super.hashCode;
|
|
bool operator ==(Object other) =>
|
|
other is DoubleConstant && identical(value, other.value);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.doubleRawType(context.nonNullable);
|
|
}
|
|
|
|
class StringConstant extends PrimitiveConstant<String> {
|
|
StringConstant(String value) : super(value) {
|
|
assert(value != null);
|
|
}
|
|
|
|
visitChildren(Visitor v) {}
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitStringConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitStringConstantReference(this);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.stringRawType(context.nonNullable);
|
|
}
|
|
|
|
class SymbolConstant extends Constant {
|
|
final String name;
|
|
final Reference libraryReference;
|
|
|
|
SymbolConstant(this.name, this.libraryReference);
|
|
|
|
visitChildren(Visitor v) {}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitSymbolConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitSymbolConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
|
|
String toStringInternal() {
|
|
return libraryReference != null
|
|
? '#${libraryReference.asLibrary.importUri}::$name'
|
|
: '#$name';
|
|
}
|
|
|
|
int get hashCode => _Hash.hash2(name, libraryReference);
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is SymbolConstant &&
|
|
other.name == name &&
|
|
other.libraryReference == libraryReference);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.symbolRawType(context.nonNullable);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitMapConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitMapConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
return '${this.runtimeType}<$keyType, $valueType>($entries)';
|
|
}
|
|
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??= _Hash.combine2Finish(
|
|
keyType.hashCode, valueType.hashCode, _Hash.combineListHash(entries));
|
|
}
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is MapConstant &&
|
|
other.keyType == keyType &&
|
|
other.valueType == valueType &&
|
|
listEquals(other.entries, entries));
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.mapType(keyType, valueType, context.nonNullable);
|
|
}
|
|
|
|
class ConstantMapEntry {
|
|
final Constant key;
|
|
final Constant value;
|
|
ConstantMapEntry(this.key, this.value);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() => '$key: $value';
|
|
|
|
int get hashCode => _Hash.hash2(key, value);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitListConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitListConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
return '${runtimeType}<${typeArgument.toStringInternal()}>($entries)';
|
|
}
|
|
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??= _Hash.combineFinish(
|
|
typeArgument.hashCode, _Hash.combineListHash(entries));
|
|
}
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is ListConstant &&
|
|
other.typeArgument == typeArgument &&
|
|
listEquals(other.entries, entries));
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.listType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
class SetConstant extends Constant {
|
|
final DartType typeArgument;
|
|
final List<Constant> entries;
|
|
|
|
SetConstant(this.typeArgument, this.entries);
|
|
|
|
visitChildren(Visitor v) {
|
|
typeArgument.accept(v);
|
|
for (final Constant constant in entries) {
|
|
constant.acceptReference(v);
|
|
}
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitSetConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitSetConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
return '${runtimeType}<${typeArgument.toStringInternal()}>($entries)';
|
|
}
|
|
|
|
int _cachedHashCode;
|
|
int get hashCode {
|
|
return _cachedHashCode ??= _Hash.combineFinish(
|
|
typeArgument.hashCode, _Hash.combineListHash(entries));
|
|
}
|
|
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
(other is SetConstant &&
|
|
other.typeArgument == typeArgument &&
|
|
listEquals(other.entries, entries));
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.setType(typeArgument, context.nonNullable);
|
|
}
|
|
|
|
class InstanceConstant extends Constant {
|
|
final Reference classReference;
|
|
final List<DartType> typeArguments;
|
|
final Map<Reference, Constant> fieldValues;
|
|
|
|
InstanceConstant(this.classReference, this.typeArguments, this.fieldValues);
|
|
|
|
Class get classNode => classReference.asClass;
|
|
|
|
visitChildren(Visitor v) {
|
|
classReference.asClass.acceptReference(v);
|
|
visitList(typeArguments, v);
|
|
for (final Reference reference in fieldValues.keys) {
|
|
reference.asField.acceptReference(v);
|
|
}
|
|
for (final Constant constant in fieldValues.values) {
|
|
constant.acceptReference(v);
|
|
}
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitInstanceConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitInstanceConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
final sb = new StringBuffer();
|
|
sb.write('${classReference.asClass}');
|
|
if (!classReference.asClass.typeParameters.isEmpty) {
|
|
sb.write('<');
|
|
sb.write(typeArguments.map((type) => type.toStringInternal()).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 ??= _Hash.combine2Finish(
|
|
classReference.hashCode,
|
|
listHashCode(typeArguments),
|
|
_Hash.combineMapHashUnordered(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(StaticTypeContext context) =>
|
|
new InterfaceType(classNode, context.nonNullable, 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);
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) =>
|
|
v.visitPartialInstantiationConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) =>
|
|
v.visitPartialInstantiationConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
return '${runtimeType}(${tearOffConstant.procedure}<'
|
|
'${types.map((t) => t.toStringInternal()).join(', ')}>)';
|
|
}
|
|
|
|
int get hashCode => _Hash.combineFinish(
|
|
tearOffConstant.hashCode, _Hash.combineListHash(types));
|
|
|
|
bool operator ==(Object other) {
|
|
return other is PartialInstantiationConstant &&
|
|
other.tearOffConstant == tearOffConstant &&
|
|
listEquals(other.types, types);
|
|
}
|
|
|
|
DartType getType(StaticTypeContext context) {
|
|
final FunctionType type = tearOffConstant.getType(context);
|
|
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);
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitTearOffConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) => v.visitTearOffConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() {
|
|
return '${runtimeType}(${procedure})';
|
|
}
|
|
|
|
int get hashCode => procedureReference.hashCode;
|
|
|
|
bool operator ==(Object other) {
|
|
return other is TearOffConstant &&
|
|
other.procedureReference == procedureReference;
|
|
}
|
|
|
|
FunctionType getType(StaticTypeContext context) {
|
|
return procedure.function.computeFunctionType(context.nonNullable);
|
|
}
|
|
}
|
|
|
|
class TypeLiteralConstant extends Constant {
|
|
final DartType type;
|
|
|
|
TypeLiteralConstant(this.type);
|
|
|
|
visitChildren(Visitor v) {
|
|
type.accept(v);
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitTypeLiteralConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) =>
|
|
v.visitTypeLiteralConstantReference(this);
|
|
|
|
String toString() => toStringInternal();
|
|
String toStringInternal() => '${runtimeType}(${type})';
|
|
|
|
int get hashCode => type.hashCode;
|
|
|
|
bool operator ==(Object other) {
|
|
return other is TypeLiteralConstant && other.type == type;
|
|
}
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.typeRawType(context.nonNullable);
|
|
}
|
|
|
|
class UnevaluatedConstant extends Constant {
|
|
final Expression expression;
|
|
|
|
UnevaluatedConstant(this.expression) {
|
|
expression?.parent = null;
|
|
}
|
|
|
|
visitChildren(Visitor v) {
|
|
expression.accept(v);
|
|
}
|
|
|
|
R accept<R>(ConstantVisitor<R> v) => v.visitUnevaluatedConstant(this);
|
|
R acceptReference<R>(Visitor<R> v) =>
|
|
v.visitUnevaluatedConstantReference(this);
|
|
|
|
DartType getType(StaticTypeContext context) =>
|
|
expression.getStaticType(context);
|
|
|
|
@override
|
|
Expression asExpression() => expression;
|
|
|
|
@override
|
|
String toString() {
|
|
return "UnevaluatedConstant(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// COMPONENT
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// A way to bundle up libraries in a component.
|
|
class Component extends TreeNode {
|
|
final CanonicalName root;
|
|
|
|
/// Problems in this [Component] encoded as json objects.
|
|
///
|
|
/// Note that this field can be null, and by convention should be null if the
|
|
/// list is empty.
|
|
List<String> problemsAsJson;
|
|
|
|
final List<Library> libraries;
|
|
|
|
/// Map from a source file URI to a line-starts table and source code.
|
|
/// Given a source file URI and a offset in that file one can translate
|
|
/// it to a line:column position in that file.
|
|
final Map<Uri, Source> uriToSource;
|
|
|
|
/// Mapping between string tags and [MetadataRepository] corresponding to
|
|
/// those tags.
|
|
final Map<String, MetadataRepository<dynamic>> metadata =
|
|
<String, MetadataRepository<dynamic>>{};
|
|
|
|
/// Reference to the main method in one of the libraries.
|
|
Reference _mainMethodName;
|
|
Reference get mainMethodName => _mainMethodName;
|
|
NonNullableByDefaultCompiledMode _mode;
|
|
NonNullableByDefaultCompiledMode get mode {
|
|
return _mode ?? NonNullableByDefaultCompiledMode.Disabled;
|
|
}
|
|
|
|
Component(
|
|
{CanonicalName nameRoot,
|
|
List<Library> libraries,
|
|
Map<Uri, Source> uriToSource})
|
|
: root = nameRoot ?? new CanonicalName.root(),
|
|
libraries = libraries ?? <Library>[],
|
|
uriToSource = uriToSource ?? <Uri, Source>{} {
|
|
adoptChildren();
|
|
}
|
|
|
|
void adoptChildren() {
|
|
if (libraries != null) {
|
|
for (int i = 0; i < libraries.length; ++i) {
|
|
// The libraries are owned by this component, and so are their canonical
|
|
// names if they exist.
|
|
Library library = libraries[i];
|
|
library.parent = this;
|
|
CanonicalName name = library.reference.canonicalName;
|
|
if (name != null && name.parent != root) {
|
|
root.adoptChild(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void computeCanonicalNames() {
|
|
for (int i = 0; i < libraries.length; ++i) {
|
|
computeCanonicalNamesForLibrary(libraries[i]);
|
|
}
|
|
}
|
|
|
|
/// This is an advanced feature. Use of this method should be coordinated
|
|
/// with the kernel team.
|
|
///
|
|
/// Makes sure all references in named nodes in this component points to said
|
|
/// named node.
|
|
///
|
|
/// The use case is advanced incremental compilation, where we want to rebuild
|
|
/// a single library and make all other libraries use the new library and the
|
|
/// content therein *while* having the option to go back to pointing (be
|
|
/// "linked") to the old library if the delta is rejected.
|
|
///
|
|
/// Please note that calling this is a potentially dangerous thing to do,
|
|
/// and that stuff *can* go wrong, and you could end up in a situation where
|
|
/// you point to several versions of "the same" library. Examples:
|
|
/// * If you only relink part (e.g. a class) if your component you can wind
|
|
/// up in an unfortunate situation where if the library (say libA) contains
|
|
/// class 'B' and class 'C', you only replace 'B' (with one in library
|
|
/// 'libAPrime'), everything pointing to 'B' via parent pointers talks
|
|
/// about 'libAPrime', whereas everything pointing to 'C' would still
|
|
/// ultimately point to 'libA'.
|
|
/// * If you relink to a library that doesn't have exactly the same members
|
|
/// as the one you're "linking from" you can wind up in an unfortunate
|
|
/// situation, e.g. if the thing you relink two is missing a static method,
|
|
/// any links to that static method will still point to the old static
|
|
/// method and thus (via parent pointers) to the old library.
|
|
/// * (probably more).
|
|
void relink() {
|
|
for (int i = 0; i < libraries.length; ++i) {
|
|
libraries[i].relink();
|
|
}
|
|
}
|
|
|
|
void computeCanonicalNamesForLibrary(Library library) {
|
|
root.getChildFromUri(library.importUri).bindTo(library.reference);
|
|
library.computeCanonicalNames();
|
|
}
|
|
|
|
void unbindCanonicalNames() {
|
|
// TODO(jensj): Get rid of this.
|
|
for (int i = 0; i < libraries.length; i++) {
|
|
Library lib = libraries[i];
|
|
for (int j = 0; j < lib.classes.length; j++) {
|
|
Class c = lib.classes[j];
|
|
c.dirty = true;
|
|
}
|
|
}
|
|
root.unbindAll();
|
|
}
|
|
|
|
Procedure get mainMethod => mainMethodName?.asProcedure;
|
|
|
|
void setMainMethodAndMode(Reference main, bool overwriteMainIfSet,
|
|
NonNullableByDefaultCompiledMode mode) {
|
|
if (_mainMethodName == null || overwriteMainIfSet) {
|
|
_mainMethodName = main;
|
|
}
|
|
_mode = mode;
|
|
}
|
|
|
|
R accept<R>(TreeVisitor<R> 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);
|
|
}
|
|
|
|
/// Translates line and column numbers to an offset in the given file.
|
|
///
|
|
/// Returns offset of the line and column in the file, or -1 if the
|
|
/// source is not available or has no lines.
|
|
/// Throws [RangeError] if line or calculated offset are out of range.
|
|
int getOffset(Uri file, int line, int column) {
|
|
return uriToSource[file]?.getOffset(line, column) ?? -1;
|
|
}
|
|
|
|
void addMetadataRepository(MetadataRepository repository) {
|
|
metadata[repository.tag] = repository;
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "Component(${toStringInternal()})";
|
|
}
|
|
|
|
@override
|
|
String toStringInternal() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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. Also, metadata is not saved on
|
|
/// Block nodes inside BlockExpressions.
|
|
static bool isSupported(TreeNode node) {
|
|
return !(node is MapEntry ||
|
|
node is Catch ||
|
|
(node is Block && node.parent is BlockExpression));
|
|
}
|
|
}
|
|
|
|
abstract class BinarySink {
|
|
int getBufferOffset();
|
|
|
|
void writeByte(int byte);
|
|
void writeBytes(List<int> bytes);
|
|
void writeUInt32(int value);
|
|
void writeUInt30(int value);
|
|
|
|
/// Write List<Byte> into the sink.
|
|
void writeByteList(List<int> bytes);
|
|
|
|
void writeNullAllowedCanonicalNameReference(CanonicalName name);
|
|
void writeStringReference(String str);
|
|
void writeName(Name node);
|
|
void writeDartType(DartType type);
|
|
void writeConstantReference(Constant constant);
|
|
void writeNode(Node node);
|
|
|
|
void enterScope(
|
|
{List<TypeParameter> typeParameters,
|
|
bool memberScope: false,
|
|
bool variableScope: false});
|
|
void leaveScope(
|
|
{List<TypeParameter> typeParameters,
|
|
bool memberScope: false,
|
|
bool variableScope: false});
|
|
}
|
|
|
|
abstract class BinarySource {
|
|
int get currentOffset;
|
|
List<int> get bytes;
|
|
|
|
int readByte();
|
|
List<int> readBytes(int length);
|
|
int readUInt();
|
|
int readUint32();
|
|
|
|
/// Read List<Byte> from the source.
|
|
List<int> readByteList();
|
|
|
|
CanonicalName readCanonicalNameReference();
|
|
String readStringReference();
|
|
Name readName();
|
|
DartType readDartType();
|
|
Constant readConstantReference();
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/// A UTF8 encoding of the original source file.
|
|
final List<int> source;
|
|
|
|
final Uri importUri;
|
|
|
|
final Uri fileUri;
|
|
|
|
String cachedText;
|
|
|
|
Source(this.lineStarts, this.source, this.importUri, this.fileUri);
|
|
|
|
/// Return the text corresponding to [line] which is a 1-based line
|
|
/// number. The returned line contains no line separators.
|
|
String getTextLine(int line) {
|
|
if (source == null ||
|
|
source.isEmpty ||
|
|
lineStarts == null ||
|
|
lineStarts.isEmpty) return null;
|
|
RangeError.checkValueInInterval(line, 1, lineStarts.length, 'line');
|
|
|
|
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 1-based line and column numbers in the given file.
|
|
Location getLocation(Uri file, int offset) {
|
|
if (lineStarts == null || lineStarts.isEmpty) {
|
|
return new Location(file, TreeNode.noOffset, TreeNode.noOffset);
|
|
}
|
|
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);
|
|
}
|
|
|
|
/// Translates 1-based line and column numbers to an offset in the given file
|
|
///
|
|
/// Returns offset of the line and column in the file, or -1 if the source
|
|
/// has no lines.
|
|
/// Throws [RangeError] if line or calculated offset are out of range.
|
|
int getOffset(int line, int column) {
|
|
if (lineStarts == null || lineStarts.isEmpty) {
|
|
return -1;
|
|
}
|
|
RangeError.checkValueInInterval(line, 1, lineStarts.length, 'line');
|
|
var offset = lineStarts[line - 1] + column - 1;
|
|
RangeError.checkValueInInterval(offset, 0, lineStarts.last, 'offset');
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
/// 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 [extension], or throws an exception if the
|
|
/// class has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the extension is `null`.
|
|
CanonicalName getCanonicalNameOfExtension(Extension extension) {
|
|
if (extension == null) return null;
|
|
if (extension.canonicalName == null) {
|
|
throw '$extension has no canonical name';
|
|
}
|
|
return extension.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;
|
|
}
|
|
|
|
/// Murmur-inspired hashing, with a fall-back to Jenkins-inspired hashing when
|
|
/// compiled to JavaScript.
|
|
///
|
|
/// A hash function should be constructed of several [combine] calls followed by
|
|
/// a [finish] call.
|
|
class _Hash {
|
|
static const int M = 0x9ddfea08eb382000 + 0xd69;
|
|
static const bool intIs64Bit = (1 << 63) != 0;
|
|
|
|
/// Primitive hash combining step.
|
|
static int combine(int value, int hash) {
|
|
if (intIs64Bit) {
|
|
value *= M;
|
|
value ^= _shru(value, 47);
|
|
value *= M;
|
|
hash ^= value;
|
|
hash *= M;
|
|
} else {
|
|
// Fall back to Jenkins-inspired hashing on JavaScript platforms.
|
|
hash = 0x1fffffff & (hash + value);
|
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
|
hash = hash ^ (hash >> 6);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/// Primitive hash finalization step.
|
|
static int finish(int hash) {
|
|
if (intIs64Bit) {
|
|
hash ^= _shru(hash, 44);
|
|
hash *= M;
|
|
hash ^= _shru(hash, 41);
|
|
} else {
|
|
// Fall back to Jenkins-inspired hashing on JavaScript platforms.
|
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
|
hash = hash ^ (hash >> 11);
|
|
hash = 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static int combineFinish(int value, int hash) {
|
|
return finish(combine(value, hash));
|
|
}
|
|
|
|
static int combine2(int value1, int value2, int hash) {
|
|
return combine(value2, combine(value1, hash));
|
|
}
|
|
|
|
static int combine2Finish(int value1, int value2, int hash) {
|
|
return finish(combine2(value1, value2, hash));
|
|
}
|
|
|
|
static int hash2(Object object1, Object object2) {
|
|
return combine2Finish(object2.hashCode, object2.hashCode, 0);
|
|
}
|
|
|
|
static int combineListHash(List list, [int hash = 1]) {
|
|
for (var item in list) {
|
|
hash = _Hash.combine(item.hashCode, hash);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static int combineList(List<int> hashes, int hash) {
|
|
for (var item in hashes) {
|
|
hash = combine(item, hash);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static int combineMapHashUnordered(Map map, [int hash = 2]) {
|
|
if (map == null || map.isEmpty) return hash;
|
|
List<int> entryHashes = List(map.length);
|
|
int i = 0;
|
|
for (var entry in map.entries) {
|
|
entryHashes[i++] = combine(entry.key.hashCode, entry.value.hashCode);
|
|
}
|
|
entryHashes.sort();
|
|
return combineList(entryHashes, hash);
|
|
}
|
|
|
|
// TODO(sra): Replace with '>>>'.
|
|
static int _shru(int v, int n) {
|
|
assert(n >= 1);
|
|
assert(intIs64Bit);
|
|
return ((v >> 1) & (0x7fffFFFFffffF000 + 0xFFF)) >> (n - 1);
|
|
}
|
|
}
|
|
|
|
int listHashCode(List list) {
|
|
return _Hash.finish(_Hash.combineListHash(list));
|
|
}
|
|
|
|
int mapHashCode(Map map) {
|
|
return mapHashCodeUnordered(map);
|
|
}
|
|
|
|
int mapHashCodeOrdered(Map map, [int hash = 2]) {
|
|
for (final Object x in map.keys) hash = _Hash.combine(x.hashCode, hash);
|
|
for (final Object x in map.values) hash = _Hash.combine(x.hashCode, hash);
|
|
return _Hash.finish(hash);
|
|
}
|
|
|
|
int mapHashCodeUnordered(Map map) {
|
|
return _Hash.finish(_Hash.combineMapHashUnordered(map));
|
|
}
|
|
|
|
bool listEquals(List a, List b) {
|
|
if (a.length != b.length) return false;
|
|
for (int i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mapEquals(Map a, Map b) {
|
|
if (a.length != b.length) return false;
|
|
for (final Object key in a.keys) {
|
|
if (!b.containsKey(key) || a[key] != b[key]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns the canonical name of [typedef_], or throws an exception if the
|
|
/// typedef has not been assigned a canonical name yet.
|
|
///
|
|
/// Returns `null` if the typedef is `null`.
|
|
CanonicalName getCanonicalNameOfTypedef(Typedef typedef_) {
|
|
if (typedef_ == null) return null;
|
|
if (typedef_.canonicalName == null) {
|
|
throw '$typedef_ has no canonical name';
|
|
}
|
|
return typedef_.canonicalName;
|
|
}
|
|
|
|
/// Annotation describing information which is not part of Dart semantics; in
|
|
/// other words, if this information (or any information it refers to) changes,
|
|
/// static analysis and runtime behavior of the library are unaffected.
|
|
const informative = null;
|
|
|
|
Location _getLocationInComponent(Component component, Uri fileUri, int offset) {
|
|
if (component != null) {
|
|
return component.getLocation(fileUri, offset);
|
|
} else {
|
|
return new Location(fileUri, TreeNode.noOffset, TreeNode.noOffset);
|
|
}
|
|
}
|
|
|
|
/// Convert the synthetic name of an implicit mixin application class
|
|
/// into a name suitable for user-faced strings.
|
|
///
|
|
/// For example, when compiling "class A extends S with M1, M2", the
|
|
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
|
|
/// This function will return "S with M1" and "S with M1, M2", respectively.
|
|
String demangleMixinApplicationName(String name) {
|
|
List<String> nameParts = name.split('&');
|
|
if (nameParts.length < 2 || name == "&") return name;
|
|
String demangledName = nameParts[1];
|
|
for (int i = 2; i < nameParts.length; i++) {
|
|
demangledName += (i == 2 ? " with " : ", ") + nameParts[i];
|
|
}
|
|
return demangledName;
|
|
}
|
|
|
|
/// Extract from the synthetic name of an implicit mixin application class
|
|
/// the name of the final subclass of the mixin application.
|
|
///
|
|
/// For example, when compiling "class A extends S with M1, M2", the
|
|
/// two synthetic classes will be named "_A&S&M1" and "_A&S&M1&M2".
|
|
/// This function will return "A" for both classes.
|
|
String demangleMixinApplicationSubclassName(String name) {
|
|
List<String> nameParts = name.split('&');
|
|
if (nameParts.length < 2) return name;
|
|
assert(nameParts[0].startsWith('_'));
|
|
return nameParts[0].substring(1);
|
|
}
|
|
|
|
/// Computes a list of [typeParameters] taken as types.
|
|
List<DartType> getAsTypeArguments(
|
|
List<TypeParameter> typeParameters, Library library) {
|
|
if (typeParameters.isEmpty) return const <DartType>[];
|
|
List<DartType> result =
|
|
new List<DartType>.filled(typeParameters.length, null, growable: false);
|
|
for (int i = 0; i < result.length; ++i) {
|
|
result[i] = new TypeParameterType.withDefaultNullabilityForLibrary(
|
|
typeParameters[i], library);
|
|
}
|
|
return result;
|
|
}
|