blob: 71d70e3bac3aa086f5d29b9446fad9ec7a0fe73e [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.
library fasta.class_builder;
import 'package:kernel/ast.dart'
show
Class,
DartType,
DynamicType,
FutureOrType,
InterfaceType,
Member,
Name,
NullType,
Nullability,
Supertype,
TreeNode,
getAsTypeArguments;
import 'package:kernel/class_hierarchy.dart'
show ClassHierarchy, ClassHierarchyBase;
import 'package:kernel/src/unaliasing.dart';
import '../fasta_codes.dart';
import '../modifier.dart';
import '../problems.dart' show internalProblem, unhandled;
import '../scope.dart';
import '../source/source_library_builder.dart';
import '../type_inference/type_schema.dart' show UnknownType;
import 'builder.dart';
import 'declaration_builder.dart';
import 'library_builder.dart';
import 'member_builder.dart';
import 'metadata_builder.dart';
import 'nullability_builder.dart';
import 'type_builder.dart';
import 'type_variable_builder.dart';
const Uri? noUri = null;
abstract class ClassBuilder implements DeclarationBuilder {
/// The type variables declared on a class, extension or mixin declaration.
List<TypeVariableBuilder>? get typeVariables;
/// The type in the `extends` clause of a class declaration.
///
/// Currently this also holds the synthesized super class for a mixin
/// declaration.
abstract TypeBuilder? supertypeBuilder;
/// The type in the `implements` clause of a class or mixin declaration.
abstract List<TypeBuilder>? interfaceBuilders;
/// The types in the `on` clause of an extension or mixin declaration.
List<TypeBuilder>? get onTypes;
ConstructorScope get constructorScope;
@override
Uri get fileUri;
bool get isAbstract;
bool get isMacro;
bool get isAugmentation;
bool get declaresConstConstructor;
bool get isMixin;
bool get isMixinApplication;
bool get isAnonymousMixinApplication;
abstract TypeBuilder? mixedInTypeBuilder;
MemberBuilder? findConstructorOrFactory(
String name, int charOffset, Uri uri, LibraryBuilder accessingLibrary);
void forEach(void f(String name, Builder builder));
/// The [Class] built by this builder.
///
/// For a patch class the origin class is returned.
Class get cls;
@override
ClassBuilder get origin;
abstract bool isNullClass;
@override
InterfaceType get thisType;
InterfaceType get legacyRawType;
InterfaceType get nullableRawType;
InterfaceType get nonNullableRawType;
InterfaceType rawType(Nullability nullability);
List<DartType> buildAliasedTypeArguments(LibraryBuilder library,
List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy);
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 a patch, interface members declared in this
/// patch are searched before searching the interface members in the origin
/// class.
Member? lookupInstanceMember(ClassHierarchy hierarchy, Name name,
{bool isSetter: false, bool isSuper: false});
/// Calls [f] for each constructor declared in this class.
void forEachConstructor(void Function(String, MemberBuilder) f);
}
abstract class ClassBuilderImpl extends DeclarationBuilderImpl
implements ClassBuilder {
@override
List<TypeVariableBuilder>? typeVariables;
@override
TypeBuilder? supertypeBuilder;
@override
List<TypeBuilder>? interfaceBuilders;
@override
List<TypeBuilder>? onTypes;
@override
final ConstructorScope constructorScope;
@override
bool isNullClass = false;
InterfaceType? _legacyRawType;
InterfaceType? _nullableRawType;
InterfaceType? _nonNullableRawType;
InterfaceType? _thisType;
ClassBuilderImpl(
List<MetadataBuilder>? metadata,
int modifiers,
String name,
this.typeVariables,
this.supertypeBuilder,
this.interfaceBuilders,
this.onTypes,
Scope scope,
this.constructorScope,
LibraryBuilder parent,
int charOffset)
: super(metadata, modifiers, name, parent, charOffset, scope);
@override
String get debugName => "ClassBuilder";
@override
bool get isAbstract => (modifiers & abstractMask) != 0;
@override
bool get isMixin => (modifiers & mixinDeclarationMask) != 0;
@override
bool get isMixinApplication => mixedInTypeBuilder != null;
@override
bool get isNamedMixinApplication {
return isMixinApplication && (modifiers & namedMixinApplicationMask) != 0;
}
@override
bool get isAnonymousMixinApplication {
return isMixinApplication && !isNamedMixinApplication;
}
@override
bool get declaresConstConstructor =>
(modifiers & declaresConstConstructorMask) != 0;
@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 = isSetter
? scope.lookupSetter(name, charOffset, fileUri, isInstanceScope: false)
: scope.lookup(name, charOffset, fileUri, isInstanceScope: false);
if (declaration == null && isPatch) {
return origin.findStaticBuilder(
name, charOffset, fileUri, accessingLibrary,
isSetter: isSetter);
}
return declaration;
}
@override
MemberBuilder? findConstructorOrFactory(
String name, int charOffset, Uri uri, LibraryBuilder accessingLibrary) {
if (accessingLibrary.nameOriginBuilder.origin !=
libraryBuilder.nameOriginBuilder.origin &&
name.startsWith("_")) {
return null;
}
MemberBuilder? declaration =
constructorScope.lookup(name == 'new' ? '' : name, charOffset, uri);
if (declaration == null && isPatch) {
return origin.findConstructorOrFactory(
name, charOffset, uri, accessingLibrary);
}
return declaration;
}
@override
Builder? lookupLocalMember(String name,
{bool setter: false, bool required: false}) {
Builder? builder = scope.lookupLocalMember(name, setter: setter);
if (builder == null && isPatch) {
builder = origin.scope.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, libraryBuilder.nonNullable,
getAsTypeArguments(cls.typeParameters, libraryBuilder.library));
}
@override
InterfaceType get legacyRawType {
return _legacyRawType ??= new InterfaceType(cls, Nullability.legacy,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
@override
InterfaceType get nullableRawType {
return _nullableRawType ??= new InterfaceType(cls, Nullability.nullable,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
@override
InterfaceType get nonNullableRawType {
return _nonNullableRawType ??= new InterfaceType(
cls,
Nullability.nonNullable,
new List<DartType>.filled(typeVariablesCount, const DynamicType()));
}
@override
InterfaceType rawType(Nullability nullability) {
switch (nullability) {
case Nullability.legacy:
return legacyRawType;
case Nullability.nullable:
return nullableRawType;
case Nullability.nonNullable:
return nonNullableRawType;
case Nullability.undetermined:
default:
return unhandled("$nullability", "rawType", TreeNode.noOffset, noUri);
}
}
@override
DartType buildAliasedTypeWithBuiltArguments(
LibraryBuilder library,
Nullability nullability,
List<DartType>? arguments,
TypeUse typeUse,
Uri fileUri,
int charOffset,
{required bool hasExplicitTypeArguments}) {
assert(arguments == null || cls.typeParameters.length == arguments.length);
if (isNullClass) {
return const NullType();
}
if (name == "FutureOr") {
LibraryBuilder parentLibrary = parent as LibraryBuilder;
if (parentLibrary.importUri.isScheme("dart") &&
parentLibrary.importUri.path == "async") {
assert(arguments != null && arguments.length == 1);
return new FutureOrType(arguments!.single, nullability);
}
}
DartType type = arguments == null
? rawType(nullability)
: new InterfaceType(cls, nullability, arguments);
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}) {
return buildAliasedTypeWithBuiltArguments(
library,
nullabilityBuilder.build(library),
buildAliasedTypeArguments(library, arguments, hierarchy),
typeUse,
fileUri,
charOffset,
hasExplicitTypeArguments: hasExplicitTypeArguments);
}
@override
Supertype buildMixedInType(
LibraryBuilder library, List<TypeBuilder>? arguments) {
Class cls = isPatch ? origin.cls : this.cls;
if (arguments != null) {
List<DartType> typeArguments =
buildAliasedTypeArguments(library, arguments, /* hierarchy = */ null);
typeArguments = unaliasTypes(typeArguments,
legacyEraseAliases: !library.isNonNullableByDefault)!;
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 (isPatch) {
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 patch 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)
: hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
if (isSuper && target == null) {
if (cls.isMixinDeclaration) {
target =
hierarchy.getInterfaceMember(instanceClass, name, setter: isSetter);
}
}
return target;
}
}
class ConstructorRedirection {
String target;
bool cycleReported;
ConstructorRedirection(this.target) : cycleReported = false;
}