| // Copyright (c) 2021, 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:front_end/src/codes/diagnostic.dart' as diag; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; | |
| import 'package:kernel/names.dart' show noSuchMethodName; | |
| import '../../base/common.dart'; | |
| import '../../base/messages.dart' show LocatedMessage, ProblemReporting; | |
| import '../../base/uri_offset.dart'; | |
| import '../../builder/declaration_builders.dart'; | |
| import '../../builder/formal_parameter_builder.dart'; | |
| import '../../builder/library_builder.dart'; | |
| import '../../builder/member_builder.dart'; | |
| import '../../builder/omitted_type_builder.dart'; | |
| import '../../builder/type_builder.dart'; | |
| import '../../source/source_class_builder.dart'; | |
| import '../combined_member_signature.dart'; | |
| import '../member_covariance.dart'; | |
| import 'class_member.dart'; | |
| import 'delayed.dart'; | |
| import 'hierarchy_builder.dart'; | |
| import 'hierarchy_node.dart'; | |
| import 'members_builder.dart'; | |
| abstract class MembersNodeBuilder { | |
| DeclarationBuilder get declarationBuilder; | |
| List<LocatedMessage> _inheritedConflictContext(ClassMember a, ClassMember b) { | |
| // TODO(ahe): Delete this method when it isn't used by [InterfaceResolver]. | |
| int compare = "${a.uriOffset.fileUri}".compareTo("${b.uriOffset.fileUri}"); | |
| if (compare == 0) { | |
| compare = a.uriOffset.fileOffset.compareTo(b.uriOffset.fileOffset); | |
| } | |
| ClassMember first; | |
| ClassMember second; | |
| if (compare < 0) { | |
| first = a; | |
| second = b; | |
| } else { | |
| first = b; | |
| second = a; | |
| } | |
| return <LocatedMessage>[ | |
| diag.inheritedMembersConflictCause1.withLocation2(first.uriOffset), | |
| diag.inheritedMembersConflictCause2.withLocation2(second.uriOffset), | |
| ]; | |
| } | |
| void reportInheritanceConflict(ClassMember a, ClassMember b) { | |
| while (a.hasDeclarations) { | |
| a = a.declarations.first; | |
| } | |
| while (b.hasDeclarations) { | |
| b = b.declarations.first; | |
| } | |
| if (a.declarationBuilder != b.declarationBuilder) { | |
| if (a.declarationBuilder == declarationBuilder) { | |
| declarationBuilder.libraryBuilder.addProblem2( | |
| diag.declaredMemberConflictsWithInheritedMember, | |
| a.uriOffset, | |
| context: <LocatedMessage>[ | |
| diag.declaredMemberConflictsWithInheritedMemberCause.withLocation2( | |
| b.uriOffset, | |
| ), | |
| ], | |
| ); | |
| } else if (b.declarationBuilder == declarationBuilder) { | |
| declarationBuilder.libraryBuilder.addProblem2( | |
| diag.declaredMemberConflictsWithInheritedMember, | |
| b.uriOffset, | |
| context: <LocatedMessage>[ | |
| diag.declaredMemberConflictsWithInheritedMemberCause.withLocation2( | |
| a.uriOffset, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| declarationBuilder.libraryBuilder.addProblem( | |
| diag.inheritedMembersConflict, | |
| declarationBuilder.fileOffset, | |
| declarationBuilder.fullNameForErrors.length, | |
| declarationBuilder.fileUri, | |
| context: _inheritedConflictContext(a, b), | |
| ); | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (a.isStatic != b.isStatic) { | |
| ClassMember staticMember; | |
| ClassMember instanceMember; | |
| if (a.isStatic) { | |
| staticMember = a; | |
| instanceMember = b; | |
| } else { | |
| staticMember = b; | |
| instanceMember = a; | |
| } | |
| if (staticMember.isSynthesized) { | |
| // TODO(johnniwinther): Move this to the creation of the shared | |
| // property builder. | |
| declarationBuilder.libraryBuilder.addProblem2( | |
| diag.instanceAndSynthesizedStaticConflict.withArguments( | |
| name: staticMember.name.text, | |
| ), | |
| instanceMember.uriOffset, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| class ClassMembersNodeBuilder extends MembersNodeBuilder { | |
| final ClassHierarchyNode _hierarchyNode; | |
| final ClassMembersBuilder _membersBuilder; | |
| new(this._membersBuilder, this._hierarchyNode); | |
| ClassHierarchyBuilder get hierarchy => _membersBuilder.hierarchyBuilder; | |
| ClassBuilder get objectClass => hierarchy.objectClassBuilder; | |
| ClassBuilder get classBuilder => _hierarchyNode.classBuilder; | |
| @override | |
| DeclarationBuilder get declarationBuilder => classBuilder; | |
| bool get shouldModifyKernel => | |
| classBuilder.libraryBuilder.loader == hierarchy.loader; | |
| static void inferMethodType( | |
| ClassHierarchyBuilder hierarchyBuilder, | |
| ClassMembersBuilder membersBuilder, | |
| SourceClassBuilder classBuilder, | |
| FunctionNode declaredFunction, | |
| TypeBuilder declaredReturnType, | |
| List<FormalParameterBuilder>? formals, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| if (declaredReturnType is InferableTypeBuilder || | |
| formals != null && | |
| formals.any( | |
| (parameter) => parameter.type is InferableTypeBuilder, | |
| )) { | |
| List<TypeParameter> declaredTypeParameters = | |
| declaredFunction.typeParameters; | |
| List<PositionalParameter> declaredPositional = | |
| declaredFunction.positionalParameters; | |
| List<NamedParameter> declaredNamed = declaredFunction.namedParameters; | |
| declaredNamed = declaredNamed.toList()..sort(compareNamedParameters); | |
| DartType? inferredReturnType; | |
| Map<FormalParameterBuilder, DartType?> inferredParameterTypes = {}; | |
| Set<ClassMember> overriddenMemberSet = toSet( | |
| classBuilder, | |
| overriddenMembers, | |
| ); | |
| CombinedMemberSignatureBase combinedMemberSignature = | |
| new CombinedClassMemberSignature( | |
| membersBuilder, | |
| classBuilder, | |
| overriddenMemberSet.toList(), | |
| forSetter: false, | |
| ); | |
| FunctionType? combinedMemberSignatureType = | |
| combinedMemberSignature.getCombinedSignatureTypeInContext( | |
| declaredTypeParameters, | |
| ) as FunctionType?; | |
| bool cantInferReturnType = false; | |
| List<FormalParameterBuilder>? cantInferParameterTypes; | |
| if (declaredReturnType is InferableTypeBuilder) { | |
| if (combinedMemberSignatureType == null) { | |
| inferredReturnType = const InvalidType(); | |
| cantInferReturnType = true; | |
| } else { | |
| inferredReturnType = combinedMemberSignatureType.returnType; | |
| } | |
| } | |
| if (formals != null) { | |
| for (int i = 0; i < declaredPositional.length; i++) { | |
| FormalParameterBuilder declaredParameter = formals[i]; | |
| if (declaredParameter.type is! InferableTypeBuilder) { | |
| continue; | |
| } | |
| DartType? inferredParameterType; | |
| if (combinedMemberSignatureType == null) { | |
| inferredParameterType = const InvalidType(); | |
| cantInferParameterTypes ??= []; | |
| cantInferParameterTypes.add(declaredParameter); | |
| } else if (i < | |
| combinedMemberSignatureType.positionalParameters.length) { | |
| inferredParameterType = | |
| combinedMemberSignatureType.positionalParameters[i]; | |
| } | |
| inferredParameterTypes[declaredParameter] = inferredParameterType; | |
| } | |
| Map<String, DartType>? namedParameterTypes; | |
| for (int i = declaredPositional.length; i < formals.length; i++) { | |
| FormalParameterBuilder declaredParameter = formals[i]; | |
| if (declaredParameter.type is! InferableTypeBuilder) { | |
| continue; | |
| } | |
| DartType? inferredParameterType; | |
| if (combinedMemberSignatureType == null) { | |
| inferredParameterType = const InvalidType(); | |
| // Coverage-ignore(suite): Not run. | |
| cantInferParameterTypes ??= []; | |
| cantInferParameterTypes.add(declaredParameter); | |
| } else { | |
| if (namedParameterTypes == null) { | |
| namedParameterTypes = {}; | |
| for (NamedType namedType | |
| in combinedMemberSignatureType.namedParameters) { | |
| namedParameterTypes[namedType.name] = namedType.type; | |
| } | |
| } | |
| inferredParameterType = namedParameterTypes[declaredParameter.name]; | |
| } | |
| inferredParameterTypes[declaredParameter] = inferredParameterType; | |
| } | |
| } | |
| if ((cantInferReturnType && cantInferParameterTypes != null) || | |
| (cantInferParameterTypes != null && | |
| cantInferParameterTypes.length > 1)) { | |
| reportCantInferTypes( | |
| classBuilder.libraryBuilder, | |
| overriddenMembers, | |
| name: name, | |
| fileUri: fileUri, | |
| nameOffset: nameOffset, | |
| nameLength: nameLength, | |
| ); | |
| } else if (cantInferReturnType) { | |
| reportCantInferReturnType( | |
| classBuilder.libraryBuilder, | |
| overriddenMembers, | |
| name: name, | |
| fileUri: fileUri, | |
| nameOffset: nameOffset, | |
| nameLength: nameLength, | |
| ); | |
| } else if (cantInferParameterTypes != null) { | |
| reportCantInferParameterType( | |
| classBuilder.libraryBuilder, | |
| cantInferParameterTypes.single, | |
| overriddenMembers, | |
| ); | |
| } | |
| if (declaredReturnType is InferableTypeBuilder) { | |
| inferredReturnType ??= const DynamicType(); | |
| declaredReturnType.registerInferredType(inferredReturnType); | |
| } | |
| if (formals != null) { | |
| for (FormalParameterBuilder declaredParameter in formals) { | |
| if (declaredParameter.type is InferableTypeBuilder) { | |
| DartType inferredParameterType = | |
| inferredParameterTypes[declaredParameter] ?? | |
| const DynamicType(); | |
| declaredParameter.type.registerInferredType(inferredParameterType); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void inferMethodSignature( | |
| ClassMembersBuilder membersBuilder, | |
| ClassMember declaredMember, | |
| Iterable<ClassMember> overriddenMembers, | |
| ) { | |
| assert(!declaredMember.isProperty); | |
| // Trigger computation of method type. | |
| Procedure declaredProcedure = | |
| declaredMember.getMember(membersBuilder) as Procedure; | |
| for (ClassMember overriddenMember in toSet( | |
| declaredMember.declarationBuilder, | |
| overriddenMembers, | |
| )) { | |
| Covariance covariance = overriddenMember.getCovariance(membersBuilder); | |
| covariance.applyCovariance(declaredProcedure); | |
| } | |
| } | |
| void inferPropertySignature( | |
| ClassMembersBuilder membersBuilder, | |
| ClassMember declaredMember, | |
| Iterable<ClassMember> overriddenMembers, | |
| ) { | |
| assert(declaredMember.isProperty); | |
| // Trigger computation of the property type. | |
| Member declaredProperty = declaredMember.getMember(membersBuilder); | |
| for (ClassMember overriddenMember in toSet( | |
| declaredMember.declarationBuilder, | |
| overriddenMembers, | |
| )) { | |
| Covariance covariance = overriddenMember.getCovariance(membersBuilder); | |
| covariance.applyCovariance(declaredProperty); | |
| } | |
| } | |
| static void inferGetterType( | |
| ClassHierarchyBuilder hierarchyBuilder, | |
| ClassMembersBuilder membersBuilder, | |
| SourceClassBuilder classBuilder, | |
| TypeBuilder declaredTypeBuilder, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| if (declaredTypeBuilder is InferableTypeBuilder) { | |
| DartType? inferredType; | |
| overriddenMembers = toSet(classBuilder, overriddenMembers); | |
| List<ClassMember> overriddenGetters = []; | |
| List<ClassMember> overriddenSetters = []; | |
| for (ClassMember overriddenMember in overriddenMembers) { | |
| if (overriddenMember.forSetter) { | |
| overriddenSetters.add(overriddenMember); | |
| } else { | |
| overriddenGetters.add(overriddenMember); | |
| } | |
| } | |
| void inferFrom(List<ClassMember> members, {required bool forSetter}) { | |
| CombinedMemberSignatureBase combinedMemberSignature = | |
| new CombinedClassMemberSignature( | |
| membersBuilder, | |
| classBuilder, | |
| members, | |
| forSetter: forSetter, | |
| ); | |
| DartType? combinedMemberSignatureType = | |
| combinedMemberSignature.combinedMemberSignatureType; | |
| if (combinedMemberSignatureType == null) { | |
| inferredType = const InvalidType(); | |
| reportCantInferReturnType( | |
| classBuilder.libraryBuilder, | |
| members, | |
| name: name, | |
| fileUri: fileUri, | |
| nameOffset: nameOffset, | |
| nameLength: nameLength, | |
| ); | |
| } else { | |
| inferredType = combinedMemberSignatureType; | |
| } | |
| } | |
| if (overriddenGetters.isNotEmpty) { | |
| // 1) The return type of a getter, parameter type of a setter or type | |
| // of a field which overrides/implements only one or more getters is | |
| // inferred to be the return type of the combined member signature of | |
| // said getter in the direct superinterfaces. | |
| // 2) The return type of a getter which overrides/implements both a | |
| // setter and a getter is inferred to be the return type of the | |
| // combined member signature of said getter in the direct | |
| // superinterfaces. | |
| inferFrom(overriddenGetters, forSetter: false); | |
| } else { | |
| // The return type of a getter, parameter type of a setter or type of | |
| // a field which overrides/implements only one or more setters is | |
| // inferred to be the parameter type of the combined member signature | |
| // of said setter in the direct superinterfaces. | |
| inferFrom(overriddenSetters, forSetter: true); | |
| } | |
| declaredTypeBuilder.registerInferredType( | |
| inferredType ?? const DynamicType(), | |
| ); | |
| } | |
| } | |
| static void inferSetterType( | |
| ClassHierarchyBuilder hierarchyBuilder, | |
| ClassMembersBuilder membersBuilder, | |
| SourceClassBuilder classBuilder, | |
| List<FormalParameterBuilder>? formals, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| if (formals == null) { | |
| // Erroneous case. | |
| return; | |
| } | |
| // Only infer the parameter type if it's the first required positional. | |
| FormalParameterBuilder? parameter; | |
| for (FormalParameterBuilder formal in formals) { | |
| if (formal.isRequiredPositional) { | |
| parameter = formal; | |
| break; | |
| } | |
| } | |
| if (parameter == null) { | |
| // Erroneous case. | |
| return; | |
| } | |
| if (parameter.type is InferableTypeBuilder) { | |
| DartType? inferredType; | |
| overriddenMembers = toSet(classBuilder, overriddenMembers); | |
| List<ClassMember> overriddenGetters = []; | |
| List<ClassMember> overriddenSetters = []; | |
| for (ClassMember overriddenMember in overriddenMembers) { | |
| if (overriddenMember.forSetter) { | |
| overriddenSetters.add(overriddenMember); | |
| } else { | |
| overriddenGetters.add(overriddenMember); | |
| } | |
| } | |
| void inferFrom(List<ClassMember> members, {required bool forSetter}) { | |
| CombinedMemberSignatureBase combinedMemberSignature = | |
| new CombinedClassMemberSignature( | |
| membersBuilder, | |
| classBuilder, | |
| members, | |
| forSetter: forSetter, | |
| ); | |
| DartType? combinedMemberSignatureType = | |
| combinedMemberSignature.combinedMemberSignatureType; | |
| if (combinedMemberSignatureType == null) { | |
| inferredType = const InvalidType(); | |
| reportCantInferReturnType( | |
| classBuilder.libraryBuilder, | |
| members, | |
| name: name, | |
| fileUri: fileUri, | |
| nameOffset: nameOffset, | |
| nameLength: name.length, | |
| ); | |
| } else { | |
| inferredType = combinedMemberSignatureType; | |
| } | |
| } | |
| if (overriddenSetters.isNotEmpty) { | |
| // 1) The return type of a getter, parameter type of a setter or type | |
| // of a field which overrides/implements only one or more setters is | |
| // inferred to be the parameter type of the combined member signature | |
| // of said setter in the direct superinterfaces. | |
| // | |
| // 2) The parameter type of a setter which overrides/implements both a | |
| // setter and a getter is inferred to be the parameter type of the | |
| // combined member signature of said setter in the direct | |
| // superinterfaces. | |
| inferFrom(overriddenSetters, forSetter: true); | |
| } else { | |
| // The return type of a getter, parameter type of a setter or type of | |
| // a field which overrides/implements only one or more getters is | |
| // inferred to be the return type of the combined member signature of | |
| // said getter in the direct superinterfaces. | |
| inferFrom(overriddenGetters, forSetter: false); | |
| } | |
| parameter.type.registerInferredType(inferredType ?? const DynamicType()); | |
| } | |
| } | |
| /// Infers the field type of [fieldBuilder] based on [overriddenMembers]. | |
| static void inferFieldType( | |
| ClassHierarchyBuilder hierarchyBuilder, | |
| ClassMembersBuilder membersBuilder, | |
| SourceClassBuilder classBuilder, | |
| TypeBuilder declaredFieldType, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| required bool isAssignable, | |
| }) { | |
| if (declaredFieldType is InferableTypeBuilder) { | |
| DartType? inferredType; | |
| overriddenMembers = toSet(classBuilder, overriddenMembers); | |
| List<ClassMember> overriddenGetters = []; | |
| List<ClassMember> overriddenSetters = []; | |
| for (ClassMember overriddenMember in overriddenMembers) { | |
| if (overriddenMember.forSetter) { | |
| overriddenSetters.add(overriddenMember); | |
| } else { | |
| overriddenGetters.add(overriddenMember); | |
| } | |
| } | |
| DartType? inferFrom( | |
| List<ClassMember> members, { | |
| required bool forSetter, | |
| }) { | |
| CombinedMemberSignatureBase combinedMemberSignature = | |
| new CombinedClassMemberSignature( | |
| membersBuilder, | |
| classBuilder, | |
| members, | |
| forSetter: forSetter, | |
| ); | |
| return combinedMemberSignature.combinedMemberSignatureType; | |
| } | |
| DartType? combinedMemberSignatureType; | |
| if (isAssignable && | |
| overriddenGetters.isNotEmpty && | |
| overriddenSetters.isNotEmpty) { | |
| // The type of a non-final field which overrides/implements both a | |
| // setter and a getter is inferred to be the parameter type of the | |
| // combined member signature of said setter in the direct | |
| // superinterfaces, if this type is the same as the return type of the | |
| // combined member signature of said getter in the direct | |
| // superinterfaces. If the types are not the same then inference fails | |
| // with an error. | |
| DartType? getterType = inferFrom(overriddenGetters, forSetter: false); | |
| DartType? setterType = inferFrom(overriddenSetters, forSetter: true); | |
| if (getterType == setterType) { | |
| combinedMemberSignatureType = getterType; | |
| } | |
| } else if (overriddenGetters.isNotEmpty) { | |
| // 1) The return type of a getter, parameter type of a setter or type | |
| // of a field which overrides/implements only one or more getters is | |
| // inferred to be the return type of the combined member signature of | |
| // said getter in the direct superinterfaces. | |
| // | |
| // 2) The type of a final field which overrides/implements both a | |
| // setter and a getter is inferred to be the return type of the | |
| // combined member signature of said getter in the direct | |
| // superinterfaces. | |
| combinedMemberSignatureType = inferFrom( | |
| overriddenGetters, | |
| forSetter: false, | |
| ); | |
| } else { | |
| // The return type of a getter, parameter type of a setter or type of | |
| // a field which overrides/implements only one or more setters is | |
| // inferred to be the parameter type of the combined member signature | |
| // of said setter in the direct superinterfaces. | |
| combinedMemberSignatureType = inferFrom( | |
| overriddenSetters, | |
| forSetter: true, | |
| ); | |
| } | |
| if (combinedMemberSignatureType == null) { | |
| inferredType = const InvalidType(); | |
| reportCantInferFieldType( | |
| classBuilder.libraryBuilder, | |
| overriddenMembers, | |
| name: name, | |
| fileUri: fileUri, | |
| nameOffset: nameOffset, | |
| nameLength: nameLength, | |
| ); | |
| } else { | |
| inferredType = combinedMemberSignatureType; | |
| } | |
| declaredFieldType.registerInferredType(inferredType); | |
| } | |
| } | |
| /// Registers that the current class has an interface member without a | |
| /// corresponding class member. | |
| /// | |
| /// This is used to report missing implementation or, in the case the class | |
| /// has a user defined concrete noSuchMethod, to insert noSuchMethod | |
| /// forwarders. (Currently, insertion of forwarders is handled elsewhere.) | |
| /// | |
| /// For instance: | |
| /// | |
| /// abstract class Interface { | |
| /// method(); | |
| /// } | |
| /// class Class1 implements Interface { | |
| /// // Missing implementation for `Interface.method`. | |
| /// } | |
| /// class Class2 implements Interface { | |
| /// noSuchMethod(_) {} | |
| /// // A noSuchMethod forwarder is added for `Interface.method`. | |
| /// } | |
| /// | |
| void registerAbstractMember( | |
| List<ClassMember> abstractMembers, | |
| ClassMember abstractMember, | |
| ) { | |
| abstractMembers.add(abstractMember); | |
| } | |
| /// Set to `true` during [build] if the class needs interfaces, that is, if it | |
| /// has any members where the interface member is different from its | |
| /// corresponding class members. | |
| /// | |
| /// This is an optimization to avoid unnecessary computation of interface | |
| /// members. | |
| bool _hasInterfaces = false; | |
| ClassMembersNode build() { | |
| ClassMembersNode? supernode = _hierarchyNode.directSuperClassNode != null | |
| ? _membersBuilder.getNodeFromClassBuilder( | |
| _hierarchyNode.directSuperClassNode!.classBuilder, | |
| ) | |
| : null; | |
| List<ClassHierarchyNode>? interfaceNodes = | |
| _hierarchyNode.directInterfaceNodes; | |
| /// Concrete user defined `noSuchMethod` member, declared or inherited. I.e. | |
| /// concrete `noSuchMethod` class member that is _not_ | |
| /// `Object.noSuchMethod`. | |
| ClassMember? userNoSuchMethodMember; | |
| Map<Name, _Tuple> memberMap = {}; | |
| Iterator<MemberBuilder> iterator = classBuilder.filteredMembersIterator( | |
| includeDuplicates: false, | |
| ); | |
| while (iterator.moveNext()) { | |
| MemberBuilder memberBuilder = iterator.current; | |
| List<ClassMember> localMembers = memberBuilder.localMembers; | |
| for (int i = 0; i < localMembers.length; i++) { | |
| ClassMember classMember = localMembers[i]; | |
| Name name = classMember.name; | |
| if (classMember.isAbstract) { | |
| _hasInterfaces = true; | |
| } | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple == null) { | |
| memberMap[name] = new _Tuple.declareMember(classMember); | |
| } else { | |
| tuple.declaredMember = classMember; | |
| } | |
| if (name == noSuchMethodName && | |
| !classMember.isAbstract && | |
| !classMember.isObjectMember(objectClass)) { | |
| userNoSuchMethodMember = classMember; | |
| } | |
| } | |
| List<ClassMember> localSetters = memberBuilder.localSetters; | |
| for (int i = 0; i < localSetters.length; i++) { | |
| ClassMember classMember = localSetters[i]; | |
| Name name = classMember.name; | |
| if (classMember.isAbstract) { | |
| _hasInterfaces = true; | |
| } | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple == null) { | |
| memberMap[name] = new _Tuple.declareSetter(classMember); | |
| } else { | |
| tuple.declaredSetter = classMember; | |
| } | |
| } | |
| } | |
| if (classBuilder.isMixinApplication) { | |
| TypeBuilder mixedInTypeBuilder = classBuilder.mixedInTypeBuilder!; | |
| TypeDeclarationBuilder? mixin = mixedInTypeBuilder.declaration; | |
| while (mixin is ClassBuilder && mixin.isNamedMixinApplication) { | |
| mixedInTypeBuilder = mixin.mixedInTypeBuilder!; | |
| mixin = mixedInTypeBuilder.declaration; | |
| } | |
| mixin = mixedInTypeBuilder.computeUnaliasedDeclaration( | |
| isUsedAsClass: true, | |
| ); | |
| if (mixin is ClassBuilder) { | |
| Iterator<MemberBuilder> iterator = mixin.filteredMembersIterator( | |
| includeDuplicates: false, | |
| ); | |
| while (iterator.moveNext()) { | |
| MemberBuilder memberBuilder = iterator.current; | |
| if (memberBuilder.isStatic) { | |
| continue; | |
| } | |
| List<ClassMember> localMembers = memberBuilder.localMembers; | |
| for (int i = 0; i < localMembers.length; i++) { | |
| ClassMember classMember = localMembers[i]; | |
| Name name = classMember.name; | |
| if (classMember.isAbstract || classMember.isNoSuchMethodForwarder) { | |
| _hasInterfaces = true; | |
| } | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple == null) { | |
| memberMap[name] = new _Tuple.mixInMember(classMember); | |
| } else { | |
| tuple.mixedInMember = classMember; | |
| } | |
| if (name == noSuchMethodName && | |
| !classMember.isAbstract && | |
| !classMember.isObjectMember(objectClass)) { | |
| userNoSuchMethodMember ??= classMember; | |
| } | |
| } | |
| List<ClassMember> localSetters = memberBuilder.localSetters; | |
| for (int i = 0; i < localSetters.length; i++) { | |
| ClassMember classMember = localSetters[i]; | |
| Name name = classMember.name; | |
| if (classMember.isAbstract || classMember.isNoSuchMethodForwarder) { | |
| _hasInterfaces = true; | |
| } | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple == null) { | |
| memberMap[name] = new _Tuple.mixInSetter(classMember); | |
| } else { | |
| tuple.mixedInSetter = classMember; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void extend(Map<Name, ClassMember>? superClassMembers) { | |
| if (superClassMembers == null) return; | |
| for (MapEntry<Name, ClassMember> entry in superClassMembers.entries) { | |
| Name name = entry.key; | |
| ClassMember superClassMember = entry.value; | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple != null) { | |
| if (superClassMember.forSetter) { | |
| tuple.extendedSetter = superClassMember; | |
| } else { | |
| tuple.extendedMember = superClassMember; | |
| } | |
| } else { | |
| if (superClassMember.forSetter) { | |
| memberMap[name] = new _Tuple.extendSetter(superClassMember); | |
| } else { | |
| memberMap[name] = new _Tuple.extendMember(superClassMember); | |
| } | |
| } | |
| } | |
| } | |
| void implement(Map<Name, ClassMember>? superInterfaceMembers) { | |
| if (superInterfaceMembers == null) return; | |
| for (MapEntry<Name, ClassMember> entry in superInterfaceMembers.entries) { | |
| Name name = entry.key; | |
| ClassMember superInterfaceMember = entry.value; | |
| _Tuple? tuple = memberMap[name]; | |
| if (tuple != null) { | |
| if (superInterfaceMember.forSetter) { | |
| tuple.addImplementedSetter(superInterfaceMember); | |
| } else { | |
| tuple.addImplementedMember(superInterfaceMember); | |
| } | |
| } else { | |
| if (superInterfaceMember.forSetter) { | |
| memberMap[superInterfaceMember.name] = new _Tuple.implementSetter( | |
| superInterfaceMember, | |
| ); | |
| } else { | |
| memberMap[superInterfaceMember.name] = new _Tuple.implementMember( | |
| superInterfaceMember, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| if (supernode == null) { | |
| // This should be Object. | |
| } else { | |
| userNoSuchMethodMember ??= supernode.userNoSuchMethodMember; | |
| extend(supernode.classMemberMap); | |
| extend(supernode.classSetterMap); | |
| if (supernode.interfaceMemberMap != null || | |
| supernode.interfaceSetterMap != null) { | |
| _hasInterfaces = true; | |
| } | |
| if (_hasInterfaces) { | |
| implement(supernode.interfaceMemberMap ?? supernode.classMemberMap); | |
| implement(supernode.interfaceSetterMap ?? supernode.classSetterMap); | |
| } | |
| if (interfaceNodes != null) { | |
| for (int i = 0; i < interfaceNodes.length; i++) { | |
| ClassMembersNode? interfaceNode = _membersBuilder | |
| .getNodeFromClassBuilder(interfaceNodes[i].classBuilder); | |
| _hasInterfaces = true; | |
| implement( | |
| interfaceNode.interfaceMemberMap ?? interfaceNode.classMemberMap, | |
| ); | |
| implement( | |
| interfaceNode.interfaceSetterMap ?? interfaceNode.classSetterMap, | |
| ); | |
| } | |
| } | |
| } | |
| /// Members (excluding setters) declared in [cls] or its superclasses. This | |
| /// includes static methods of [cls], but not its superclasses. | |
| Map<Name, ClassMember> classMemberMap = {}; | |
| /// Setters declared in [cls] or its superclasses. This includes static | |
| /// setters of [cls], but not its superclasses. | |
| Map<Name, ClassMember> classSetterMap = {}; | |
| /// Members (excluding setters) inherited from interfaces. This contains no | |
| /// static members. If no interfaces are implemented by this class or its | |
| /// superclasses this is identical to [classMemberMap] and we do not store | |
| /// it in the [ClassHierarchyNode]. | |
| Map<Name, ClassMember>? interfaceMemberMap = {}; | |
| /// Setters inherited from interfaces. This contains no static setters. If | |
| /// no interfaces are implemented by this class or its superclasses this is | |
| /// identical to [classSetterMap] and we do not store it in the | |
| /// [ClassHierarchyNode]. | |
| Map<Name, ClassMember>? interfaceSetterMap = {}; | |
| /// Map for members declared in this class to the members that they | |
| /// override. This is used for checking valid overrides and to ensure that | |
| /// override inference correctly propagates inferred types through the | |
| /// class hierarchy. | |
| Map<ClassMember, Set<ClassMember>> declaredOverridesMap = {}; | |
| /// In case this class is a mixin application, this maps members declared in | |
| /// the mixin to the members that they override. This is used for checking | |
| /// valid overrides but _not_ as for [declaredOverridesMap] for override | |
| /// inference. | |
| Map<ClassMember, Set<ClassMember>> mixinApplicationOverridesMap = {}; | |
| /// In case this class is concrete, this maps concrete members that are | |
| /// inherited into this class to the members they should override to validly | |
| /// implement the interface of this class. | |
| Map<ClassMember, Set<ClassMember>> inheritedImplementsMap = {}; | |
| /// In case this class is concrete, this holds the interface members | |
| /// without a corresponding class member. These are either reported as | |
| /// missing implementations or trigger insertion of noSuchMethod forwarders. | |
| List<ClassMember>? abstractMembers = []; | |
| ClassMember? noSuchMethodMember; | |
| ClassHierarchyNodeDataForTesting? dataForTesting; | |
| if (retainDataForTesting) { | |
| // Coverage-ignore-block(suite): Not run. | |
| dataForTesting = new ClassHierarchyNodeDataForTesting( | |
| abstractMembers, | |
| declaredOverridesMap, | |
| mixinApplicationOverridesMap, | |
| inheritedImplementsMap, | |
| ); | |
| } | |
| void computeClassInterfaceMember(Name name, _Tuple tuple) { | |
| /// The computation starts by sanitizing the members. Conflicts between | |
| /// methods and properties (getters/setters) or between static and | |
| /// instance members are reported. Conflicting members and members | |
| /// overridden by duplicates are removed. | |
| /// | |
| /// Conflicts between the getable and setable are reported afterwards. | |
| var (_SanitizedMember? getable, _SanitizedMember? setable) = tuple | |
| .sanitize(this); | |
| _Overrides overrides = new _Overrides( | |
| classBuilder: classBuilder, | |
| inheritedImplementsMap: inheritedImplementsMap, | |
| dataForTesting: dataForTesting, | |
| ); | |
| ClassMember? interfaceGetable; | |
| if (getable != null) { | |
| interfaceGetable = getable.computeMembers( | |
| this, | |
| overrides, | |
| noSuchMethodMember: noSuchMethodMember, | |
| userNoSuchMethodMember: userNoSuchMethodMember, | |
| abstractMembers: abstractMembers, | |
| classMemberMap: classMemberMap, | |
| interfaceMemberMap: interfaceMemberMap, | |
| dataForTesting: dataForTesting, | |
| ); | |
| } | |
| ClassMember? interfaceSetable; | |
| if (setable != null) { | |
| interfaceSetable = setable.computeMembers( | |
| this, | |
| overrides, | |
| noSuchMethodMember: noSuchMethodMember, | |
| userNoSuchMethodMember: userNoSuchMethodMember, | |
| abstractMembers: abstractMembers, | |
| classMemberMap: classSetterMap, | |
| interfaceMemberMap: interfaceSetterMap, | |
| dataForTesting: dataForTesting, | |
| ); | |
| } | |
| if (classBuilder is SourceClassBuilder) { | |
| if (interfaceGetable != null && | |
| interfaceSetable != null && | |
| interfaceGetable.isProperty && | |
| interfaceSetable.isProperty && | |
| interfaceGetable.isStatic == interfaceSetable.isStatic && | |
| !interfaceGetable.isSameDeclaration(interfaceSetable)) { | |
| /// We need to check that the getter type is a subtype of the setter | |
| /// type. For instance | |
| /// | |
| /// class Super { | |
| /// int get property1 => null; | |
| /// num get property2 => null; | |
| /// } | |
| /// class Mixin { | |
| /// void set property1(num value) {} | |
| /// void set property2(int value) {} | |
| /// } | |
| /// class Class = Super with Mixin; | |
| /// | |
| /// Here `Super.property1` and `Mixin.property1` form a valid getter/ | |
| /// setter pair in `Class` because the type of the getter | |
| /// `Super.property1` is a subtype of the setter `Mixin.property1`. | |
| /// | |
| /// In contrast the pair `Super.property2` and `Mixin.property2` is | |
| /// not a valid getter/setter in `Class` because the type of the getter | |
| /// `Super.property2` is _not_ a subtype of the setter | |
| /// `Mixin.property1`. | |
| SourceClassBuilder sourceClassBuilder = | |
| classBuilder as SourceClassBuilder; | |
| if (!sourceClassBuilder | |
| .libraryBuilder | |
| .libraryFeatures | |
| .getterSetterError | |
| .isEnabled) { | |
| _membersBuilder.registerGetterSetterCheck( | |
| new DelayedClassGetterSetterCheck( | |
| sourceClassBuilder, | |
| name, | |
| interfaceGetable, | |
| interfaceSetable, | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| overrides.collectOverrides( | |
| getable: getable, | |
| setable: setable, | |
| mixinApplicationOverridesMap: mixinApplicationOverridesMap, | |
| declaredOverridesMap: declaredOverridesMap, | |
| ); | |
| } | |
| // Compute the 'noSuchMethod' member first so we know the target for | |
| // noSuchMethod forwarders. | |
| _Tuple? noSuchMethod = memberMap.remove(noSuchMethodName); | |
| if (noSuchMethod != null) { | |
| // The noSuchMethod is always available - unless Object is not valid. | |
| // See for instance pkg/front_end/test/base/object_supertype_test.dart | |
| computeClassInterfaceMember(noSuchMethodName, noSuchMethod); | |
| } | |
| noSuchMethodMember = | |
| interfaceMemberMap[noSuchMethodName] ?? | |
| // Coverage-ignore(suite): Not run. | |
| classMemberMap[noSuchMethodName]; | |
| memberMap.forEach(computeClassInterfaceMember); | |
| if (classBuilder is SourceClassBuilder) { | |
| // TODO(johnniwinther): Avoid duplicate override check computations | |
| // between [declaredOverridesMap], [mixinApplicationOverridesMap] and | |
| // [inheritedImplementsMap]. | |
| // TODO(johnniwinther): Ensure that a class member is only checked to | |
| // validly override another member once. Currently it can happen multiple | |
| // times as an inherited implementation. | |
| declaredOverridesMap.forEach(( | |
| ClassMember classMember, | |
| Set<ClassMember> overriddenMembers, | |
| ) { | |
| /// A declared member can inherit its type from the overridden members. | |
| /// | |
| /// We register this with the class member itself so the it can force | |
| /// computation of type on the overridden members before determining its | |
| /// own type. | |
| /// | |
| /// Member types can be queried at arbitrary points during top level | |
| /// inference so we need to ensure that types are computed in dependency | |
| /// order. | |
| classMember.registerOverrideDependency( | |
| _membersBuilder, | |
| overriddenMembers, | |
| ); | |
| /// Not all member type are queried during top level inference so we | |
| /// register delayed computation to ensure that all types have been | |
| /// computed before override checks are performed. | |
| DelayedTypeComputation computation = new DelayedTypeComputation( | |
| this, | |
| classMember, | |
| overriddenMembers, | |
| ); | |
| _membersBuilder.registerDelayedTypeComputation(computation); | |
| /// Declared members must be checked to validly override the | |
| /// overridden members. | |
| /// In case error is found, the corresponding local member, either | |
| /// synthesized or declared, is marked erroneous. | |
| ClassMember? localMember = classMember.forSetter | |
| ? (interfaceSetterMap?[classMember.name]) | |
| : (interfaceMemberMap?[classMember.name]); | |
| if (localMember?.declarationBuilder != classBuilder) { | |
| localMember = null; | |
| } | |
| _membersBuilder.registerOverrideCheck( | |
| classBuilder as SourceClassBuilder, | |
| classMember, | |
| overriddenMembers, | |
| localMember: localMember, | |
| ); | |
| }); | |
| mixinApplicationOverridesMap.forEach(( | |
| ClassMember classMember, | |
| Set<ClassMember> overriddenMembers, | |
| ) { | |
| /// Declared mixed in members must be checked to validly override the | |
| /// overridden members. | |
| /// In case error is found, the corresponding local member, either | |
| /// synthesized or declared, is marked erroneous. | |
| ClassMember? localMember = classMember.forSetter | |
| ? (interfaceSetterMap?[classMember.name]) | |
| : (interfaceMemberMap?[classMember.name]); | |
| if (localMember?.declarationBuilder != classBuilder) { | |
| localMember = null; | |
| } | |
| _membersBuilder.registerOverrideCheck( | |
| classBuilder as SourceClassBuilder, | |
| classMember, | |
| overriddenMembers, | |
| localMember: localMember, | |
| ); | |
| }); | |
| inheritedImplementsMap.forEach(( | |
| ClassMember classMember, | |
| Set<ClassMember> overriddenMembers, | |
| ) { | |
| /// Concrete members must be checked to validly override the overridden | |
| /// members in concrete classes. | |
| /// In case error is found, the corresponding local member, either | |
| /// synthesized or declared, is marked erroneous. | |
| ClassMember? localMember = classMember.forSetter | |
| ? (interfaceSetterMap?[classMember.name]) | |
| : (interfaceMemberMap?[classMember.name]); | |
| if (localMember?.declarationBuilder != classBuilder) { | |
| localMember = null; | |
| } | |
| _membersBuilder.registerOverrideCheck( | |
| classBuilder as SourceClassBuilder, | |
| classMember, | |
| overriddenMembers, | |
| localMember: localMember, | |
| ); | |
| }); | |
| } | |
| if (!_hasInterfaces) { | |
| /// All interface members also class members to we don't need to store | |
| /// the interface members separately. | |
| assert( | |
| classMemberMap.length == interfaceMemberMap.length, | |
| "Class/interface member mismatch. Class members: " | |
| "$classMemberMap, interface members: $interfaceMemberMap.", | |
| ); | |
| assert( | |
| classSetterMap.length == interfaceSetterMap.length, | |
| "Class/interface setter mismatch. Class setters: " | |
| "$classSetterMap, interface setters: $interfaceSetterMap.", | |
| ); | |
| assert( | |
| classMemberMap.keys.every( | |
| (Name name) => | |
| identical(classMemberMap[name], interfaceMemberMap?[name]), | |
| ), | |
| "Class/interface member mismatch. Class members: " | |
| "$classMemberMap, interface members: $interfaceMemberMap.", | |
| ); | |
| assert( | |
| classSetterMap.keys.every( | |
| (Name name) => | |
| identical(classSetterMap[name], interfaceSetterMap?[name]), | |
| ), | |
| "Class/interface setter mismatch. Class setters: " | |
| "$classSetterMap, interface setters: $interfaceSetterMap.", | |
| ); | |
| interfaceMemberMap = null; | |
| interfaceSetterMap = null; | |
| } | |
| reportMissingMembers(abstractMembers); | |
| return new ClassMembersNode( | |
| classBuilder, | |
| supernode, | |
| classMemberMap, | |
| classSetterMap, | |
| interfaceMemberMap, | |
| interfaceSetterMap, | |
| userNoSuchMethodMember, | |
| dataForTesting, | |
| ); | |
| } | |
| void reportMissingMembers(List<ClassMember> abstractMembers) { | |
| if (abstractMembers.isEmpty) return; | |
| Map<String, LocatedMessage> contextMap = <String, LocatedMessage>{}; | |
| for (ClassMember declaration in unfoldDeclarations(abstractMembers)) { | |
| if (classBuilder.isEnum && | |
| declaration.declarationBuilder == classBuilder) { | |
| classBuilder.libraryBuilder.addProblem2( | |
| diag.enumAbstractMember, | |
| declaration.uriOffset, | |
| ); | |
| } else { | |
| String name = declaration.fullNameForErrors; | |
| String className = declaration.declarationBuilder.fullNameForErrors; | |
| String displayName = declaration.isSetter | |
| ? "$className.$name=" | |
| : "$className.$name"; | |
| contextMap[displayName] = diag.missingImplementationCause | |
| .withArguments(name: displayName) | |
| .withLocation2(declaration.uriOffset); | |
| } | |
| } | |
| if (contextMap.isEmpty) return; | |
| List<String> names = new List<String>.of(contextMap.keys)..sort(); | |
| List<LocatedMessage> context = <LocatedMessage>[]; | |
| for (int i = 0; i < names.length; i++) { | |
| context.add(contextMap[names[i]]!); | |
| } | |
| classBuilder.libraryBuilder.addProblem( | |
| diag.missingImplementationNotAbstract.withArguments( | |
| className: classBuilder.fullNameForErrors, | |
| memberNames: names, | |
| ), | |
| classBuilder.fileOffset, | |
| classBuilder.fullNameForErrors.length, | |
| classBuilder.fileUri, | |
| context: context, | |
| ); | |
| } | |
| } | |
| class ClassMembersNode( | |
| final ClassBuilder classBuilder, | |
| final ClassMembersNode? supernode, | |
| /// All the members of this class including [classMembers] of its | |
| /// superclasses. | |
| final Map<Name, ClassMember> classMemberMap, | |
| /// Similar to [classMembers] but for setters. | |
| final Map<Name, ClassMember> classSetterMap, | |
| /// All the interface members of this class including [interfaceMembers] of | |
| /// its supertypes. | |
| /// | |
| /// In addition to the members of [classMembers] this also contains members | |
| /// from interfaces. | |
| /// | |
| /// This may be null, in which case [classMembers] is the interface members. | |
| final Map<Name, ClassMember>? interfaceMemberMap, | |
| /// Similar to [interfaceMembers] but for setters. | |
| /// | |
| /// This may be null, in which case [classSetters] is the interface setters. | |
| final Map<Name, ClassMember>? interfaceSetterMap, | |
| /// The user defined noSuchMethod, i.e. not Object.noSuchMethod, if declared | |
| /// or inherited. | |
| final ClassMember? userNoSuchMethodMember, | |
| final ClassHierarchyNodeDataForTesting? dataForTesting, | |
| ) { | |
| @override | |
| String toString() { | |
| StringBuffer sb = new StringBuffer(); | |
| sb | |
| ..write(classBuilder.fullNameForErrors) | |
| ..writeln(":"); | |
| printMemberMap(classMemberMap, sb, "classMembers"); | |
| printMemberMap(classSetterMap, sb, "classSetters"); | |
| if (interfaceMemberMap != null) { | |
| printMemberMap(interfaceMemberMap!, sb, "interfaceMembers"); | |
| } | |
| if (interfaceSetterMap != null) { | |
| printMemberMap(interfaceSetterMap!, sb, "interfaceSetters"); | |
| } | |
| return "$sb"; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| void printMembers( | |
| List<ClassMember> members, | |
| StringBuffer sb, | |
| String heading, | |
| ) { | |
| sb.write(" "); | |
| sb.write(heading); | |
| sb.writeln(":"); | |
| for (ClassMember member in members) { | |
| sb | |
| ..write(" ") | |
| ..write(member.declarationBuilder.fullNameForErrors) | |
| ..write(".") | |
| ..write(member.fullNameForErrors) | |
| ..writeln(); | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| void printMemberMap( | |
| Map<Name, ClassMember> memberMap, | |
| StringBuffer sb, | |
| String heading, | |
| ) { | |
| List<ClassMember> members = memberMap.values.toList(); | |
| members.sort((ClassMember a, ClassMember b) { | |
| if (a == b) return 0; | |
| return ClassHierarchy.compareNames(a.name, b.name); | |
| }); | |
| printMembers(members, sb, heading); | |
| } | |
| ClassMember? getInterfaceMember(Name name, bool isSetter) { | |
| ClassMember? result = isSetter | |
| ? (interfaceSetterMap ?? classSetterMap)[name] | |
| : (interfaceMemberMap ?? classMemberMap)[name]; | |
| if (result == null) { | |
| return null; | |
| } | |
| if (result.isStatic) { | |
| return null; | |
| } | |
| return result; | |
| } | |
| ClassMember? getDispatchTarget(Name name, bool isSetter) { | |
| ClassMember? result = isSetter | |
| ? | |
| // Coverage-ignore(suite): Not run. | |
| classSetterMap[name] | |
| : classMemberMap[name]; | |
| if (result == null) { | |
| return null; | |
| } | |
| if (result.isStatic) { | |
| // TODO(johnniwinther): Can we avoid putting static members in the | |
| // [classMemberMap]/[classSetterMap] maps? | |
| return supernode?.getDispatchTarget(name, isSetter); | |
| } | |
| return result; | |
| } | |
| ClassMember? getStaticMember(Name name, bool isSetter) { | |
| ClassMember? result = isSetter | |
| ? | |
| // Coverage-ignore(suite): Not run. | |
| classSetterMap[name] | |
| : classMemberMap[name]; | |
| if (result == null) { | |
| return null; | |
| } else if (result.isStatic) { | |
| return result; | |
| } | |
| return null; | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class ClassHierarchyNodeDataForTesting( | |
| final List<ClassMember> abstractMembers, | |
| final Map<ClassMember, Set<ClassMember>> declaredOverrides, | |
| final Map<ClassMember, Set<ClassMember>> mixinApplicationOverrides, | |
| final Map<ClassMember, Set<ClassMember>> inheritedImplements, | |
| ) { | |
| final Map<ClassMember, ClassMember> aliasMap = {}; | |
| } | |
| class _Tuple { | |
| final Name name; | |
| ClassMember? _declaredGetable; | |
| ClassMember? _declaredSetable; | |
| ClassMember? _mixedInGetable; | |
| ClassMember? _mixedInSetable; | |
| ClassMember? _extendedGetable; | |
| ClassMember? _extendedSetable; | |
| List<ClassMember>? _implementedGetables; | |
| List<ClassMember>? _implementedSetables; | |
| new declareMember(ClassMember declaredMember) | |
| : assert(!declaredMember.forSetter), | |
| this._declaredGetable = declaredMember, | |
| this.name = declaredMember.name; | |
| new mixInMember(ClassMember mixedInMember) | |
| : assert(!mixedInMember.forSetter), | |
| this._mixedInGetable = mixedInMember, | |
| this.name = mixedInMember.name; | |
| new extendMember(ClassMember extendedMember) | |
| : assert(!extendedMember.forSetter), | |
| this._extendedGetable = extendedMember, | |
| this.name = extendedMember.name; | |
| new implementMember(ClassMember implementedMember) | |
| : assert(!implementedMember.forSetter), | |
| this.name = implementedMember.name, | |
| _implementedGetables = <ClassMember>[implementedMember]; | |
| new declareSetter(ClassMember declaredSetter) | |
| : assert(declaredSetter.forSetter), | |
| this._declaredSetable = declaredSetter, | |
| this.name = declaredSetter.name; | |
| new mixInSetter(ClassMember mixedInSetter) | |
| : assert(mixedInSetter.forSetter), | |
| this._mixedInSetable = mixedInSetter, | |
| this.name = mixedInSetter.name; | |
| new extendSetter(ClassMember extendedSetter) | |
| : assert(extendedSetter.forSetter), | |
| this._extendedSetable = extendedSetter, | |
| this.name = extendedSetter.name; | |
| new implementSetter(ClassMember implementedSetter) | |
| : assert(implementedSetter.forSetter), | |
| this.name = implementedSetter.name, | |
| _implementedSetables = <ClassMember>[implementedSetter]; | |
| ClassMember? get declaredMember => _declaredGetable; | |
| void set declaredMember(ClassMember? value) { | |
| assert(!value!.forSetter); | |
| assert( | |
| _declaredGetable == null, | |
| "Declared member already set to $_declaredGetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _declaredGetable = value; | |
| } | |
| ClassMember? get declaredSetter => _declaredSetable; | |
| void set declaredSetter(ClassMember? value) { | |
| assert(value!.forSetter); | |
| assert( | |
| _declaredSetable == null, | |
| "Declared setter already set to $_declaredSetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _declaredSetable = value; | |
| } | |
| ClassMember? get extendedMember => _extendedGetable; | |
| void set extendedMember(ClassMember? value) { | |
| assert(!value!.forSetter); | |
| assert( | |
| _extendedGetable == null, | |
| "Extended member already set to $_extendedGetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _extendedGetable = value; | |
| } | |
| ClassMember? get extendedSetter => _extendedSetable; | |
| void set extendedSetter(ClassMember? value) { | |
| assert(value!.forSetter); | |
| assert( | |
| _extendedSetable == null, | |
| "Extended setter already set to $_extendedSetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _extendedSetable = value; | |
| } | |
| ClassMember? get mixedInMember => _mixedInGetable; | |
| void set mixedInMember(ClassMember? value) { | |
| assert(!value!.forSetter); | |
| assert( | |
| _mixedInGetable == null, | |
| "Mixed in member already set to $_mixedInGetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _mixedInGetable = value; | |
| } | |
| ClassMember? get mixedInSetter => _mixedInSetable; | |
| void set mixedInSetter(ClassMember? value) { | |
| assert(value!.forSetter); | |
| assert( | |
| _mixedInSetable == null, | |
| "Mixed in setter already set to $_mixedInSetable, " | |
| "trying to set it to $value.", | |
| ); | |
| _mixedInSetable = value; | |
| } | |
| List<ClassMember>? get implementedMembers => _implementedGetables; | |
| void addImplementedMember(ClassMember value) { | |
| assert(!value.forSetter); | |
| _implementedGetables ??= <ClassMember>[]; | |
| _implementedGetables!.add(value); | |
| } | |
| List<ClassMember>? get implementedSetters => _implementedSetables; | |
| void addImplementedSetter(ClassMember value) { | |
| assert(value.forSetter); | |
| _implementedSetables ??= <ClassMember>[]; | |
| _implementedSetables!.add(value); | |
| } | |
| @override | |
| String toString() { | |
| StringBuffer sb = new StringBuffer(); | |
| String comma = ''; | |
| sb.write('Tuple('); | |
| if (_declaredGetable != null) { | |
| sb.write(comma); | |
| sb.write('declaredMember='); | |
| sb.write(_declaredGetable); | |
| comma = ','; | |
| } | |
| if (_declaredSetable != null) { | |
| sb.write(comma); | |
| sb.write('declaredSetter='); | |
| sb.write(_declaredSetable); | |
| comma = ','; | |
| } | |
| if (_mixedInGetable != null) { | |
| sb.write(comma); | |
| sb.write('mixedInMember='); | |
| sb.write(_mixedInGetable); | |
| comma = ','; | |
| } | |
| if (_mixedInSetable != null) { | |
| sb.write(comma); | |
| sb.write('mixedInSetter='); | |
| sb.write(_mixedInSetable); | |
| comma = ','; | |
| } | |
| if (_extendedGetable != null) { | |
| sb.write(comma); | |
| sb.write('extendedMember='); | |
| sb.write(_extendedGetable); | |
| comma = ','; | |
| } | |
| if (_extendedSetable != null) { | |
| sb.write(comma); | |
| sb.write('extendedSetter='); | |
| sb.write(_extendedSetable); | |
| comma = ','; | |
| } | |
| if (_implementedGetables != null) { | |
| sb.write(comma); | |
| sb.write('implementedMembers='); | |
| sb.write(_implementedGetables); | |
| comma = ','; | |
| } | |
| if (_implementedSetables != null) { | |
| sb.write(comma); | |
| sb.write('implementedSetters='); | |
| sb.write(_implementedSetables); | |
| comma = ','; | |
| } | |
| sb.write(')'); | |
| return sb.toString(); | |
| } | |
| /// Sanitizing the members of this tuple. | |
| /// | |
| /// Conflicts between methods and properties (getters/setters) or between | |
| /// static and instance members are reported. Conflicting members and members | |
| /// overridden by duplicates are removed. | |
| /// | |
| /// For this [definingGetable] and [definingSetable] hold the first member | |
| /// of its kind found among declared, mixed in, extended and implemented | |
| /// members. | |
| /// | |
| /// Conflicts between [definingGetable] and [definingSetable] are reported | |
| /// afterwards. | |
| (_SanitizedMember?, _SanitizedMember?) sanitize( | |
| ClassMembersNodeBuilder builder, | |
| ) { | |
| ClassMember? definingGetable; | |
| ClassMember? definingSetable; | |
| ClassMember? declaredGetable = this.declaredMember; | |
| if (declaredGetable != null) { | |
| /// class Class { | |
| /// method() {} | |
| /// } | |
| definingGetable = declaredGetable; | |
| } | |
| ClassMember? declaredSetable = this.declaredSetter; | |
| if (declaredSetable != null) { | |
| /// class Class { | |
| /// set setter(value) {} | |
| /// } | |
| definingSetable = declaredSetable; | |
| } | |
| ClassMember? mixedInGetable; | |
| ClassMember? tupleMixedInMember = this.mixedInMember; | |
| if (tupleMixedInMember != null && | |
| !tupleMixedInMember.isStatic && | |
| !tupleMixedInMember.isDuplicate && | |
| !tupleMixedInMember.isSynthesized) { | |
| /// We treat | |
| /// | |
| /// opt-in: | |
| /// class Interface { | |
| /// method3() {} | |
| /// } | |
| /// opt-out: | |
| /// class Mixin implements Interface { | |
| /// static method1() {} | |
| /// method2() {} | |
| /// method2() {} | |
| /// /*member-signature*/ method3() {} | |
| /// } | |
| /// class Class with Mixin {} | |
| /// | |
| /// as | |
| /// | |
| /// class Mixin {} | |
| /// class Class with Mixin {} | |
| /// | |
| /// Note that skipped synthetic getable 'method3' is still included | |
| /// in the implemented getables, but its type will not define the type | |
| /// when mixed in. For instance | |
| /// | |
| /// opt-in: | |
| /// abstract class Interface { | |
| /// num get getter; | |
| /// } | |
| /// opt-out: | |
| /// abstract class Super { | |
| /// int get getter; | |
| /// } | |
| /// abstract class Mixin implements Interface { | |
| /// /*member-signature*/ num get getter; | |
| /// } | |
| /// abstract class Class extends Super with Mixin {} | |
| /// | |
| /// Here the type of `Class.getter` should not be defined from the | |
| /// synthetic member signature `Mixin.getter` but as a combined member | |
| /// signature of `Super.getter` and `Mixin.getter`, resulting in type | |
| /// `int` instead of `num`. | |
| if (definingGetable == null) { | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class with Mixin {} | |
| definingGetable = mixedInGetable = tupleMixedInMember; | |
| } else if (!definingGetable.isDuplicate) { | |
| // This case is currently unreachable from source code since classes | |
| // cannot both declare and mix in members. From dill, this can occur | |
| // but should not conflicting members. | |
| // | |
| // The case is handled for consistency. | |
| if (definingGetable.isStatic || | |
| definingGetable.isProperty != tupleMixedInMember.isProperty) { | |
| // Coverage-ignore-block(suite): Not run. | |
| builder.reportInheritanceConflict( | |
| definingGetable, | |
| tupleMixedInMember, | |
| ); | |
| } else { | |
| mixedInGetable = tupleMixedInMember; | |
| } | |
| } | |
| } | |
| ClassMember? mixedInSetable; | |
| ClassMember? tupleMixedInSetter = this.mixedInSetter; | |
| if (tupleMixedInSetter != null && | |
| !tupleMixedInSetter.isStatic && | |
| !tupleMixedInSetter.isDuplicate && | |
| !tupleMixedInSetter.isSynthesized) { | |
| /// We treat | |
| /// | |
| /// class Mixin { | |
| /// static set setter1(value) {} | |
| /// set setter2(value) {} | |
| /// set setter2(value) {} | |
| /// /*member-signature*/ setter3() {} | |
| /// } | |
| /// class Class with Mixin {} | |
| /// | |
| /// as | |
| /// | |
| /// class Mixin {} | |
| /// class Class with Mixin {} | |
| /// | |
| /// Note that skipped synthetic setable 'setter3' is still included | |
| /// in the implemented setables, but its type will not define the type | |
| /// when mixed in. For instance | |
| /// | |
| /// opt-in: | |
| /// abstract class Interface { | |
| /// void set setter(int value); | |
| /// } | |
| /// opt-out: | |
| /// abstract class Super { | |
| /// void set setter(num value); | |
| /// } | |
| /// abstract class Mixin implements Interface { | |
| /// /*member-signature*/ num get getter; | |
| /// } | |
| /// abstract class Class extends Super with Mixin {} | |
| /// | |
| /// Here the type of `Class.setter` should not be defined from the | |
| /// synthetic member signature `Mixin.setter` but as a combined member | |
| /// signature of `Super.setter` and `Mixin.setter`, resulting in type | |
| /// `num` instead of `int`. | |
| if (definingSetable == null) { | |
| /// class Mixin { | |
| /// set setter(value) {} | |
| /// } | |
| /// class Class with Mixin {} | |
| definingSetable = mixedInSetable = tupleMixedInSetter; | |
| } else if (!definingSetable.isDuplicate) { | |
| if (definingSetable.isStatic || | |
| definingSetable.isProperty != tupleMixedInSetter.isProperty) { | |
| // Coverage-ignore-block(suite): Not run. | |
| builder.reportInheritanceConflict( | |
| definingSetable, | |
| tupleMixedInSetter, | |
| ); | |
| } else { | |
| mixedInSetable = tupleMixedInSetter; | |
| } | |
| } | |
| } | |
| ClassMember? extendedGetable; | |
| ClassMember? tupleExtendedMember = this.extendedMember; | |
| if (tupleExtendedMember != null && | |
| !tupleExtendedMember.isStatic && | |
| !tupleExtendedMember.isDuplicate) { | |
| /// We treat | |
| /// | |
| /// class Super { | |
| /// static method1() {} | |
| /// method2() {} | |
| /// method2() {} | |
| /// } | |
| /// class Class extends Super {} | |
| /// | |
| /// as | |
| /// | |
| /// class Super {} | |
| /// class Class extends Super {} | |
| /// | |
| if (definingGetable == null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super {} | |
| definingGetable = extendedGetable = tupleExtendedMember; | |
| } else if (!definingGetable.isDuplicate) { | |
| if (definingGetable.isStatic || | |
| definingGetable.isProperty != tupleExtendedMember.isProperty) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// static method() {} | |
| /// } | |
| /// | |
| /// or | |
| /// | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// get method => 0; | |
| /// } | |
| builder.reportInheritanceConflict( | |
| definingGetable, | |
| tupleExtendedMember, | |
| ); | |
| } else { | |
| extendedGetable = tupleExtendedMember; | |
| } | |
| } | |
| } | |
| ClassMember? extendedSetable; | |
| ClassMember? tupleExtendedSetter = this.extendedSetter; | |
| if (tupleExtendedSetter != null && | |
| !tupleExtendedSetter.isStatic && | |
| !tupleExtendedSetter.isDuplicate) { | |
| /// We treat | |
| /// | |
| /// class Super { | |
| /// static set setter1(value) {} | |
| /// set setter2(value) {} | |
| /// set setter2(value) {} | |
| /// } | |
| /// class Class extends Super {} | |
| /// | |
| /// as | |
| /// | |
| /// class Super {} | |
| /// class Class extends Super {} | |
| /// | |
| if (definingSetable == null) { | |
| /// class Super { | |
| /// set setter(value) {} | |
| /// } | |
| /// class Class extends Super {} | |
| definingSetable = extendedSetable = tupleExtendedSetter; | |
| } else if (!definingSetable.isDuplicate) { | |
| if (definingSetable.isStatic || | |
| definingSetable.isProperty != tupleExtendedSetter.isProperty) { | |
| builder.reportInheritanceConflict( | |
| definingSetable, | |
| tupleExtendedSetter, | |
| ); | |
| } else { | |
| extendedSetable = tupleExtendedSetter; | |
| } | |
| } | |
| } | |
| // TODO(johnniwinther): Remove extended and mixed in members/setters | |
| // from implemented members/setters. Mixin applications always implement | |
| // the mixin class leading to unnecessary interface members. | |
| List<ClassMember>? implementedGetables; | |
| List<ClassMember>? tupleImplementedMembers = this.implementedMembers; | |
| if (tupleImplementedMembers != null && | |
| // Skip implemented members if we already have a duplicate. | |
| !(definingGetable != null && definingGetable.isDuplicate)) { | |
| for (int i = 0; i < tupleImplementedMembers.length; i++) { | |
| ClassMember? implementedGetable = tupleImplementedMembers[i]; | |
| if (implementedGetable.isStatic || implementedGetable.isDuplicate) { | |
| /// We treat | |
| /// | |
| /// class Interface { | |
| /// static method1() {} | |
| /// method2() {} | |
| /// method2() {} | |
| /// } | |
| /// class Class implements Interface {} | |
| /// | |
| /// as | |
| /// | |
| /// class Interface {} | |
| /// class Class implements Interface {} | |
| /// | |
| implementedGetable = null; | |
| } else { | |
| if (definingGetable == null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface {} | |
| definingGetable = implementedGetable; | |
| } else if (definingGetable.isStatic || | |
| definingGetable.isProperty != implementedGetable.isProperty) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface { | |
| /// static method() {} | |
| /// } | |
| /// | |
| /// or | |
| /// | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface { | |
| /// get getter => 0; | |
| /// } | |
| builder.reportInheritanceConflict( | |
| definingGetable, | |
| implementedGetable, | |
| ); | |
| implementedGetable = null; | |
| } | |
| } | |
| if (implementedGetable == null) { | |
| // On the first skipped member we add all previous. | |
| implementedGetables ??= tupleImplementedMembers.take(i).toList(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (implementedGetables != null) { | |
| // If already skipping members we add [implementedGetable] | |
| // explicitly. | |
| implementedGetables.add(implementedGetable); | |
| } | |
| } | |
| if (implementedGetables == null) { | |
| // No members were skipped so we use the full list. | |
| implementedGetables = tupleImplementedMembers; | |
| } else if (implementedGetables.isEmpty) { | |
| // No members were included. | |
| implementedGetables = null; | |
| } | |
| } | |
| List<ClassMember>? implementedSetables; | |
| List<ClassMember>? tupleImplementedSetters = this.implementedSetters; | |
| if (tupleImplementedSetters != null && | |
| // Skip implemented setters if we already have a duplicate. | |
| !(definingSetable != null && definingSetable.isDuplicate)) { | |
| for (int i = 0; i < tupleImplementedSetters.length; i++) { | |
| ClassMember? implementedSetable = tupleImplementedSetters[i]; | |
| if (implementedSetable.isStatic || implementedSetable.isDuplicate) { | |
| /// We treat | |
| /// | |
| /// class Interface { | |
| /// static set setter1(value) {} | |
| /// set setter2(value) {} | |
| /// set setter2(value) {} | |
| /// } | |
| /// class Class implements Interface {} | |
| /// | |
| /// as | |
| /// | |
| /// class Interface {} | |
| /// class Class implements Interface {} | |
| /// | |
| implementedSetable = null; | |
| } else { | |
| if (definingSetable == null) { | |
| /// class Interface { | |
| /// set setter(value) {} | |
| /// } | |
| /// class Class implements Interface {} | |
| definingSetable = implementedSetable; | |
| } else if (definingSetable.isStatic || | |
| definingSetable.isProperty != implementedSetable.isProperty) { | |
| /// class Interface { | |
| /// set setter(value) {} | |
| /// } | |
| /// class Class implements Interface { | |
| /// static set setter(value) {} | |
| /// } | |
| builder.reportInheritanceConflict( | |
| definingSetable, | |
| implementedSetable, | |
| ); | |
| implementedSetable = null; | |
| } | |
| } | |
| if (implementedSetable == null) { | |
| // On the first skipped setter we add all previous. | |
| implementedSetables ??= tupleImplementedSetters.take(i).toList(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (implementedSetables != null) { | |
| // If already skipping setters we add [implementedSetable] | |
| // explicitly. | |
| implementedSetables.add(implementedSetable); | |
| } | |
| } | |
| if (implementedSetables == null) { | |
| // No setters were skipped so we use the full list. | |
| implementedSetables = tupleImplementedSetters; | |
| } else if (implementedSetables.isEmpty) { | |
| // No setters were included. | |
| implementedSetables = null; | |
| } | |
| } | |
| if (definingGetable != null && definingSetable != null) { | |
| if (definingGetable.isStatic != definingSetable.isStatic || | |
| definingGetable.isProperty != definingSetable.isProperty) { | |
| builder.reportInheritanceConflict(definingGetable, definingSetable); | |
| // TODO(johnniwinther): Should we remove [definingSetable]? If we | |
| // leave it in this conflict will also be reported in subclasses. If | |
| // we remove it, any write to the setable will be unresolved. | |
| } | |
| } | |
| // TODO(johnniwinther): Handle declared members together with mixed in | |
| // members. This should only occur from .dill, though. | |
| if (mixedInGetable != null) { | |
| declaredGetable = null; | |
| } | |
| if (mixedInSetable != null) { | |
| declaredSetable = null; | |
| } | |
| return ( | |
| definingGetable != null | |
| ? new _SanitizedMember( | |
| name, | |
| definingGetable, | |
| declaredGetable, | |
| mixedInGetable, | |
| extendedGetable, | |
| implementedGetables, | |
| ) | |
| : null, | |
| definingSetable != null | |
| ? new _SanitizedMember( | |
| name, | |
| definingSetable, | |
| declaredSetable, | |
| mixedInSetable, | |
| extendedSetable, | |
| implementedSetables, | |
| ) | |
| : null, | |
| ); | |
| } | |
| } | |
| /// The [ClassMember]s involved in defined the [name] getable or setable of | |
| /// a class. | |
| /// | |
| /// The values are sanitized to avoid duplicates and conflicting members. | |
| /// | |
| /// The [_definingMember] hold the first member found among declared, mixed in, | |
| /// extended and implemented members. | |
| /// | |
| /// This is computed by [_Tuple.sanitize]. | |
| class _SanitizedMember { | |
| final Name name; | |
| /// The member which defines whether the computation is for a method, a getter | |
| /// or a setter. | |
| final ClassMember _definingMember; | |
| /// The member declared in the current class, if any. | |
| final ClassMember? _declaredMember; | |
| /// The member declared in a mixin that is mixed into the current class, if | |
| /// any. | |
| final ClassMember? _mixedInMember; | |
| /// The member inherited from the super class. | |
| final ClassMember? _extendedMember; | |
| /// The members inherited from the super interfaces, if none this is `null`. | |
| final List<ClassMember>? _implementedMembers; | |
| new( | |
| this.name, | |
| this._definingMember, | |
| this._declaredMember, | |
| this._mixedInMember, | |
| this._extendedMember, | |
| this._implementedMembers, | |
| ); | |
| /// Computes the class and interface members for this [_SanitizedMember]. | |
| /// | |
| /// The computed class and interface members are added to [classMemberMap] | |
| /// and [interfaceMemberMap], respectively. | |
| /// | |
| /// [ | |
| ClassMember? computeMembers( | |
| ClassMembersNodeBuilder builder, | |
| _Overrides overrides, { | |
| required ClassMember? noSuchMethodMember, | |
| required ClassMember? userNoSuchMethodMember, | |
| required List<ClassMember> abstractMembers, | |
| required Map<Name, ClassMember> classMemberMap, | |
| required Map<Name, ClassMember>? interfaceMemberMap, | |
| required ClassHierarchyNodeDataForTesting? dataForTesting, | |
| }) { | |
| ClassBuilder classBuilder = builder.classBuilder; | |
| ClassMember? classMember; | |
| ClassMember? interfaceMember; | |
| /// A noSuchMethodForwarder can be inserted in non-abstract class | |
| /// if a user defined noSuchMethod implementation is available or | |
| /// if the member is not accessible from this library; | |
| bool canHaveNoSuchMethodForwarder = | |
| !classBuilder.isAbstract && | |
| (userNoSuchMethodMember != null || | |
| !isNameVisibleIn(name, classBuilder.libraryBuilder)); | |
| if (_mixedInMember != null) { | |
| if (_mixedInMember.isAbstract || _mixedInMember.isNoSuchMethodForwarder) { | |
| /// class Mixin { | |
| /// method(); | |
| /// } | |
| /// class Class = Object with Mixin; | |
| /// Interface members from the extended, mixed in, and implemented | |
| /// members define the combined member signature. | |
| Set<ClassMember> interfaceMembers = {}; | |
| if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method(); | |
| /// } | |
| /// class Class = Super with Mixin; | |
| interfaceMembers.add(_extendedMember.interfaceMember); | |
| } | |
| interfaceMembers.add(_mixedInMember); | |
| if (_implementedMembers != null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method(); | |
| /// } | |
| /// class Class = Object with Mixin implements Interface; | |
| interfaceMembers.addAll(_implementedMembers); | |
| } | |
| ClassMember? noSuchMethodTarget; | |
| if (canHaveNoSuchMethodForwarder && | |
| (_extendedMember == null || | |
| _extendedMember.isNoSuchMethodForwarder)) { | |
| /// class Super { | |
| /// noSuchMethod(_) => null; | |
| /// _extendedMember(); | |
| /// } | |
| /// abstract class Mixin { | |
| /// mixinMethod(); | |
| /// _extendedMember(); | |
| /// } | |
| /// class Class = Super with Mixin /* | |
| /// mixinMethod() => ...; // noSuchMethod forwarder created | |
| /// */; | |
| noSuchMethodTarget = noSuchMethodMember; | |
| } | |
| /// We always create a synthesized interface member, even in the | |
| /// case of [interfaceMembers] being a singleton, to insert the | |
| /// abstract mixin stub. | |
| interfaceMember = new SynthesizedInterfaceMember( | |
| classBuilder, | |
| name, | |
| interfaceMembers.toList(), | |
| superClassMember: _extendedMember, | |
| // [definingMember] and [mixedInMember] are always the same | |
| // here. Use the latter here and the former below to show the | |
| // the member is canonical _because_ its the mixed in member and | |
| // it defines the isProperty/forSetter properties _because_ it | |
| // is the defining member. | |
| canonicalMember: _mixedInMember, | |
| mixedInMember: _mixedInMember, | |
| noSuchMethodTarget: noSuchMethodTarget, | |
| memberKind: _definingMember.memberKind, | |
| shouldModifyKernel: builder.shouldModifyKernel, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(interfaceMember); | |
| if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method(); | |
| /// } | |
| /// class Class = Super with Mixin; | |
| /// | |
| /// The concrete extended member is the class member but might | |
| /// be overwritten by a concrete forwarding stub: | |
| /// | |
| /// class Super { | |
| /// method(int i) {} | |
| /// } | |
| /// class Interface { | |
| /// method(covariant int i) {} | |
| /// } | |
| /// class Mixin { | |
| /// method(int i); | |
| /// } | |
| /// // A concrete forwarding stub | |
| /// // method(covariant int i) => super.method(i); | |
| /// // will be inserted. | |
| /// class Class = Super with Mixin implements Interface; | |
| /// | |
| classMember = new InheritedClassMemberImplementsInterface( | |
| classBuilder, | |
| name, | |
| inheritedClassMember: _extendedMember, | |
| implementedInterfaceMember: interfaceMember, | |
| memberKind: _definingMember.memberKind, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(classMember); | |
| if (!classBuilder.isAbstract) { | |
| overrides.registerInheritedImplements(_extendedMember, { | |
| interfaceMember, | |
| }, aliasForTesting: classMember); | |
| } | |
| } else if (noSuchMethodTarget != null) { | |
| classMember = interfaceMember; | |
| } else if (!classBuilder.isAbstract) { | |
| assert(!canHaveNoSuchMethodForwarder); | |
| /// class Mixin { | |
| /// method(); // Missing implementation. | |
| /// } | |
| /// class Class = Object with Mixin; | |
| builder.registerAbstractMember(abstractMembers, interfaceMember); | |
| } | |
| assert(!_mixedInMember.isSynthesized); | |
| if (!_mixedInMember.isSynthesized) { | |
| /// Members declared in the mixin must override extended and | |
| /// implemented members. | |
| /// | |
| /// When loading from .dill the mixed in member might be | |
| /// synthesized, for instance a member signature or forwarding | |
| /// stub, and this should not be checked to override the extended | |
| /// and implemented members: | |
| /// | |
| /// // Opt-out library, from source: | |
| /// class Mixin {} | |
| /// // Opt-out library, from .dill: | |
| /// class Mixin { | |
| /// ... | |
| /// String* toString(); // member signature | |
| /// } | |
| /// // Opt-out library, from source: | |
| /// class Class = Object with Mixin; | |
| /// // Mixin.toString should not be checked to override | |
| /// // Object.toString. | |
| /// | |
| overrides.registerMixedInOverride( | |
| _mixedInMember, | |
| aliasForTesting: interfaceMember, | |
| ); | |
| } | |
| } else { | |
| assert(!_mixedInMember.isAbstract); | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Object with Mixin; | |
| /// | |
| /// Interface members from the extended, mixed in, and implemented | |
| /// members define the combined member signature. | |
| Set<ClassMember> interfaceMembers = {}; | |
| if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Super with Mixin; | |
| interfaceMembers.add(_extendedMember.interfaceMember); | |
| } | |
| interfaceMembers.add(_mixedInMember); | |
| if (_implementedMembers != null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Object with Mixin implements Interface; | |
| interfaceMembers.addAll(_implementedMembers); | |
| } | |
| /// We always create a synthesized interface member, even in the | |
| /// case of [interfaceMembers] being a singleton, to insert the | |
| /// concrete mixin stub. | |
| interfaceMember = new SynthesizedInterfaceMember( | |
| classBuilder, | |
| name, | |
| interfaceMembers.toList(), | |
| superClassMember: _mixedInMember, | |
| // [definingMember] and [mixedInMember] are always the same | |
| // here. Use the latter here and the former below to show the | |
| // the member is canonical _because_ its the mixed in member and | |
| // it defines the isProperty/forSetter properties _because_ it | |
| // is the defining member. | |
| canonicalMember: _mixedInMember, | |
| mixedInMember: _mixedInMember, | |
| memberKind: _definingMember.memberKind, | |
| shouldModifyKernel: builder.shouldModifyKernel, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(interfaceMember); | |
| /// The concrete mixed in member is the class member but will | |
| /// be overwritten by a concrete mixin stub: | |
| /// | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// // A concrete mixin stub | |
| /// // method() => super.method(); | |
| /// // will be inserted. | |
| /// class Class = Object with Mixin; | |
| /// | |
| classMember = new InheritedClassMemberImplementsInterface( | |
| classBuilder, | |
| name, | |
| inheritedClassMember: _mixedInMember, | |
| implementedInterfaceMember: interfaceMember, | |
| memberKind: _definingMember.memberKind, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(classMember); | |
| if (!classBuilder.isAbstract) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Object with Mixin; | |
| /// | |
| /// [mixinMember] must implemented interface member. | |
| overrides.registerInheritedImplements(_mixedInMember, { | |
| interfaceMember, | |
| }, aliasForTesting: classMember); | |
| } | |
| assert(!_mixedInMember.isSynthesized); | |
| if (!_mixedInMember.isSynthesized) { | |
| /// Members declared in the mixin must override extended and | |
| /// implemented members. | |
| /// | |
| /// When loading from .dill the mixed in member might be | |
| /// synthesized, for instance a member signature or forwarding | |
| /// stub, and this should not be checked to override the extended | |
| /// and implemented members. | |
| /// | |
| /// These synthesized mixed in members should always be abstract | |
| /// and therefore not be handled here, but we handled them here | |
| /// for consistency. | |
| overrides.registerMixedInOverride(_mixedInMember); | |
| } | |
| } | |
| } else if (_declaredMember != null) { | |
| if (_declaredMember.isAbstract) { | |
| /// class Class { | |
| /// method(); | |
| /// } | |
| interfaceMember = _declaredMember; | |
| /// Interface members from the declared, extended, and implemented | |
| /// members define the combined member signature. | |
| Set<ClassMember> interfaceMembers = {}; | |
| if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// method(); | |
| /// } | |
| interfaceMembers.add(_extendedMember); | |
| } | |
| interfaceMembers.add(_declaredMember); | |
| if (_implementedMembers != null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface { | |
| /// method(); | |
| /// } | |
| interfaceMembers.addAll(_implementedMembers); | |
| } | |
| ClassMember? noSuchMethodTarget; | |
| if (canHaveNoSuchMethodForwarder && | |
| (_extendedMember == null || | |
| _extendedMember.isNoSuchMethodForwarder)) { | |
| /// class Super { | |
| /// noSuchMethod(_) => null; | |
| /// _extendedMember(); | |
| /// } | |
| /// class Class extends Super { | |
| /// declaredMethod(); // noSuchMethod forwarder created | |
| /// _extendedMember(); | |
| /// } | |
| noSuchMethodTarget = noSuchMethodMember; | |
| } | |
| /// If only one member defines the interface member there is no | |
| /// need for a synthesized interface member, since its result will | |
| /// simply be that one member. | |
| if (interfaceMembers.length > 1 || noSuchMethodTarget != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super implements Interface { | |
| /// method(); | |
| /// } | |
| interfaceMember = new SynthesizedInterfaceMember( | |
| classBuilder, | |
| name, | |
| interfaceMembers.toList(), | |
| superClassMember: _extendedMember, | |
| // [definingMember] and [declaredMember] are always the same | |
| // here. Use the latter here and the former below to show the | |
| // the member is canonical _because_ its the declared member | |
| // and it defines the isProperty/forSetter properties | |
| // _because_ it is the defining member. | |
| canonicalMember: _declaredMember, | |
| noSuchMethodTarget: noSuchMethodTarget, | |
| memberKind: _definingMember.memberKind, | |
| shouldModifyKernel: builder.shouldModifyKernel, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(interfaceMember); | |
| } | |
| if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// method(); | |
| /// } | |
| /// | |
| /// The concrete extended member is the class member but might | |
| /// be overwritten by a concrete forwarding stub: | |
| /// | |
| /// class Super { | |
| /// method(int i) {} | |
| /// } | |
| /// class Interface { | |
| /// method(covariant int i) {} | |
| /// } | |
| /// class Class extends Super implements Interface { | |
| /// // This will be turned into the concrete forwarding stub | |
| /// // method(covariant int i) => super.method(i); | |
| /// method(int i); | |
| /// } | |
| /// | |
| classMember = new InheritedClassMemberImplementsInterface( | |
| classBuilder, | |
| name, | |
| inheritedClassMember: _extendedMember, | |
| implementedInterfaceMember: interfaceMember, | |
| memberKind: _definingMember.memberKind, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(classMember); | |
| if (!classBuilder.isAbstract && noSuchMethodTarget == null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// method(); | |
| /// } | |
| /// | |
| /// [_extendedMember] must implemented interface member. | |
| overrides.registerInheritedImplements(_extendedMember, { | |
| interfaceMember, | |
| }, aliasForTesting: classMember); | |
| } | |
| } else if (noSuchMethodTarget != null) { | |
| classMember = interfaceMember; | |
| } else if (!classBuilder.isAbstract) { | |
| assert(!canHaveNoSuchMethodForwarder); | |
| /// class Class { | |
| /// method(); // Missing implementation. | |
| /// } | |
| builder.registerAbstractMember(abstractMembers, _declaredMember); | |
| } | |
| /// The declared member must override extended and implemented | |
| /// members. | |
| overrides.registerDeclaredOverride( | |
| _declaredMember, | |
| aliasForTesting: interfaceMember, | |
| ); | |
| } else { | |
| assert(!_declaredMember.isAbstract); | |
| /// class Class { | |
| /// method() {} | |
| /// } | |
| classMember = _declaredMember; | |
| /// The declared member must override extended and implemented | |
| /// members. | |
| overrides.registerDeclaredOverride(_declaredMember); | |
| } | |
| } else if (_extendedMember != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super {} | |
| assert( | |
| !_extendedMember.isAbstract, | |
| "Abstract extended member: ${_extendedMember}", | |
| ); | |
| classMember = _extendedMember; | |
| if (_implementedMembers != null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super implements Interface {} | |
| ClassMember extendedInterfaceMember = _extendedMember.interfaceMember; | |
| /// Interface members from the extended and implemented | |
| /// members define the combined member signature. | |
| Set<ClassMember> interfaceMembers = {extendedInterfaceMember}; | |
| // TODO(johnniwinther): The extended member might be included in | |
| // a synthesized implemented member. For instance: | |
| // | |
| // class Super { | |
| // void method() {} | |
| // } | |
| // class Interface { | |
| // void method() {} | |
| // } | |
| // abstract class Class extends Super implements Interface { | |
| // // Synthesized interface member of | |
| // // {Super.method, Interface.method} | |
| // } | |
| // class Sub extends Class { | |
| // // Super.method implements Class.method = | |
| // // {Super.method, Interface.method} | |
| // // Synthesized interface member of | |
| // // {Super.method, Class.method} | |
| // } | |
| // | |
| // Maybe we should recognize this. | |
| interfaceMembers.addAll(_implementedMembers); | |
| ClassMember? noSuchMethodTarget; | |
| if (!classBuilder.isAbstract) { | |
| if (_extendedMember.isNoSuchMethodForwarder && | |
| userNoSuchMethodMember != null) { | |
| // If we inherit a noSuchMethod forwarder we might need to create | |
| // a new one in this class. | |
| // | |
| // For instance | |
| // | |
| // class Super { | |
| // noSuchMethod(_) => null; | |
| // method1(); // noSuchMethod forwarder created for this. | |
| // method2(int i); // noSuchMethod forwarder created for this. | |
| // method3(int i); // noSuchMethod forwarder created for this. | |
| // method4(int i) {} | |
| // } | |
| // abstract class Abstract extends Super { | |
| // method2(num i); // No noSuchMethod forwarder will be | |
| // // inserted here. | |
| // } | |
| // class Class extends Abstract { | |
| // method1(); // noSuchMethod forwarder from Super is valid. | |
| // /* method2(num i) */ // A new noSuchMethod forwarder is | |
| // // created. | |
| // method3(num i); // A new noSuchMethod forwarder is created. | |
| // method4(num i); // No noSuchMethod forwarder will be | |
| // // inserted and this will be an error. | |
| // } | |
| noSuchMethodTarget = noSuchMethodMember; | |
| } | |
| if (!isNameVisibleIn(name, classBuilder.libraryBuilder)) { | |
| // [name] is not visible, so there is no requirement that | |
| // [_extendedMember] implements the [interfaceMembers]. We will | |
| // create a noSuchMethod forwarder if it doesn't. | |
| // | |
| // For instance | |
| // | |
| // // library a.dart | |
| // class A { | |
| // void _m(int i) {} | |
| // } | |
| // abstract class B extends A { | |
| // void _m(num i); | |
| // } | |
| // | |
| // // library b.dart | |
| // import 'a.dart'; | |
| // class C extends B { | |
| // /* _m(num i) */ // A._m doesn't implement B._m so a | |
| // // noSuchMethod forwarder is created. | |
| // } | |
| noSuchMethodTarget = noSuchMethodMember; | |
| } | |
| } | |
| /// Normally, if only one member defines the interface member there | |
| /// is no need for a synthesized interface member, since its result | |
| /// will simply be that one member, but if the extended member is | |
| /// from an opt-in library and the current class is from an opt-out | |
| /// library we need to create a member signature: | |
| /// | |
| /// // Opt-in: | |
| /// class Super { | |
| /// int? method() => null; | |
| /// } | |
| /// class Interface implements Super {} | |
| /// // Opt-out: | |
| /// class Class extends Super implements Interface { | |
| /// // Member signature added: | |
| /// int* method(); | |
| /// } | |
| /// | |
| if (interfaceMembers.length == 1 && noSuchMethodTarget == null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface implements Super {} | |
| /// class Class extends Super implements Interface {} | |
| interfaceMember = interfaceMembers.first; | |
| } else { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super implements Interface {} | |
| interfaceMember = new SynthesizedInterfaceMember( | |
| classBuilder, | |
| name, | |
| interfaceMembers.toList(), | |
| superClassMember: _extendedMember, | |
| noSuchMethodTarget: noSuchMethodTarget, | |
| memberKind: _definingMember.memberKind, | |
| shouldModifyKernel: builder.shouldModifyKernel, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(interfaceMember); | |
| } | |
| if (interfaceMember == classMember) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface implements Super {} | |
| /// class Class extends Super implements Interface {} | |
| /// | |
| /// We keep track of whether a class needs interfaces, that is, | |
| /// whether is has any members that have an interface member | |
| /// different from its corresponding class member, so we set | |
| /// [interfaceMember] to `null` so show that the interface member | |
| /// is not needed. | |
| interfaceMember = null; | |
| } else { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super implements Interface {} | |
| /// | |
| /// The concrete extended member is the class member but might | |
| /// be overwritten by a concrete forwarding stub: | |
| /// | |
| /// class Super { | |
| /// method(int i) {} | |
| /// } | |
| /// class Interface { | |
| /// method(covariant int i) {} | |
| /// } | |
| /// class Class extends Super implements Interface { | |
| /// // A concrete forwarding stub will be created: | |
| /// // method(covariant int i) => super.method(i); | |
| /// } | |
| /// | |
| classMember = new InheritedClassMemberImplementsInterface( | |
| classBuilder, | |
| name, | |
| inheritedClassMember: _extendedMember, | |
| implementedInterfaceMember: interfaceMember, | |
| memberKind: _definingMember.memberKind, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(classMember); | |
| if (!classBuilder.isAbstract && noSuchMethodTarget == null) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super implements Interface {} | |
| overrides.registerInheritedImplements(_extendedMember, { | |
| interfaceMember, | |
| }, aliasForTesting: classMember); | |
| } | |
| } | |
| } | |
| } else if (_implementedMembers != null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface {} | |
| Set<ClassMember> interfaceMembers = _implementedMembers.toSet(); | |
| if (interfaceMembers.isNotEmpty) { | |
| ClassMember? noSuchMethodTarget; | |
| if (canHaveNoSuchMethodForwarder) { | |
| /// abstract class Interface { | |
| /// implementedMember(); | |
| /// } | |
| /// class Class implements Interface { | |
| /// noSuchMethod(_) => null; | |
| /// implementedMember(); // noSuchMethod forwarder created | |
| /// } | |
| noSuchMethodTarget = noSuchMethodMember; | |
| } | |
| /// Normally, if only one member defines the interface member there | |
| /// is no need for a synthesized interface member, since its result | |
| /// will simply be that one member, but if the implemented member is | |
| /// from an opt-in library and the current class is from an opt-out | |
| /// library we need to create a member signature: | |
| /// | |
| /// // Opt-in: | |
| /// class Interface { | |
| /// int? method() => null; | |
| /// } | |
| /// // Opt-out: | |
| /// class Class implements Interface { | |
| /// // Member signature added: | |
| /// int* method(); | |
| /// } | |
| /// | |
| if (interfaceMembers.length == 1 && noSuchMethodTarget == null) { | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface {} | |
| interfaceMember = interfaceMembers.first; | |
| } else { | |
| /// class Interface1 { | |
| /// method() {} | |
| /// } | |
| /// class Interface2 { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface1, Interface2 {} | |
| interfaceMember = new SynthesizedInterfaceMember( | |
| classBuilder, | |
| name, | |
| interfaceMembers.toList(), | |
| noSuchMethodTarget: noSuchMethodTarget, | |
| memberKind: _definingMember.memberKind, | |
| shouldModifyKernel: builder.shouldModifyKernel, | |
| ); | |
| builder._membersBuilder.registerMemberComputation(interfaceMember); | |
| } | |
| if (noSuchMethodTarget != null) { | |
| classMember = interfaceMember; | |
| } else if (!classBuilder.isAbstract) { | |
| assert(!canHaveNoSuchMethodForwarder); | |
| /// class Interface { | |
| /// method() {} | |
| /// } | |
| /// class Class implements Interface {} | |
| builder.registerAbstractMember(abstractMembers, interfaceMember); | |
| } | |
| } | |
| } | |
| if (interfaceMember != null) { | |
| // We have an explicit interface. | |
| builder._hasInterfaces = true; | |
| } | |
| if (classMember != null) { | |
| classMemberMap[name] = classMember; | |
| interfaceMember ??= classMember.interfaceMember; | |
| } | |
| if (interfaceMember != null) { | |
| interfaceMemberMap![name] = interfaceMember; | |
| } | |
| return interfaceMember; | |
| } | |
| Set<ClassMember> computeOverrides() { | |
| Set<ClassMember> set = {}; | |
| if (_extendedMember != null) { | |
| /// (abstract) class Super { | |
| /// method() {} | |
| /// int get property => 0; | |
| /// } | |
| /// (abstract) class Class extends Super { | |
| /// method() {} | |
| /// set property(int value) {} | |
| /// } | |
| /// | |
| /// or | |
| /// | |
| /// (abstract) class Super { | |
| /// set setter(int value) {} | |
| /// set property(int value) {} | |
| /// } | |
| /// (abstract) class Class extends Super { | |
| /// set setter(int value) {} | |
| /// int get property => 0; | |
| /// } | |
| set.add(_extendedMember.interfaceMember); | |
| } | |
| if (_implementedMembers != null) { | |
| /// (abstract) class Interface { | |
| /// method() {} | |
| /// int get property => 0; | |
| /// } | |
| /// (abstract) class Class implements Interface { | |
| /// method() {} | |
| /// set property(int value) {} | |
| /// } | |
| /// | |
| /// or | |
| /// | |
| /// (abstract) class Interface { | |
| /// set setter(int value) {} | |
| /// set property(int value) {} | |
| /// } | |
| /// (abstract) class Class implements Interface { | |
| /// set setter(int value) {} | |
| /// int get property => 0; | |
| /// } | |
| set.addAll(_implementedMembers); | |
| } | |
| return set; | |
| } | |
| } | |
| /// Object that collects data of overrides found during | |
| /// [_SanitizedMember.computeMembers]. | |
| class _Overrides { | |
| final ClassBuilder _classBuilder; | |
| /// In case this class is concrete, this maps concrete members that are | |
| /// inherited into this class to the members they should override to validly | |
| /// implement the interface of this class. | |
| final Map<ClassMember, Set<ClassMember>> _inheritedImplementsMap; | |
| final ClassHierarchyNodeDataForTesting? _dataForTesting; | |
| /// Set to `true` if declared members have been registered in | |
| /// [registerDeclaredOverride] or [registerMixedInOverride]. | |
| bool hasDeclaredMembers = false; | |
| /// Declared methods, getters and setters registered in | |
| /// [registerDeclaredOverride]. | |
| ClassMember? declaredMethod; | |
| List<ClassMember>? declaredProperties; | |
| /// Declared methods, getters and setters registered in | |
| /// [registerDeclaredOverride]. | |
| ClassMember? mixedInMethod; | |
| List<ClassMember>? mixedInProperties; | |
| new({ | |
| required ClassBuilder classBuilder, | |
| required Map<ClassMember, Set<ClassMember>> inheritedImplementsMap, | |
| required ClassHierarchyNodeDataForTesting? dataForTesting, | |
| }) : _classBuilder = classBuilder, | |
| _inheritedImplementsMap = inheritedImplementsMap, | |
| _dataForTesting = dataForTesting; | |
| /// Registers that [declaredMember] overrides extended and implemented | |
| /// members. | |
| /// | |
| /// Getters and setters share overridden members so the registration | |
| /// of override relations is performed after the interface members have | |
| /// been computed. | |
| /// | |
| /// Declared members must be checked for valid override of the overridden | |
| /// members _and_ must register an override dependency with the overridden | |
| /// members so that override inference can propagate inferred types | |
| /// correctly. For instance: | |
| /// | |
| /// class Super { | |
| /// int get property => 42; | |
| /// } | |
| /// class Class extends Super { | |
| /// void set property(value) {} | |
| /// } | |
| /// | |
| /// Here the parameter type of the setter `Class.property` must be | |
| /// inferred from the type of the getter `Super.property`. | |
| void registerDeclaredOverride( | |
| ClassMember declaredMember, { | |
| ClassMember? aliasForTesting, | |
| }) { | |
| if (_classBuilder is SourceClassBuilder && !declaredMember.isStatic) { | |
| assert( | |
| declaredMember.isSourceDeclaration && | |
| declaredMember.declarationBuilder == _classBuilder, | |
| "Only declared members can override: ${declaredMember}", | |
| ); | |
| hasDeclaredMembers = true; | |
| if (declaredMember.isProperty) { | |
| declaredProperties ??= []; | |
| declaredProperties!.add(declaredMember); | |
| } else { | |
| assert( | |
| declaredMethod == null, | |
| "Multiple methods unexpectedly declared: " | |
| "${declaredMethod} and ${declaredMember}.", | |
| ); | |
| declaredMethod = declaredMember; | |
| } | |
| if (_dataForTesting != null && aliasForTesting != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _dataForTesting.aliasMap[aliasForTesting] = declaredMember; | |
| } | |
| } | |
| } | |
| /// Registers that [mixedMember] overrides extended and implemented | |
| /// members through application. | |
| /// | |
| /// Getters and setters share overridden members so the registration | |
| /// of override relations in performed after the interface members have | |
| /// been computed. | |
| /// | |
| /// Declared mixed in members must be checked for valid override of the | |
| /// overridden members but _not_ register an override dependency with the | |
| /// overridden members. This is in contrast to declared members. For | |
| /// instance: | |
| /// | |
| /// class Super { | |
| /// int get property => 42; | |
| /// } | |
| /// class Mixin { | |
| /// void set property(value) {} | |
| /// } | |
| /// class Class = Super with Mixin; | |
| /// | |
| /// Here the parameter type of the setter `Mixin.property` must _not_ be | |
| /// inferred from the type of the getter `Super.property`, but should | |
| /// instead default to `dynamic`. | |
| void registerMixedInOverride( | |
| ClassMember mixedInMember, { | |
| ClassMember? aliasForTesting, | |
| }) { | |
| assert( | |
| mixedInMember.declarationBuilder != _classBuilder, | |
| "Only mixin members can override by application: ${mixedInMember}", | |
| ); | |
| if (_classBuilder is SourceClassBuilder) { | |
| hasDeclaredMembers = true; | |
| if (mixedInMember.isProperty) { | |
| mixedInProperties ??= []; | |
| mixedInProperties!.add(mixedInMember); | |
| } else { | |
| assert( | |
| mixedInMethod == null, | |
| "Multiple methods unexpectedly declared in mixin: " | |
| "${mixedInMethod} and ${mixedInMember}.", | |
| ); | |
| mixedInMethod = mixedInMember; | |
| } | |
| if (_dataForTesting != null && aliasForTesting != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _dataForTesting.aliasMap[aliasForTesting] = mixedInMember; | |
| } | |
| } | |
| } | |
| /// Registers that [inheritedMember] should be checked to validly override | |
| /// [overrides]. | |
| /// | |
| /// This is needed in the case where a concrete member is inherited into | |
| /// a concrete subclass. For instance: | |
| /// | |
| /// class Super { | |
| /// void method() {} | |
| /// } | |
| /// abstract class Interface { | |
| /// void method(); | |
| /// } | |
| /// class Class extends Super implements Interface {} | |
| /// | |
| /// Here `Super.method` must be checked to be a valid implementation for | |
| /// `Interface.method` by being a valid override of it. | |
| void registerInheritedImplements( | |
| ClassMember inheritedMember, | |
| Set<ClassMember> overrides, { | |
| required ClassMember aliasForTesting, | |
| }) { | |
| if (_classBuilder is SourceClassBuilder) { | |
| assert( | |
| inheritedMember.declarationBuilder != _classBuilder, | |
| "Only inherited members can implement by inheritance: " | |
| "${inheritedMember}", | |
| ); | |
| _inheritedImplementsMap[inheritedMember] = overrides; | |
| if (_dataForTesting != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _dataForTesting.aliasMap[aliasForTesting] = inheritedMember; | |
| } | |
| } | |
| } | |
| /// Collects overrides of [getable] and [setable] in [declaredOverridesMap] | |
| /// and [mixinApplicationOverridesMap] to set up need override checks. | |
| void collectOverrides({ | |
| required _SanitizedMember? getable, | |
| required _SanitizedMember? setable, | |
| required Map<ClassMember, Set<ClassMember>> declaredOverridesMap, | |
| required Map<ClassMember, Set<ClassMember>> mixinApplicationOverridesMap, | |
| }) { | |
| if (hasDeclaredMembers) { | |
| Set<ClassMember> getableOverrides = getable?.computeOverrides() ?? {}; | |
| Set<ClassMember> setableOverrides = setable?.computeOverrides() ?? {}; | |
| if (getableOverrides.isNotEmpty || setableOverrides.isNotEmpty) { | |
| if (declaredMethod != null && getableOverrides.isNotEmpty) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// method() {} | |
| /// } | |
| declaredOverridesMap[declaredMethod!] = getableOverrides; | |
| } | |
| if (declaredProperties != null) { | |
| Set<ClassMember> overrides; | |
| if (declaredMethod != null) { | |
| /// class Super { | |
| /// set setter() {} | |
| /// } | |
| /// class Class extends Super { | |
| /// method() {} | |
| /// } | |
| overrides = setableOverrides; | |
| } else { | |
| /// class Super { | |
| /// get property => null | |
| /// void set property(value) {} | |
| /// } | |
| /// class Class extends Super { | |
| /// get property => null | |
| /// void set property(value) {} | |
| /// } | |
| overrides = {...getableOverrides, ...setableOverrides}; | |
| } | |
| if (overrides.isNotEmpty) { | |
| for (ClassMember declaredMember in declaredProperties!) { | |
| declaredOverridesMap[declaredMember] = overrides; | |
| } | |
| } | |
| } | |
| if (mixedInMethod != null && getableOverrides.isNotEmpty) { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Super with Mixin; | |
| mixinApplicationOverridesMap[mixedInMethod!] = getableOverrides; | |
| } | |
| if (mixedInProperties != null) { | |
| Set<ClassMember> overrides; | |
| if (mixedInMethod != null) { | |
| /// class Super { | |
| /// set setter() {} | |
| /// } | |
| /// class Mixin { | |
| /// method() {} | |
| /// } | |
| /// class Class = Super with Mixin; | |
| overrides = setableOverrides; | |
| } else { | |
| /// class Super { | |
| /// method() {} | |
| /// } | |
| /// class Mixin extends Super { | |
| /// method() {} | |
| /// } | |
| overrides = {...getableOverrides, ...setableOverrides}; | |
| } | |
| if (overrides.isNotEmpty) { | |
| for (ClassMember mixedInMember in mixedInProperties!) { | |
| mixinApplicationOverridesMap[mixedInMember] = overrides; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Set<ClassMember> toSet( | |
| DeclarationBuilder declarationBuilder, | |
| Iterable<ClassMember> members, | |
| ) { | |
| Set<ClassMember> result = <ClassMember>{}; | |
| _toSet(declarationBuilder, members, result); | |
| return result; | |
| } | |
| void _toSet( | |
| DeclarationBuilder declarationBuilder, | |
| Iterable<ClassMember> members, | |
| Set<ClassMember> result, | |
| ) { | |
| for (ClassMember member in members) { | |
| if (member.hasDeclarations && | |
| declarationBuilder == member.declarationBuilder) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _toSet(declarationBuilder, member.declarations, result); | |
| } else { | |
| result.add(member); | |
| } | |
| } | |
| } | |
| void reportCantInferParameterType( | |
| ProblemReporting problemReporting, | |
| FormalParameterBuilder parameter, | |
| Iterable<ClassMember> overriddenMembers, | |
| ) { | |
| String name = parameter.name; | |
| List<LocatedMessage> context = overriddenMembers | |
| .map((ClassMember overriddenMember) { | |
| return diag.declaredMemberConflictsWithOverriddenMembersCause | |
| .withLocation2(overriddenMember.uriOffset); | |
| }) | |
| // Call toSet to avoid duplicate context for instance of fields that are | |
| // overridden both as getters and setters. | |
| .toSet() | |
| .toList(); | |
| problemReporting.addProblem( | |
| diag.cantInferTypeDueToNoCombinedSignature.withArguments(name: name), | |
| parameter.fileOffset, | |
| name.length, | |
| parameter.fileUri, | |
| wasHandled: true, | |
| context: context, | |
| ); | |
| } | |
| void reportCantInferTypes( | |
| ProblemReporting problemReporting, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| List<LocatedMessage> context = overriddenMembers | |
| .map((ClassMember overriddenMember) { | |
| return diag.declaredMemberConflictsWithOverriddenMembersCause | |
| .withLocation2(overriddenMember.uriOffset); | |
| }) | |
| // Call toSet to avoid duplicate context for instance of fields that are | |
| // overridden both as getters and setters. | |
| .toSet() | |
| .toList(); | |
| problemReporting.addProblem( | |
| diag.cantInferTypesDueToNoCombinedSignature.withArguments(name: name), | |
| nameOffset, | |
| nameLength, | |
| fileUri, | |
| wasHandled: true, | |
| context: context, | |
| ); | |
| } | |
| void reportCantInferReturnType( | |
| ProblemReporting problemReporting, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| List<LocatedMessage> context = overriddenMembers | |
| .map((ClassMember overriddenMember) { | |
| return diag.declaredMemberConflictsWithOverriddenMembersCause | |
| .withLocation2(overriddenMember.uriOffset); | |
| }) | |
| // Call toSet to avoid duplicate context for instance of fields that are | |
| // overridden both as getters and setters. | |
| .toSet() | |
| .toList(); | |
| problemReporting.addProblem( | |
| diag.cantInferReturnTypeDueToNoCombinedSignature.withArguments(name: name), | |
| nameOffset, | |
| nameLength, | |
| fileUri, | |
| wasHandled: true, | |
| context: context, | |
| ); | |
| } | |
| void reportCantInferFieldType( | |
| ProblemReporting problemReporting, | |
| Iterable<ClassMember> overriddenMembers, { | |
| required String name, | |
| required Uri fileUri, | |
| required int nameOffset, | |
| required int nameLength, | |
| }) { | |
| List<LocatedMessage> context = overriddenMembers | |
| .map((ClassMember overriddenMember) { | |
| return diag.declaredMemberConflictsWithOverriddenMembersCause | |
| .withLocation2(overriddenMember.uriOffset); | |
| }) | |
| // Call toSet to avoid duplicate context for instance of fields that are | |
| // overridden both as getters and setters. | |
| .toSet() | |
| .toList(); | |
| problemReporting.addProblem( | |
| diag.cantInferTypeDueToNoCombinedSignature.withArguments(name: name), | |
| nameOffset, | |
| nameLength, | |
| fileUri, | |
| wasHandled: true, | |
| context: context, | |
| ); | |
| } | |
| bool isNameVisibleIn(Name name, LibraryBuilder libraryBuilder) { | |
| return !name.isPrivate || name.library == libraryBuilder.library; | |
| } | |
| Set<ClassMember> unfoldDeclarations(Iterable<ClassMember> members) { | |
| Set<ClassMember> result = <ClassMember>{}; | |
| _unfoldDeclarations(members, result); | |
| return result; | |
| } | |
| void _unfoldDeclarations( | |
| Iterable<ClassMember> members, | |
| Set<ClassMember> result, | |
| ) { | |
| for (ClassMember member in members) { | |
| if (member.hasDeclarations) { | |
| _unfoldDeclarations(member.declarations, result); | |
| } else { | |
| result.add(member); | |
| } | |
| } | |
| } |