blob: a7f63ad20abb12f6fd0f55d3af98d791a843f40f [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of 'declaration_builders.dart';
const Uri? noUri = null;
abstract class ClassMemberAccess {
/// [Iterator] for all constructors declared in this class or any of its
/// augmentations.
///
/// Duplicates and augmenting constructor are _not_ included.
///
/// For instance:
///
/// class Class {
/// Class(); // declared, so it is included
/// Class.named(); // declared, so it is included
/// Class.named(); // duplicate, so it is *not* included
/// }
///
/// augment class Class {
/// augment Class(); // augmenting, so it is *not* included
/// Class.extra(); // declared, so it is included
/// }
///
Iterator<T> fullConstructorIterator<T extends MemberBuilder>();
/// [NameIterator] for all constructors declared in this class or any of its
/// augmentations.
///
/// Duplicates and augmenting constructors are _not_ included.
///
/// For instance:
///
/// class Class {
/// Class(); // declared, so it is included
/// Class.named(); // declared, so it is included
/// Class.named(); // duplicate, so it is *not* included
/// }
///
/// augment class Class {
/// augment Class(); // augmenting, so it is *not* included
/// Class.extra(); // declared, so it is included
/// }
///
NameIterator<T> fullConstructorNameIterator<T extends MemberBuilder>();
/// [Iterator] for all members declared in this class or any of its
/// augmentations.
///
/// Duplicates and augmenting members are _not_ included.
///
/// For instance:
///
/// class Class {
/// method() {} // Declared, so it is included.
/// method2() {} // Declared, so it is included.
/// method2() {} // Duplicate, so it is *not* included.
/// }
///
/// augment class Class {
/// augment method() {} // Augmenting, so it is *not* included.
/// extra() {} // Declared, so it is included.
/// }
///
Iterator<T> fullMemberIterator<T extends Builder>();
/// [NameIterator] for all members declared in this class or any of its
/// augmentations.
///
/// Duplicates and augmenting members are _not_ included.
///
/// For instance:
///
/// class Class {
/// method() {} // Declared, so it is included.
/// method2() {} // Declared, so it is included.
/// method2() {} // Duplicate, so it is *not* included.
/// }
///
/// augment class Class {
/// augment method() {} // Augmenting, so it is *not* included.
/// extra() {} // Declared, so it is included.
/// }
///
NameIterator<T> fullMemberNameIterator<T extends Builder>();
}
abstract class ClassBuilder implements DeclarationBuilder, ClassMemberAccess {
/// The type variables declared on a class, extension or mixin declaration.
List<NominalVariableBuilder>? get typeVariables;
/// The type in the `extends` clause of a class declaration.
///
/// Currently this also holds the synthesized super class for a mixin
/// declaration.
TypeBuilder? get supertypeBuilder;
/// The type in the `implements` clause of a class or mixin declaration.
List<TypeBuilder>? get interfaceBuilders;
/// The types in the `on` clause of an extension or mixin declaration.
List<TypeBuilder>? get onTypes;
@override
Uri get fileUri;
bool get isAbstract;
bool get isMacro;
bool get isSealed;
bool get isBase;
bool get isInterface;
@override
bool get isFinal;
bool get declaresConstConstructor;
bool get isMixinClass;
bool get isMixinDeclaration;
bool get isMixinApplication;
bool get isAnonymousMixinApplication;
abstract TypeBuilder? mixedInTypeBuilder;
bool get isFutureOr;
/// The [Class] built by this builder.
///
/// For an augmentation class the origin class is returned.
Class get cls;
@override
ClassBuilder get origin;
abstract bool isNullClass;
@override
InterfaceType get thisType;
Supertype buildMixedInType(
LibraryBuilder library, List<TypeBuilder>? arguments);
/// Looks up the member by [name] on the class built by this class builder.
///
/// If [isSetter] is `false`, only fields, methods, and getters with that name
/// will be found. If [isSetter] is `true`, only non-final fields and setters
/// will be found.
///
/// If [isSuper] is `false`, the member is found among the interface members
/// the class built by this class builder. If [isSuper] is `true`, the member
/// is found among the class members of the superclass.
///
/// If this class builder is an augmentation, interface members declared in
/// this augmentation are searched before searching the interface members in
/// the origin class.
///
/// Unused in interface; left in on purpose.
Member? lookupInstanceMember(ClassHierarchy hierarchy, Name name,
{bool isSetter = false, bool isSuper = false});
}
abstract class ClassBuilderImpl extends DeclarationBuilderImpl
implements ClassBuilder {
@override
bool isNullClass = false;
InterfaceType? _legacyRawType;
InterfaceType? _nullableRawType;
InterfaceType? _nonNullableRawType;
InterfaceType? _thisType;
ClassBuilderImpl(
String name, LibraryBuilder parent, Uri fileUri, int fileOffset)
: super(name, parent, fileUri, fileOffset);
@override
bool get isMixinApplication => mixedInTypeBuilder != null;
@override
bool get isAnonymousMixinApplication {
return isMixinApplication &&
// Coverage-ignore(suite): Not run.
!isNamedMixinApplication;
}
@override
Builder? findStaticBuilder(
String name, int charOffset, Uri fileUri, LibraryBuilder accessingLibrary,
{bool isSetter = false}) {
if (accessingLibrary.nameOriginBuilder.origin !=
libraryBuilder.nameOriginBuilder.origin &&
name.startsWith("_")) {
return null;
}
Builder? declaration = normalizeLookup(
getable: nameSpace.lookupLocalMember(name, setter: false),
setable: nameSpace.lookupLocalMember(name, setter: true),
name: name,
charOffset: charOffset,
fileUri: fileUri,
classNameOrDebugName: this.name,
isSetter: isSetter,
forStaticAccess: true);
if (declaration == null && isAugmenting) {
return origin.findStaticBuilder(
name, charOffset, fileUri, accessingLibrary,
isSetter: isSetter);
}
return declaration;
}
@override
Builder? lookupLocalMember(String name,
{bool setter = false, bool required = false}) {
Builder? builder = nameSpace.lookupLocalMember(name, setter: setter);
if (builder == null && isAugmenting) {
// Coverage-ignore-block(suite): Not run.
builder = origin.nameSpace.lookupLocalMember(name, setter: setter);
}
if (required && builder == null) {
internalProblem(
templateInternalProblemNotFoundIn.withArguments(
name, fullNameForErrors),
-1,
null);
}
return builder;
}
/// Find the first member of this class with [name]. This method isn't
/// suitable for scope lookups as it will throw an error if the name isn't
/// declared. The [scope] should be used for that. This method is used to
/// find a member that is known to exist and it will pick the first
/// declaration if the name is ambiguous.
///
/// For example, this method is convenient for use when building synthetic
/// members, such as those of an enum.
MemberBuilder? firstMemberNamed(String name) {
MemberBuilder declaration =
lookupLocalMember(name, required: true) as MemberBuilder;
while (declaration.next != null) {
declaration = declaration.next as MemberBuilder;
}
return declaration;
}
@override
InterfaceType get thisType {
return _thisType ??= new InterfaceType(cls, Nullability.nonNullable,
getAsTypeArguments(cls.typeParameters, libraryBuilder.library));
}
// Coverage-ignore(suite): Not run.
InterfaceType get legacyRawType {
return _legacyRawType ??= new InterfaceType(cls, Nullability.legacy,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
InterfaceType get nullableRawType {
return _nullableRawType ??= new InterfaceType(cls, Nullability.nullable,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
InterfaceType get nonNullableRawType {
return _nonNullableRawType ??= new InterfaceType(
cls,
Nullability.nonNullable,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
InterfaceType rawType(Nullability nullability) {
switch (nullability) {
case Nullability.legacy:
// Coverage-ignore(suite): Not run.
return legacyRawType;
case Nullability.nullable:
return nullableRawType;
case Nullability.nonNullable:
return nonNullableRawType;
// Coverage-ignore(suite): Not run.
case Nullability.undetermined:
return unhandled("$nullability", "rawType", TreeNode.noOffset, noUri);
}
}
InterfaceType? aliasedTypeWithBuiltArgumentsCacheNonNullable;
InterfaceType? aliasedTypeWithBuiltArgumentsCacheNullable;
@override
bool get isFutureOr {
if (name == "FutureOr") {
LibraryBuilder parentLibrary = parent as LibraryBuilder;
if (parentLibrary.importUri.isScheme("dart") &&
parentLibrary.importUri.path == "async") {
return true;
}
}
return false;
}
@override
DartType buildAliasedTypeWithBuiltArguments(
LibraryBuilder library,
Nullability nullability,
List<DartType> arguments,
TypeUse typeUse,
Uri fileUri,
int charOffset,
{required bool hasExplicitTypeArguments}) {
assert(cls.typeParameters.length == arguments.length);
if (isNullClass) {
return const NullType();
}
if (isFutureOr) {
assert(arguments.length == 1);
return new FutureOrType(arguments.single, nullability);
}
if (arguments.isEmpty) {
return rawType(nullability);
}
if (aliasedTypeWithBuiltArgumentsCacheNonNullable != null &&
// Coverage-ignore(suite): Not run.
nullability == Nullability.nonNullable) {
// Coverage-ignore-block(suite): Not run.
assert(aliasedTypeWithBuiltArgumentsCacheNonNullable!.classReference ==
cls.reference);
assert(arguments.isEmpty);
return aliasedTypeWithBuiltArgumentsCacheNonNullable!;
} else if (aliasedTypeWithBuiltArgumentsCacheNullable != null &&
// Coverage-ignore(suite): Not run.
nullability == Nullability.nullable) {
// Coverage-ignore-block(suite): Not run.
assert(aliasedTypeWithBuiltArgumentsCacheNullable!.classReference ==
cls.reference);
assert(arguments.isEmpty);
return aliasedTypeWithBuiltArgumentsCacheNullable!;
}
InterfaceType type = new InterfaceType(cls, nullability, arguments);
if (arguments.isEmpty) {
// Coverage-ignore-block(suite): Not run.
assert(typeVariablesCount == 0);
if (nullability == Nullability.nonNullable) {
aliasedTypeWithBuiltArgumentsCacheNonNullable = type;
} else if (nullability == Nullability.nullable) {
aliasedTypeWithBuiltArgumentsCacheNullable = type;
}
}
if (typeVariablesCount != 0 && library is SourceLibraryBuilder) {
library.registerBoundsCheck(type, fileUri, charOffset, typeUse,
inferred: !hasExplicitTypeArguments);
}
return type;
}
@override
DartType buildAliasedType(
LibraryBuilder library,
NullabilityBuilder nullabilityBuilder,
List<TypeBuilder>? arguments,
TypeUse typeUse,
Uri fileUri,
int charOffset,
ClassHierarchyBase? hierarchy,
{required bool hasExplicitTypeArguments}) {
if (name == "Record" &&
libraryBuilder.importUri.scheme == "dart" &&
libraryBuilder.importUri.path == "core" &&
library is SourceLibraryBuilder &&
!isRecordAccessAllowed(library)) {
// Coverage-ignore-block(suite): Not run.
library.reportFeatureNotEnabled(
library.libraryFeatures.records, fileUri, charOffset, name.length);
return const InvalidType();
}
return buildAliasedTypeWithBuiltArguments(
library,
nullabilityBuilder.build(),
buildAliasedTypeArguments(library, arguments, hierarchy),
typeUse,
fileUri,
charOffset,
hasExplicitTypeArguments: hasExplicitTypeArguments);
}
@override
Supertype buildMixedInType(
LibraryBuilder library, List<TypeBuilder>? arguments) {
Class cls = isAugmenting
?
// Coverage-ignore(suite): Not run.
origin.cls
: this.cls;
if (arguments != null) {
List<DartType> typeArguments =
buildAliasedTypeArguments(library, arguments, /* hierarchy = */ null);
typeArguments = unaliasTypes(typeArguments, legacyEraseAliases: false)!;
return new Supertype(cls, typeArguments);
} else {
return new Supertype(
cls,
new List<DartType>.filled(
cls.typeParameters.length, const UnknownType(),
growable: true));
}
}
@override
String get fullNameForErrors {
return isMixinApplication && !isNamedMixinApplication
? "${supertypeBuilder!.fullNameForErrors} with "
"${mixedInTypeBuilder!.fullNameForErrors}"
: name;
}
@override
Member? lookupInstanceMember(ClassHierarchy hierarchy, Name name,
{bool isSetter = false, bool isSuper = false}) {
Class? instanceClass = cls;
if (isAugmenting) {
// Coverage-ignore-block(suite): Not run.
assert(identical(instanceClass, origin.cls),
"Found ${origin.cls} expected $instanceClass");
if (isSuper) {
// The super class is only correctly found through the origin class.
instanceClass = origin.cls;
} else {
Member? member =
hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
if (member?.parent == instanceClass) {
// Only if the member is found in the augmentation can we use it.
return member;
} else {
// Otherwise, we need to keep searching in the origin class.
instanceClass = origin.cls;
}
}
}
if (isSuper) {
instanceClass = instanceClass.superclass;
if (instanceClass == null) return null;
}
Member? target = isSuper
? hierarchy.getDispatchTarget(instanceClass, name, setter: isSetter)
:
// Coverage-ignore(suite): Not run.
hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
if (isSuper && target == null) {
if (cls.isMixinDeclaration) {
target =
hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
}
}
return target;
}
@override
Nullability computeNullabilityWithArguments(List<TypeBuilder>? typeArguments,
{required Map<TypeVariableBuilder, TraversalState>
typeVariablesTraversalState}) {
if (isNullClass) {
return Nullability.nullable;
} else if (isFutureOr) {
if (typeArguments != null && typeArguments.length == 1) {
return typeArguments.single.computeNullability(
typeVariablesTraversalState: typeVariablesTraversalState);
} else {
// This is `FutureOr<dynamic>`.
return Nullability.nullable;
}
}
return Nullability.nonNullable;
}
}
class ConstructorRedirection {
String target;
bool cycleReported;
ConstructorRedirection(this.target) : cycleReported = false;
}