blob: ba7dae68416998099efaa301fa81c6760981e2d4 [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.
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart'
show ClassHierarchy, ClassHierarchyBase, ClassHierarchyMembers;
import 'package:kernel/core_types.dart';
import 'package:kernel/names.dart' show equalsName;
import 'package:kernel/reference_from_index.dart'
show IndexedClass, IndexedLibrary;
import 'package:kernel/src/bounds_checks.dart';
import 'package:kernel/src/types.dart' show Types;
import 'package:kernel/type_algebra.dart'
show
FreshTypeParameters,
Substitution,
getFreshTypeParameters,
substitute,
updateBoundNullabilities;
import 'package:kernel/type_environment.dart';
import '../base/messages.dart';
import '../base/modifiers.dart';
import '../base/name_space.dart';
import '../base/problems.dart' show unexpected, unhandled, unimplemented;
import '../base/scope.dart';
import '../builder/augmentation_iterator.dart';
import '../builder/builder.dart';
import '../builder/constructor_reference_builder.dart';
import '../builder/declaration_builders.dart';
import '../builder/formal_parameter_builder.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/metadata_builder.dart';
import '../builder/name_iterator.dart';
import '../builder/named_type_builder.dart';
import '../builder/never_type_declaration_builder.dart';
import '../builder/nullability_builder.dart';
import '../builder/synthesized_type_builder.dart';
import '../builder/type_builder.dart';
import '../fragment/fragment.dart';
import '../kernel/body_builder_context.dart';
import '../kernel/hierarchy/hierarchy_builder.dart';
import '../kernel/hierarchy/hierarchy_node.dart';
import '../kernel/kernel_helper.dart';
import '../kernel/type_algorithms.dart';
import '../kernel/utils.dart' show compareProcedures;
import 'builder_factory.dart';
import 'class_declaration.dart';
import 'name_scheme.dart';
import 'source_builder_mixins.dart';
import 'source_constructor_builder.dart';
import 'source_factory_builder.dart';
import 'source_library_builder.dart';
import 'source_loader.dart';
import 'source_member_builder.dart';
import 'type_parameter_scope_builder.dart';
Class initializeClass(
List<NominalParameterBuilder>? typeParameters,
String name,
Uri fileUri,
int startOffset,
int nameOffset,
int endOffset,
IndexedClass? indexedClass,
{required bool isAugmentation}) {
Class cls = new Class(
name: name,
typeParameters:
NominalParameterBuilder.typeParametersFromBuilders(typeParameters),
// If the class is an augmentation class it shouldn't use the reference
// from index even when available.
// TODO(johnniwinther): Avoid creating [Class] so early in the builder
// that we end up creating unneeded nodes.
reference: isAugmentation ? null : indexedClass?.reference,
fileUri: fileUri);
if (cls.startFileOffset == TreeNode.noOffset) {
cls.startFileOffset = startOffset;
}
if (cls.fileOffset == TreeNode.noOffset) {
cls.fileOffset = nameOffset;
}
if (cls.fileEndOffset == TreeNode.noOffset) {
cls.fileEndOffset = endOffset;
}
return cls;
}
class SourceClassBuilder extends ClassBuilderImpl
with ClassDeclarationBuilderMixin
implements
Comparable<SourceClassBuilder>,
ClassDeclarationBuilder,
SourceDeclarationBuilder {
@override
final SourceLibraryBuilder libraryBuilder;
final int nameOffset;
@override
final String name;
@override
final Uri fileUri;
final Modifiers _modifiers;
final List<MetadataBuilder>? metadata;
final Class actualCls;
final DeclarationNameSpaceBuilder nameSpaceBuilder;
late final LookupScope _scope;
late final DeclarationNameSpace _nameSpace;
late final ConstructorScope _constructorScope;
@override
List<NominalParameterBuilder>? typeParameters;
/// The scope in which the [typeParameters] are declared.
final LookupScope typeParameterScope;
TypeBuilder? _supertypeBuilder;
@override
List<TypeBuilder>? interfaceBuilders;
@override
List<TypeBuilder>? onTypes;
@override
final List<ConstructorReferenceBuilder>? constructorReferences;
@override
TypeBuilder? mixedInTypeBuilder;
final IndexedClass? indexedClass;
@override
bool isMixinDeclaration;
bool? _isConflictingAugmentationMember;
/// Returns `true` if this class is a class declared in an augmentation
/// library that conflicts with a declaration in the origin library.
bool get isConflictingAugmentationMember {
return _isConflictingAugmentationMember ??= false;
}
void set isConflictingAugmentationMember(bool value) {
assert(_isConflictingAugmentationMember == null,
'$this.isConflictingAugmentationMember has already been fixed.');
_isConflictingAugmentationMember = value;
}
List<SourceClassBuilder>? _augmentations;
MergedClassMemberScope? _mergedScope;
ClassDeclaration _classDeclaration;
SourceClassBuilder(
{required this.metadata,
required Modifiers modifiers,
required this.name,
required this.typeParameters,
required this.interfaceBuilders,
required this.onTypes,
required this.typeParameterScope,
required this.nameSpaceBuilder,
required this.libraryBuilder,
required this.constructorReferences,
required this.fileUri,
required int startOffset,
required this.nameOffset,
required int endOffset,
this.indexedClass,
this.mixedInTypeBuilder,
this.isMixinDeclaration = false,
required ClassDeclaration classDeclaration})
: _modifiers = modifiers,
_classDeclaration = classDeclaration,
actualCls = initializeClass(typeParameters, name, fileUri, startOffset,
nameOffset, endOffset, indexedClass,
isAugmentation: modifiers.isAugment) {
actualCls.hasConstConstructor = declaresConstConstructor;
}
@override
int get fileOffset => nameOffset;
@override
bool get isAbstract => _modifiers.isAbstract;
@override
bool get isNamedMixinApplication {
return isMixinApplication && _modifiers.isNamedMixinApplication;
}
@override
bool get declaresConstConstructor => _modifiers.declaresConstConstructor;
@override
bool get isSealed => _modifiers.isSealed;
@override
bool get isBase => _modifiers.isBase;
@override
bool get isInterface => _modifiers.isInterface;
@override
bool get isFinal => _modifiers.isFinal;
/// Set to `true` if this class is declared using the `augment` modifier.
bool get isAugmentation => _modifiers.isAugment;
@override
bool get isMixinClass => _modifiers.isMixin;
@override
// Coverage-ignore(suite): Not run.
bool get isConst => _modifiers.isConst;
@override
// Coverage-ignore(suite): Not run.
bool get isStatic => _modifiers.isStatic;
@override
bool get isAugment => _modifiers.isAugment;
@override
LookupScope get scope => _scope;
@override
DeclarationNameSpace get nameSpace => _nameSpace;
@override
ConstructorScope get constructorScope => _constructorScope;
@override
void buildScopes(LibraryBuilder coreLibrary) {
_nameSpace = nameSpaceBuilder.buildNameSpace(
loader: libraryBuilder.loader,
problemReporting: libraryBuilder,
enclosingLibraryBuilder: libraryBuilder,
declarationBuilder: this,
indexedLibrary: libraryBuilder.indexedLibrary,
indexedContainer: indexedClass,
containerType: ContainerType.Class,
containerName: new ClassName(name));
_scope = new NameSpaceLookupScope(
_nameSpace, ScopeKind.declaration, "class $name",
parent: typeParameterScope);
_constructorScope =
new DeclarationNameSpaceConstructorScope(name, _nameSpace);
}
MergedClassMemberScope get mergedScope => _mergedScope ??= isAugmenting
?
// Coverage-ignore(suite): Not run.
origin.mergedScope
: new MergedClassMemberScope(this);
// Coverage-ignore(suite): Not run.
List<SourceClassBuilder>? get augmentationsForTesting => _augmentations;
SourceClassBuilder? actualOrigin;
bool _hasComputedSupertype = false;
TypeBuilder? computeSupertypeBuilder({
required SourceLoader loader,
required ProblemReporting problemReporting,
required List<NominalParameterBuilder> unboundNominalParameters,
required IndexedLibrary? indexedLibrary,
required Map<SourceClassBuilder, TypeBuilder> mixinApplications,
required void Function(SourceClassBuilder) addAnonymousMixinClassBuilder,
}) {
assert(!_hasComputedSupertype, "Supertype has already been computed.");
_hasComputedSupertype = true;
return _supertypeBuilder = _applyMixins(
unboundNominalParameters: unboundNominalParameters,
compilationUnitScope: _classDeclaration.compilationUnitScope,
problemReporting: problemReporting,
objectTypeBuilder: loader.target.objectType,
enclosingLibraryBuilder: libraryBuilder,
fileUri: _classDeclaration.fileUri,
indexedLibrary: indexedLibrary,
supertype: _classDeclaration.supertype,
mixins: _classDeclaration.mixins,
mixinApplications: mixinApplications,
startOffset: _classDeclaration.startOffset,
nameOffset: _classDeclaration.nameOffset,
endOffset: _classDeclaration.endOffset,
subclassName: _classDeclaration.name,
isMixinDeclaration: _classDeclaration.isMixinDeclaration,
typeParameters: _classDeclaration.typeParameters,
modifiers: Modifiers.empty,
onAnonymousMixin: (SourceClassBuilder anonymousMixinBuilder) {
Reference? reference = anonymousMixinBuilder.indexedClass?.reference;
if (reference != null) {
loader.buildersCreatedWithReferences[reference] =
anonymousMixinBuilder;
}
addAnonymousMixinClassBuilder(anonymousMixinBuilder);
anonymousMixinBuilder.buildScopes(loader.coreLibrary);
});
}
void markAsCyclic(ClassBuilder objectClass) {
assert(
_hasComputedSupertype, "Supertype of $this has not been computed yet.");
// Ensure that the cycle is broken by removing superclass and
// implemented interfaces.
cls.implementedTypes.clear();
cls.supertype = null;
cls.mixedInType = null;
_supertypeBuilder = new NamedTypeBuilderImpl.fromTypeDeclarationBuilder(
objectClass, const NullabilityBuilder.omitted(),
instanceTypeParameterAccess:
InstanceTypeParameterAccessState.Unexpected);
interfaceBuilders = null;
mixedInTypeBuilder = null;
// TODO(johnniwinther): Update the message for when a class depends on
// a cycle but does not depend on itself.
addProblem(templateCyclicClassHierarchy.withArguments(fullNameForErrors),
fileOffset, noLength);
}
@override
TypeBuilder? get supertypeBuilder {
assert(
_hasComputedSupertype, "Supertype of $this has not been computed yet.");
return _supertypeBuilder;
}
void set supertypeBuilder(TypeBuilder? value) {
_supertypeBuilder = value;
}
@override
SourceClassBuilder get origin => actualOrigin ?? this;
@override
Class get cls => origin.actualCls;
@override
SourceLibraryBuilder get parent => libraryBuilder;
Class build(LibraryBuilder coreLibrary) {
void buildBuilders(Builder declaration) {
if (declaration.parent != this) {
if (declaration.parent?.origin != origin) {
// Coverage-ignore-block(suite): Not run.
if (fileUri != declaration.parent?.fileUri) {
unexpected("$fileUri", "${declaration.parent?.fileUri}", fileOffset,
fileUri);
} else {
unexpected(
fullNameForErrors,
declaration.parent?.fullNameForErrors ?? '',
fileOffset,
fileUri);
}
}
} else if (declaration is SourceMemberBuilder) {
SourceMemberBuilder memberBuilder = declaration;
memberBuilder.buildOutlineNodes((
{required Member member,
Member? tearOff,
required BuiltMemberKind kind}) {
_addMemberToClass(declaration, member);
if (tearOff != null) {
_addMemberToClass(declaration, tearOff);
}
});
} else {
unhandled("${declaration.runtimeType}", "buildBuilders",
declaration.fileOffset, declaration.fileUri);
}
}
nameSpace.unfilteredIterator.forEach(buildBuilders);
nameSpace.unfilteredConstructorIterator.forEach(buildBuilders);
if (_supertypeBuilder != null) {
_supertypeBuilder = _checkSupertype(_supertypeBuilder!);
}
Supertype? supertype = supertypeBuilder?.buildSupertype(libraryBuilder,
isMixinDeclaration ? TypeUse.mixinOnType : TypeUse.classExtendsType);
if (supertype != null &&
LibraryBuilder.isFunction(supertype.classNode, coreLibrary)) {
supertype = null;
_supertypeBuilder = null;
}
if (!isMixinDeclaration &&
actualCls.supertype != null &&
// Coverage-ignore(suite): Not run.
actualCls.superclass!.isMixinDeclaration) {
// Coverage-ignore-block(suite): Not run.
// Declared mixins have interfaces that can be implemented, but they
// cannot be extended. However, a mixin declaration with a single
// superclass constraint is encoded with the constraint as the supertype,
// and that is allowed to be a mixin's interface.
libraryBuilder.addProblem(
templateSupertypeIsIllegal.withArguments(actualCls.superclass!.name),
fileOffset,
noLength,
fileUri);
supertype = null;
}
if (supertype == null && _supertypeBuilder is! NamedTypeBuilder) {
_supertypeBuilder = null;
}
actualCls.supertype = supertype;
if (mixedInTypeBuilder != null) {
mixedInTypeBuilder = _checkSupertype(mixedInTypeBuilder!);
}
Supertype? mixedInType =
mixedInTypeBuilder?.buildMixedInType(libraryBuilder);
if (mixedInType != null &&
LibraryBuilder.isFunction(mixedInType.classNode, coreLibrary)) {
mixedInType = null;
mixedInTypeBuilder = null;
actualCls.isAnonymousMixin = false;
isMixinDeclaration = false;
}
actualCls.isMixinDeclaration = isMixinDeclaration;
actualCls.mixedInType = mixedInType;
// TODO(ahe): If `cls.supertype` is null, and this isn't Object, report a
// compile-time error.
cls.isAbstract = isAbstract;
cls.isMixinClass = isMixinClass;
cls.isSealed = isSealed;
cls.isBase = isBase;
cls.isInterface = isInterface;
cls.isFinal = isFinal;
if (interfaceBuilders != null) {
for (int i = 0; i < interfaceBuilders!.length; ++i) {
interfaceBuilders![i] = _checkSupertype(interfaceBuilders![i]);
Supertype? supertype = interfaceBuilders![i]
.buildSupertype(libraryBuilder, TypeUse.classImplementsType);
if (supertype != null) {
if (LibraryBuilder.isFunction(supertype.classNode, coreLibrary)) {
continue;
}
// TODO(ahe): Report an error if supertype is null.
actualCls.implementedTypes.add(supertype);
}
}
}
cls.procedures.sort(compareProcedures);
return cls;
}
BodyBuilderContext createBodyBuilderContext() {
return new ClassBodyBuilderContext(this);
}
void buildOutlineExpressions(ClassHierarchy classHierarchy,
List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
void build(Builder declaration) {
SourceMemberBuilder member = declaration as SourceMemberBuilder;
member.buildOutlineExpressions(
classHierarchy, delayedDefaultValueCloners);
}
MetadataBuilder.buildAnnotations(
isAugmenting ? origin.cls : cls,
metadata,
createBodyBuilderContext(),
libraryBuilder,
fileUri,
libraryBuilder.scope,
createFileUriExpression: isAugmenting);
if (typeParameters != null) {
for (int i = 0; i < typeParameters!.length; i++) {
typeParameters![i].buildOutlineExpressions(libraryBuilder,
createBodyBuilderContext(), classHierarchy, typeParameterScope);
}
}
nameSpace
.filteredConstructorIterator(
parent: this, includeDuplicates: false, includeAugmentations: true)
.forEach(build);
nameSpace
.filteredIterator(
parent: this, includeDuplicates: false, includeAugmentations: true)
.forEach(build);
}
@override
// Coverage-ignore(suite): Not run.
Iterator<T> localMemberIterator<T extends Builder>() =>
new ClassDeclarationMemberIterator<SourceClassBuilder, T>.local(this,
includeDuplicates: false);
@override
// Coverage-ignore(suite): Not run.
Iterator<T> localConstructorIterator<T extends MemberBuilder>() =>
new ClassDeclarationConstructorIterator<SourceClassBuilder, T>.local(this,
includeDuplicates: false);
@override
Iterator<T> fullMemberIterator<T extends Builder>() =>
new ClassDeclarationMemberIterator<SourceClassBuilder, T>.full(
const _SourceClassBuilderAugmentationAccess(), this,
includeDuplicates: false);
@override
// Coverage-ignore(suite): Not run.
NameIterator<T> fullMemberNameIterator<T extends Builder>() =>
new ClassDeclarationMemberNameIterator<SourceClassBuilder, T>(
const _SourceClassBuilderAugmentationAccess(), this,
includeDuplicates: false);
@override
Iterator<T> fullConstructorIterator<T extends MemberBuilder>() =>
new ClassDeclarationConstructorIterator<SourceClassBuilder, T>.full(
const _SourceClassBuilderAugmentationAccess(), this,
includeDuplicates: false);
@override
NameIterator<T> fullConstructorNameIterator<T extends MemberBuilder>() =>
new ClassDeclarationConstructorNameIterator<SourceClassBuilder, T>(
const _SourceClassBuilderAugmentationAccess(), this,
includeDuplicates: false);
/// Looks up the constructor by [name] on the class built by this class
/// builder.
SourceConstructorBuilder? lookupConstructor(Name name) {
if (name.text == "new") {
name = new Name("", name.library);
}
Builder? builder = nameSpace.lookupConstructor(name.text);
if (builder is SourceConstructorBuilder) {
return builder;
}
return null;
}
/// Looks up the super constructor by [name] on the superclass of the class
/// built by this class builder.
Constructor? lookupSuperConstructor(Name name) {
if (name.text == "new") {
name = new Name("", name.library);
}
Class? superclass = cls.superclass;
if (superclass != null) {
for (Constructor constructor in superclass.constructors) {
if (constructor.name == name) {
return constructor;
}
}
}
return null;
}
@override
int get typeParametersCount => typeParameters?.length ?? 0;
@override
List<DartType> buildAliasedTypeArguments(LibraryBuilder library,
List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy) {
if (arguments == null && typeParameters == null) {
return <DartType>[];
}
if (arguments == null && typeParameters != null) {
// TODO(johnniwinther): Use i2b here when needed.
List<DartType> result = new List<DartType>.generate(
typeParameters!.length,
(int i) => typeParameters![i]
.defaultType!
// TODO(johnniwinther): Using [libraryBuilder] here instead of
// [library] preserves the nullability of the original
// declaration. Should we legacy erase this?
.buildAliased(
libraryBuilder, TypeUse.defaultTypeAsTypeArgument, hierarchy),
growable: true);
return result;
}
if (arguments != null && arguments.length != typeParametersCount) {
// Coverage-ignore-block(suite): Not run.
assert(libraryBuilder.loader.assertProblemReportedElsewhere(
"SourceClassBuilder.buildAliasedTypeArguments: "
"the numbers of type parameters and type arguments don't match.",
expectedPhase: CompilationPhaseForProblemReporting.outline));
return unhandled(
templateTypeArgumentMismatch
.withArguments(typeParametersCount)
.problemMessage,
"buildTypeArguments",
-1,
null);
}
assert(arguments!.length == typeParametersCount);
List<DartType> result = new List<DartType>.generate(
arguments!.length,
(int i) =>
arguments[i].buildAliased(library, TypeUse.typeArgument, hierarchy),
growable: true);
return result;
}
/// Returns a map which maps the type parameters of [superclass] to their
/// respective values as defined by the superclass clause of this class (and
/// its superclasses).
///
/// It's assumed that [superclass] is a superclass of this class.
///
/// For example, given:
///
/// class Box<T> {}
/// class BeatBox extends Box<Beat> {}
/// class Beat {}
///
/// We have:
///
/// [[BeatBox]].getSubstitutionMap([[Box]]) -> {[[Box::T]]: Beat]]}.
///
/// It's an error if [superclass] isn't a superclass.
Map<TypeParameter, DartType> getSubstitutionMap(Class superclass) {
Supertype? supertype = cls.supertype;
Map<TypeParameter, DartType> substitutionMap = <TypeParameter, DartType>{};
List<DartType> arguments;
List<TypeParameter> variables;
Class? classNode;
while (classNode != superclass) {
classNode = supertype!.classNode;
arguments = supertype.typeArguments;
variables = classNode.typeParameters;
supertype = classNode.supertype;
if (variables.isNotEmpty) {
Map<TypeParameter, DartType> directSubstitutionMap =
<TypeParameter, DartType>{};
for (int i = 0; i < variables.length; i++) {
DartType argument =
i < arguments.length ? arguments[i] : const DynamicType();
// TODO(ahe): Investigate if requiring the caller to use
// `substituteDeep` from `package:kernel/type_algebra.dart` instead
// of `substitute` is faster. If so, we can simply this code.
argument = substitute(argument, substitutionMap);
directSubstitutionMap[variables[i]] = argument;
}
substitutionMap = directSubstitutionMap;
}
}
return substitutionMap;
}
@override
void applyAugmentation(Builder augmentation) {
if (augmentation is SourceClassBuilder) {
augmentation.actualOrigin = this;
(_augmentations ??= []).add(augmentation);
mergedScope.addAugmentationScope(augmentation);
int originLength = typeParameters?.length ?? 0;
int augmentationLength = augmentation.typeParameters?.length ?? 0;
if (originLength != augmentationLength) {
// Coverage-ignore-block(suite): Not run.
augmentation.addProblem(messagePatchClassTypeParametersMismatch,
augmentation.fileOffset, noLength, context: [
messagePatchClassOrigin.withLocation(fileUri, fileOffset, noLength)
]);
} else if (typeParameters != null) {
int count = 0;
for (NominalParameterBuilder t in augmentation.typeParameters!) {
typeParameters![count++].applyAugmentation(t);
}
}
} else {
// Coverage-ignore-block(suite): Not run.
libraryBuilder.addProblem(messagePatchDeclarationMismatch,
augmentation.fileOffset, noLength, augmentation.fileUri, context: [
messagePatchDeclarationOrigin.withLocation(
fileUri, fileOffset, noLength)
]);
}
}
void checkSupertypes(
CoreTypes coreTypes,
ClassHierarchyBuilder hierarchyBuilder,
Class objectClass,
Class enumClass,
Class underscoreEnumClass) {
// This method determines whether the class (that's being built) its super
// class appears both in 'extends' and 'implements' clauses and whether any
// interface appears multiple times in the 'implements' clause.
// Moreover, it checks that `FutureOr` and `void` are not among the
// supertypes and that `Enum` is not implemented by non-abstract classes.
// Anonymous mixins have to propagate certain class modifiers.
if (cls.isAnonymousMixin) {
Class? superclass = cls.superclass;
Class? mixedInClass = cls.mixedInClass;
// If either [superclass] or [mixedInClass] is sealed, the current
// anonymous mixin is sealed.
if (superclass != null && superclass.isSealed ||
mixedInClass != null && mixedInClass.isSealed) {
cls.isSealed = true;
} else {
// Otherwise, if either [superclass] or [mixedInClass] is base or final,
// then the current anonymous mixin is final.
bool superclassIsBaseOrFinal =
superclass != null && (superclass.isBase || superclass.isFinal);
bool mixedInClassIsBaseOrFinal = mixedInClass != null &&
(mixedInClass.isBase || mixedInClass.isFinal);
if (superclassIsBaseOrFinal || mixedInClassIsBaseOrFinal) {
cls.isFinal = true;
}
}
}
ClassHierarchyNode classHierarchyNode =
hierarchyBuilder.getNodeFromClass(cls);
if (libraryBuilder.libraryFeatures.enhancedEnums.isEnabled && !isEnum) {
bool hasEnumSuperinterface = false;
const List<String> restrictedNames = ["index", "hashCode", "=="];
Map<String, ClassBuilder> restrictedMembersInSuperclasses = {};
ClassBuilder? superclassDeclaringConcreteValues;
List<Supertype> interfaces = classHierarchyNode.superclasses;
for (int i = 0; !hasEnumSuperinterface && i < interfaces.length; i++) {
Class interfaceClass = interfaces[i].classNode;
if (interfaceClass == enumClass) {
hasEnumSuperinterface = true;
}
if (!interfaceClass.isEnum &&
interfaceClass != objectClass &&
interfaceClass != underscoreEnumClass) {
ClassHierarchyNode superclassHierarchyNode =
hierarchyBuilder.getNodeFromClass(interfaceClass);
for (String restrictedMemberName in restrictedNames) {
// TODO(johnniwinther): Handle injected members.
Builder? member = superclassHierarchyNode.classBuilder.nameSpace
.lookupLocalMember(restrictedMemberName, setter: false);
if (member is MemberBuilder && !member.isAbstract) {
restrictedMembersInSuperclasses[restrictedMemberName] ??=
superclassHierarchyNode.classBuilder;
}
}
Builder? member = superclassHierarchyNode.classBuilder.nameSpace
.lookupLocalMember("values", setter: false);
if (member is MemberBuilder && !member.isAbstract) {
superclassDeclaringConcreteValues ??= member.classBuilder;
}
}
}
interfaces = classHierarchyNode.interfaces;
for (int i = 0; !hasEnumSuperinterface && i < interfaces.length; i++) {
if (interfaces[i].classNode == enumClass) {
hasEnumSuperinterface = true;
}
}
if (!cls.isAbstract && !cls.isEnum && hasEnumSuperinterface) {
addProblem(templateEnumSupertypeOfNonAbstractClass.withArguments(name),
fileOffset, noLength);
}
if (hasEnumSuperinterface && cls != underscoreEnumClass) {
// Instance members named `values` are restricted.
Builder? customValuesDeclaration =
nameSpace.lookupLocalMember("values", setter: false);
if (customValuesDeclaration != null &&
!customValuesDeclaration.isStatic) {
// Retrieve the earliest declaration for error reporting.
while (customValuesDeclaration?.next != null) {
// Coverage-ignore-block(suite): Not run.
customValuesDeclaration = customValuesDeclaration?.next;
}
libraryBuilder.addProblem(
templateEnumImplementerContainsValuesDeclaration
.withArguments(this.name),
customValuesDeclaration!.fileOffset,
customValuesDeclaration.fullNameForErrors.length,
fileUri);
}
customValuesDeclaration =
nameSpace.lookupLocalMember("values", setter: true);
if (customValuesDeclaration != null &&
!customValuesDeclaration.isStatic) {
// Retrieve the earliest declaration for error reporting.
while (customValuesDeclaration?.next != null) {
// Coverage-ignore-block(suite): Not run.
customValuesDeclaration = customValuesDeclaration?.next;
}
libraryBuilder.addProblem(
templateEnumImplementerContainsValuesDeclaration
.withArguments(this.name),
customValuesDeclaration!.fileOffset,
customValuesDeclaration.fullNameForErrors.length,
fileUri);
}
if (superclassDeclaringConcreteValues != null) {
libraryBuilder.addProblem(
templateInheritedRestrictedMemberOfEnumImplementer.withArguments(
"values", superclassDeclaringConcreteValues.name),
fileOffset,
noLength,
fileUri);
}
// Non-setter concrete instance members named `index` and hashCode and
// operator == are restricted.
for (String restrictedMemberName in restrictedNames) {
Builder? member =
nameSpace.lookupLocalMember(restrictedMemberName, setter: false);
if (member is MemberBuilder && !member.isAbstract) {
libraryBuilder.addProblem(
templateEnumImplementerContainsRestrictedInstanceDeclaration
.withArguments(this.name, restrictedMemberName),
member.fileOffset,
member.fullNameForErrors.length,
fileUri);
}
if (restrictedMembersInSuperclasses
.containsKey(restrictedMemberName)) {
ClassBuilder restrictedNameMemberProvider =
restrictedMembersInSuperclasses[restrictedMemberName]!;
libraryBuilder.addProblem(
templateInheritedRestrictedMemberOfEnumImplementer
.withArguments(restrictedMemberName,
restrictedNameMemberProvider.name),
fileOffset,
noLength,
fileUri);
}
}
}
}
void fail(TypeBuilder target, Message message,
TypeDeclarationBuilder? aliasBuilder) {
int nameOffset = target.typeName!.nameOffset;
int nameLength = target.typeName!.nameLength;
if (aliasBuilder is TypeAliasBuilder) {
// Coverage-ignore-block(suite): Not run.
addProblem(message, nameOffset, nameLength, context: [
messageTypedefCause.withLocation(
aliasBuilder.fileUri, aliasBuilder.fileOffset, noLength),
]);
} else {
addProblem(message, nameOffset, nameLength);
}
}
// Extract and check superclass (if it exists).
ClassBuilder? superClass;
TypeBuilder? superClassType = supertypeBuilder;
if (superClassType != null) {
TypeDeclarationBuilder? superDeclaration = superClassType.declaration;
TypeDeclarationBuilder? unaliasedSuperDeclaration =
superClassType.computeUnaliasedDeclaration(isUsedAsClass: true);
// TODO(eernst): Should gather 'restricted supertype' checks in one place,
// e.g., dynamic/int/String/Null and more are checked elsewhere.
if (unaliasedSuperDeclaration is NeverTypeDeclarationBuilder) {
fail(superClassType, messageExtendsNever, superDeclaration);
} else if (unaliasedSuperDeclaration is ClassBuilder) {
superClass = unaliasedSuperDeclaration;
}
}
if (cls.isMixinClass) {
// Check that the class does not have a constructor.
Iterator<SourceMemberBuilder> constructorIterator =
fullConstructorIterator<SourceMemberBuilder>();
while (constructorIterator.moveNext()) {
SourceMemberBuilder constructor = constructorIterator.current;
// Assumes the constructor isn't synthetic since
// [installSyntheticConstructors] hasn't been called yet.
if (constructor is DeclaredSourceConstructorBuilder) {
// Report an error if a mixin class has a constructor with parameters,
// is external, or is a redirecting constructor.
if (constructor.isRedirecting ||
(constructor.formals != null &&
constructor.formals!.isNotEmpty) ||
constructor.isExternal) {
addProblem(
templateIllegalMixinDueToConstructors
.withArguments(fullNameForErrors),
constructor.fileOffset,
noLength);
}
}
}
// Check that the class has 'Object' as their superclass.
if (superClass != null &&
superClassType != null &&
superClass.cls != objectClass) {
addProblem(templateMixinInheritsFromNotObject.withArguments(name),
superClassType.charOffset ?? TreeNode.noOffset, noLength);
}
}
if (classHierarchyNode.isMixinApplication) {
assert(mixedInTypeBuilder != null,
"No mixed in type builder for mixin application $this.");
ClassHierarchyNode mixedInNode = classHierarchyNode.mixedInNode!;
ClassHierarchyNode? mixinSuperClassNode =
mixedInNode.directSuperClassNode;
if (mixinSuperClassNode != null &&
mixinSuperClassNode.classBuilder.cls != objectClass &&
!mixedInNode.classBuilder.cls.isMixinDeclaration) {
addProblem(
templateMixinInheritsFromNotObject
.withArguments(mixedInNode.classBuilder.name),
mixedInTypeBuilder!.charOffset ?? TreeNode.noOffset,
noLength);
}
}
if (interfaceBuilders == null) return;
// Validate interfaces.
Map<ClassBuilder, int>? problems;
Map<ClassBuilder, int>? problemsOffsets;
Set<ClassBuilder> implemented = new Set<ClassBuilder>();
for (TypeBuilder type in interfaceBuilders!) {
TypeDeclarationBuilder? typeDeclaration = type.declaration;
TypeDeclarationBuilder? unaliasedDeclaration =
type.computeUnaliasedDeclaration(isUsedAsClass: true);
if (unaliasedDeclaration is ClassBuilder) {
ClassBuilder interface = unaliasedDeclaration;
if (superClass == interface) {
addProblem(templateImplementsSuperClass.withArguments(interface.name),
this.fileOffset, noLength);
} else if (interface.cls.name == "FutureOr" &&
// Coverage-ignore(suite): Not run.
interface.cls.enclosingLibrary.importUri.isScheme("dart") &&
// Coverage-ignore(suite): Not run.
interface.cls.enclosingLibrary.importUri.path == "async") {
// Coverage-ignore-block(suite): Not run.
addProblem(messageImplementsFutureOr, this.fileOffset, noLength);
} else if (implemented.contains(interface)) {
// Aggregate repetitions.
problems ??= <ClassBuilder, int>{};
problems[interface] ??= 0;
problems[interface] = problems[interface]! + 1;
problemsOffsets ??= <ClassBuilder, int>{};
problemsOffsets[interface] ??= type.charOffset ?? TreeNode.noOffset;
} else {
implemented.add(interface);
}
}
if (unaliasedDeclaration != superClass) {
// TODO(eernst): Have all 'restricted supertype' checks in one place.
if (unaliasedDeclaration is NeverTypeDeclarationBuilder) {
fail(type, messageImplementsNever, typeDeclaration);
}
}
}
if (problems != null) {
problems.forEach((ClassBuilder interface, int repetitions) {
addProblem(
templateImplementsRepeated.withArguments(
interface.name, repetitions),
problemsOffsets![interface]!,
noLength);
});
}
}
void checkMixinApplication(ClassHierarchy hierarchy, CoreTypes coreTypes) {
TypeEnvironment typeEnvironment = new TypeEnvironment(coreTypes, hierarchy);
// A mixin declaration can only be applied to a class that implements all
// the declaration's superclass constraints.
InterfaceType supertype = cls.supertype!.asInterfaceType;
Substitution substitution = Substitution.fromSupertype(cls.mixedInType!);
for (Supertype constraint in cls.mixedInClass!.onClause) {
InterfaceType requiredInterface =
substitution.substituteSupertype(constraint).asInterfaceType;
InterfaceType? implementedInterface =
hierarchy.getInterfaceTypeAsInstanceOfClass(
supertype, requiredInterface.classNode);
if (implementedInterface == null ||
!typeEnvironment.areMutualSubtypes(implementedInterface,
requiredInterface, SubtypeCheckMode.withNullabilities)) {
libraryBuilder.addProblem(
templateMixinApplicationIncompatibleSupertype.withArguments(
supertype, requiredInterface, cls.mixedInType!.asInterfaceType),
cls.fileOffset,
noLength,
cls.fileUri);
}
}
}
void checkRedirectingFactories(TypeEnvironment typeEnvironment) {
Iterator<SourceFactoryBuilder> iterator =
nameSpace.filteredConstructorIterator(
parent: this, includeDuplicates: true, includeAugmentations: true);
while (iterator.moveNext()) {
iterator.current.checkRedirectingFactories(typeEnvironment);
}
}
Map<String, ConstructorRedirection>? _redirectingConstructors;
/// Registers a constructor redirection for this class and returns true if
/// this redirection gives rise to a cycle that has not been reported before.
bool checkConstructorCyclic(String source, String target) {
ConstructorRedirection? redirect = new ConstructorRedirection(target);
_redirectingConstructors ??= <String, ConstructorRedirection>{};
_redirectingConstructors![source] = redirect;
while (redirect != null) {
if (redirect.cycleReported) return false;
if (redirect.target == source) {
redirect.cycleReported = true;
return true;
}
redirect = _redirectingConstructors![redirect.target];
}
return false;
}
TypeBuilder _checkSupertype(TypeBuilder supertype) {
if (typeParameters == null) return supertype;
Message? message;
for (int i = 0; i < typeParameters!.length; ++i) {
NominalParameterBuilder typeParameterBuilder = typeParameters![i];
Variance variance = supertype
.computeTypeParameterBuilderVariance(typeParameterBuilder,
sourceLoader: libraryBuilder.loader)
.variance!;
if (!variance.greaterThanOrEqual(typeParameters![i].variance)) {
if (typeParameters![i].parameter.isLegacyCovariant) {
message = templateInvalidTypeParameterInSupertype.withArguments(
typeParameters![i].name,
variance.keyword,
supertype.typeName!.name);
} else {
// Coverage-ignore-block(suite): Not run.
message =
templateInvalidTypeParameterInSupertypeWithVariance.withArguments(
typeParameters![i].variance.keyword,
typeParameters![i].name,
variance.keyword,
supertype.typeName!.name);
}
libraryBuilder.addProblem(message, fileOffset, noLength, fileUri);
}
}
if (message != null) {
TypeName typeName = supertype.typeName!;
return new NamedTypeBuilderImpl(
typeName, const NullabilityBuilder.omitted(),
fileUri: fileUri,
charOffset: fileOffset,
instanceTypeParameterAccess:
InstanceTypeParameterAccessState.Unexpected)
..bind(
libraryBuilder,
new InvalidTypeDeclarationBuilder(typeName.name,
message.withLocation(fileUri, fileOffset, noLength)));
}
return supertype;
}
void checkVarianceInField(TypeEnvironment typeEnvironment,
{required DartType fieldType,
required bool isInstanceMember,
required bool hasSetter,
required bool isCovariantByDeclaration,
required Uri fileUri,
required int fileOffset}) {
List<TypeParameter> typeParameters = cls.typeParameters;
if (typeParameters.isNotEmpty) {
for (TypeParameter typeParameter in typeParameters) {
Variance fieldVariance = computeVariance(typeParameter, fieldType);
if (isInstanceMember) {
reportVariancePositionIfInvalid(
fieldVariance, typeParameter, fileUri, fileOffset);
}
if (isInstanceMember && hasSetter && !isCovariantByDeclaration) {
fieldVariance = Variance.contravariant.combine(fieldVariance);
reportVariancePositionIfInvalid(
fieldVariance, typeParameter, fileUri, fileOffset);
}
}
}
}
void checkVarianceInTypeParameters(TypeEnvironment typeEnvironment,
List<NominalParameterBuilder>? typeParameters) {
List<TypeParameter> classTypeParameters = cls.typeParameters;
if (typeParameters != null && classTypeParameters.isNotEmpty) {
for (NominalParameterBuilder nominalParameter in typeParameters) {
for (TypeParameter classTypeParameter in classTypeParameters) {
Variance typeVariance = Variance.invariant.combine(computeVariance(
classTypeParameter, nominalParameter.actualParameter.bound));
reportVariancePositionIfInvalid(typeVariance, classTypeParameter,
fileUri, nominalParameter.fileOffset);
}
}
}
}
void checkVarianceInFormals(
TypeEnvironment type, List<FormalParameterBuilder>? formals) {
List<TypeParameter> classTypeParameters = cls.typeParameters;
if (formals != null && classTypeParameters.isNotEmpty) {
for (FormalParameterBuilder formal in formals) {
if (!formal.isCovariantByDeclaration) {
for (TypeParameter typeParameter in classTypeParameters) {
Variance formalVariance = Variance.contravariant
.combine(computeVariance(typeParameter, formal.variable!.type));
reportVariancePositionIfInvalid(formalVariance, typeParameter,
formal.fileUri, formal.fileOffset);
}
}
}
}
}
void checkVarianceInReturnType(TypeEnvironment type, DartType returnType,
{required Uri fileUri, required fileOffset}) {
List<TypeParameter> classTypeParameters = cls.typeParameters;
if (classTypeParameters.isNotEmpty) {
for (TypeParameter typeParameter in classTypeParameters) {
Variance returnTypeVariance =
computeVariance(typeParameter, returnType);
reportVariancePositionIfInvalid(
returnTypeVariance, typeParameter, fileUri, fileOffset,
isReturnType: true);
}
}
}
// Coverage-ignore(suite): Not run.
void checkVarianceInFunction(Procedure procedure,
TypeEnvironment typeEnvironment, List<TypeParameter> typeParameters) {
List<TypeParameter> functionTypeParameters =
procedure.function.typeParameters;
List<VariableDeclaration> positionalParameters =
procedure.function.positionalParameters;
List<VariableDeclaration> namedParameters =
procedure.function.namedParameters;
DartType returnType = procedure.function.returnType;
for (TypeParameter functionParameter in functionTypeParameters) {
for (TypeParameter typeParameter in typeParameters) {
Variance typeVariance = Variance.invariant
.combine(computeVariance(typeParameter, functionParameter.bound));
reportVariancePositionIfInvalid(
typeVariance, typeParameter, fileUri, functionParameter.fileOffset);
}
}
for (VariableDeclaration formal in positionalParameters) {
if (!formal.isCovariantByDeclaration) {
for (TypeParameter typeParameter in typeParameters) {
Variance formalVariance = Variance.contravariant
.combine(computeVariance(typeParameter, formal.type));
reportVariancePositionIfInvalid(
formalVariance, typeParameter, fileUri, formal.fileOffset);
}
}
}
for (VariableDeclaration named in namedParameters) {
for (TypeParameter typeParameter in typeParameters) {
Variance namedVariance = Variance.contravariant
.combine(computeVariance(typeParameter, named.type));
reportVariancePositionIfInvalid(
namedVariance, typeParameter, fileUri, named.fileOffset);
}
}
for (TypeParameter typeParameter in typeParameters) {
Variance returnTypeVariance = computeVariance(typeParameter, returnType);
reportVariancePositionIfInvalid(returnTypeVariance, typeParameter,
fileUri, procedure.function.fileOffset,
isReturnType: true);
}
}
void reportVariancePositionIfInvalid(Variance variance,
TypeParameter typeParameter, Uri fileUri, int fileOffset,
{bool isReturnType = false}) {
SourceLibraryBuilder library = this.libraryBuilder;
if (!typeParameter.isLegacyCovariant &&
!variance.greaterThanOrEqual(typeParameter.variance)) {
Message message;
if (isReturnType) {
message = templateInvalidTypeParameterVariancePositionInReturnType
.withArguments(typeParameter.variance.keyword, typeParameter.name!,
variance.keyword);
} else {
message = templateInvalidTypeParameterVariancePosition.withArguments(
typeParameter.variance.keyword,
typeParameter.name!,
variance.keyword);
}
library.reportTypeArgumentIssue(message, fileUri, fileOffset,
typeParameter: typeParameter);
}
}
@override
int computeDefaultTypes(ComputeDefaultTypeContext context) {
bool hasErrors = context.reportNonSimplicityIssues(this, typeParameters);
int count = context.computeDefaultTypesForVariables(typeParameters,
inErrorRecovery: hasErrors);
Iterator<SourceMemberBuilder> iterator =
nameSpace.filteredConstructorIterator<SourceMemberBuilder>(
parent: this, includeDuplicates: false, includeAugmentations: true);
while (iterator.moveNext()) {
count += iterator.current
.computeDefaultTypes(context, inErrorRecovery: hasErrors);
}
Iterator<SourceMemberBuilder> memberIterator =
fullMemberIterator<SourceMemberBuilder>();
while (memberIterator.moveNext()) {
count += memberIterator.current
.computeDefaultTypes(context, inErrorRecovery: hasErrors);
}
return count;
}
void checkTypesInOutline(TypeEnvironment typeEnvironment) {
Iterator<SourceMemberBuilder> memberIterator =
fullMemberIterator<SourceMemberBuilder>();
while (memberIterator.moveNext()) {
SourceMemberBuilder builder = memberIterator.current;
builder.checkVariance(this, typeEnvironment);
builder.checkTypes(libraryBuilder, nameSpace, typeEnvironment);
}
Iterator<SourceMemberBuilder> constructorIterator =
fullConstructorIterator<SourceMemberBuilder>();
while (constructorIterator.moveNext()) {
SourceMemberBuilder builder = constructorIterator.current;
builder.checkTypes(libraryBuilder, nameSpace, typeEnvironment);
}
}
void addSyntheticConstructor(
SyntheticSourceConstructorBuilder constructorBuilder) {
String name = constructorBuilder.name;
constructorBuilder.next = nameSpace.lookupConstructor(name);
nameSpace.addConstructor(name, constructorBuilder);
// Synthetic constructors are created after the component has been built
// so we need to add the constructor to the class.
cls.addConstructor(constructorBuilder.invokeTarget);
if (constructorBuilder.readTarget != constructorBuilder.invokeTarget) {
cls.addProcedure(constructorBuilder.readTarget as Procedure);
}
if (constructorBuilder.isConst) {
cls.hasConstConstructor = true;
}
}
int buildBodyNodes() {
adjustAnnotationFileUri(cls, cls.fileUri);
int count = 0;
void buildMembers(Builder builder) {
if (builder.parent != this) {
return;
}
if (builder is SourceMemberBuilder) {
count += builder.buildBodyNodes((
{required Member member,
Member? tearOff,
required BuiltMemberKind kind}) {
_addMemberToClass(builder, member);
if (tearOff != null) {
// Coverage-ignore-block(suite): Not run.
_addMemberToClass(builder, tearOff);
}
});
}
}
nameSpace
.filteredIterator(
parent: this, includeDuplicates: true, includeAugmentations: true)
.forEach(buildMembers);
nameSpace
.filteredConstructorIterator(
parent: this, includeDuplicates: true, includeAugmentations: true)
.forEach(buildMembers);
return count;
}
void _addMemberToClass(SourceMemberBuilder memberBuilder, Member member) {
member.parent = cls;
if (!memberBuilder.isAugmenting &&
!memberBuilder.isDuplicate &&
!memberBuilder.isConflictingSetter) {
if (memberBuilder.isConflictingAugmentationMember) {
if (member is Field &&
// Coverage-ignore(suite): Not run.
member.isStatic ||
member is Procedure && member.isStatic) {
member.name = new Name(
'${member.name}'
'#${memberBuilder.libraryBuilder.augmentationIndex}',
member.name.library);
} else {
return;
}
}
if (member is Procedure) {
cls.addProcedure(member);
} else if (member is Field) {
cls.addField(member);
} else if (member is Constructor) {
cls.addConstructor(member);
} else {
unhandled("${member.runtimeType}", "getMember", member.fileOffset,
member.fileUri);
}
}
}
/// Return a map whose keys are the supertypes of this [SourceClassBuilder]
/// after expansion of type aliases, if any. For each supertype key, the
/// corresponding value is the type alias which was unaliased in order to
/// find the supertype, or null if the supertype was not aliased.
Map<TypeDeclarationBuilder?, TypeAliasBuilder?> computeDirectSupertypes(
ClassBuilder objectClass) {
final Map<TypeDeclarationBuilder?, TypeAliasBuilder?> result = {};
final TypeBuilder? supertype = this.supertypeBuilder;
if (supertype != null) {
TypeDeclarationBuilder? declaration = supertype.declaration;
TypeDeclarationBuilder? unaliasedDeclaration =
supertype.computeUnaliasedDeclaration(isUsedAsClass: true);
result[unaliasedDeclaration] =
declaration is TypeAliasBuilder ? declaration : null;
} else if (objectClass != this) {
result[objectClass] = null;
}
final List<TypeBuilder>? interfaces = this.interfaceBuilders;
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
TypeBuilder interface = interfaces[i];
TypeDeclarationBuilder? declaration = interface.declaration;
TypeDeclarationBuilder? unaliasedDeclaration =
interface.computeUnaliasedDeclaration(isUsedAsClass: true);
result[unaliasedDeclaration] =
declaration is TypeAliasBuilder ? declaration : null;
}
}
final TypeBuilder? mixedInTypeBuilder = this.mixedInTypeBuilder;
if (mixedInTypeBuilder != null) {
TypeDeclarationBuilder? declaration = mixedInTypeBuilder.declaration;
TypeDeclarationBuilder? unaliasedDeclaration =
mixedInTypeBuilder.computeUnaliasedDeclaration(isUsedAsClass: true);
result[unaliasedDeclaration] =
declaration is TypeAliasBuilder ? declaration : null;
}
return result;
}
@override
int compareTo(SourceClassBuilder other) {
int result = "$fileUri".compareTo("${other.fileUri}");
if (result != 0) return result;
return fileOffset.compareTo(other.fileOffset);
}
void _handleSeenCovariant(
ClassHierarchyMembers memberHierarchy,
Member interfaceMember,
bool isSetter,
callback(Member interfaceMember, bool isSetter)) {
// When a parameter is covariant we have to check that we also
// override the same member in all parents.
for (Supertype supertype in interfaceMember.enclosingClass!.supers) {
Member? member = memberHierarchy.getInterfaceMember(
supertype.classNode, interfaceMember.name,
setter: isSetter);
if (member != null) {
callback(member, isSetter);
}
}
}
void checkOverride(
Types types,
ClassHierarchyMembers memberHierarchy,
Member declaredMember,
Member interfaceMember,
bool isSetter,
callback(Member interfaceMember, bool isSetter),
{required bool isInterfaceCheck}) {
if (declaredMember == interfaceMember) {
return;
}
Member interfaceMemberOrigin =
interfaceMember.memberSignatureOrigin ?? interfaceMember;
if (declaredMember is Constructor || interfaceMember is Constructor) {
unimplemented(
"Constructor in override check.", declaredMember.fileOffset, fileUri);
}
if (declaredMember is Procedure && interfaceMember is Procedure) {
if (declaredMember.kind == interfaceMember.kind) {
if (declaredMember.kind == ProcedureKind.Method ||
declaredMember.kind == ProcedureKind.Operator) {
bool seenCovariant = checkMethodOverride(types, declaredMember,
interfaceMember, interfaceMemberOrigin, isInterfaceCheck);
if (seenCovariant) {
_handleSeenCovariant(
memberHierarchy, interfaceMember, isSetter, callback);
}
} else if (declaredMember.kind == ProcedureKind.Getter) {
checkGetterOverride(types, declaredMember, interfaceMember,
interfaceMemberOrigin, isInterfaceCheck);
} else if (declaredMember.kind == ProcedureKind.Setter) {
bool seenCovariant = checkSetterOverride(types, declaredMember,
interfaceMember, interfaceMemberOrigin, isInterfaceCheck);
if (seenCovariant) {
_handleSeenCovariant(
memberHierarchy, interfaceMember, isSetter, callback);
}
} else {
// Coverage-ignore-block(suite): Not run.
assert(
false,
"Unexpected procedure kind in override check: "
"${declaredMember.kind}");
}
}
} else {
bool declaredMemberHasGetter = declaredMember is Field ||
declaredMember is Procedure && declaredMember.isGetter;
bool interfaceMemberHasGetter = interfaceMember is Field ||
interfaceMember is Procedure && interfaceMember.isGetter;
bool declaredMemberHasSetter = (declaredMember is Field &&
!declaredMember.isFinal &&
!declaredMember.isConst) ||
declaredMember is Procedure && declaredMember.isSetter;
bool interfaceMemberHasSetter = (interfaceMember is Field &&
!interfaceMember.isFinal &&
!interfaceMember.isConst) ||
interfaceMember is Procedure && interfaceMember.isSetter;
if (declaredMemberHasGetter && interfaceMemberHasGetter) {
checkGetterOverride(types, declaredMember, interfaceMember,
interfaceMemberOrigin, isInterfaceCheck);
}
if (declaredMemberHasSetter && interfaceMemberHasSetter) {
bool seenCovariant = checkSetterOverride(types, declaredMember,
interfaceMember, interfaceMemberOrigin, isInterfaceCheck);
if (seenCovariant) {
_handleSeenCovariant(
memberHierarchy, interfaceMember, isSetter, callback);
}
}
}
// TODO(ahe): Handle other cases: accessors, operators, and fields.
}
Uri _getMemberUri(Member member) {
if (member is Field) return member.fileUri;
if (member is Procedure) return member.fileUri;
// Other member types won't be seen because constructors don't participate
// in override relationships
return unhandled('${member.runtimeType}', '_getMemberUri', -1, null);
}
Substitution? _computeInterfaceSubstitution(
Types types,
Member declaredMember,
Member interfaceMember,
Member interfaceMemberOrigin,
FunctionNode? declaredFunction,
FunctionNode? interfaceFunction,
bool isInterfaceCheck) {
Substitution? interfaceSubstitution;
if (interfaceMember.enclosingClass!.typeParameters.isNotEmpty) {
Class enclosingClass = interfaceMember.enclosingClass!;
interfaceSubstitution = Substitution.fromPairs(
enclosingClass.typeParameters,
types.hierarchy.getInterfaceTypeArgumentsAsInstanceOfClass(
thisType, enclosingClass)!);
}
if (declaredFunction?.typeParameters.length !=
interfaceFunction?.typeParameters.length) {
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideTypeParametersMismatch.withArguments(
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
"${interfaceMemberOrigin.enclosingClass!.name}."
"${interfaceMemberOrigin.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(_getMemberUri(interfaceMemberOrigin),
interfaceMemberOrigin.fileOffset, noLength)
]);
} else if (declaredFunction?.typeParameters != null) {
// Since the bound of `interfaceFunction!.parameter[i]` may have changed
// during substitution, it can affect the nullabilities of the types in
// the substitution map. The first parameter to
// [TypeParameterType.forAlphaRenaming] should be updated to account for
// the change.
List<TypeParameter> interfaceTypeParameters;
if (interfaceSubstitution == null) {
interfaceTypeParameters = interfaceFunction!.typeParameters;
} else {
FreshTypeParameters freshTypeParameters =
getFreshTypeParameters(interfaceFunction!.typeParameters);
interfaceTypeParameters = freshTypeParameters.freshTypeParameters;
for (TypeParameter parameter in interfaceTypeParameters) {
parameter.bound =
interfaceSubstitution.substituteType(parameter.bound);
}
updateBoundNullabilities(interfaceTypeParameters);
}
Substitution substitution;
if (declaredFunction!.typeParameters.isEmpty) {
substitution = Substitution.empty;
} else if (declaredFunction.typeParameters.length == 1) {
substitution = Substitution.fromSingleton(
interfaceFunction.typeParameters[0],
new TypeParameterType.withDefaultNullability(
declaredFunction.typeParameters[0]));
} else {
Map<TypeParameter, DartType> substitutionMap =
<TypeParameter, DartType>{};
for (int i = 0; i < declaredFunction.typeParameters.length; ++i) {
substitutionMap[interfaceFunction.typeParameters[i]] =
new TypeParameterType.withDefaultNullability(
declaredFunction.typeParameters[i]);
}
substitution = Substitution.fromMap(substitutionMap);
}
for (int i = 0; i < declaredFunction.typeParameters.length; ++i) {
TypeParameter declaredParameter = declaredFunction.typeParameters[i];
TypeParameter interfaceParameter = interfaceFunction.typeParameters[i];
if (!interfaceParameter.isCovariantByClass) {
DartType declaredBound = declaredParameter.bound;
DartType interfaceBound = interfaceParameter.bound;
if (interfaceSubstitution != null) {
declaredBound = interfaceSubstitution.substituteType(declaredBound);
interfaceBound =
interfaceSubstitution.substituteType(interfaceBound);
}
DartType computedBound = substitution.substituteType(interfaceBound);
if (!types
.performNullabilityAwareMutualSubtypesCheck(
declaredBound, computedBound)
.isSubtypeWhenUsingNullabilities()) {
// Coverage-ignore-block(suite): Not run.
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideTypeParametersBoundMismatch.withArguments(
declaredBound,
declaredParameter.name!,
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
computedBound,
"${interfaceMemberOrigin.enclosingClass!.name}."
"${interfaceMemberOrigin.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(_getMemberUri(interfaceMemberOrigin),
interfaceMemberOrigin.fileOffset, noLength)
]);
}
}
}
if (interfaceSubstitution != null) {
interfaceSubstitution =
Substitution.combine(interfaceSubstitution, substitution);
} else {
interfaceSubstitution = substitution;
}
}
return interfaceSubstitution;
}
Substitution? _computeDeclaredSubstitution(
Types types, Member declaredMember) {
Substitution? declaredSubstitution;
if (declaredMember.enclosingClass!.typeParameters.isNotEmpty) {
Class enclosingClass = declaredMember.enclosingClass!;
declaredSubstitution = Substitution.fromPairs(
enclosingClass.typeParameters,
types.hierarchy.getInterfaceTypeArgumentsAsInstanceOfClass(
thisType, enclosingClass)!);
}
return declaredSubstitution;
}
void _checkTypes(
Types types,
Substitution? interfaceSubstitution,
Substitution? declaredSubstitution,
Member declaredMember,
Member interfaceMember,
Member interfaceMemberOrigin,
DartType declaredType,
DartType interfaceType,
bool isCovariantByDeclaration,
VariableDeclaration? declaredParameter,
bool isInterfaceCheck,
{bool asIfDeclaredParameter = false}) {
if (interfaceSubstitution != null) {
interfaceType = interfaceSubstitution.substituteType(interfaceType);
}
if (declaredSubstitution != null) {
declaredType = declaredSubstitution.substituteType(declaredType);
}
bool inParameter = declaredParameter != null || asIfDeclaredParameter;
DartType subtype = inParameter ? interfaceType : declaredType;
DartType supertype = inParameter ? declaredType : interfaceType;
if (types.isSubtypeOf(
subtype, supertype, SubtypeCheckMode.withNullabilities)) {
// No problem--the proper subtyping relation is satisfied.
} else if (isCovariantByDeclaration &&
types.isSubtypeOf(
supertype, subtype, SubtypeCheckMode.withNullabilities)) {
// No problem--the overriding parameter is marked "covariant" and has
// a type which is a subtype of the parameter it overrides.
} else if (subtype is InvalidType || supertype is InvalidType) {
// Don't report a problem as something else is wrong that has already
// been reported.
} else {
// Report an error.
String declaredMemberName = '${declaredMember.enclosingClass!.name}'
'.${declaredMember.name.text}';
String interfaceMemberName =
'${interfaceMemberOrigin.enclosingClass!.name}'
'.${interfaceMemberOrigin.name.text}';
Message message;
int fileOffset;
if (declaredParameter == null) {
if (asIfDeclaredParameter) {
// Setter overridden by field
message = templateOverrideTypeMismatchSetter.withArguments(
declaredMemberName,
declaredType,
interfaceType,
interfaceMemberName);
} else {
message = templateOverrideTypeMismatchReturnType.withArguments(
declaredMemberName,
declaredType,
interfaceType,
interfaceMemberName);
}
fileOffset = declaredMember.fileOffset;
} else {
message = templateOverrideTypeMismatchParameter.withArguments(
declaredParameter.name!,
declaredMemberName,
declaredType,
interfaceType,
interfaceMemberName);
fileOffset = declaredParameter.fileOffset;
}
reportInvalidOverride(
isInterfaceCheck, declaredMember, message, fileOffset, noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(_getMemberUri(interfaceMemberOrigin),
interfaceMemberOrigin.fileOffset, noLength)
]);
}
}
/// Checks whether [declaredMember] correctly overrides [interfaceMember].
///
/// If an error is reporter [interfaceMemberOrigin] is used as the context
/// for where [interfaceMember] was declared, since [interfaceMember] might
/// itself be synthesized.
///
/// Returns whether a covariant parameter was seen and more methods thus have
/// to be checked.
bool checkMethodOverride(
Types types,
Procedure declaredMember,
Procedure interfaceMember,
Member interfaceMemberOrigin,
bool isInterfaceCheck) {
assert(declaredMember.kind == interfaceMember.kind);
assert(declaredMember.kind == ProcedureKind.Method ||
declaredMember.kind == ProcedureKind.Operator);
bool seenCovariant = false;
FunctionNode declaredFunction = declaredMember.function;
FunctionType? declaredSignatureType = declaredMember.signatureType;
FunctionNode interfaceFunction = interfaceMember.function;
FunctionType? interfaceSignatureType = interfaceMember.signatureType;
Substitution? interfaceSubstitution = _computeInterfaceSubstitution(
types,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredFunction,
interfaceFunction,
isInterfaceCheck);
Substitution? declaredSubstitution =
_computeDeclaredSubstitution(types, declaredMember);
_checkTypes(
types,
interfaceSubstitution,
declaredSubstitution,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredFunction.returnType,
interfaceFunction.returnType,
/* isCovariantByDeclaration = */ false,
/* declaredParameter = */ null,
isInterfaceCheck);
if (declaredFunction.positionalParameters.length <
interfaceFunction.positionalParameters.length) {
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideFewerPositionalArguments.withArguments(
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
"${interfaceMemberOrigin.enclosingClass!.name}."
"${interfaceMemberOrigin.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(interfaceMemberOrigin.fileUri,
interfaceMemberOrigin.fileOffset, noLength)
]);
}
if (interfaceFunction.requiredParameterCount <
declaredFunction.requiredParameterCount) {
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideMoreRequiredArguments.withArguments(
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
"${interfaceMemberOrigin.enclosingClass!.name}."
"${interfaceMemberOrigin.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(interfaceMemberOrigin.fileUri,
interfaceMemberOrigin.fileOffset, noLength)
]);
}
for (int i = 0;
i < declaredFunction.positionalParameters.length &&
i < interfaceFunction.positionalParameters.length;
i++) {
VariableDeclaration declaredParameter =
declaredFunction.positionalParameters[i];
VariableDeclaration interfaceParameter =
interfaceFunction.positionalParameters[i];
DartType declaredParameterType = declaredParameter.type;
if (declaredSignatureType != null) {
declaredParameterType = declaredSignatureType.positionalParameters[i];
}
DartType interfaceParameterType = interfaceParameter.type;
if (interfaceSignatureType != null) {
interfaceParameterType = interfaceSignatureType.positionalParameters[i];
}
if (i == 0 &&
declaredMember.name == equalsName &&
declaredParameterType ==
types.hierarchy.coreTypes.objectNonNullableRawType &&
interfaceParameter.type is DynamicType) {
// TODO(johnniwinther): Add check for opt-in overrides of operator ==.
// `operator ==` methods in opt-out classes have type
// `bool Function(dynamic)`.
continue;
}
_checkTypes(
types,
interfaceSubstitution,
declaredSubstitution,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredParameterType,
interfaceParameterType,
declaredParameter.isCovariantByDeclaration ||
interfaceParameter.isCovariantByDeclaration,
declaredParameter,
isInterfaceCheck);
if (declaredParameter.isCovariantByDeclaration) seenCovariant = true;
}
if (declaredFunction.namedParameters.isEmpty &&
interfaceFunction.namedParameters.isEmpty) {
return seenCovariant;
}
if (declaredFunction.namedParameters.length <
interfaceFunction.namedParameters.length) {
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideFewerNamedArguments.withArguments(
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
"${interfaceMemberOrigin.enclosingClass!.name}."
"${interfaceMemberOrigin.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(interfaceMemberOrigin.fileUri,
interfaceMemberOrigin.fileOffset, noLength)
]);
}
int compareNamedParameters(VariableDeclaration p0, VariableDeclaration p1) {
return p0.name!.compareTo(p1.name!);
}
List<VariableDeclaration> sortedFromDeclared =
new List.of(declaredFunction.namedParameters)
..sort(compareNamedParameters);
List<VariableDeclaration> sortedFromInterface =
new List.of(interfaceFunction.namedParameters)
..sort(compareNamedParameters);
Iterator<VariableDeclaration> declaredNamedParameters =
sortedFromDeclared.iterator;
Iterator<VariableDeclaration> interfaceNamedParameters =
sortedFromInterface.iterator;
outer:
while (declaredNamedParameters.moveNext() &&
interfaceNamedParameters.moveNext()) {
while (declaredNamedParameters.current.name !=
interfaceNamedParameters.current.name) {
if (!declaredNamedParameters.moveNext()) {
// Coverage-ignore-block(suite): Not run.
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideMismatchNamedParameter.withArguments(
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
interfaceNamedParameters.current.name!,
"${interfaceMember.enclosingClass!.name}."
"${interfaceMember.name.text}"),
declaredMember.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMember.name.text)
.withLocation(interfaceMember.fileUri,
interfaceMember.fileOffset, noLength)
]);
break outer;
}
}
VariableDeclaration declaredParameter = declaredNamedParameters.current;
_checkTypes(
types,
interfaceSubstitution,
declaredSubstitution,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredParameter.type,
interfaceNamedParameters.current.type,
declaredParameter.isCovariantByDeclaration,
declaredParameter,
isInterfaceCheck);
if (declaredParameter.isRequired &&
!interfaceNamedParameters.current.isRequired) {
// Coverage-ignore-block(suite): Not run.
reportInvalidOverride(
isInterfaceCheck,
declaredMember,
templateOverrideMismatchRequiredNamedParameter.withArguments(
declaredParameter.name!,
"${declaredMember.enclosingClass!.name}."
"${declaredMember.name.text}",
"${interfaceMember.enclosingClass!.name}."
"${interfaceMember.name.text}"),
declaredParameter.fileOffset,
noLength,
context: [
templateOverriddenMethodCause
.withArguments(interfaceMemberOrigin.name.text)
.withLocation(_getMemberUri(interfaceMemberOrigin),
interfaceMemberOrigin.fileOffset, noLength)
]);
}
if (declaredParameter.isCovariantByDeclaration) seenCovariant = true;
}
return seenCovariant;
}
/// Checks whether [declaredMember] correctly overrides [interfaceMember].
///
/// If an error is reporter [interfaceMemberOrigin] is used as the context
/// for where [interfaceMember] was declared, since [interfaceMember] might
/// itself be synthesized.
void checkGetterOverride(
Types types,
Member declaredMember,
Member interfaceMember,
Member interfaceMemberOrigin,
bool isInterfaceCheck) {
Substitution? interfaceSubstitution = _computeInterfaceSubstitution(
types,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
/* declaredFunction = */
null,
/* interfaceFunction = */
null,
isInterfaceCheck);
Substitution? declaredSubstitution =
_computeDeclaredSubstitution(types, declaredMember);
DartType declaredType = declaredMember.getterType;
DartType interfaceType = interfaceMember.getterType;
_checkTypes(
types,
interfaceSubstitution,
declaredSubstitution,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredType,
interfaceType,
/* isCovariantByDeclaration = */
false,
/* declaredParameter = */
null,
isInterfaceCheck);
}
/// Checks whether [declaredMember] correctly overrides [interfaceMember].
///
/// If an error is reporter [interfaceMemberOrigin] is used as the context
/// for where [interfaceMember] was declared, since [interfaceMember] might
/// itself be synthesized.
///
/// Returns whether a covariant parameter was seen and more methods thus have
/// to be checked.
bool checkSetterOverride(
Types types,
Member declaredMember,
Member interfaceMember,
Member interfaceMemberOrigin,
bool isInterfaceCheck) {
Substitution? interfaceSubstitution = _computeInterfaceSubstitution(
types,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
/* declaredFunction = */
null,
/* interfaceFunction = */
null,
isInterfaceCheck);
Substitution? declaredSubstitution =
_computeDeclaredSubstitution(types, declaredMember);
DartType declaredType = declaredMember.setterType;
DartType interfaceType = interfaceMember.setterType;
VariableDeclaration? declaredParameter =
declaredMember.function?.positionalParameters.elementAt(0);
bool isCovariantByDeclaration =
declaredParameter?.isCovariantByDeclaration ?? false;
if (!isCovariantByDeclaration && declaredMember is Field) {
isCovariantByDeclaration = declaredMember.isCovariantByDeclaration;
}
if (!isCovariantByDeclaration && interfaceMember is Field) {
isCovariantByDeclaration = interfaceMember.isCovariantByDeclaration;
}
_checkTypes(
types,
interfaceSubstitution,
declaredSubstitution,
declaredMember,
interfaceMember,
interfaceMemberOrigin,
declaredType,
interfaceType,
isCovariantByDeclaration,
declaredParameter,
isInterfaceCheck,
asIfDeclaredParameter: true);
return isCovariantByDeclaration;
}
// When the overriding member is inherited, report the class containing
// the conflict as the main error.
void reportInvalidOverride(bool isInterfaceCheck, Member declaredMember,
Message message, int fileOffset, int length,
{List<LocatedMessage>? context}) {
if (shouldOverrideProblemBeOverlooked(this)) {
return;
}
if (declaredMember.enclosingClass == cls) {
// Ordinary override
libraryBuilder.addProblem(
message, fileOffset, length, declaredMember.fileUri,
context: context);
} else {
context = [
message.withLocation(declaredMember.fileUri, fileOffset, length),
...?context
];
if (isInterfaceCheck) {
// Interface check
libraryBuilder.addProblem(
templateInterfaceCheck.withArguments(
declaredMember.name.text, cls.name),
cls.fileOffset,
cls.name.length,
cls.fileUri,
context: context);
} else {
if (cls.isAnonymousMixin) {
// Implicit mixin application class
String baseName = cls.superclass!.demangledName;
String mixinName = cls.mixedInClass!.name;
int classNameLength = cls.nameAsMixinApplicationSubclass.length;
libraryBuilder.addProblem(
templateImplicitMixinOverride.withArguments(
mixinName, baseName, declaredMember.name.text),
cls.fileOffset,
classNameLength,
cls.fileUri,
context: context);
} else {
// Named mixin application class
libraryBuilder.addProblem(
templateNamedMixinOverride.withArguments(
cls.name, declaredMember.name.text),
cls.fileOffset,
cls.name.length,
cls.fileUri,
context: context);
}
}
}
}
// Coverage-ignore(suite): Not run.
/// Returns an iterator the origin class and all augmentations in application
/// order.
Iterator<SourceClassBuilder> get declarationIterator =>
new AugmentationIterator<SourceClassBuilder>(
origin, origin._augmentations);
@override
// Coverage-ignore(suite): Not run.
Reference get reference => cls.reference;
}
/// Returns `true` if override problems should be overlooked.
///
/// This is needed for the current encoding of some JavaScript implementation
/// classes that are not valid Dart. For instance `JSInt` in
/// 'dart:_interceptors' that implements both `int` and `double`, and `JsArray`
/// in `dart:js` that implement both `ListMixin` and `JsObject`.
bool shouldOverrideProblemBeOverlooked(ClassBuilder classBuilder) {
return getOverlookedOverrideProblemChoice(classBuilder) != null;
}
/// Returns the index of the member to use if an override problems should be
/// overlooked.
///
/// This is needed for the current encoding of some JavaScript implementation
/// classes that are not valid Dart. For instance `JSInt` in
/// 'dart:_interceptors' that implements both `int` and `double`, and `JsArray`
/// in `dart:js` that implement both `ListMixin` and `JsObject`.
int? getOverlookedOverrideProblemChoice(DeclarationBuilder declarationBuilder) {
String uri = '${declarationBuilder.libraryBuilder.importUri}';
if (uri == 'dart:js' &&
// Coverage-ignore(suite): Not run.
declarationBuilder.fileUri.pathSegments.last == 'js.dart') {
return 0;
} else if (uri == 'dart:_interceptors' &&
// Coverage-ignore(suite): Not run.
declarationBuilder.fileUri.pathSegments.last == 'js_number.dart') {
return 1;
}
return null;
}
class _SourceClassBuilderAugmentationAccess
implements ClassDeclarationAugmentationAccess<SourceClassBuilder> {
const _SourceClassBuilderAugmentationAccess();
@override
SourceClassBuilder getOrigin(SourceClassBuilder classDeclaration) =>
classDeclaration.origin;
@override
Iterable<SourceClassBuilder>? getAugmentations(
SourceClassBuilder classDeclaration) =>
classDeclaration._augmentations;
}
abstract class ClassDeclaration {
String get name;
LookupScope get compilationUnitScope;
Uri get fileUri;
int get nameOffset;
int get startOffset;
int get endOffset;
List<NominalParameterBuilder>? get typeParameters;
bool get isMixinDeclaration;
TypeBuilder? get supertype;
List<TypeBuilder>? get mixins;
}
class RegularClassDeclaration implements ClassDeclaration {
final ClassFragment _fragment;
RegularClassDeclaration(this._fragment);
@override
LookupScope get compilationUnitScope => _fragment.compilationUnitScope;
@override
Uri get fileUri => _fragment.fileUri;
@override
int get endOffset => _fragment.endOffset;
@override
String get name => _fragment.name;
@override
int get nameOffset => _fragment.nameOffset;
@override
int get startOffset => _fragment.startOffset;
@override
List<NominalParameterBuilder>? get typeParameters => _fragment.typeParameters;
@override
bool get isMixinDeclaration => false;
@override
TypeBuilder? get supertype => _fragment.supertype;
@override
List<TypeBuilder>? get mixins => _fragment.mixins;
}
class EnumDeclaration implements ClassDeclaration {
final EnumFragment _fragment;
@override
final TypeBuilder supertype;
EnumDeclaration(this._fragment, this.supertype);
@override
LookupScope get compilationUnitScope => _fragment.compilationUnitScope;
@override
Uri get fileUri => _fragment.fileUri;
@override
int get endOffset => _fragment.endOffset;
@override
String get name => _fragment.name;
@override
int get nameOffset => _fragment.nameOffset;
@override
int get startOffset => _fragment.startOffset;
@override
List<NominalParameterBuilder>? get typeParameters => _fragment.typeParameters;
@override
bool get isMixinDeclaration => false;
@override
List<TypeBuilder>? get mixins => _fragment.mixins;
}
class NamedMixinApplication implements ClassDeclaration {
final NamedMixinApplicationFragment _fragment;
@override
final List<TypeBuilder> mixins;
NamedMixinApplication(this._fragment, this.mixins);
@override
LookupScope get compilationUnitScope => _fragment.compilationUnitScope;
@override
Uri get fileUri => _fragment.fileUri;
@override
int get endOffset => _fragment.endOffset;
@override
String get name => _fragment.name;
@override
int get nameOffset => _fragment.nameOffset;
@override
int get startOffset => _fragment.startOffset;
@override
List<NominalParameterBuilder>? get typeParameters => _fragment.typeParameters;
@override
bool get isMixinDeclaration => false;
@override
TypeBuilder? get supertype => _fragment.supertype;
}
class AnonymousMixinApplication implements ClassDeclaration {
@override
final String name;
@override
final LookupScope compilationUnitScope;
@override
final int nameOffset;
@override
final int startOffset;
@override
final int endOffset;
@override
final Uri fileUri;
@override
final List<NominalParameterBuilder>? typeParameters;
@override
final TypeBuilder? supertype;
@override
bool get isMixinDeclaration => false;
@override
List<TypeBuilder>? get mixins => null;
AnonymousMixinApplication(
{required this.name,
required this.compilationUnitScope,
required this.fileUri,
required this.nameOffset,
required this.startOffset,
required this.endOffset,
required this.typeParameters,
required this.supertype});
}
class MixinDeclaration implements ClassDeclaration {
final MixinFragment _fragment;
MixinDeclaration(this._fragment);
@override
LookupScope get compilationUnitScope => _fragment.compilationUnitScope;
@override
Uri get fileUri => _fragment.fileUri;
@override
int get endOffset => _fragment.endOffset;
@override
String get name => _fragment.name;
@override
int get nameOffset => _fragment.nameOffset;
@override
int get startOffset => _fragment.startOffset;
@override
List<NominalParameterBuilder>? get typeParameters => _fragment.typeParameters;
@override
bool get isMixinDeclaration => true;
@override
TypeBuilder? get supertype => _fragment.supertype;
@override
List<TypeBuilder>? get mixins => _fragment.mixins;
}
TypeBuilder? _applyMixins(
{required ProblemReporting problemReporting,
required SourceLibraryBuilder enclosingLibraryBuilder,
required List<NominalParameterBuilder> unboundNominalParameters,
required TypeBuilder? supertype,
required List<TypeBuilder>? mixins,
required int startOffset,
required int nameOffset,
required int endOffset,
required String subclassName,
required bool isMixinDeclaration,
required IndexedLibrary? indexedLibrary,
required LookupScope compilationUnitScope,
required Map<SourceClassBuilder, TypeBuilder> mixinApplications,
required Uri fileUri,
List<NominalParameterBuilder>? typeParameters,
required Modifiers modifiers,
required TypeBuilder objectTypeBuilder,
required void Function(SourceClassBuilder) onAnonymousMixin}) {
if (mixins == null) {
return supertype;
}
// Documentation below assumes the given mixin application is in one of
// these forms:
//
// class C extends S with M1, M2, M3;
// class Named = S with M1, M2, M3;
//
// When we refer to the subclass, we mean `C` or `Named`.
/// The current supertype.
///
/// Starts out having the value `S` and on each iteration of the loop
/// below, it will take on the value corresponding to:
///
/// 1. `S with M1`.
/// 2. `(S with M1) with M2`.
/// 3. `((S with M1) with M2) with M3`.
supertype ??= objectTypeBuilder;
/// The variable part of the mixin application's synthetic name. It
/// starts out as the name of the superclass, but is only used after it
/// has been combined with the name of the current mixin. In the examples
/// from above, it will take these values:
///
/// 1. `S&M1`
/// 2. `S&M1&M2`
/// 3. `S&M1&M2&M3`.
///
/// The full name of the mixin application is obtained by prepending the
/// name of the subclass (`C` or `Named` in the above examples) to the
/// running name. For the example `C`, that leads to these full names:
///
/// 1. `_C&S&M1`
/// 2. `_C&S&M1&M2`
/// 3. `_C&S&M1&M2&M3`.
///
/// For a named mixin application, the last name has been given by the
/// programmer, so for the example `Named` we see these full names:
///
/// 1. `_Named&S&M1`
/// 2. `_Named&S&M1&M2`
/// 3. `Named`.
String runningName;
if (supertype.typeName == null) {
assert(supertype is FunctionTypeBuilder);
// Function types don't have names, and we can supply any string that
// doesn't have to be unique. The actual supertype of the mixin will
// not be built in that case.
runningName = "";
} else {
runningName = supertype.typeName!.name;
}
/// The names of the type parameters of the subclass.
Set<String>? typeParameterNames;
if (typeParameters != null) {
typeParameterNames = new Set<String>();
for (NominalParameterBuilder typeParameter in typeParameters) {
typeParameterNames.add(typeParameter.name);
}
}
/// Iterate over the mixins from left to right. At the end of each
/// iteration, a new [supertype] is computed that is the mixin
/// application of [supertype] with the current mixin.
for (int i = 0; i < mixins.length; i++) {
TypeBuilder mixin = mixins[i];
bool isGeneric = false;
if (typeParameterNames != null) {
if (supertype != null) {
isGeneric =
isGeneric || supertype.usesTypeParameters(typeParameterNames);
}
isGeneric = isGeneric || mixin.usesTypeParameters(typeParameterNames);
}
TypeName? typeName = mixin.typeName;
if (typeName != null) {
runningName += "&${typeName.name}";
}
String fullname = "_$subclassName&$runningName";
List<NominalParameterBuilder>? applicationTypeParameters;
List<TypeBuilder>? applicationTypeArguments;
ClassDeclaration classDeclaration;
final int computedStartOffset;
// Otherwise, we pass the fresh type parameters to the mixin
// application in the same order as they're declared on the subclass.
if (isGeneric) {
NominalParameterNameSpace nominalParameterNameSpace =
new NominalParameterNameSpace();
NominalParameterCopy nominalVariableCopy =
NominalParameterCopy.copyTypeParameters(
unboundNominalParameters, typeParameters,
kind: TypeParameterKind.extensionSynthesized,
instanceTypeParameterAccess:
InstanceTypeParameterAccessState.Allowed)!;
applicationTypeParameters = nominalVariableCopy.newParameterBuilders;
Map<NominalParameterBuilder, NominalParameterBuilder>
newToOldVariableMap = nominalVariableCopy.newToOldParameterMap;
Map<NominalParameterBuilder, TypeBuilder> substitutionMap =
nominalVariableCopy.substitutionMap;
applicationTypeArguments = [];
for (NominalParameterBuilder typeParameter in typeParameters!) {
TypeBuilder applicationTypeArgument =
new NamedTypeBuilderImpl.fromTypeDeclarationBuilder(
// The type parameter types passed as arguments to the
// generic class representing the anonymous mixin
// application should refer back to the type parameters of
// the class that extend the anonymous mixin application.
typeParameter,
const NullabilityBuilder.omitted(),
fileUri: fileUri,
charOffset: nameOffset,
instanceTypeParameterAccess:
InstanceTypeParameterAccessState.Allowed);
applicationTypeArguments.add(applicationTypeArgument);
}
nominalParameterNameSpace.addTypeParameters(
problemReporting, applicationTypeParameters,
ownerName: fullname, allowNameConflict: true);
if (supertype != null) {
supertype = new SynthesizedTypeBuilder(
supertype, newToOldVariableMap, substitutionMap);
}
mixin = new SynthesizedTypeBuilder(
mixin, newToOldVariableMap, substitutionMap);
}
computedStartOffset = startOffset;
classDeclaration = new AnonymousMixinApplication(
name: fullname,
compilationUnitScope: compilationUnitScope,
supertype: isMixinDeclaration ? null : supertype,
typeParameters: applicationTypeParameters,
fileUri: fileUri,
startOffset: computedStartOffset,
nameOffset: nameOffset,
endOffset: endOffset);
IndexedClass? indexedClass;
if (indexedLibrary != null) {
indexedClass = indexedLibrary.lookupIndexedClass(fullname);
}
LookupScope typeParameterScope =
TypeParameterScope.fromList(compilationUnitScope, typeParameters);
DeclarationNameSpaceBuilder nameSpaceBuilder =
new DeclarationNameSpaceBuilder.empty();
SourceClassBuilder application = new SourceClassBuilder(
metadata: null,
modifiers: Modifiers.Abstract,
name: fullname,
typeParameters: applicationTypeParameters,
interfaceBuilders: isMixinDeclaration ? [supertype!, mixin] : null,
onTypes: null,
typeParameterScope: typeParameterScope,
nameSpaceBuilder: nameSpaceBuilder,
libraryBuilder: enclosingLibraryBuilder,
constructorReferences: [],
fileUri: fileUri,
startOffset: computedStartOffset,
nameOffset: nameOffset,
endOffset: endOffset,
indexedClass: indexedClass,
mixedInTypeBuilder: isMixinDeclaration ? null : mixin,
classDeclaration: classDeclaration);
// TODO(ahe, kmillikin): Should always be true?
// pkg/analyzer/test/src/summary/resynthesize_kernel_test.dart can't
// handle that :(
application.cls.isAnonymousMixin = true;
onAnonymousMixin(application);
supertype = new NamedTypeBuilderImpl.fromTypeDeclarationBuilder(
application, const NullabilityBuilder.omitted(),
arguments: applicationTypeArguments,
fileUri: fileUri,
charOffset: nameOffset,
instanceTypeParameterAccess: InstanceTypeParameterAccessState.Allowed);
mixinApplications[application] = mixin;
}
return supertype;
}