blob: 7ebeaf2202832d3dca6e7a0d6c07f77dab33aa7b [file] [log] [blame]
// 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';
// ------------------------------------------------------------------------
// MEMBERS
// ------------------------------------------------------------------------
sealed 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;
@override
List<int>? get fileOffsetsIfMultiple => [fileOffset, fileEndOffset];
/// 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) : super(reference);
/// The enclosing [TypeDeclaration] if this member a class member or an
/// abstract extension type member.
TypeDeclaration? get enclosingTypeDeclaration =>
parent is TypeDeclaration ? parent as TypeDeclaration : null;
/// The enclosing [Class] if this member a class member.
///
/// This includes both declared and inherited members, and both static and
/// instance members.
Class? get enclosingClass => parent is Class ? parent as Class : null;
/// The enclosing [ExtensionTypeDeclaration] if this member an abstract
/// extension type member.
///
/// This includes abstract getters for representation fields and combined
/// member signatures from inherited non-extension type members.
ExtensionTypeDeclaration? get enclosingExtensionTypeDeclaration =>
parent is ExtensionTypeDeclaration
? parent as ExtensionTypeDeclaration
: null;
Library get enclosingLibrary {
TreeNode? parent = this.parent;
if (parent is Class) {
return parent.enclosingLibrary;
} else if (parent is ExtensionTypeDeclaration) {
return parent.enclosingLibrary;
}
return 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;
/// 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 compiled from a member declared in an extension
/// type declaration.
///
/// For instance `field`, `method1` and `method2` in:
///
/// extension type A(B it) {
/// static var field;
/// B method1() => this;
/// static B method2() => new B();
/// }
///
bool get isExtensionTypeMember;
/// 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;
/// If `true` some compile-time errors were emitted for the member.
bool get isErroneous => 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;
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,
bool isEnumElement = false})
: this.getterReference = getterReference ?? new Reference(),
this.setterReference = null,
super(name, fileUri, fieldReference) {
this.getterReference.node = this;
initializer?.parent = this;
this.isCovariantByDeclaration = isCovariantByDeclaration;
this.isFinal = isFinal;
this.isConst = isConst;
this.isStatic = isStatic;
this.isLate = isLate;
this.isEnumElement = isEnumElement;
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 FlagInternalImplementation = 1 << 7;
static const int FlagEnumElement = 1 << 8;
static const int FlagExtensionTypeMember = 1 << 9;
static const int FlagErroneous = 1 << 10;
/// 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;
@override
bool get isExtensionTypeMember => flags & FlagExtensionTypeMember != 0;
@override
bool get isErroneous => flags & FlagErroneous != 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.
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;
/// If `true` this field is an enum element.
///
/// For instance
///
/// enum A {
/// a, b;
/// static const A c = A.a;
/// }
///
/// the fields `a` and `b` are enum elements whereas `c` is a regular field.
bool get isEnumElement => flags & FlagEnumElement != 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);
}
void set isEnumElement(bool value) {
flags = value ? (flags | FlagEnumElement) : (flags & ~FlagEnumElement);
}
void set isExtensionTypeMember(bool value) {
flags = value
? (flags | FlagExtensionTypeMember)
: (flags & ~FlagExtensionTypeMember);
}
void set isErroneous(bool value) {
flags = value ? (flags | FlagErroneous) : (flags & ~FlagErroneous);
}
@override
bool get isInstanceMember => !isStatic;
@override
bool get hasGetter => true;
@override
bool get hasSetter => setterReference != null;
@override
bool get isExternal => false;
@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,
viaForErrorMessage: "Field '$name'");
}
@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;
@override
List<int>? get fileOffsetsIfMultiple =>
[fileOffset, startFileOffset, fileEndOffset];
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>[],
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 FlagErroneous = 1 << 3;
@override
bool get isConst => flags & FlagConst != 0;
@override
bool get isExternal => flags & FlagExternal != 0;
@override
bool get isErroneous => flags & FlagErroneous != 0;
/// True if this is a synthetic constructor inserted in a class that
/// does not otherwise declare any constructors.
bool get isSynthetic => flags & FlagSynthetic != 0;
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isSynthetic(bool value) {
flags = value ? (flags | FlagSynthetic) : (flags & ~FlagSynthetic);
}
void set isErroneous(bool value) {
flags = value ? (flags | FlagErroneous) : (flags & ~FlagErroneous);
}
@override
bool get isInstanceMember => false;
@override
bool get hasGetter => false;
@override
bool get hasSetter => false;
@override
bool get isExtensionMember => false;
@override
bool get isExtensionTypeMember => false;
@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);
function = v.transform(function);
function.parent = this;
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
v.transformInitializerList(initializers, this);
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,
viaForErrorMessage: "Constructor '$name'");
}
}
/// 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,
/// The representation field of an extension type declaration, encoded as
/// an abstract getter.
///
/// The stub target is `null`.
RepresentationField,
}
/// 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 implements GenericFunction {
/// 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;
@override
List<int>? get fileOffsetsIfMultiple =>
[fileOffset, fileStartOffset, fileEndOffset];
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.
///
/// When [signatureType] is null, you can compute the function type with
/// `function.computeFunctionType(Nullability.nonNullable)`. Alternatively,
/// you can use [computeSignatureOrFunctionType] that computes the interface
/// member signature type accounting for the possibility of [signatureType]
/// being null.
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 isExtensionTypeMember = 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,
isExtensionTypeMember: isExtensionTypeMember,
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 isExtensionTypeMember = false,
bool isSynthetic = false,
int transformerFlags = 0,
required Uri fileUri,
Reference? reference,
this.stubKind = ProcedureStubKind.Regular,
this.stubTargetReference})
: super(name, fileUri, reference) {
function.parent = this;
this.isAbstract = isAbstract;
this.isStatic = isStatic;
this.isExternal = isExternal;
this.isConst = isConst;
this.isExtensionMember = isExtensionMember;
this.isExtensionTypeMember = isExtensionTypeMember;
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.");
}
@override
List<TypeParameter> get typeParameters => function.typeParameters;
// 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.
static const int FlagExtensionMember = 1 << 4;
static const int FlagSynthetic = 1 << 5;
static const int FlagInternalImplementation = 1 << 6;
static const int FlagExtensionTypeMember = 1 << 7;
static const int FlagHasWeakTearoffReferencePragma = 1 << 8;
static const int FlagErroneous = 1 << 9;
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 function.redirectingFactoryTarget != null;
}
/// 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;
@override
bool get isExtensionTypeMember => flags & FlagExtensionTypeMember != 0;
void set isStatic(bool value) {
flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
}
void set isAbstract(bool value) {
flags = value ? (flags | FlagAbstract) : (flags & ~FlagAbstract);
}
void set isExternal(bool value) {
flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
}
void set isConst(bool value) {
flags = value ? (flags | FlagConst) : (flags & ~FlagConst);
}
void set isExtensionMember(bool value) {
flags =
value ? (flags | FlagExtensionMember) : (flags & ~FlagExtensionMember);
}
void set isExtensionTypeMember(bool value) {
flags = value
? (flags | FlagExtensionTypeMember)
: (flags & ~FlagExtensionTypeMember);
}
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;
Member? get concreteForwardingStubTarget =>
stubKind == ProcedureStubKind.ConcreteForwardingStub
? stubTargetReference?.asMember
: null;
Member? get abstractForwardingStubTarget =>
stubKind == ProcedureStubKind.AbstractForwardingStub
? stubTargetReference?.asMember
: null;
Member? get stubTarget => stubTargetReference?.asMember;
void set stubTarget(Member? target) {
stubTargetReference = getMemberReferenceBasedOnProcedureKind(target, kind);
}
@override
Member? get memberSignatureOrigin =>
stubKind == ProcedureStubKind.MemberSignature
? stubTargetReference?.asMember
: null;
bool get hasWeakTearoffReferencePragma =>
flags & FlagHasWeakTearoffReferencePragma != 0;
void set hasWeakTearoffReferencePragma(bool value) {
flags = value
? (flags | FlagHasWeakTearoffReferencePragma)
: (flags & ~FlagHasWeakTearoffReferencePragma);
}
@override
bool get isErroneous => flags & FlagErroneous != 0;
void set isErroneous(bool value) {
flags = value ? (flags | FlagErroneous) : (flags & ~FlagErroneous);
}
/// Computes the interface member signature type of the procedure.
///
/// In case [signatureType] is set, returns [signatureType]. Otherwise,
/// computes the function type of the function node.
FunctionType computeSignatureOrFunctionType() {
return signatureType ??
function.computeFunctionType(Nullability.nonNullable);
}
@override
R accept<R>(MemberVisitor<R> v) => v.visitProcedure(this);
@override
R accept1<R, A>(MemberVisitor1<R, A> v, A arg) => v.visitProcedure(this, arg);
@override
R acceptReference<R>(MemberReferenceVisitor<R> v) =>
v.visitProcedureReference(this);
@override
void visitChildren(Visitor v) {
visitList(annotations, v);
name.accept(v);
function.accept(v);
}
@override
void transformChildren(Transformer v) {
v.transformList(annotations, this);
function = v.transform(function);
function.parent = this;
if (signatureType != null) {
signatureType = v.visitDartType(signatureType!) as FunctionType;
}
}
@override
void transformOrRemoveChildren(RemovingTransformer v) {
v.transformExpressionList(annotations, this);
function = v.transform(function);
function.parent = this;
if (signatureType != null) {
DartType newSignatureType =
v.visitDartType(signatureType!, dummyDartType);
if (identical(newSignatureType, dummyDartType)) {
signatureType = null;
} else {
signatureType = newSignatureType as FunctionType;
}
}
}
@override
DartType get getterType {
return isGetter
? (signatureType?.returnType ?? function.returnType)
: (signatureType ??
function.computeFunctionType(enclosingLibrary.nonNullable));
}
@override
DartType get superGetterType {
return isGetter
? function.returnType
: function.computeFunctionType(enclosingLibrary.nonNullable);
}
@override
DartType get setterType {
return isSetter
? (signatureType?.positionalParameters[0] ??
function.positionalParameters[0].type)
: const NeverType.nonNullable();
}
@override
DartType get superSetterType {
return isSetter
? function.positionalParameters[0].type
: const NeverType.nonNullable();
}
@override
Location? _getLocationInEnclosingFile(int offset) {
return _getLocationInComponent(enclosingComponent, fileUri, offset,
viaForErrorMessage: "Procedure '$name'");
}
}
enum ProcedureKind {
Method,
Getter,
Setter,
Operator,
Factory,
}
/// The target constructor and passed type arguments of a redirecting factory,
/// or if erroneous, the message for the error.
class RedirectingFactoryTarget {
/// The reference to the target constructor if this is a valid redirecting
/// factory. `null` otherwise.
final Reference? targetReference;
/// The type arguments passed to the target constructor if this is a valid
/// redirecting factory. `null` otherwise.
final List<DartType>? typeArguments;
/// The message for the error, if this is an erroneous redirection. `null`
/// otherwise.
final String? errorMessage;
RedirectingFactoryTarget(Member target, List<DartType> typeArguments)
: this.byReference(target.reference, typeArguments);
RedirectingFactoryTarget.byReference(
Reference this.targetReference, List<DartType> this.typeArguments)
: errorMessage = null;
RedirectingFactoryTarget.error(String this.errorMessage)
: targetReference = null,
typeArguments = null;
/// The target constructor if this is a valid redirecting factory. `null`
/// otherwise.
Member? get target => targetReference?.asMember;
/// If `true`, this is an erroneous redirection.
bool get isError => errorMessage != null;
@override
String toString() => 'RedirectingFactoryTarget('
'${isError ? '$errorMessage' : '$target,$typeArguments'})';
}