blob: 74fe9433eec02e0d9a8d7d59255ee4a8d10dd4b0 [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// -----------------------------------------------------------------------
/// WHEN CHANGING THIS FILE:
/// -----------------------------------------------------------------------
///
/// If you are adding/removing/modifying fields/classes of the AST, you must
/// also update the following files:
///
/// - binary/ast_to_binary.dart
/// - binary/ast_from_binary.dart
/// - text/ast_to_text.dart
/// - clone.dart
/// - binary.md
/// - type_checker.dart (if relevant)
///
/// -----------------------------------------------------------------------
/// ERROR HANDLING
/// -----------------------------------------------------------------------
///
/// As a rule of thumb, errors that can be detected statically are handled by
/// the frontend, typically by translating the erroneous code into a 'throw' or
/// a call to 'noSuchMethod'.
///
/// For example, there are no arity mismatches in static invocations, and
/// there are no direct invocations of a constructor on a abstract class.
///
/// -----------------------------------------------------------------------
/// STATIC vs TOP-LEVEL
/// -----------------------------------------------------------------------
///
/// The term `static` includes both static class members and top-level members.
///
/// "Static class member" is the preferred term for non-top level statics.
///
/// Static class members are not lifted to the library level because mirrors
/// and stack traces can observe that they are class members.
///
/// -----------------------------------------------------------------------
/// PROCEDURES
/// -----------------------------------------------------------------------
///
/// "Procedure" is an umbrella term for method, getter, setter, index-getter,
/// index-setter, operator overloader, and factory constructor.
///
/// Generative constructors, field initializers, local functions are NOT
/// procedures.
///
/// -----------------------------------------------------------------------
/// TRANSFORMATIONS
/// -----------------------------------------------------------------------
///
/// AST transformations can be performed using [TreeNode.replaceWith] or the
/// [Transformer] visitor class.
///
/// Use [Transformer] for bulk transformations that are likely to transform lots
/// of nodes, and [TreeNode.replaceWith] for sparse transformations that mutate
/// relatively few nodes. Or use whichever is more convenient.
///
/// The AST can also be mutated by direct field manipulation, but the user then
/// has to update parent pointers manually.
///
library kernel.ast;
import 'dart:collection' show ListBase;
import 'dart:convert' show utf8;
import 'visitor.dart';
export 'visitor.dart';
import 'canonical_name.dart' show CanonicalName, Reference;
export 'canonical_name.dart' show CanonicalName, Reference;
import 'default_language_version.dart' show defaultLanguageVersion;
export 'default_language_version.dart' show defaultLanguageVersion;
import 'transformations/flags.dart';
import 'text/ast_to_text.dart' as astToText;
import 'core_types.dart';
import 'class_hierarchy.dart';
import 'type_algebra.dart';
import 'type_environment.dart';
import 'src/assumptions.dart';
import 'src/non_null.dart';
import 'src/printer.dart';
import 'src/text_util.dart';
/// Any type of node in the IR.
abstract class Node {
const Node();
R accept<R>(Visitor<R> v);
R accept1<R, A>(Visitor1<R, A> v, A arg);
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.
@override
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() => toText(defaultAstTextStrategy);
/// 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);
String toText(AstTextStrategy strategy) {
AstPrinter printer = new AstPrinter(strategy);
toTextInternal(printer);
return printer.getText();
}
void toTextInternal(AstPrinter printer);
}
/// 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;
@override
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;
@override
R accept<R>(TreeVisitor<R> v);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg);
@override
void visitChildren(Visitor v);
void transformChildren(Transformer v);
void transformOrRemoveChildren(RemovingTransformer 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.
///
/// [replacement] must be non-null.
void replaceChild(TreeNode child, TreeNode replacement) {
// ignore: unnecessary_null_comparison
assert(replacement != null);
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.
///
/// [replacement] must be non-null.
void replaceWith(TreeNode replacement) {
// ignore: unnecessary_null_comparison
assert(replacement != null);
parent!.replaceChild(this, replacement);
parent = null;
}
// TODO(johnniwinther): Make this non-nullable.
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;
}
/// 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;
}
/// Computes the canonical names for this node using the [parent] as the
/// canonical name of the parent node.
void bindCanonicalNames(CanonicalName parent);
}
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);
}
// ------------------------------------------------------------------------
// LIBRARIES and CLASSES
// ------------------------------------------------------------------------
enum NonNullableByDefaultCompiledMode { Weak, Strong, Agnostic, Invalid }
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.
@override
Uri fileUri;
Version? _languageVersion;
Version get languageVersion => _languageVersion ?? defaultLanguageVersion;
void setLanguageVersion(Version languageVersion) {
// ignore: unnecessary_null_comparison
if (languageVersion == null) {
throw new StateError("Trying to set language version 'null'");
}
_languageVersion = languageVersion;
}
static const int SyntheticFlag = 1 << 0;
static const int NonNullableByDefaultFlag = 1 << 1;
static const int NonNullableByDefaultModeBit1 = 1 << 2;
static const int NonNullableByDefaultModeBit2 = 1 << 3;
static const int IsUnsupportedFlag = 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 bit1 = (flags & NonNullableByDefaultModeBit1) != 0;
bool bit2 = (flags & NonNullableByDefaultModeBit2) != 0;
if (!bit1 && !bit2) return NonNullableByDefaultCompiledMode.Weak;
if (bit1 && !bit2) return NonNullableByDefaultCompiledMode.Strong;
if (bit1 && bit2) return NonNullableByDefaultCompiledMode.Agnostic;
if (!bit1 && bit2) return NonNullableByDefaultCompiledMode.Invalid;
throw new StateError("Unused bit-pattern for compilation mode");
}
void set nonNullableByDefaultCompiledMode(
NonNullableByDefaultCompiledMode mode) {
switch (mode) {
case NonNullableByDefaultCompiledMode.Weak:
flags = (flags & ~NonNullableByDefaultModeBit1) &
~NonNullableByDefaultModeBit2;
break;
case NonNullableByDefaultCompiledMode.Strong:
flags = (flags | NonNullableByDefaultModeBit1) &
~NonNullableByDefaultModeBit2;
break;
case NonNullableByDefaultCompiledMode.Agnostic:
flags = (flags | NonNullableByDefaultModeBit1) |
NonNullableByDefaultModeBit2;
break;
case NonNullableByDefaultCompiledMode.Invalid:
flags = (flags & ~NonNullableByDefaultModeBit1) |
NonNullableByDefaultModeBit2;
break;
}
}
/// If true, the library is not supported through the 'dart.library.*' value
/// used in conditional imports and `bool.fromEnvironment` constants.
bool get isUnsupported => flags & IsUnsupportedFlag != 0;
void set isUnsupported(bool value) {
flags = value ? (flags | IsUnsupportedFlag) : (flags & ~IsUnsupportedFlag);
}
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;
@override
List<Expression> annotations;
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
List<LibraryPart> parts;
List<Typedef> _typedefs;
List<Class> _classes;
List<Extension> _extensions;
List<Procedure> _procedures;
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,
required this.fileUri,
Reference? reference})
// ignore: unnecessary_null_comparison
: assert(fileUri != null),
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);
}
List<Typedef> get typedefs => _typedefs;
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding typedefs when reading the dill file.
void set typedefsInternal(List<Typedef> typedefs) {
_typedefs = typedefs;
}
List<Class> get classes => _classes;
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding classes when reading the dill file.
void set classesInternal(List<Class> classes) {
_classes = classes;
}
List<Extension> get extensions => _extensions;
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding extensions when reading the dill file.
void set extensionsInternal(List<Extension> extensions) {
_extensions = extensions;
}
List<Procedure> get procedures => _procedures;
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding procedures when reading the dill file.
void set proceduresInternal(List<Procedure> procedures) {
_procedures = procedures;
}
List<Field> get fields => _fields;
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding fields when reading the dill file.
void set fieldsInternal(List<Field> fields) {
_fields = fields;
}
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);
@override
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_);
}
@override
CanonicalName bindCanonicalNames(CanonicalName parent) {
return parent.getChildFromUri(importUri)..bindTo(reference);
}
/// Computes the canonical name for this library and all its members.
void ensureCanonicalNames(CanonicalName parent) {
CanonicalName canonicalName = bindCanonicalNames(parent);
for (int i = 0; i < typedefs.length; ++i) {
typedefs[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < fields.length; ++i) {
fields[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < procedures.length; ++i) {
procedures[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < classes.length; ++i) {
classes[i].ensureCanonicalNames(canonicalName);
}
for (int i = 0; i < extensions.length; ++i) {
extensions[i].bindCanonicalNames(canonicalName);
}
}
/// 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();
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);
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitLibrary(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitLibrary(this, arg);
@override
void 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);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(dependencies, this);
v.transformList(parts, this);
v.transformList(typedefs, this);
v.transformList(classes, this);
v.transformList(extensions, this);
v.transformList(procedures, this);
v.transformList(fields, this);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformLibraryDependencyList(dependencies, this);
v.transformLibraryPartList(parts, this);
v.transformTypedefList(typedefs, this);
v.transformClassList(classes, this);
v.transformExtensionList(extensions, this);
v.transformProcedureList(procedures, this);
v.transformFieldList(fields, this);
}
static int _libraryIdCounter = 0;
int _libraryId = ++_libraryIdCounter;
int get libraryIdForTesting => _libraryId;
@override
int compareTo(Library other) => _libraryId - other._libraryId;
/// Returns a possibly synthesized name for this library, consistent with
/// the names across all [toString] calls.
@override
String toString() => libraryNameToString(this);
@override
void toTextInternal(AstPrinter printer) {
printer.write(libraryNameToString(this));
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
@override
String leakingDebugToString() => astToText.debugLibraryToString(this);
}
/// 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 implements Annotatable {
int flags;
@override
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 as Library;
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;
@override
void addAnnotation(Expression annotation) {
annotations.add(annotation..parent = this);
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitLibraryDependency(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) =>
v.visitLibraryDependency(this, arg);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
visitList(combinators, v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(combinators, this);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformCombinatorList(combinators, this);
}
@override
String toString() {
return "LibraryDependency(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
// TODO(johnniwinther): Implement this.
}
}
/// A part declaration in a library.
///
/// part <url>;
///
/// optionally with metadata.
class LibraryPart extends TreeNode implements Annotatable {
@override
final List<Expression> annotations;
final String partUri;
LibraryPart(this.annotations, this.partUri) {
setParents(annotations, this);
}
@override
void addAnnotation(Expression annotation) {
annotations.add(annotation..parent = this);
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitLibraryPart(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitLibraryPart(this, arg);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
}
@override
String toString() {
return "LibraryPart(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
// TODO(johnniwinther): Implement this.
}
}
/// A `show` or `hide` clause for an import or export.
class Combinator extends TreeNode {
bool isShow;
final List<String> names;
LibraryDependency get dependency => parent as LibraryDependency;
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
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitCombinator(this, arg);
@override
void visitChildren(Visitor v) {}
@override
void transformChildren(Transformer v) {}
@override
void transformOrRemoveChildren(RemovingTransformer v) {}
@override
String toString() {
return "Combinator(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
// TODO(johnniwinther): Implement this.
}
}
/// Declaration of a type alias.
class Typedef extends NamedNode implements FileUriNode, Annotatable {
/// The URI of the source file that contains the declaration of this typedef.
@override
Uri fileUri;
@override
List<Expression> annotations = const <Expression>[];
String name;
final List<TypeParameter> typeParameters;
// TODO(johnniwinther): Make this non-nullable.
DartType? type;
Typedef(this.name, this.type,
{Reference? reference,
required this.fileUri,
List<TypeParameter>? typeParameters,
List<TypeParameter>? typeParametersOfFunctionType,
List<VariableDeclaration>? positionalParameters,
List<VariableDeclaration>? namedParameters})
// ignore: unnecessary_null_comparison
: assert(fileUri != null),
this.typeParameters = typeParameters ?? <TypeParameter>[],
super(reference) {
setParents(this.typeParameters, this);
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChildFromTypedef(this).bindTo(reference);
}
Library get enclosingLibrary => parent as Library;
@override
R accept<R>(TreeVisitor<R> v) => v.visitTypedef(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitTypedef(this, arg);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
visitList(typeParameters, v);
type?.accept(v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(typeParameters, this);
if (type != null) {
type = v.visitDartType(type!);
}
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformTypeParameterList(typeParameters, this);
if (type != null) {
DartType newType = v.visitDartType(type!, dummyDartType);
if (identical(newType, dummyDartType)) {
type = null;
} else {
type = newType;
}
}
}
@override
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
@override
String toString() {
return "Typedef(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
printer.writeTypedefName(reference);
}
}
/// 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;
/// List of metadata annotations on the class.
///
/// This defaults to an immutable empty list. Use [addAnnotation] to add
/// annotations if needed.
@override
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 FlagAbstract = 1 << 0;
static const int FlagEnum = 1 << 1;
static const int FlagAnonymousMixin = 1 << 2;
static const int FlagEliminatedMixin = 1 << 3;
static const int FlagMixinDeclaration = 1 << 4;
static const int FlagHasConstConstructor = 1 << 5;
static const int FlagMacro = 1 << 6;
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 macro class.
bool get isMacro => flags & FlagMacro != 0;
void set isMacro(bool value) {
flags = value ? (flags | FlagMacro) : (flags & ~FlagMacro);
}
/// 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);
}
/// If this class is a mixin declaration, this list contains the types from
/// the `on` clause. Otherwise the list is empty.
List<Supertype> get onClause => _onClause ??= _computeOnClause();
List<Supertype> _computeOnClause() {
List<Supertype> 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.
@override
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.
List<Supertype> implementedTypes;
List<Supertype>? _onClause;
/// 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() {
void Function()? lazyBuilderLocal = lazyBuilder;
if (lazyBuilderLocal != null) {
lazyBuilder = null;
lazyBuilderLocal();
}
}
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;
return _fieldsView ??= new DirtifyingList(this, _fieldsInternal);
}
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding fields when reading the dill file.
void set fieldsInternal(List<Field> fields) {
_fieldsInternal = fields;
_fieldsView = null;
}
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;
return _constructorsView ??=
new DirtifyingList(this, _constructorsInternal);
}
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding constructors when reading the dill file.
void set constructorsInternal(List<Constructor> constructors) {
_constructorsInternal = constructors;
_constructorsView = null;
}
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;
return _proceduresView ??= new DirtifyingList(this, _proceduresInternal);
}
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding procedures when reading the dill file.
void set proceduresInternal(List<Procedure> procedures) {
_proceduresInternal = procedures;
_proceduresView = null;
}
List<RedirectingFactory> _redirectingFactoriesInternal;
DirtifyingList<RedirectingFactory>? _redirectingFactoriesView;
/// Redirecting factory constructors declared in the class.
///
/// For mixin applications this should be empty.
List<RedirectingFactory> get redirectingFactories {
ensureLoaded();
// If already dirty the caller just might as well add stuff directly too.
if (dirty) return _redirectingFactoriesInternal;
return _redirectingFactoriesView ??=
new DirtifyingList(this, _redirectingFactoriesInternal);
}
/// Internal. Should *ONLY* be used from within kernel.
///
/// Used for adding redirecting factory constructor when reading the dill
/// file.
void set redirectingFactoryConstructorsInternal(
List<RedirectingFactory> redirectingFactoryConstructors) {
_redirectingFactoriesInternal = redirectingFactoryConstructors;
_redirectingFactoriesView = null;
}
Class(
{required 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<RedirectingFactory>? redirectingFactoryConstructors,
required this.fileUri,
Reference? reference})
// ignore: unnecessary_null_comparison
: assert(name != null),
// ignore: unnecessary_null_comparison
assert(fileUri != null),
this.typeParameters = typeParameters ?? <TypeParameter>[],
this.implementedTypes = implementedTypes ?? <Supertype>[],
this._fieldsInternal = fields ?? <Field>[],
this._constructorsInternal = constructors ?? <Constructor>[],
this._proceduresInternal = procedures ?? <Procedure>[],
this._redirectingFactoriesInternal =
redirectingFactoryConstructors ?? <RedirectingFactory>[],
super(reference) {
setParents(this.typeParameters, this);
setParents(this._constructorsInternal, this);
setParents(this._proceduresInternal, this);
setParents(this._fieldsInternal, this);
setParents(this._redirectingFactoriesInternal, this);
this.isAbstract = isAbstract;
this.isAnonymousMixin = isAnonymousMixin;
}
@override
CanonicalName bindCanonicalNames(CanonicalName parent) {
return parent.getChild(name)..bindTo(reference);
}
/// Computes the canonical name for this class and all its members.
void ensureCanonicalNames(CanonicalName parent) {
CanonicalName canonicalName = bindCanonicalNames(parent);
if (!dirty) return;
for (int i = 0; i < fields.length; ++i) {
fields[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < procedures.length; ++i) {
procedures[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < constructors.length; ++i) {
constructors[i].bindCanonicalNames(canonicalName);
}
for (int i = 0; i < redirectingFactories.length; ++i) {
redirectingFactories[i].bindCanonicalNames(canonicalName);
}
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 < redirectingFactories.length; ++i) {
RedirectingFactory member = redirectingFactories[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,
redirectingFactories
].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 as Library;
/// 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 constructor to this class.
void addConstructor(Constructor constructor) {
dirty = true;
constructor.parent = this;
_constructorsInternal.add(constructor);
}
/// Adds a procedure to this class.
void addProcedure(Procedure procedure) {
dirty = true;
procedure.parent = this;
_proceduresInternal.add(procedure);
}
/// Adds a field to this class.
void addField(Field field) {
dirty = true;
field.parent = this;
_fieldsInternal.add(field);
}
/// Adds a field to this class.
void addRedirectingFactory(RedirectingFactory redirectingFactory) {
dirty = true;
redirectingFactory.parent = this;
_redirectingFactoriesInternal.add(redirectingFactory);
}
@override
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitClass(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitClass(this, arg);
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);
}
@override
String toString() => 'Class(${toStringInternal()})';
@override
void toTextInternal(AstPrinter printer) {
printer.writeClassName(reference);
}
@override
void 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(redirectingFactories, v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(typeParameters, this);
if (supertype != null) {
supertype = v.visitSupertype(supertype!);
}
if (mixedInType != null) {
mixedInType = v.visitSupertype(mixedInType!);
}
v.transformSupertypeList(implementedTypes);
v.transformList(constructors, this);
v.transformList(procedures, this);
v.transformList(fields, this);
v.transformList(redirectingFactories, this);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformTypeParameterList(typeParameters, this);
if (supertype != null) {
Supertype newSupertype = v.visitSupertype(supertype!, dummySupertype);
if (identical(newSupertype, dummySupertype)) {
supertype = null;
} else {
supertype = newSupertype;
}
}
if (mixedInType != null) {
Supertype newMixedInType = v.visitSupertype(mixedInType!, dummySupertype);
if (identical(newMixedInType, dummySupertype)) {
mixedInType = null;
} else {
mixedInType = newMixedInType;
}
}
v.transformSupertypeList(implementedTypes);
v.transformConstructorList(constructors, this);
v.transformProcedureList(procedures, this);
v.transformFieldList(fields, this);
v.transformRedirectingFactoryList(redirectingFactories, this);
}
@override
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 Annotatable, 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.
@override
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 {}
///
/// The 'on clause' appears also in the experimental feature 'extension
/// types' as a part of an extension type declaration, for example:
///
/// class A {}
/// extension type B on A {}
late DartType onType;
/// The 'show' and 'hide' clauses of an extension type declaration.
ExtensionTypeShowHideClause? showHideClause;
/// The members declared by the extension.
///
/// The members are converted into top-level members and only accessible
/// by reference through [ExtensionMemberDescriptor].
List<ExtensionMemberDescriptor> members;
@override
List<Expression> annotations = const <Expression>[];
// Must match serialized bit positions.
static const int FlagExtensionTypeDeclaration = 1 << 0;
int flags = 0;
@override
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
Extension(
{required this.name,
List<TypeParameter>? typeParameters,
DartType? onType,
List<ExtensionMemberDescriptor>? members,
required this.fileUri,
Reference? reference})
// ignore: unnecessary_null_comparison
: assert(name != null),
// ignore: unnecessary_null_comparison
assert(fileUri != null),
this.typeParameters = typeParameters ?? <TypeParameter>[],
this.members = members ?? <ExtensionMemberDescriptor>[],
super(reference) {
setParents(this.typeParameters, this);
if (onType != null) {
this.onType = onType;
}
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChild(name).bindTo(reference);
}
Library get enclosingLibrary => parent as Library;
bool get isExtensionTypeDeclaration {
return flags & FlagExtensionTypeDeclaration != 0;
}
void set isExtensionTypeDeclaration(bool value) {
flags = value
? (flags | FlagExtensionTypeDeclaration)
: (flags & ~FlagExtensionTypeDeclaration);
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitExtension(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.visitExtension(this, arg);
R acceptReference<R>(Visitor<R> v) => v.visitExtensionReference(this);
@override
void visitChildren(Visitor v) {
visitList(typeParameters, v);
onType.accept(v);
if (showHideClause != null) {
visitList(showHideClause!.shownSupertypes, v);
visitList(showHideClause!.hiddenSupertypes, v);
}
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(typeParameters, this);
// ignore: unnecessary_null_comparison
if (onType != null) {
onType = v.visitDartType(onType);
}
if (showHideClause != null) {
v.transformSupertypeList(showHideClause!.shownSupertypes);
v.transformSupertypeList(showHideClause!.hiddenSupertypes);
}
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformTypeParameterList(typeParameters, this);
// ignore: unnecessary_null_comparison
if (onType != null) {
onType = v.visitDartType(onType, cannotRemoveSentinel);
}
if (showHideClause != null) {
v.transformSupertypeList(showHideClause!.shownSupertypes);
v.transformSupertypeList(showHideClause!.hiddenSupertypes);
}
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
@override
String toString() {
return "Extension(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
printer.writeExtensionName(reference);
}
}
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 in 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.
final Reference member;
ExtensionMemberDescriptor(
{required this.name,
required this.kind,
bool isStatic: false,
required 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);
}
@override
String toString() {
return 'ExtensionMemberDescriptor($name,$kind,'
'${member.toStringInternal()},isStatic=${isStatic})';
}
}
enum CallSiteAccessKind {
methodInvocation,
getterInvocation,
setterInvocation,
operatorInvocation,
}
/// Elements of the 'show' and 'hide' clauses of an extension type declaration.
class ExtensionTypeShowHideClause {
/// The types in the 'show clause' of the extension type declaration.
///
/// For instance A, B in:
///
/// class A {}
/// class B {}
/// class C extends B implements A {}
/// extension type E on C show B, A {}
final List<Supertype> shownSupertypes = <Supertype>[];
/// The methods in the 'show clause' of the extension type declaration.
///
/// For instance foo in
///
/// class A {
/// void foo() {}
/// }
/// extension type E on A show foo {}
final List<Reference> shownMethods = <Reference>[];
/// The getters in the 'show clause' of the extension type declaration.
///
/// For instance foo, bar, baz in
///
/// class A {
/// void foo() {}
/// int? bar;
/// int get baz => 42;
/// }
/// extension type E on A show get foo, get bar, get baz {}
final List<Reference> shownGetters = <Reference>[];
/// The setters in the 'show clause' of the extension type declaration.
///
/// For instance foo, bar in
///
/// class A {
/// int? foo;
/// void set bar(int value) {}
/// }
/// extension type E on A show set foo, set bar {}
final List<Reference> shownSetters = <Reference>[];
/// The operators in the 'show clause' of the extension type declaration.
///
/// For instance +, * in
///
/// class A {
/// A operator+(A other) => other;
/// A operator*(A other) => this;
/// }
/// extension type E on A show operator +, operator * {}
final List<Reference> shownOperators = <Reference>[];
/// The types in the 'hide clause' of the extension type declaration.
///
/// For instance A, B in:
///
/// class A {}
/// class B {}
/// class C extends B implements A {}
/// extension E on C hide A, B {}
final List<Supertype> hiddenSupertypes = <Supertype>[];
/// The methods in the 'hide clause' of the extension type declaration.
///
/// For instance foo in
///
/// class A {
/// void foo() {}
/// }
/// extension type E on A hide foo {}
final List<Reference> hiddenMethods = <Reference>[];
/// The getters in the 'hide clause' of the extension type declaration.
///
/// For instance foo, bar, baz in
///
/// class A {
/// void foo() {}
/// int? bar;
/// int get baz => 42;
/// }
/// extension type E on A hide get foo, get bar, get baz {}
final List<Reference> hiddenGetters = <Reference>[];
/// The setters in the 'hide clause' of the extension type declaration.
///
/// For instance foo, bar in
///
/// class A {
/// int? foo;
/// void set bar(int value) {}
/// }
/// extension type E on A hide set foo, set bar {}
final List<Reference> hiddenSetters = <Reference>[];
/// The operators in the 'hide clause' of the extension type declaration.
///
/// For instance +, * in
///
/// class A {
/// A operator+(A other) => other;
/// A operator*(A other) => this;
/// }
/// extension type E on A hide operator +, operator * {}
final List<Reference> hiddenOperators = <Reference>[];
Reference? findShownReference(Name name,
CallSiteAccessKind callSiteAccessKind, ClassHierarchy hierarchy) {
List<Reference> shownReferences;
List<Reference> hiddenReferences;
switch (callSiteAccessKind) {
case CallSiteAccessKind.getterInvocation:
shownReferences = shownGetters;
hiddenReferences = hiddenGetters;
break;
case CallSiteAccessKind.setterInvocation:
shownReferences = shownSetters;
hiddenReferences = hiddenSetters;
break;
case CallSiteAccessKind.methodInvocation:
shownReferences = shownMethods;
hiddenReferences = hiddenMethods;
break;
case CallSiteAccessKind.operatorInvocation:
shownReferences = shownOperators;
hiddenReferences = hiddenOperators;
break;
}
Reference? reference = _findMember(
name, shownReferences, shownSupertypes, hierarchy, callSiteAccessKind);
if (reference != null &&
_findMember(name, hiddenReferences, hiddenSupertypes, hierarchy,
callSiteAccessKind) ==
null) {
return reference;
}
return null;
}
Reference? _findMember(
Name name,
List<Reference> references,
List<Supertype> interfaces,
ClassHierarchy hierarchy,
CallSiteAccessKind callSiteAccessKind) {
for (Reference reference in references) {
if (reference.asMember.name == name) {
return reference;
}
}
for (Supertype interface in interfaces) {
Member? member = hierarchy.getInterfaceMember(interface.classNode, name,
setter: callSiteAccessKind == CallSiteAccessKind.setterInvocation);
if (member != null) {
return member.reference;
}
}
return null;
}
}
// ------------------------------------------------------------------------
// 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.
@override
List<Expression> annotations = const <Expression>[];
Name name;
/// The URI of the source file this member was loaded from.
@override
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)
// ignore: unnecessary_null_comparison
: assert(name != null),
// ignore: unnecessary_null_comparison
assert(fileUri != null),
super(reference);
Class? get enclosingClass => parent is Class ? parent as Class : null;
Library get enclosingLibrary =>
(parent is Class ? parent!.parent : parent) as Library;
@override
R accept<R>(MemberVisitor<R> v);
@override
R accept1<R, A>(MemberVisitor1<R, A> v, A arg);
R acceptReference<R>(MemberReferenceVisitor<R> 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);
/// If `true` this procedure is not part of the interface but only part of the
/// class members.
///
/// This is `true` for instance for augmented procedures and synthesized
/// fields added for the late lowering.
bool get isInternalImplementation => false;
/// The function signature and 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
void toTextInternal(AstPrinter printer) {
printer.writeMemberName(reference);
}
@override
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
/// Returns the type of this member when accessed as a getter.
///
/// For a field, this is the field type. For a getter, this is the return
/// type. For a method or constructor, this is the tear off type.
///
/// For a setter, this is undefined. Currently, non-nullable `Never` is
/// returned.
// TODO(johnniwinther): Should we use `InvalidType` for the undefined cases?
DartType get getterType;
/// Returns the type of this member when access as a getter on a super class.
///
/// This is in most cases the same as for [getterType].
///
/// An exception is for forwarding semi stubs:
///
/// class Super {
/// void method(num a) {}
/// }
/// class Class extends Super {
/// void method(covariant int a);
/// }
/// class Subclass extends Class {
/// void method(int a) {
/// super.method; // Type `void Function(num)`.
/// Class().method; // Type `void Function(int)`.
/// }
/// }
///
/// Here, `Class.method` is turned into a forwarding semi stub
///
/// void method(covariant num a) => super.method(a);
///
/// with [signatureType] `void Function(int)`. When `Class.method` is used
/// as the target of a super get, it has getter type `void Function(num)` and
/// as the target of an instance get, it has getter type `void Function(int)`.
DartType get superGetterType => getterType;
/// Returns the type of this member when accessed as a setter.
///
/// For an assignable field, this is the field type. For a setter this is the
/// parameter type.
///
/// For other members, including unassignable fields, this is undefined.
/// Currently, non-nullable `Never` is returned.
// TODO(johnniwinther): Should we use `InvalidType` for the undefined cases?
DartType get setterType;
/// Returns the type of this member when access as a setter on a super class.
///
/// This is in most cases the same as for [setterType].
///
/// An exception is for forwarding semi stubs:
///
/// class Super {
/// void set setter(num a) {}
/// }
/// class Class extends Super {
/// void set setter(covariant int a);
/// }
/// class Subclass extends Class {
/// void set setter(int a) {
/// super.setter = 0.5; // Valid.
/// Class().setter = 0.5; // Invalid.
/// }
/// }
///
/// Here, `Class.setter` is turned into a forwarding semi stub
///
/// void set setter(covariant num a) => super.setter = a;
///
/// with [signatureType] `void Function(int)`. When `Class.setter` is used
/// as the target of a super set, it has setter type `num` and as the target
/// of an instance set, it has setter type `int`.
DartType get superSetterType => setterType;
bool get containsSuperCalls {
return transformerFlags & TransformerFlag.superCalls != 0;
}
/// If this member is a member signature, [memberSignatureOrigin] is one of
/// the non-member signature members from which it was created.
Member? get memberSignatureOrigin => null;
}
/// 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.
/// Reference used for reading from this field.
///
/// This should be used as the target in [StaticGet], [InstanceGet], and
/// [SuperPropertyGet].
final Reference getterReference;
/// Reference used for writing to this field.
///
/// This should be used as the target in [StaticSet], [InstanceSet], and
/// [SuperPropertySet].
final Reference? setterReference;
@override
@Deprecated("Use the specific getterReference/setterReference instead")
Reference get reference => super.reference;
/// Reference used for initializing this field.
///
/// This should be used as the target in [FieldInitializer] and as the key
/// in the field values of [InstanceConstant].
Reference get fieldReference => super.reference;
Field.mutable(Name name,
{this.type: const DynamicType(),
this.initializer,
bool isCovariantByDeclaration: false,
bool isFinal: false,
bool isStatic: false,
bool isLate: false,
int transformerFlags: 0,
required Uri fileUri,
Reference? fieldReference,
Reference? getterReference,
Reference? setterReference})
: this.getterReference = getterReference ?? new Reference(),
this.setterReference = setterReference ?? new Reference(),
super(name, fileUri, fieldReference) {
this.getterReference.node = this;
this.setterReference!.node = this;
// ignore: unnecessary_null_comparison
assert(type != null);
initializer?.parent = this;
this.isCovariantByDeclaration = isCovariantByDeclaration;
this.isFinal = isFinal;
this.isStatic = isStatic;
this.isLate = isLate;
this.transformerFlags = transformerFlags;
}
Field.immutable(Name name,
{this.type: const DynamicType(),
this.initializer,
bool isCovariantByDeclaration: false,
bool isFinal: false,
bool isConst: false,
bool isStatic: false,
bool isLate: false,
int transformerFlags: 0,
required Uri fileUri,
Reference? fieldReference,
Reference? getterReference})
: this.getterReference = getterReference ?? new Reference(),
this.setterReference = null,
super(name, fileUri, fieldReference) {
this.getterReference.node = this;
// ignore: unnecessary_null_comparison
assert(type != null);
initializer?.parent = this;
this.isCovariantByDeclaration = isCovariantByDeclaration;
this.isFinal = isFinal;
this.isConst = isConst;
this.isStatic = isStatic;
this.isLate = isLate;
this.transformerFlags = transformerFlags;
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChildFromField(this).bindTo(fieldReference);
parent.getChildFromFieldGetter(this).bindTo(getterReference);
if (hasSetter) {
parent.getChildFromFieldSetter(this).bindTo(setterReference!);
}
}
@override
void _relinkNode() {
this.fieldReference.node = this;
this.getterReference.node = this;
if (hasSetter) {
this.setterReference!.node = this;
}
}
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 FlagCovariant = 1 << 3;
static const int FlagCovariantByClass = 1 << 4;
static const int FlagLate = 1 << 5;
static const int FlagExtensionMember = 1 << 6;
static const int FlagNonNullableByDefault = 1 << 7;
static const int FlagInternalImplementation = 1 << 8;
/// Whether the field is declared with the `covariant` keyword.
bool get isCovariantByDeclaration => flags & FlagCovariant != 0;
bool get isFinal => flags & FlagFinal != 0;
@override
bool get isConst => flags & FlagConst != 0;
bool get isStatic => flags & FlagStatic != 0;
@override
bool get isExtensionMember => flags & FlagExtensionMember != 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 isCovariantByClass => flags & FlagCovariantByClass != 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.
@override
bool get isInternalImplementation => flags & FlagInternalImplementation != 0;
void set isCovariantByDeclaration(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 isCovariantByClass(bool value) {
flags = value
? (flags | FlagCovariantByClass)
: (flags & ~FlagCovariantByClass);
}
void set isLate(bool value) {
flags = value ? (flags | FlagLate) : (flags & ~FlagLate);
}
void set isInternalImplementation(bool value) {
flags = value
? (flags | FlagInternalImplementation)
: (flags & ~FlagInternalImplementation);
}
@override
bool get isInstanceMember => !isStatic;
@override
bool get hasGetter => true;
@override
bool get hasSetter => setterReference != null;
@override
bool get isExternal => false;
@override
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);
}
@override
R accept<R>(MemberVisitor<R> v) => v.visitField(this);
@override
R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => v.visitField(this, arg);
@override
R acceptReference<R>(MemberReferenceVisitor<R> v) =>
v.visitFieldReference(this);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
type.accept(v);
name.accept(v);
initializer?.accept(v);
}
@override
void transformChildren(Transformer v) {
type = v.visitDartType(type);
v.transformList(annotations, this);
if (initializer != null) {
initializer = v.transform(initializer!);
initializer?.parent = this;
}
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
type = v.visitDartType(type, null);
v.transformExpressionList(annotations, this);
if (initializer != null) {
initializer = v.transformOrRemoveExpression(initializer!);
initializer?.parent = this;
}
}
@override
DartType get getterType => type;
@override
DartType get setterType => hasSetter ? type : const NeverType.nonNullable();
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
@override
void toTextInternal(AstPrinter printer) {
printer.writeMemberName(fieldReference);
}
}
/// 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;
@override
FunctionNode function;
List<Initializer> initializers;
Constructor(this.function,
{required Name name,
bool isConst: false,
bool isExternal: false,
bool isSynthetic: false,
List<Initializer>? initializers,
int transformerFlags: 0,
required Uri fileUri,
Reference? reference})
: this.initializers = initializers ?? <Initializer>[],
// ignore: unnecessary_null_comparison
assert(function != null),
super(name, fileUri, reference) {
function.parent = this;
setParents(this.initializers, this);
this.isConst = isConst;
this.isExternal = isExternal;
this.isSynthetic = isSynthetic;
this.transformerFlags = transformerFlags;
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChildFromConstructor(this).bindTo(reference);
}
@override
Class get enclosingClass => parent as Class;
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;
@override
bool get isConst => flags & FlagConst != 0;
@override
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);
}
@override
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isSynthetic(bool value) {
flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
}
@override
bool get isInstanceMember => false;
@override
bool get hasGetter => false;
@override
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);
}
@override
R accept<R>(MemberVisitor<R> v) => v.visitConstructor(this);
@override
R accept1<R, A>(MemberVisitor1<R, A> v, A arg) =>
v.visitConstructor(this, arg);
@override
R acceptReference<R>(MemberReferenceVisitor<R> v) =>
v.visitConstructorReference(this);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
name.accept(v);
visitList(initializers, v);
function.accept(v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(initializers, this);
// ignore: unnecessary_null_comparison
if (function != null) {
function = v.transform(function);
function.parent = this;
}
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformInitializerList(initializers, this);
// ignore: unnecessary_null_comparison
if (function != null) {
function = v.transform(function);
function.parent = this;
}
}
// TODO(johnniwinther): Provide the tear off type here.
@override
DartType get getterType => const NeverType.nonNullable();
@override
DartType get setterType => const NeverType.nonNullable();
@override
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.
///
/// [RedirectingFactory]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 RedirectingFactory extends Member {
int flags = 0;
/// [RedirectingFactory]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
/// [RedirectingFactory] redirects to.
// TODO(johnniwinther): Make this non-nullable.
Reference? targetReference;
/// [FunctionNode] that holds the type parameters, copied from the enclosing
/// class, and the parameters defined on the redirecting factory.
///
/// The `FunctionNode.body` is `null` or a synthesized [ConstructorInvocation]
/// of the [targetReference] constructor using the [typeArguments] and
/// [VariableGet] of the parameters.
@override
FunctionNode function;
RedirectingFactory(this.targetReference,
{required Name name,
bool isConst: false,
bool isExternal: false,
int transformerFlags: 0,
List<DartType>? typeArguments,
required this.function,
required Uri fileUri,
Reference? reference})
: this.typeArguments = typeArguments ?? <DartType>[],
super(name, fileUri, reference) {
function.parent = this;
this.isConst = isConst;
this.isExternal = isExternal;
this.transformerFlags = transformerFlags;
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChildFromRedirectingFactory(this).bindTo(reference);
}
@override
Class get enclosingClass => parent as Class;
static const int FlagConst = 1 << 0; // Must match serialized bit positions.
static const int FlagExternal = 1 << 1;
static const int FlagNonNullableByDefault = 1 << 2;
@override
bool get isConst => flags & FlagConst != 0;
@override
bool get isExternal => flags & FlagExternal != 0;
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
@override
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
@override
bool get isInstanceMember => false;
@override
bool get hasGetter => false;
@override
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 = getMemberReferenceGetter(member);
}
@override
R accept<R>(MemberVisitor<R> v) => v.visitRedirectingFactory(this);
@override
R accept1<R, A>(MemberVisitor1<R, A> v, A arg) =>
v.visitRedirectingFactory(this, arg);
@override
R acceptReference<R>(MemberReferenceVisitor<R> v) =>
v.visitRedirectingFactoryReference(this);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
target?.acceptReference(v);
visitList(typeArguments, v);
name.accept(v);
function.accept(v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformDartTypeList(typeArguments);
function = v.transform(function)..parent = this;
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformDartTypeList(typeArguments);
function = v.transform(function)..parent = this;
}
@override
DartType get getterType =>
function.computeFunctionType(enclosingLibrary.nonNullable);
@override
DartType get setterType => const NeverType.nonNullable();
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset);
}
}
/// Enum for the semantics of the `Procedure.stubTarget` property.
enum ProcedureStubKind {
/// A regular procedure declared in source code.
///
/// The stub target is `null`.
Regular,
/// An abstract procedure inserted to add `isCovariantByDeclaration` and
/// `isCovariantByClass` to parameters for a set of overridden members.
///
/// The stub is inserted when not all of the overridden members agree on
/// the covariance flags. For instance:
///
/// class A<T> {
/// void method1(num o) {}
/// void method2(T o) {}
/// }
/// class B {
/// void method1(covariant int o) {}
/// void method2(int o) {}
/// }
/// class C implements A<int>, B {
/// // Abstract forwarding stub needed because the parameter is
/// // covariant in `B.method1` but not in `A.method1`.
/// void method1(covariant num o);
/// // Abstract forwarding stub needed because the parameter is a
/// // generic covariant impl in `A.method2` but not in `B.method2`.
/// void method2(/*generic-covariant-impl*/ int o);
/// }
///
/// The stub target is one of the overridden members.
AbstractForwardingStub,
/// A concrete procedure inserted to add `isCovariantByDeclaration` and
/// `isCovariantByClass` checks to parameters before calling the
/// overridden member in the superclass.
///
/// The stub is inserted when not all of the overridden members agree on
/// the covariance flags and the overridden super class member does not
/// have the same covariance flags. For instance:
///
/// class A<T> {
/// void method1(num o) {}
/// void method2(T o) {}
/// }
/// class B {
/// void method1(covariant int o) {}
/// void method2(int o) {}
/// }
/// class C extends A<int> implements B {
/// // Concrete forwarding stub needed because the parameter is
/// // covariant in `B.method1` but not in `A.method1`.
/// void method1(covariant num o) => super.method1(o);
/// // No need for a concrete forwarding stub for `A.method2` because
/// // it has the right covariance flags already.
/// }
///
/// The stub target is the called superclass member.
ConcreteForwardingStub,
/// A concrete procedure inserted to forward calls to `noSuchMethod` for
/// an inherited member that it does not implement.
///
/// The stub is inserted when a class implements private members of another
/// library or declares/inherits a user-defined `noSuchMethod` method. For
/// instance:
///
/// // lib1:
/// class A {
/// void _privateMethod() {}
/// }
/// // lib2:
/// class B implements A {
/// // Forwarding stub inserted to forward calls to `A._privateMethod`.
/// void _privateMethod() => noSuchMethod(#_privateMethod, ...);
/// }
/// class C {
/// void method() {}
/// }
/// class D implements C {
/// noSuchMethod(o) { ... }
/// // Forwarding stub inserted to forward calls to `C.method`.
/// void method() => noSuchMethod(#method, ...);
/// }
///
///
/// The stub target is `null` if the procedure preexisted as an abstract
/// procedure. Otherwise the stub target is one of the inherited members.
NoSuchMethodForwarder,
/// An abstract procedure inserted to show the combined member signature type
/// of set of overridden members.
///
/// The stub is inserted when an opt-in member is inherited into an opt-out
/// library or when NNBD_TOP_MERGE was used to compute the type of a merge
/// point in an opt-in library. For instance:
///
/// // lib1: opt-in
/// class A {
/// int? method1() => null;
/// void method2(Object? o) {}
/// }
/// class B {
/// dynamic method2(dynamic o);
/// }
/// class C implements A, B {
/// // Member signature inserted for the NNBD_TOP_MERGE type of
/// // `A.method2` and `B.method2`.
/// Object? method2(Object? o);
/// }
/// // lib2: opt-out
/// class D extends A {
/// // Member signature inserted for the LEGACY_ERASURE type of
/// // `A.method1` and `A.method2` with types `int* Function()`
/// // and `void Function(Object*)`, respectively.
/// int method1();
/// void method2(Object o);
/// }
///
/// The stub target is one of the overridden members.
MemberSignature,
/// An abstract procedure inserted for the application of an abstract mixin
/// member.
///
/// The stub is inserted when an abstract member is mixed into a mixin
/// application. For instance:
///
/// class Super {}
/// abstract class Mixin {
/// void method();
/// }
/// class Class = Super with Mixin
/// // An abstract mixin stub for `A.method` is added to `Class`
/// void method();
/// ;
///
/// This is added to ensure that interface targets are resolved consistently
/// in face of cloning. For instance, without the abstract mixin stub, this
/// call:
///
/// method(Class c) => c.method();
///
/// would use `Mixin.method` as its target, but after loading from a VM .dill
/// (which clones all mixin members) the call would resolve to `Class.method`
/// instead. By adding the mixin stub to `Class`, all accesses both before
/// and after .dill will point to `Class.method`.
///
/// The stub target is the mixin member.
AbstractMixinStub,
/// A concrete procedure inserted for the application of a concrete mixin
/// member. The implementation calls the mixin member via a super-call.
///
/// The stub is inserted when a concrete member is mixed into a mixin
/// application. For instance:
///
/// class Super {}
/// abstract class Mixin {
/// void method() {}
/// }
/// class Class = Super with Mixin
/// // A concrete mixin stub for `A.method` is added to `Class` which
/// // calls `A.method`.
/// void method() => super.method();
/// ;
///
/// This is added to ensure that super accesses are resolved correctly, even
/// in face of cloning. For instance, without the concrete mixin stub, this
/// super call:
///
/// class Subclass extends Class {
/// method(Class c) => super.method();
/// }
///
/// would use `Mixin.method` as its target, which would need to be updated to
/// match the clone of the mixin member performed for instance by the VM. By
/// adding the concrete mixin stub to `Class`, all accesses both before and
/// after cloning will point to `Class.method`.
///
/// The stub target is the called mixin member.
ConcreteMixinStub,
}
/// 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 fileStartOffset = TreeNode.noOffset;
final ProcedureKind kind;
int flags = 0;
@override
FunctionNode function;
ProcedureStubKind stubKind;
Reference? stubTargetReference;
/// The interface member signature type of this procedure.
///
/// Normally this is derived from the parameter types and return type of
/// [function]. In rare cases, the interface member signature type is
/// different from the class member type, in which case the interface member
/// signature type is stored here.
///
/// For instance
///
/// class Super {
/// void method(num a) {}
/// }
/// class Class extends Super {
/// void method(covariant int a);
/// }
///
/// Here the member `Class.method` is turned into a forwarding semi stub to
/// ensure that arguments passed to `Super.method` are checked as covariant.
/// Since `Super.method` allows `num` as argument, the inserted covariant
/// check must be against `num` and not `int`, and the parameter type of the
/// forwarding semi stub must be changed to `num`. Still, the interface of
/// `Class` requires that `Class.method` is `void Function(int)`, so for this,
/// it is stored explicitly as the [signatureType] on the procedure.
FunctionType? signatureType;
Procedure(Name name, ProcedureKind kind, FunctionNode function,
{bool isAbstract: false,
bool isStatic: false,
bool isExternal: false,
bool isConst: false,
bool isExtensionMember: false,
bool isSynthetic: false,
int transformerFlags: 0,
required Uri fileUri,
Reference? reference,
ProcedureStubKind stubKind: ProcedureStubKind.Regular,
Member? stubTarget})
: this._byReferenceRenamed(name, kind, function,
isAbstract: isAbstract,
isStatic: isStatic,
isExternal: isExternal,
isConst: isConst,
isExtensionMember: isExtensionMember,
isSynthetic: isSynthetic,
transformerFlags: transformerFlags,
fileUri: fileUri,
reference: reference,
stubKind: stubKind,
stubTargetReference:
getMemberReferenceBasedOnProcedureKind(stubTarget, kind));
Procedure._byReferenceRenamed(Name name, this.kind, this.function,
{bool isAbstract: false,
bool isStatic: false,
bool isExternal: false,
bool isConst: false,
bool isExtensionMember: false,
bool isSynthetic: false,
int transformerFlags: 0,
required Uri fileUri,
Reference? reference,
this.stubKind: ProcedureStubKind.Regular,
this.stubTargetReference})
// ignore: unnecessary_null_comparison
: assert(kind != null),
// ignore: unnecessary_null_comparison
assert(function != null),
super(name, fileUri, reference) {
function.parent = this;
this.isAbstract = isAbstract;
this.isStatic = isStatic;
this.isExternal = isExternal;
this.isConst = isConst;
this.isExtensionMember = isExtensionMember;
this.isSynthetic = isSynthetic;
setTransformerFlagsWithoutLazyLoading(transformerFlags);
assert(!(isMemberSignature && stubTargetReference == null),
"No member signature origin for member signature $this.");
assert(
!(memberSignatureOrigin is Procedure &&
(memberSignatureOrigin as Procedure).isMemberSignature),
"Member signature origin cannot be a member signature "
"$memberSignatureOrigin for $this.");
}
// 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.
@override
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.
@override
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;
}
@override
void bindCanonicalNames(CanonicalName parent) {
parent.getChildFromProcedure(this).bindTo(reference);
}
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.
// TODO(29841): Remove this flag after the issue is resolved.
static const int FlagRedirectingFactory = 1 << 4;
static const int FlagExtensionMember = 1 << 5;
static const int FlagNonNullableByDefault = 1 << 6;
static const int FlagSynthetic = 1 << 7;
static const int FlagInternalImplementation = 1 << 8;
bool get isStatic => flags & FlagStatic != 0;
@override
bool get isAbstract => flags & FlagAbstract != 0;
@override
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`.
@override
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
/// [concreteForwardingStubTarget].
///
/// 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 =>
stubKind == ProcedureStubKind.AbstractForwardingStub ||
stubKind == ProcedureStubKind.ConcreteForwardingStub;
/// 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 => !isSynthetic && isForwardingStub;
/// 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 => stubKind == ProcedureStubKind.MemberSignature;
// Indicates if this [Procedure] represents a redirecting factory constructor
// and doesn't have a runnable body.
bool get isRedirectingFactory {
return flags & FlagRedirectingFactory != 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 [concreteForwardingStubTarget].
bool get isSyntheticForwarder => isForwardingStub && !isForwardingSemiStub;
bool get isSynthetic => flags & FlagSynthetic != 0;
bool get isNoSuchMethodForwarder =>
stubKind == ProcedureStubKind.NoSuchMethodForwarder;
/// If `true` this procedure is not part of the interface but only part of the
/// class members.
///
/// This is `true` for instance for augmented procedures.
@override
bool get isInternalImplementation => flags & FlagInternalImplementation != 0;
void set isInternalImplementation(bool value) {
flags = value
? (flags | FlagInternalImplementation)
: (flags & ~FlagInternalImplementation);
}
@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);
}
@override
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isRedirectingFactory(bool value) {
flags = value
? (flags | FlagRedirectingFactory)
: (flags & ~FlagRedirectingFactory);
}
void set isExtensionMember(bool value) {
flags =
value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember);
}
void set isSynthetic(bool value) {
flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
}
@override
bool get isInstanceMember => !isStatic;
bool get isGetter => kind == ProcedureKind.Getter;
bool get isSetter => kind == ProcedureKind.Setter;
bool get isAccessor => isGetter || isSetter;
@override
bool get hasGetter => kind != ProcedureKind.Setter;
@override
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);
}