| // 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 FlagSealed = 1 << 6; | |
| static const int FlagMixinClass = 1 << 7; | |
| static const int FlagBase = 1 << 8; | |
| static const int FlagInterface = 1 << 9; | |
| static const int FlagFinal = 1 << 10; | |
| 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 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 | |
| TypeParameterList 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; | |
| } | |
| new({ | |
| required this.name, | |
| bool isAbstract = false, | |
| bool isAnonymousMixin = false, | |
| this.supertype, | |
| this.mixedInType, | |
| TypeParameterList? typeParameters, | |
| List<Supertype>? implementedTypes, | |
| List<Constructor>? constructors, | |
| List<Procedure>? procedures, | |
| List<Field>? fields, | |
| required this.fileUri, | |
| Reference? reference, | |
| }) : this.typeParameters = typeParameters ?? TypeParameterList.empty, | |
| 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; | |
| } | |
| void clearCanonicalNames() { | |
| reference.canonicalName = null; | |
| for (int i = 0; i < fields.length; ++i) { | |
| final Field field = fields[i]; | |
| field.fieldReference.canonicalName = null; | |
| field.getterReference.canonicalName = null; | |
| field.setterReference?.canonicalName = null; | |
| } | |
| for (int i = 0; i < procedures.length; ++i) { | |
| procedures[i].reference.canonicalName = null; | |
| } | |
| for (int i = 0; i < constructors.length; ++i) { | |
| constructors[i].reference.canonicalName = null; | |
| } | |
| dirty = true; | |
| } | |
| /// 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, | |
| DartTypeList.filledWithDynamic(typeParameters.length), | |
| ); | |
| } | |
| 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 | |
| TypeParameterList 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; | |
| } | |
| new({ | |
| required this.name, | |
| TypeParameterList? typeParameters, | |
| DartType? onType, | |
| List<ExtensionMemberDescriptor>? memberDescriptors, | |
| required this.fileUri, | |
| Reference? reference, | |
| }) : this.typeParameters = typeParameters ?? TypeParameterList.empty, | |
| 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; | |
| new({ | |
| 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 | |
| TypeParameterList 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; | |
| } | |
| new({ | |
| required this.name, | |
| TypeParameterList? typeParameters, | |
| DartType? declaredRepresentationType, | |
| List<ExtensionTypeMemberDescriptor>? memberDescriptors, | |
| List<TypeDeclarationType>? implements, | |
| List<Procedure>? procedures, | |
| required this.fileUri, | |
| Reference? reference, | |
| }) : this.typeParameters = typeParameters ?? TypeParameterList.empty, | |
| 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; | |
| new({ | |
| 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()})'; | |
| } | |
| } |