blob: 301a9260f1032d2012b021024b46d87e8c27dc23 [file] [log] [blame] [edit]
// Copyright (c) 2024, 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.
part of '../../ast.dart';
// ------------------------------------------------------------------------
// DECLARATIONS: CLASSES, EXTENSIONS, and EXTENSION TYPES
// ------------------------------------------------------------------------
/// Common interface for [Class] and [ExtensionTypeDeclaration].
sealed class TypeDeclaration
implements Annotatable, FileUriNode, GenericDeclaration {
/// The name of the declaration.
///
/// This must be unique within the library.
String get name;
}
/// 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 TypeDeclaration {
/// 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;
@override
List<int>? get fileOffsetsIfMultiple =>
[fileOffset, startFileOffset, fileEndOffset];
/// 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.
@override
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;
static const int FlagSealed = 1 << 7;
static const int FlagMixinClass = 1 << 8;
static const int FlagBase = 1 << 9;
static const int FlagInterface = 1 << 10;
static const int FlagFinal = 1 << 11;
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 sealed class.
bool get isSealed => flags & FlagSealed != 0;
void set isSealed(bool value) {
flags = value ? (flags | FlagSealed) : (flags & ~FlagSealed);
}
/// Whether this class is a base class.
bool get isBase => flags & FlagBase != 0;
void set isBase(bool value) {
flags = value ? (flags | FlagBase) : (flags & ~FlagBase);
}
/// Whether this class is an interface class.
bool get isInterface => flags & FlagInterface != 0;
void set isInterface(bool value) {
flags = value ? (flags | FlagInterface) : (flags & ~FlagInterface);
}
/// Whether this class is a final class.
bool get isFinal => flags & FlagFinal != 0;
void set isFinal(bool value) {
flags = value ? (flags | FlagFinal) : (flags & ~FlagFinal);
}
/// 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);
}
/// Whether this class is a mixin class.
///
/// The `mixin` modifier was added to the class declaration which allows the
/// class to be used as a mixin. The class can be mixed in by other classes
/// outside of its library. Otherwise, classes are not able to be used as a
/// mixin outside of its library from version 3.0 and later.
bool get isMixinClass => flags & FlagMixinClass != 0;
void set isMixinClass(bool value) {
flags = value ? (flags | FlagMixinClass) : (flags & ~FlagMixinClass);
}
/// 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 &&
// While we expect 2, with erroneous code we don't always have that.
current.classNode.implementedTypes.length == 2) {
Class currentClass = current.classNode;
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;
@override
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;
}
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,
required this.fileUri,
Reference? reference})
: this.typeParameters = typeParameters ?? <TypeParameter>[],
this.implementedTypes = implementedTypes ?? <Supertype>[],
this._fieldsInternal = fields ?? <Field>[],
this._constructorsInternal = constructors ?? <Constructor>[],
this._proceduresInternal = procedures ?? <Procedure>[],
super(reference) {
setParents(this.typeParameters, this);
setParents(this._constructorsInternal, this);
setParents(this._proceduresInternal, this);
setParents(this._fieldsInternal, 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);
}
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();
}
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,
].expand((x) => x);
void forEachMember(void action(Member element)) {
fields.forEach(action);
constructors.forEach(action);
procedures.forEach(action);
}
/// 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);
}
@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);
}
@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);
}
@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);
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset,
viaForErrorMessage: "Class '$name'");
}
}
/// 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, GenericDeclaration {
/// 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.
@override
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 members declared by the extension.
///
/// The members are converted into top-level members and only accessible
/// by reference through [ExtensionMemberDescriptor].
List<ExtensionMemberDescriptor> memberDescriptors;
@override
List<Expression> annotations = const <Expression>[];
// Must match serialized bit positions.
static const int FlagUnnamedExtension = 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>? memberDescriptors,
required this.fileUri,
Reference? reference})
: this.typeParameters = typeParameters ?? <TypeParameter>[],
this.memberDescriptors =
memberDescriptors ?? <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 isUnnamedExtension {
return flags & FlagUnnamedExtension != 0;
}
void set isUnnamedExtension(bool value) {
flags = value
? (flags | FlagUnnamedExtension)
: (flags & ~FlagUnnamedExtension);
}
@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);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
visitList(typeParameters, v);
onType.accept(v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(typeParameters, this);
onType = v.visitDartType(onType);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformTypeParameterList(typeParameters, this);
onType = v.visitDartType(onType, cannotRemoveSentinel);
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset,
viaForErrorMessage: "Extension '$name'");
}
@override
String toString() {
return "Extension(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
printer.writeExtensionName(reference);
}
}
enum ExtensionMemberKind {
Field,
Method,
Getter,
Setter,
Operator,
}
/// Information about an member declaration in an extension.
class ExtensionMemberDescriptor {
// Must match serialized bit positions:
static const int FlagStatic = 1 << 0;
static const int FlagInternalImplementation = 1 << 1;
/// 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.
final 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`.
///
final ExtensionMemberKind kind;
int flags = 0;
/// Reference to the top-level member created for the extension method.
/// This member reference is not null after the front-end but can
/// be cleared by certain back-ends (e.g. VM/AOT) if member is not used.
final Reference? memberReference;
/// Reference to the top-level member created for the extension member tear
/// off, if any.
final Reference? tearOffReference;
ExtensionMemberDescriptor(
{required this.name,
required this.kind,
bool isStatic = false,
bool isInternalImplementation = false,
required this.memberReference,
required this.tearOffReference}) {
this.isStatic = isStatic;
this.isInternalImplementation = isInternalImplementation;
assert(memberReference != null || tearOffReference != null);
}
/// Return `true` if the extension member was declared as `static`.
bool get isStatic => flags & FlagStatic != 0;
void set isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
/// Returns `true` if member is not part of the extension API but only
/// internal to the extension implementation.
///
/// This is `true` for instance for synthesized fields added for the late
/// lowering.
bool get isInternalImplementation => flags & FlagInternalImplementation != 0;
void set isInternalImplementation(bool value) {
flags = value
? (flags | FlagInternalImplementation)
: (flags & ~FlagInternalImplementation);
}
@override
String toString() {
return 'ExtensionMemberDescriptor($name,$kind,'
'${memberReference?.toStringInternal()},'
'isStatic=$isStatic,'
'isInternalImplementation=$isInternalImplementation)';
}
}
/// Declaration of an extension type.
///
/// The members are converted into top-level procedures and only accessible
/// by reference in the [ExtensionTypeDeclaration] node.
class ExtensionTypeDeclaration extends NamedNode implements TypeDeclaration {
/// Name of the extension type declaration.
@override
String name;
/// The URI of the source file this class was loaded from.
@override
Uri fileUri;
/// Type parameters declared on the extension.
@override
final List<TypeParameter> typeParameters;
/// The type in the underlying representation of the extension type
/// declaration.
///
/// For instance A in the extension type declaration B:
///
/// class A {}
/// extension type B(A it) {}
///
late DartType declaredRepresentationType;
/// The name of the representation field.
///
/// For instance 'it' in the extension type declaration B:
///
/// class A {}
/// extension type B(A it) {}
///
/// This name is used for accessing underlying representation from an
/// extension type. If the name starts with '_' is private wrt. the enclosing
/// library of the extension type declaration.
late String representationName;
/// Abstract procedures that are part of the extension type declaration
/// interface.
///
/// This includes a getter for the representation field and member signatures
/// computed as the combined member signature of inherited non-extension type
/// members.
List<Procedure> _procedures;
/// The members declared by the extension type declaration.
///
/// The members are converted into top-level members and only accessible
/// by reference through [ExtensionTypeMemberDescriptor].
List<ExtensionTypeMemberDescriptor> memberDescriptors;
@override
List<Expression> annotations = const <Expression>[];
List<TypeDeclarationType> implements;
int flags = 0;
@override
void addAnnotation(Expression node) {
if (annotations.isEmpty) {
annotations = <Expression>[];
}
annotations.add(node);
node.parent = this;
}
ExtensionTypeDeclaration(
{required this.name,
List<TypeParameter>? typeParameters,
DartType? declaredRepresentationType,
List<ExtensionTypeMemberDescriptor>? memberDescriptors,
List<TypeDeclarationType>? implements,
List<Procedure>? procedures,
required this.fileUri,
Reference? reference})
: this.typeParameters = typeParameters ?? <TypeParameter>[],
this.memberDescriptors =
memberDescriptors ?? <ExtensionTypeMemberDescriptor>[],
this.implements = implements ?? <TypeDeclarationType>[],
this._procedures = procedures ?? <Procedure>[],
super(reference) {
setParents(this.typeParameters, this);
setParents(this._procedures, this);
if (declaredRepresentationType != null) {
this.declaredRepresentationType = declaredRepresentationType;
}
}
@override
CanonicalName bindCanonicalNames(CanonicalName parent) {
return parent.getChild(name)..bindTo(reference);
}
/// Computes the canonical name for this extension type declarations and all
/// its members.
void ensureCanonicalNames(CanonicalName parent) {
CanonicalName canonicalName = bindCanonicalNames(parent);
for (int i = 0; i < procedures.length; ++i) {
procedures[i].bindCanonicalNames(canonicalName);
}
}
Library get enclosingLibrary => parent as Library;
void addProcedure(Procedure procedure) {
procedure.parent = this;
procedures.add(procedure);
}
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;
}
/// 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 extension type
/// declaration points to said named node.
void relink() {
this.reference.node = this;
for (int i = 0; i < procedures.length; ++i) {
Procedure member = procedures[i];
member._relinkNode();
}
}
@override
R accept<R>(TreeVisitor<R> v) => v.visitExtensionTypeDeclaration(this);
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) =>
v.visitExtensionTypeDeclaration(this, arg);
R acceptReference<R>(Visitor<R> v) =>
v.visitExtensionTypeDeclarationReference(this);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
visitList(typeParameters, v);
declaredRepresentationType.accept(v);
visitList(procedures, v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
v.transformList(typeParameters, this);
declaredRepresentationType = v.visitDartType(declaredRepresentationType);
v.transformList(procedures, this);
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformTypeParameterList(typeParameters, this);
declaredRepresentationType =
v.visitDartType(declaredRepresentationType, cannotRemoveSentinel);
v.transformProcedureList(procedures, this);
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset,
viaForErrorMessage: "Extension type '$name'");
}
@override
String toString() {
return "ExtensionTypeDeclaration(${toStringInternal()})";
}
@override
void toTextInternal(AstPrinter printer) {
printer.writeExtensionTypeDeclarationName(reference);
}
/// Returns the inherent nullability of this extension type declaration.
///
/// An extension type declaration is inherently non-nullable if it implements
/// a non-extension type or a non-nullable extension type declaration.
Nullability get inherentNullability {
for (DartType supertype in implements) {
if (supertype is! ExtensionType) {
// A supertype that is not an extension type has to be non-nullable and
// implement `Object` directly or indirectly.
return Nullability.nonNullable;
} else if (supertype.extensionTypeDeclaration.inherentNullability !=
Nullability.undetermined) {
// If an extension type is non-nullable, it implements `Object` directly
// or indirectly.
return Nullability.nonNullable;
}
}
// Direct or indirect implementation of `Objects` isn't found.
return Nullability.undetermined;
}
}
enum ExtensionTypeMemberKind {
Constructor,
Factory,
Field,
Method,
Getter,
Setter,
Operator,
RedirectingFactory,
}
/// Information about an member declaration in an extension type declaration.
class ExtensionTypeMemberDescriptor {
// Must match serialized bit positions:
static const int FlagStatic = 1 << 0;
static const int FlagInternalImplementation = 1 << 1;
/// The name of the extension type declaration member.
///
/// The name of the generated top-level member is mangled to ensure
/// uniqueness. This name is used to lookup a member in the extension type
/// declaration itself.
final Name name;
/// [ExtensionTypeMemberKind] kind of the original member.
///
/// An extension type declaration member is converted into a regular top-level
/// method. For instance:
///
/// class A {
/// var foo;
/// }
/// extension type B(A it) {
/// 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`.
///
final ExtensionTypeMemberKind kind;
int flags = 0;
/// Reference to the top-level member created for the extension type
/// declaration member.
/// This member reference is not null after the front-end but can
/// be cleared by certain back-ends (e.g. VM/AOT) if member is not used.
final Reference? memberReference;
/// Reference to the top-level member created for the extension type
/// declaration member tear off, if any.
final Reference? tearOffReference;
ExtensionTypeMemberDescriptor(
{required this.name,
required this.kind,
bool isStatic = false,
bool isInternalImplementation = false,
required this.memberReference,
required this.tearOffReference}) {
this.isStatic = isStatic;
this.isInternalImplementation = isInternalImplementation;
assert(memberReference != null || tearOffReference != null);
}
/// Return `true` if the extension type declaration member was declared as
/// `static`.
bool get isStatic => flags & FlagStatic != 0;
void set isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
/// Returns `true` if member is not part of the extension type API but only
/// internal to the extension implementation.
///
/// This is `true` for instance for synthesized fields added for the late
/// lowering.
bool get isInternalImplementation => flags & FlagInternalImplementation != 0;
void set isInternalImplementation(bool value) {
flags = value
? (flags | FlagInternalImplementation)
: (flags & ~FlagInternalImplementation);
}
@override
String toString() {
return 'ExtensionTypeMemberDescriptor($name,$kind,'
'${memberReference?.toStringInternal()},isStatic=${isStatic},'
'${tearOffReference?.toStringInternal()})';
}
}