| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library fasta.class_hierarchy_builder; |
| |
| import 'package:kernel/ast.dart' |
| show |
| Class, |
| DartType, |
| Field, |
| FunctionNode, |
| InterfaceType, |
| InvalidType, |
| Member, |
| Name, |
| Procedure, |
| ProcedureKind, |
| Supertype, |
| TypeParameter, |
| TypeParameterType, |
| VariableDeclaration; |
| |
| import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; |
| |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| |
| import 'package:kernel/type_algebra.dart' show Substitution; |
| import 'package:kernel/type_environment.dart'; |
| |
| import '../dill/dill_member_builder.dart' show DillMemberBuilder; |
| |
| import '../loader.dart' show Loader; |
| |
| import '../messages.dart' |
| show |
| LocatedMessage, |
| Message, |
| messageDeclaredMemberConflictsWithInheritedMember, |
| messageDeclaredMemberConflictsWithInheritedMemberCause, |
| messageInheritedMembersConflict, |
| messageInheritedMembersConflictCause1, |
| messageInheritedMembersConflictCause2, |
| messageStaticAndInstanceConflict, |
| messageStaticAndInstanceConflictCause, |
| templateCantInferReturnTypeDueToInconsistentOverrides, |
| templateCantInferTypeDueToInconsistentOverrides, |
| templateCombinedMemberSignatureFailed, |
| templateDuplicatedDeclaration, |
| templateDuplicatedDeclarationCause, |
| templateDuplicatedDeclarationUse, |
| templateMissingImplementationCause, |
| templateMissingImplementationNotAbstract; |
| |
| import '../names.dart' show noSuchMethodName; |
| |
| import '../problems.dart' show unhandled; |
| |
| import '../scope.dart' show Scope; |
| |
| import '../source/source_library_builder.dart' show SourceLibraryBuilder; |
| |
| import '../source/source_loader.dart' show SourceLoader; |
| |
| import '../type_inference/standard_bounds.dart' show StandardBounds; |
| |
| import '../type_inference/type_constraint_gatherer.dart' |
| show TypeConstraintGatherer; |
| |
| import '../type_inference/type_inferrer.dart' show MixinInferrer; |
| |
| import '../type_inference/type_schema.dart' show UnknownType; |
| |
| import '../type_inference/type_schema_environment.dart' show TypeConstraint; |
| |
| import 'forwarding_node.dart' show ForwardingNode; |
| |
| import 'kernel_builder.dart' |
| show |
| Builder, |
| FormalParameterBuilder, |
| ImplicitFieldType, |
| ClassBuilder, |
| FieldBuilder, |
| NamedTypeBuilder, |
| ProcedureBuilder, |
| LibraryBuilder, |
| MemberBuilder, |
| NullabilityBuilder, |
| TypeBuilder, |
| TypeVariableBuilder; |
| |
| import 'types.dart' show Types; |
| |
| const DebugLogger debug = |
| const bool.fromEnvironment("debug.hierarchy") ? const DebugLogger() : null; |
| |
| class DebugLogger { |
| const DebugLogger(); |
| void log(Object message) => print(message); |
| } |
| |
| int compareDeclarations(Builder a, Builder b) { |
| return ClassHierarchy.compareMembers(a.target, b.target); |
| } |
| |
| ProcedureKind memberKind(Member member) { |
| return member is Procedure ? member.kind : null; |
| } |
| |
| bool isNameVisibleIn(Name name, LibraryBuilder libraryBuilder) { |
| return !name.isPrivate || name.library == libraryBuilder.library; |
| } |
| |
| /// Returns true if [a] is a class member conflict with [b]. [a] is assumed to |
| /// be declared in the class, [b] is assumed to be inherited. |
| /// |
| /// See the section named "Class Member Conflicts" in [Dart Programming |
| /// Language Specification]( |
| /// ../../../../../../docs/language/dartLangSpec.tex#classMemberConflicts). |
| bool isInheritanceConflict(Builder a, Builder b) { |
| if (a.isStatic) return true; |
| if (memberKind(a.target) == memberKind(b.target)) return false; |
| if (a.isField) return !(b.isField || b.isGetter || b.isSetter); |
| if (b.isField) return !(a.isField || a.isGetter || a.isSetter); |
| if (a.isSetter) return !(b.isGetter || b.isSetter); |
| if (b.isSetter) return !(a.isGetter || a.isSetter); |
| if (a is InterfaceConflict || b is InterfaceConflict) return false; |
| return true; |
| } |
| |
| bool impliesSetter(Builder declaration) { |
| return declaration.isField && !(declaration.isFinal || declaration.isConst); |
| } |
| |
| bool hasSameSignature(FunctionNode a, FunctionNode b) { |
| List<TypeParameter> aTypeParameters = a.typeParameters; |
| List<TypeParameter> bTypeParameters = b.typeParameters; |
| int typeParameterCount = aTypeParameters.length; |
| if (typeParameterCount != bTypeParameters.length) return false; |
| Substitution substitution; |
| if (typeParameterCount != 0) { |
| List<DartType> types = new List<DartType>(typeParameterCount); |
| for (int i = 0; i < typeParameterCount; i++) { |
| types[i] = new TypeParameterType(aTypeParameters[i]); |
| } |
| substitution = Substitution.fromPairs(bTypeParameters, types); |
| for (int i = 0; i < typeParameterCount; i++) { |
| DartType aBound = aTypeParameters[i].bound; |
| DartType bBound = substitution.substituteType(bTypeParameters[i].bound); |
| if (aBound != bBound) return false; |
| } |
| } |
| |
| if (a.requiredParameterCount != b.requiredParameterCount) return false; |
| List<VariableDeclaration> aPositionalParameters = a.positionalParameters; |
| List<VariableDeclaration> bPositionalParameters = b.positionalParameters; |
| if (aPositionalParameters.length != bPositionalParameters.length) { |
| return false; |
| } |
| for (int i = 0; i < aPositionalParameters.length; i++) { |
| VariableDeclaration aParameter = aPositionalParameters[i]; |
| VariableDeclaration bParameter = bPositionalParameters[i]; |
| if (aParameter.isCovariant != bParameter.isCovariant) return false; |
| DartType aType = aParameter.type; |
| DartType bType = bParameter.type; |
| if (substitution != null) { |
| bType = substitution.substituteType(bType); |
| } |
| if (aType != bType) return false; |
| } |
| |
| List<VariableDeclaration> aNamedParameters = a.namedParameters; |
| List<VariableDeclaration> bNamedParameters = b.namedParameters; |
| if (aNamedParameters.length != bNamedParameters.length) return false; |
| for (int i = 0; i < aNamedParameters.length; i++) { |
| VariableDeclaration aParameter = aNamedParameters[i]; |
| VariableDeclaration bParameter = bNamedParameters[i]; |
| if (aParameter.isCovariant != bParameter.isCovariant) return false; |
| if (aParameter.name != bParameter.name) return false; |
| DartType aType = aParameter.type; |
| DartType bType = bParameter.type; |
| if (substitution != null) { |
| bType = substitution.substituteType(bType); |
| } |
| if (aType != bType) return false; |
| } |
| |
| DartType aReturnType = a.returnType; |
| DartType bReturnType = b.returnType; |
| if (substitution != null) { |
| bReturnType = substitution.substituteType(bReturnType); |
| } |
| |
| return aReturnType == bReturnType; |
| } |
| |
| class ClassHierarchyBuilder { |
| final Map<Class, ClassHierarchyNode> nodes = <Class, ClassHierarchyNode>{}; |
| |
| final ClassBuilder objectClassBuilder; |
| |
| final Loader loader; |
| |
| final Class objectClass; |
| |
| final Class futureClass; |
| |
| final Class futureOrClass; |
| |
| final Class functionClass; |
| |
| final Class nullClass; |
| |
| final List<DelayedOverrideCheck> overrideChecks = <DelayedOverrideCheck>[]; |
| |
| final List<DelayedMember> delayedMemberChecks = <DelayedMember>[]; |
| |
| // TODO(dmitryas): Consider removing this. |
| final CoreTypes coreTypes; |
| |
| Types types; |
| |
| ClassHierarchyBuilder(this.objectClassBuilder, this.loader, this.coreTypes) |
| : objectClass = objectClassBuilder.cls, |
| futureClass = coreTypes.futureClass, |
| futureOrClass = coreTypes.futureOrClass, |
| functionClass = coreTypes.functionClass, |
| nullClass = coreTypes.nullClass { |
| types = new Types(this); |
| } |
| |
| ClassHierarchyNode getNodeFromClass(ClassBuilder classBuilder) { |
| return nodes[classBuilder.cls] ??= |
| new ClassHierarchyNodeBuilder(this, classBuilder).build(); |
| } |
| |
| ClassHierarchyNode getNodeFromType(TypeBuilder type) { |
| ClassBuilder cls = getClass(type); |
| return cls == null ? null : getNodeFromClass(cls); |
| } |
| |
| ClassHierarchyNode getNodeFromKernelClass(Class cls) { |
| return nodes[cls] ?? |
| getNodeFromClass(loader.computeClassBuilderFromTargetClass(cls)); |
| } |
| |
| TypeBuilder asSupertypeOf(Class cls, Class supertype) { |
| ClassHierarchyNode clsNode = getNodeFromKernelClass(cls); |
| if (cls == supertype) { |
| return new NamedTypeBuilder( |
| clsNode.classBuilder.name, const NullabilityBuilder.omitted(), null) |
| ..bind(clsNode.classBuilder); |
| } |
| ClassHierarchyNode supertypeNode = getNodeFromKernelClass(supertype); |
| List<TypeBuilder> supertypes = clsNode.superclasses; |
| int depth = supertypeNode.depth; |
| Builder supertypeDeclaration = supertypeNode.classBuilder; |
| if (depth < supertypes.length) { |
| TypeBuilder asSupertypeOf = supertypes[depth]; |
| if (asSupertypeOf.declaration == supertypeDeclaration) { |
| return asSupertypeOf; |
| } |
| } |
| supertypes = clsNode.interfaces; |
| for (int i = 0; i < supertypes.length; i++) { |
| TypeBuilder type = supertypes[i]; |
| if (type.declaration == supertypeDeclaration) return type; |
| } |
| return null; |
| } |
| |
| InterfaceType getKernelTypeAsInstanceOf( |
| InterfaceType type, Class superclass) { |
| Class kernelClass = type.classNode; |
| if (kernelClass == superclass) return type; |
| if (kernelClass == nullClass) { |
| if (superclass.typeParameters.isEmpty) { |
| return coreTypes.legacyRawType(superclass); |
| } else { |
| // This is a safe fall-back for dealing with `Null`. It will likely be |
| // faster to check for `Null` before calling this method. |
| return new InterfaceType( |
| superclass, |
| new List<DartType>.filled( |
| superclass.typeParameters.length, coreTypes.nullType)); |
| } |
| } |
| NamedTypeBuilder supertype = asSupertypeOf(kernelClass, superclass); |
| if (supertype == null) return null; |
| if (supertype.arguments == null && superclass.typeParameters.isEmpty) { |
| return coreTypes.legacyRawType(superclass); |
| } |
| return Substitution.fromInterfaceType(type) |
| .substituteType(supertype.build(null)); |
| } |
| |
| InterfaceType getKernelLegacyLeastUpperBound( |
| InterfaceType type1, InterfaceType type2) { |
| if (type1 == type2) return type1; |
| ClassHierarchyNode node1 = getNodeFromKernelClass(type1.classNode); |
| ClassHierarchyNode node2 = getNodeFromKernelClass(type2.classNode); |
| Set<ClassHierarchyNode> nodes1 = node1.computeAllSuperNodes(this).toSet(); |
| List<ClassHierarchyNode> nodes2 = node2.computeAllSuperNodes(this); |
| List<ClassHierarchyNode> common = <ClassHierarchyNode>[]; |
| |
| for (int i = 0; i < nodes2.length; i++) { |
| ClassHierarchyNode node = nodes2[i]; |
| if (node == null) continue; |
| if (nodes1.contains(node)) { |
| DartType candidate1 = |
| getKernelTypeAsInstanceOf(type1, node.classBuilder.cls); |
| DartType candidate2 = |
| getKernelTypeAsInstanceOf(type2, node.classBuilder.cls); |
| if (candidate1 == candidate2) { |
| common.add(node); |
| } |
| } |
| } |
| |
| if (common.length == 1) return coreTypes.objectLegacyRawType; |
| common.sort(ClassHierarchyNode.compareMaxInheritancePath); |
| |
| for (int i = 0; i < common.length - 1; i++) { |
| ClassHierarchyNode node = common[i]; |
| if (node.maxInheritancePath != common[i + 1].maxInheritancePath) { |
| return getKernelTypeAsInstanceOf(type1, node.classBuilder.cls); |
| } else { |
| do { |
| i++; |
| } while (node.maxInheritancePath == common[i + 1].maxInheritancePath); |
| } |
| } |
| return coreTypes.objectLegacyRawType; |
| } |
| |
| Member getInterfaceMemberKernel(Class cls, Name name, bool isSetter) { |
| return getNodeFromKernelClass(cls) |
| .getInterfaceMember(name, isSetter) |
| ?.target; |
| } |
| |
| Member getDispatchTargetKernel(Class cls, Name name, bool isSetter) { |
| return getNodeFromKernelClass(cls) |
| .getDispatchTarget(name, isSetter) |
| ?.target; |
| } |
| |
| Member getCombinedMemberSignatureKernel(Class cls, Name name, bool isSetter, |
| int charOffset, SourceLibraryBuilder library) { |
| Builder declaration = |
| getNodeFromKernelClass(cls).getInterfaceMember(name, isSetter); |
| if (declaration?.isStatic ?? true) return null; |
| if (declaration.next != null) { |
| library?.addProblem( |
| templateDuplicatedDeclarationUse.withArguments(name.name), |
| charOffset, |
| name.name.length, |
| library.fileUri); |
| return null; |
| } |
| if (declaration is DelayedMember) { |
| return declaration.check(this); |
| } else { |
| return declaration.target; |
| } |
| } |
| |
| static ClassHierarchyBuilder build(ClassBuilder objectClass, |
| List<ClassBuilder> classes, SourceLoader loader, CoreTypes coreTypes) { |
| ClassHierarchyBuilder hierarchy = |
| new ClassHierarchyBuilder(objectClass, loader, coreTypes); |
| for (int i = 0; i < classes.length; i++) { |
| ClassBuilder classBuilder = classes[i]; |
| if (!classBuilder.isPatch) { |
| hierarchy.nodes[classBuilder.cls] = |
| new ClassHierarchyNodeBuilder(hierarchy, classBuilder).build(); |
| } else { |
| // TODO(ahe): Merge the injected members of patch into the hierarchy |
| // node of `cls.origin`. |
| } |
| } |
| return hierarchy; |
| } |
| } |
| |
| class ClassHierarchyNodeBuilder { |
| final ClassHierarchyBuilder hierarchy; |
| |
| final ClassBuilder classBuilder; |
| |
| bool hasNoSuchMethod = false; |
| |
| List<Builder> abstractMembers = null; |
| |
| ClassHierarchyNodeBuilder(this.hierarchy, this.classBuilder); |
| |
| ClassBuilder get objectClass => hierarchy.objectClassBuilder; |
| |
| final Map<Class, Substitution> substitutions = <Class, Substitution>{}; |
| |
| /// When merging `aList` and `bList`, [a] (from `aList`) and [b] (from |
| /// `bList`) each have the same name. |
| /// |
| /// If [mergeKind] is `MergeKind.superclass`, [a] should override [b]. |
| /// |
| /// If [mergeKind] is `MergeKind.interfaces`, we need to record them and |
| /// solve the conflict later. |
| /// |
| /// If [mergeKind] is `MergeKind.supertypes`, [a] should implement [b], and |
| /// [b] is implicitly abstract. |
| Builder handleMergeConflict(Builder a, Builder b, MergeKind mergeKind) { |
| debug?.log( |
| "handleMergeConflict: ${fullName(a)} ${fullName(b)} ${mergeKind}"); |
| // TODO(ahe): Enable this optimization, but be careful about abstract |
| // methods overriding concrete methods. |
| // if (cls is DillClassBuilder) return a; |
| if (a == b) return a; |
| if (a.next != null || b.next != null) { |
| // Don't check overrides involving duplicated members. |
| return a; |
| } |
| Builder result = checkInheritanceConflict(a, b); |
| if (result != null) return result; |
| result = a; |
| switch (mergeKind) { |
| case MergeKind.superclassMembers: |
| case MergeKind.superclassSetters: |
| // [a] is a method declared in [cls]. This means it defines the |
| // interface of this class regardless if its abstract. |
| debug?.log("superclass: checkValidOverride(" |
| "${classBuilder.fullNameForErrors}, " |
| "${fullName(a)}, ${fullName(b)})"); |
| checkValidOverride( |
| a, AbstractMemberOverridingImplementation.selectAbstract(b)); |
| |
| if (isAbstract(a)) { |
| if (isAbstract(b)) { |
| recordAbstractMember(a); |
| } else { |
| if (!classBuilder.isAbstract) { |
| // The interface of this class is [a]. But the implementation is |
| // [b]. So [b] must implement [a], unless [cls] is abstract. |
| checkValidOverride(b, a); |
| } |
| result = new AbstractMemberOverridingImplementation( |
| classBuilder, |
| a, |
| AbstractMemberOverridingImplementation.selectConcrete(b), |
| mergeKind == MergeKind.superclassSetters, |
| classBuilder.library.loader == hierarchy.loader); |
| hierarchy.delayedMemberChecks.add(result); |
| } |
| } else if (classBuilder.isMixinApplication && |
| a.parent != classBuilder) { |
| result = InheritedImplementationInterfaceConflict.combined( |
| classBuilder, |
| a, |
| b, |
| mergeKind == MergeKind.superclassSetters, |
| classBuilder.library.loader == hierarchy.loader, |
| isInheritableConflict: false); |
| if (result is DelayedMember) { |
| hierarchy.delayedMemberChecks.add(result); |
| } |
| } |
| |
| Member target = result.target; |
| if (target.enclosingClass != objectClass.cls && |
| target.name == noSuchMethodName) { |
| hasNoSuchMethod = true; |
| } |
| break; |
| |
| case MergeKind.membersWithSetters: |
| case MergeKind.settersWithMembers: |
| if (a.parent == classBuilder && b.parent != classBuilder) { |
| if (a is FieldBuilder) { |
| if (a.isFinal && b.isSetter) { |
| hierarchy.overrideChecks |
| .add(new DelayedOverrideCheck(classBuilder, a, b)); |
| } else { |
| if (!inferFieldTypes(a, b)) { |
| hierarchy.overrideChecks |
| .add(new DelayedOverrideCheck(classBuilder, a, b)); |
| } |
| } |
| } else if (a is ProcedureBuilder) { |
| if (!inferMethodTypes(a, b)) { |
| hierarchy.overrideChecks |
| .add(new DelayedOverrideCheck(classBuilder, a, b)); |
| } |
| } |
| } |
| break; |
| |
| case MergeKind.interfacesMembers: |
| result = InterfaceConflict.combined(classBuilder, a, b, false, |
| classBuilder.library.loader == hierarchy.loader); |
| break; |
| |
| case MergeKind.interfacesSetters: |
| result = InterfaceConflict.combined(classBuilder, a, b, true, |
| classBuilder.library.loader == hierarchy.loader); |
| break; |
| |
| case MergeKind.supertypesMembers: |
| case MergeKind.supertypesSetters: |
| // [b] is inherited from an interface so it is implicitly abstract. |
| |
| a = AbstractMemberOverridingImplementation.selectAbstract(a); |
| b = AbstractMemberOverridingImplementation.selectAbstract(b); |
| |
| // If [a] is declared in this class, it defines the interface. |
| if (a.parent == classBuilder) { |
| debug?.log("supertypes: checkValidOverride(" |
| "${classBuilder.fullNameForErrors}, " |
| "${fullName(a)}, ${fullName(b)})"); |
| checkValidOverride(a, b); |
| if (a is DelayedMember && !a.isInheritableConflict) { |
| if (b is DelayedMember) { |
| b.addAllDeclarationsTo(a.declarations); |
| } else { |
| addDeclarationIfDifferent(b, a.declarations); |
| } |
| } |
| } else { |
| if (isAbstract(a)) { |
| result = InterfaceConflict.combined( |
| classBuilder, |
| a, |
| b, |
| mergeKind == MergeKind.supertypesSetters, |
| classBuilder.library.loader == hierarchy.loader); |
| } else { |
| result = InheritedImplementationInterfaceConflict.combined( |
| classBuilder, |
| a, |
| b, |
| mergeKind == MergeKind.supertypesSetters, |
| classBuilder.library.loader == hierarchy.loader); |
| } |
| debug?.log("supertypes: ${result}"); |
| if (result is DelayedMember) { |
| hierarchy.delayedMemberChecks.add(result); |
| } |
| } |
| break; |
| } |
| |
| return result; |
| } |
| |
| Builder checkInheritanceConflict(Builder a, Builder b) { |
| if (a is DelayedMember) { |
| Builder result; |
| for (int i = 0; i < a.declarations.length; i++) { |
| Builder d = checkInheritanceConflict(a.declarations[i], b); |
| result ??= d; |
| } |
| return result; |
| } |
| if (b is DelayedMember) { |
| Builder result; |
| for (int i = 0; i < b.declarations.length; i++) { |
| Builder d = checkInheritanceConflict(a, b.declarations[i]); |
| result ??= d; |
| } |
| return result; |
| } |
| if (isInheritanceConflict(a, b)) { |
| reportInheritanceConflict(a, b); |
| return a; |
| } |
| return null; |
| } |
| |
| bool inferMethodTypes(ProcedureBuilder a, Builder b) { |
| debug?.log( |
| "Trying to infer types for ${fullName(a)} based on ${fullName(b)}"); |
| if (b is DelayedMember) { |
| bool hasSameSignature = true; |
| List<Builder> declarations = b.declarations; |
| for (int i = 0; i < declarations.length; i++) { |
| if (!inferMethodTypes(a, declarations[i])) { |
| hasSameSignature = false; |
| } |
| } |
| return hasSameSignature; |
| } |
| if (a.isGetter) { |
| return inferGetterType(a, b); |
| } else if (a.isSetter) { |
| return inferSetterType(a, b); |
| } |
| bool hadTypesInferred = a.hadTypesInferred; |
| ClassBuilder aClassBuilder = a.parent; |
| Substitution aSubstitution; |
| if (classBuilder != aClassBuilder) { |
| assert( |
| substitutions.containsKey(aClassBuilder.cls), |
| "${classBuilder.fullNameForErrors} " |
| "${aClassBuilder.fullNameForErrors}"); |
| aSubstitution = substitutions[aClassBuilder.cls]; |
| debug?.log("${classBuilder.fullNameForErrors} -> " |
| "${aClassBuilder.fullNameForErrors} $aSubstitution"); |
| } |
| ClassBuilder bClassBuilder = b.parent; |
| Substitution bSubstitution; |
| if (classBuilder != bClassBuilder) { |
| assert( |
| substitutions.containsKey(bClassBuilder.cls), |
| "${classBuilder.fullNameForErrors} " |
| "${bClassBuilder.fullNameForErrors}"); |
| bSubstitution = substitutions[bClassBuilder.cls]; |
| debug?.log("${classBuilder.fullNameForErrors} -> " |
| "${bClassBuilder.fullNameForErrors} $bSubstitution"); |
| } |
| Procedure aProcedure = a.procedure; |
| if (b.target is! Procedure) { |
| debug?.log("Giving up 1"); |
| return false; |
| } |
| Procedure bProcedure = b.target; |
| FunctionNode aFunction = aProcedure.function; |
| FunctionNode bFunction = bProcedure.function; |
| |
| List<TypeParameter> aTypeParameters = aFunction.typeParameters; |
| List<TypeParameter> bTypeParameters = bFunction.typeParameters; |
| int typeParameterCount = aTypeParameters.length; |
| if (typeParameterCount != bTypeParameters.length) { |
| debug?.log("Giving up 2"); |
| return false; |
| } |
| Substitution substitution; |
| if (typeParameterCount != 0) { |
| for (int i = 0; i < typeParameterCount; i++) { |
| copyTypeParameterCovariance( |
| a.parent, aTypeParameters[i], bTypeParameters[i]); |
| } |
| List<DartType> types = new List<DartType>(typeParameterCount); |
| for (int i = 0; i < typeParameterCount; i++) { |
| types[i] = new TypeParameterType(aTypeParameters[i]); |
| } |
| substitution = Substitution.fromPairs(bTypeParameters, types); |
| for (int i = 0; i < typeParameterCount; i++) { |
| DartType aBound = aTypeParameters[i].bound; |
| DartType bBound = substitution.substituteType(bTypeParameters[i].bound); |
| if (aBound != bBound) { |
| debug?.log("Giving up 3"); |
| return false; |
| } |
| } |
| } |
| |
| DartType aReturnType = aFunction.returnType; |
| if (aSubstitution != null) { |
| aReturnType = aSubstitution.substituteType(aReturnType); |
| } |
| DartType bReturnType = bFunction.returnType; |
| if (bSubstitution != null) { |
| bReturnType = bSubstitution.substituteType(bReturnType); |
| } |
| if (substitution != null) { |
| bReturnType = substitution.substituteType(bReturnType); |
| } |
| bool result = true; |
| if (aFunction.requiredParameterCount > bFunction.requiredParameterCount) { |
| debug?.log("Giving up 4"); |
| return false; |
| } |
| List<VariableDeclaration> aPositional = aFunction.positionalParameters; |
| List<VariableDeclaration> bPositional = bFunction.positionalParameters; |
| if (aPositional.length < bPositional.length) { |
| debug?.log("Giving up 5"); |
| return false; |
| } |
| |
| if (aReturnType != bReturnType) { |
| if (a.parent == classBuilder && a.returnType == null) { |
| result = inferReturnType( |
| classBuilder, a, bReturnType, hadTypesInferred, hierarchy); |
| } else { |
| debug?.log("Giving up 6"); |
| result = false; |
| } |
| } |
| |
| for (int i = 0; i < bPositional.length; i++) { |
| VariableDeclaration aParameter = aPositional[i]; |
| VariableDeclaration bParameter = bPositional[i]; |
| copyParameterCovariance(a.parent, aParameter, bParameter); |
| DartType aType = aParameter.type; |
| if (aSubstitution != null) { |
| aType = aSubstitution.substituteType(aType); |
| } |
| DartType bType = bParameter.type; |
| if (bSubstitution != null) { |
| bType = bSubstitution.substituteType(bType); |
| } |
| if (substitution != null) { |
| bType = substitution.substituteType(bType); |
| } |
| if (aType != bType) { |
| if (a.parent == classBuilder && a.formals[i].type == null) { |
| result = inferParameterType(classBuilder, a, a.formals[i], bType, |
| hadTypesInferred, hierarchy); |
| } else { |
| debug?.log("Giving up 8"); |
| result = false; |
| } |
| } |
| } |
| |
| List<VariableDeclaration> aNamed = aFunction.namedParameters; |
| List<VariableDeclaration> bNamed = bFunction.namedParameters; |
| named: |
| if (aNamed.isNotEmpty || bNamed.isNotEmpty) { |
| if (aPositional.length != bPositional.length) { |
| debug?.log("Giving up 9"); |
| result = false; |
| break named; |
| } |
| if (aFunction.requiredParameterCount != |
| bFunction.requiredParameterCount) { |
| debug?.log("Giving up 10"); |
| result = false; |
| break named; |
| } |
| |
| aNamed = aNamed.toList()..sort(compareNamedParameters); |
| bNamed = bNamed.toList()..sort(compareNamedParameters); |
| int aCount = 0; |
| for (int bCount = 0; bCount < bNamed.length; bCount++) { |
| String name = bNamed[bCount].name; |
| for (; aCount < aNamed.length; aCount++) { |
| if (aNamed[aCount].name == name) break; |
| } |
| if (aCount == aNamed.length) { |
| debug?.log("Giving up 11"); |
| result = false; |
| break named; |
| } |
| VariableDeclaration aParameter = aNamed[aCount]; |
| VariableDeclaration bParameter = bNamed[bCount]; |
| copyParameterCovariance(a.parent, aParameter, bParameter); |
| DartType aType = aParameter.type; |
| if (aSubstitution != null) { |
| aType = aSubstitution.substituteType(aType); |
| } |
| DartType bType = bParameter.type; |
| if (bSubstitution != null) { |
| bType = bSubstitution.substituteType(bType); |
| } |
| if (substitution != null) { |
| bType = substitution.substituteType(bType); |
| } |
| if (aType != bType) { |
| FormalParameterBuilder parameter; |
| for (int i = aPositional.length; i < a.formals.length; ++i) { |
| if (a.formals[i].name == name) { |
| parameter = a.formals[i]; |
| break; |
| } |
| } |
| if (a.parent == classBuilder && parameter.type == null) { |
| result = inferParameterType( |
| classBuilder, a, parameter, bType, hadTypesInferred, hierarchy); |
| } else { |
| debug?.log("Giving up 12"); |
| result = false; |
| } |
| } |
| } |
| } |
| debug?.log("Inferring types for ${fullName(a)} based on ${fullName(b)} " + |
| (result ? "succeeded." : "failed.")); |
| return result; |
| } |
| |
| bool inferGetterType(ProcedureBuilder a, Builder b) { |
| debug?.log( |
| "Inferring getter types for ${fullName(a)} based on ${fullName(b)}"); |
| Member bTarget = b.target; |
| DartType bType; |
| if (bTarget is Field) { |
| bType = bTarget.type; |
| } else if (bTarget is Procedure) { |
| if (b.isSetter) { |
| VariableDeclaration bParameter = |
| bTarget.function.positionalParameters.single; |
| bType = bParameter.type; |
| if (!hasExplicitlyTypedFormalParameter(b, 0)) { |
| debug?.log("Giving up (type may be inferred)"); |
| return false; |
| } |
| } else if (b.isGetter) { |
| bType = bTarget.function.returnType; |
| if (!hasExplicitReturnType(b)) { |
| debug?.log("Giving up (return type may be inferred)"); |
| return false; |
| } |
| } else { |
| debug?.log("Giving up (not accessor: ${bTarget.kind})"); |
| return false; |
| } |
| } else { |
| debug?.log("Giving up (not field/procedure: ${bTarget.runtimeType})"); |
| return false; |
| } |
| return a.procedure.function.returnType == bType; |
| } |
| |
| bool inferSetterType(ProcedureBuilder a, Builder b) { |
| debug?.log( |
| "Inferring setter types for ${fullName(a)} based on ${fullName(b)}"); |
| Member bTarget = b.target; |
| Procedure aProcedure = a.procedure; |
| VariableDeclaration aParameter = |
| aProcedure.function.positionalParameters.single; |
| DartType bType; |
| if (bTarget is Field) { |
| bType = bTarget.type; |
| copyParameterCovarianceFromField(a.parent, aParameter, bTarget); |
| } |
| if (bTarget is Procedure) { |
| if (b.isSetter) { |
| VariableDeclaration bParameter = |
| bTarget.function.positionalParameters.single; |
| bType = bParameter.type; |
| copyParameterCovariance(a.parent, aParameter, bParameter); |
| if (!hasExplicitlyTypedFormalParameter(b, 0) || |
| !hasExplicitlyTypedFormalParameter(a, 0)) { |
| debug?.log("Giving up (type may be inferred)"); |
| return false; |
| } |
| } else if (b.isGetter) { |
| bType = bTarget.function.returnType; |
| if (!hasExplicitReturnType(b)) { |
| debug?.log("Giving up (return type may be inferred)"); |
| return false; |
| } |
| } else { |
| debug?.log("Giving up (not accessor: ${bTarget.kind})"); |
| return false; |
| } |
| } else { |
| debug?.log("Giving up (not field/procedure: ${bTarget.runtimeType})"); |
| return false; |
| } |
| return aParameter.type == bType; |
| } |
| |
| void checkValidOverride(Builder a, Builder b) { |
| debug?.log( |
| "checkValidOverride(${fullName(a)}, ${fullName(b)}) ${a.runtimeType}"); |
| if (a is ProcedureBuilder) { |
| if (inferMethodTypes(a, b)) return; |
| } else if (a.isField) { |
| if (inferFieldTypes(a, b)) return; |
| } |
| Member aTarget = a.target; |
| Member bTarget = b.target; |
| if (aTarget is Procedure && !aTarget.isAccessor && bTarget is Procedure) { |
| if (hasSameSignature(aTarget.function, bTarget.function)) return; |
| } |
| |
| if (b is DelayedMember) { |
| for (int i = 0; i < b.declarations.length; i++) { |
| hierarchy.overrideChecks |
| .add(new DelayedOverrideCheck(classBuilder, a, b.declarations[i])); |
| } |
| } else { |
| hierarchy.overrideChecks |
| .add(new DelayedOverrideCheck(classBuilder, a, b)); |
| } |
| } |
| |
| bool inferFieldTypes(MemberBuilder a, Builder b) { |
| debug?.log("Trying to infer field types for ${fullName(a)} " |
| "based on ${fullName(b)}"); |
| if (b is DelayedMember) { |
| bool hasSameSignature = true; |
| List<Builder> declarations = b.declarations; |
| for (int i = 0; i < declarations.length; i++) { |
| if (!inferFieldTypes(a, declarations[i])) { |
| hasSameSignature = false; |
| } |
| } |
| return hasSameSignature; |
| } |
| Member bTarget = b.target; |
| DartType inheritedType; |
| if (bTarget is Procedure) { |
| if (bTarget.isSetter) { |
| VariableDeclaration parameter = |
| bTarget.function.positionalParameters.single; |
| // inheritedType = parameter.type; |
| copyFieldCovarianceFromParameter(a.parent, a.member, parameter); |
| if (!hasExplicitlyTypedFormalParameter(b, 0)) { |
| debug?.log("Giving up (type may be inferred)"); |
| return false; |
| } |
| } else if (bTarget.isGetter) { |
| if (!hasExplicitReturnType(b)) return false; |
| inheritedType = bTarget.function.returnType; |
| } |
| } else if (bTarget is Field) { |
| copyFieldCovariance(a.parent, a.member, bTarget); |
| inheritedType = bTarget.type; |
| } |
| if (inheritedType == null) { |
| debug?.log("Giving up (inheritedType == null)\n${StackTrace.current}"); |
| return false; |
| } |
| ClassBuilder aClassBuilder = a.parent; |
| Substitution aSubstitution; |
| if (classBuilder != aClassBuilder) { |
| assert( |
| substitutions.containsKey(aClassBuilder.cls), |
| "${classBuilder.fullNameForErrors} " |
| "${aClassBuilder.fullNameForErrors}"); |
| aSubstitution = substitutions[aClassBuilder.cls]; |
| debug?.log("${classBuilder.fullNameForErrors} -> " |
| "${aClassBuilder.fullNameForErrors} $aSubstitution"); |
| } |
| ClassBuilder bClassBuilder = b.parent; |
| Substitution bSubstitution; |
| if (classBuilder != bClassBuilder) { |
| assert( |
| substitutions.containsKey(bClassBuilder.cls), |
| "${classBuilder.fullNameForErrors} " |
| "${bClassBuilder.fullNameForErrors}"); |
| bSubstitution = substitutions[bClassBuilder.cls]; |
| debug?.log("${classBuilder.fullNameForErrors} -> " |
| "${bClassBuilder.fullNameForErrors} $bSubstitution"); |
| } |
| if (bSubstitution != null && inheritedType is! ImplicitFieldType) { |
| inheritedType = bSubstitution.substituteType(inheritedType); |
| } |
| |
| Field aField = a.member; |
| DartType declaredType = aField.type; |
| if (aSubstitution != null) { |
| declaredType = aSubstitution.substituteType(declaredType); |
| } |
| if (declaredType == inheritedType) return true; |
| |
| bool result = false; |
| if (a is FieldBuilder) { |
| if (a.parent == classBuilder && a.type == null) { |
| if (a.hadTypesInferred) { |
| reportCantInferFieldType(classBuilder, a); |
| inheritedType = const InvalidType(); |
| } else { |
| result = true; |
| a.hadTypesInferred = true; |
| } |
| if (inheritedType is ImplicitFieldType) { |
| SourceLibraryBuilder library = classBuilder.library; |
| (library.implicitlyTypedFields ??= <FieldBuilder>[]).add(a); |
| } |
| a.field.type = inheritedType; |
| } |
| } |
| return result; |
| } |
| |
| void copyParameterCovariance(Builder parent, VariableDeclaration aParameter, |
| VariableDeclaration bParameter) { |
| if (parent == classBuilder) { |
| if (bParameter.isCovariant) { |
| aParameter.isCovariant = true; |
| } |
| if (bParameter.isGenericCovariantImpl) { |
| aParameter.isGenericCovariantImpl = true; |
| } |
| } |
| } |
| |
| void copyParameterCovarianceFromField( |
| Builder parent, VariableDeclaration aParameter, Field bField) { |
| if (parent == classBuilder) { |
| if (bField.isCovariant) { |
| aParameter.isCovariant = true; |
| } |
| if (bField.isGenericCovariantImpl) { |
| aParameter.isGenericCovariantImpl = true; |
| } |
| } |
| } |
| |
| void copyFieldCovariance(Builder parent, Field aField, Field bField) { |
| if (parent == classBuilder) { |
| if (bField.isCovariant) { |
| aField.isCovariant = true; |
| } |
| if (bField.isGenericCovariantImpl) { |
| aField.isGenericCovariantImpl = true; |
| } |
| } |
| } |
| |
| void copyFieldCovarianceFromParameter( |
| Builder parent, Field aField, VariableDeclaration bParameter) { |
| if (parent == classBuilder) { |
| if (bParameter.isCovariant) { |
| aField.isCovariant = true; |
| } |
| if (bParameter.isGenericCovariantImpl) { |
| aField.isGenericCovariantImpl = true; |
| } |
| } |
| } |
| |
| void copyTypeParameterCovariance( |
| Builder parent, TypeParameter aParameter, TypeParameter bParameter) { |
| if (parent == classBuilder) { |
| if (bParameter.isGenericCovariantImpl) { |
| aParameter.isGenericCovariantImpl = true; |
| } |
| } |
| } |
| |
| void reportInheritanceConflict(Builder a, Builder b) { |
| String name = a.fullNameForErrors; |
| if (a.parent != b.parent) { |
| if (a.parent == classBuilder) { |
| classBuilder.addProblem( |
| messageDeclaredMemberConflictsWithInheritedMember, |
| a.charOffset, |
| name.length, |
| context: <LocatedMessage>[ |
| messageDeclaredMemberConflictsWithInheritedMemberCause |
| .withLocation(b.fileUri, b.charOffset, name.length) |
| ]); |
| } else { |
| classBuilder.addProblem(messageInheritedMembersConflict, |
| classBuilder.charOffset, classBuilder.fullNameForErrors.length, |
| context: inheritedConflictContext(a, b)); |
| } |
| } else if (a.isStatic != b.isStatic) { |
| Builder staticMember; |
| Builder instanceMember; |
| if (a.isStatic) { |
| staticMember = a; |
| instanceMember = b; |
| } else { |
| staticMember = b; |
| instanceMember = a; |
| } |
| classBuilder.library.addProblem(messageStaticAndInstanceConflict, |
| staticMember.charOffset, name.length, staticMember.fileUri, |
| context: <LocatedMessage>[ |
| messageStaticAndInstanceConflictCause.withLocation( |
| instanceMember.fileUri, instanceMember.charOffset, name.length) |
| ]); |
| } else { |
| // This message can be reported twice (when merging localMembers with |
| // classSetters, or localSetters with classMembers). By ensuring that |
| // we always report the one with higher charOffset as the duplicate, |
| // the message duplication logic ensures that we only report this |
| // problem once. |
| Builder existing; |
| Builder duplicate; |
| assert(a.fileUri == b.fileUri); |
| if (a.charOffset < b.charOffset) { |
| existing = a; |
| duplicate = b; |
| } else { |
| existing = b; |
| duplicate = a; |
| } |
| classBuilder.library.addProblem( |
| templateDuplicatedDeclaration.withArguments(name), |
| duplicate.charOffset, |
| name.length, |
| duplicate.fileUri, |
| context: <LocatedMessage>[ |
| templateDuplicatedDeclarationCause.withArguments(name).withLocation( |
| existing.fileUri, existing.charOffset, name.length) |
| ]); |
| } |
| } |
| |
| /// When merging `aList` and `bList`, [member] was only found in `aList`. |
| /// |
| /// If [mergeKind] is `MergeKind.superclass` [member] is declared in current |
| /// class, and isn't overriding a method from the superclass. |
| /// |
| /// If [mergeKind] is `MergeKind.interfaces`, [member] is ignored for now. |
| /// |
| /// If [mergeKind] is `MergeKind.supertypes`, [member] isn't |
| /// implementing/overriding anything. |
| void handleOnlyA(Builder member, MergeKind mergeKind) { |
| if (mergeKind == MergeKind.interfacesMembers || |
| mergeKind == MergeKind.interfacesSetters) { |
| return; |
| } |
| // TODO(ahe): Enable this optimization: |
| // if (cls is DillClassBuilder) return; |
| // assert(mergeKind == MergeKind.interfaces || |
| // member is! InterfaceConflict); |
| if ((mergeKind == MergeKind.superclassMembers || |
| mergeKind == MergeKind.superclassSetters) && |
| isAbstract(member)) { |
| recordAbstractMember(member); |
| } |
| } |
| |
| /// When merging `aList` and `bList`, [member] was only found in `bList`. |
| /// |
| /// If [mergeKind] is `MergeKind.superclass` [member] is being inherited from |
| /// a superclass. |
| /// |
| /// If [mergeKind] is `MergeKind.interfaces`, [member] is ignored for now. |
| /// |
| /// If [mergeKind] is `MergeKind.supertypes`, [member] is implicitly |
| /// abstract, and not implemented. |
| Builder handleOnlyB(Builder member, MergeKind mergeKind) { |
| if (mergeKind == MergeKind.interfacesMembers || |
| mergeKind == MergeKind.interfacesSetters) { |
| return member; |
| } |
| // TODO(ahe): Enable this optimization: |
| // if (cls is DillClassBuilder) return member; |
| Member target = member.target; |
| if ((mergeKind == MergeKind.supertypesMembers || |
| mergeKind == MergeKind.supertypesSetters) || |
| ((mergeKind == MergeKind.superclassMembers || |
| mergeKind == MergeKind.superclassSetters) && |
| target.isAbstract)) { |
| if (isNameVisibleIn(target.name, classBuilder.library)) { |
| recordAbstractMember(member); |
| } |
| } |
| if (mergeKind == MergeKind.superclassMembers && |
| target.enclosingClass != objectClass.cls && |
| target.name == noSuchMethodName) { |
| hasNoSuchMethod = true; |
| } |
| if (mergeKind != MergeKind.membersWithSetters && |
| mergeKind != MergeKind.settersWithMembers && |
| member is DelayedMember && |
| member.isInheritableConflict) { |
| hierarchy.delayedMemberChecks.add(member.withParent(classBuilder)); |
| } |
| return member; |
| } |
| |
| void recordAbstractMember(Builder member) { |
| abstractMembers ??= <Builder>[]; |
| if (member is DelayedMember) { |
| abstractMembers.addAll(member.declarations); |
| } else { |
| abstractMembers.add(member); |
| } |
| } |
| |
| ClassHierarchyNode build() { |
| assert(!classBuilder.isPatch); |
| ClassHierarchyNode supernode; |
| if (objectClass != classBuilder.origin) { |
| supernode = hierarchy.getNodeFromType(classBuilder.supertype); |
| if (supernode == null) { |
| supernode = hierarchy.getNodeFromClass(objectClass); |
| } |
| assert(supernode != null); |
| } |
| |
| Scope scope = classBuilder.scope; |
| if (classBuilder.isMixinApplication) { |
| Builder mixin = classBuilder.mixedInType.declaration; |
| inferMixinApplication(); |
| // recordSupertype(cls.mixedInType); |
| while (mixin.isNamedMixinApplication) { |
| ClassBuilder named = mixin; |
| // recordSupertype(named.mixedInType); |
| mixin = named.mixedInType.declaration; |
| } |
| if (mixin is ClassBuilder) { |
| scope = mixin.scope.computeMixinScope(); |
| } |
| } |
| |
| /// Members (excluding setters) declared in [cls]. |
| List<Builder> localMembers = new List<Builder>.from(scope.local.values) |
| ..sort(compareDeclarations); |
| |
| /// Setters declared in [cls]. |
| List<Builder> localSetters = new List<Builder>.from(scope.setters.values) |
| ..sort(compareDeclarations); |
| |
| // Add implied setters from fields in [localMembers]. |
| localSetters = mergeAccessors(localMembers, localSetters); |
| |
| /// Members (excluding setters) declared in [cls] or its superclasses. This |
| /// includes static methods of [cls], but not its superclasses. |
| List<Builder> classMembers; |
| |
| /// Setters declared in [cls] or its superclasses. This includes static |
| /// setters of [cls], but not its superclasses. |
| List<Builder> classSetters; |
| |
| /// Members (excluding setters) inherited from interfaces. This contains no |
| /// static members. Is null if no interfaces are implemented by this class |
| /// or its superclasses. |
| List<Builder> interfaceMembers; |
| |
| /// Setters inherited from interfaces. This contains no static setters. Is |
| /// null if no interfaces are implemented by this class or its |
| /// superclasses. |
| List<Builder> interfaceSetters; |
| |
| List<TypeBuilder> superclasses; |
| |
| List<TypeBuilder> interfaces; |
| |
| int maxInheritancePath; |
| |
| if (supernode == null) { |
| // This should be Object. |
| classMembers = localMembers; |
| classSetters = localSetters; |
| superclasses = new List<TypeBuilder>(0); |
| interfaces = new List<TypeBuilder>(0); |
| maxInheritancePath = 0; |
| } else { |
| maxInheritancePath = supernode.maxInheritancePath + 1; |
| superclasses = new List<TypeBuilder>(supernode.superclasses.length + 1); |
| superclasses.setRange(0, superclasses.length - 1, |
| substSupertypes(classBuilder.supertype, supernode.superclasses)); |
| superclasses[superclasses.length - 1] = |
| recordSupertype(classBuilder.supertype); |
| |
| List<TypeBuilder> directInterfaces = |
| ignoreFunction(classBuilder.interfaces); |
| if (classBuilder.isMixinApplication) { |
| if (directInterfaces == null) { |
| directInterfaces = <TypeBuilder>[classBuilder.mixedInType]; |
| } else { |
| directInterfaces = <TypeBuilder>[classBuilder.mixedInType] |
| ..addAll(directInterfaces); |
| } |
| } |
| if (directInterfaces != null) { |
| for (int i = 0; i < directInterfaces.length; i++) { |
| recordSupertype(directInterfaces[i]); |
| } |
| } |
| List<TypeBuilder> superclassInterfaces = supernode.interfaces; |
| if (superclassInterfaces != null) { |
| superclassInterfaces = |
| substSupertypes(classBuilder.supertype, superclassInterfaces); |
| } |
| |
| classMembers = merge( |
| localMembers, supernode.classMembers, MergeKind.superclassMembers); |
| classSetters = merge( |
| localSetters, supernode.classSetters, MergeKind.superclassSetters); |
| |
| if (directInterfaces != null) { |
| MergeResult result = mergeInterfaces(supernode, directInterfaces); |
| interfaceMembers = result.mergedMembers; |
| interfaceSetters = result.mergedSetters; |
| interfaces = <TypeBuilder>[]; |
| if (superclassInterfaces != null) { |
| for (int i = 0; i < superclassInterfaces.length; i++) { |
| addInterface(interfaces, superclasses, superclassInterfaces[i]); |
| } |
| } |
| for (int i = 0; i < directInterfaces.length; i++) { |
| TypeBuilder directInterface = directInterfaces[i]; |
| addInterface(interfaces, superclasses, directInterface); |
| ClassHierarchyNode interfaceNode = |
| hierarchy.getNodeFromType(directInterface); |
| if (interfaceNode != null) { |
| if (maxInheritancePath < interfaceNode.maxInheritancePath + 1) { |
| maxInheritancePath = interfaceNode.maxInheritancePath + 1; |
| } |
| List<TypeBuilder> types = |
| substSupertypes(directInterface, interfaceNode.superclasses); |
| for (int i = 0; i < types.length; i++) { |
| addInterface(interfaces, superclasses, types[i]); |
| } |
| |
| if (interfaceNode.interfaces != null) { |
| List<TypeBuilder> types = |
| substSupertypes(directInterface, interfaceNode.interfaces); |
| for (int i = 0; i < types.length; i++) { |
| addInterface(interfaces, superclasses, types[i]); |
| } |
| } |
| } |
| } |
| } else { |
| interfaceMembers = supernode.interfaceMembers; |
| interfaceSetters = supernode.interfaceSetters; |
| interfaces = superclassInterfaces; |
| } |
| |
| // Check if local members conflict with inherited setters. This check has |
| // already been performed in the superclass, so we only need to check the |
| // local members. These checks have to occur late to enable inferring |
| // types between setters and getters, or from a setter to a final field. |
| merge(localMembers, classSetters, MergeKind.membersWithSetters); |
| |
| // Check if local setters conflict with inherited members. As above, we |
| // only need to check the local setters. |
| merge(localSetters, classMembers, MergeKind.settersWithMembers); |
| |
| if (interfaceMembers != null) { |
| interfaceMembers = |
| merge(classMembers, interfaceMembers, MergeKind.supertypesMembers); |
| |
| // Check if class setters conflict with members inherited from |
| // interfaces. |
| merge(classSetters, interfaceMembers, MergeKind.settersWithMembers); |
| } |
| if (interfaceSetters != null) { |
| interfaceSetters = |
| merge(classSetters, interfaceSetters, MergeKind.supertypesSetters); |
| |
| // Check if class members conflict with setters inherited from |
| // interfaces. |
| merge(classMembers, interfaceSetters, MergeKind.membersWithSetters); |
| } |
| } |
| if (abstractMembers != null && !classBuilder.isAbstract) { |
| if (!hasNoSuchMethod) { |
| reportMissingMembers(); |
| } else { |
| installNsmHandlers(); |
| } |
| } |
| return new ClassHierarchyNode( |
| classBuilder, |
| classMembers, |
| classSetters, |
| interfaceMembers, |
| interfaceSetters, |
| superclasses, |
| interfaces, |
| maxInheritancePath, |
| hasNoSuchMethod, |
| ); |
| } |
| |
| TypeBuilder recordSupertype(TypeBuilder supertype) { |
| if (supertype is NamedTypeBuilder) { |
| debug?.log("In ${this.classBuilder.fullNameForErrors} " |
| "recordSupertype(${supertype.fullNameForErrors})"); |
| Builder declaration = supertype.declaration; |
| if (declaration is! ClassBuilder) return supertype; |
| ClassBuilder classBuilder = declaration; |
| if (classBuilder.isMixinApplication) { |
| recordSupertype(classBuilder.mixedInType); |
| } |
| List<TypeVariableBuilder> typeVariableBuilders = |
| classBuilder.typeVariables; |
| if (typeVariableBuilders == null) { |
| substitutions[classBuilder.cls] = Substitution.empty; |
| assert(classBuilder.cls.typeParameters.isEmpty); |
| } else { |
| List<TypeBuilder> arguments = |
| supertype.arguments ?? computeDefaultTypeArguments(supertype); |
| if (arguments.length != typeVariableBuilders.length) { |
| arguments = computeDefaultTypeArguments(supertype); |
| } |
| List<DartType> typeArguments = new List<DartType>(arguments.length); |
| List<TypeParameter> typeParameters = |
| new List<TypeParameter>(arguments.length); |
| for (int i = 0; i < arguments.length; i++) { |
| typeParameters[i] = typeVariableBuilders[i].parameter; |
| typeArguments[i] = arguments[i].build(this.classBuilder.parent); |
| } |
| substitutions[classBuilder.cls] = |
| Substitution.fromPairs(typeParameters, typeArguments); |
| } |
| } |
| return supertype; |
| } |
| |
| List<TypeBuilder> substSupertypes( |
| NamedTypeBuilder supertype, List<TypeBuilder> supertypes) { |
| Builder declaration = supertype.declaration; |
| if (declaration is! ClassBuilder) return supertypes; |
| ClassBuilder cls = declaration; |
| List<TypeVariableBuilder> typeVariables = cls.typeVariables; |
| if (typeVariables == null) { |
| debug?.log("In ${this.classBuilder.fullNameForErrors} " |
| "$supertypes aren't substed"); |
| for (int i = 0; i < supertypes.length; i++) { |
| recordSupertype(supertypes[i]); |
| } |
| return supertypes; |
| } |
| Map<TypeVariableBuilder, TypeBuilder> substitution = |
| <TypeVariableBuilder, TypeBuilder>{}; |
| List<TypeBuilder> arguments = |
| supertype.arguments ?? computeDefaultTypeArguments(supertype); |
| for (int i = 0; i < typeVariables.length; i++) { |
| substitution[typeVariables[i]] = arguments[i]; |
| } |
| List<TypeBuilder> result; |
| for (int i = 0; i < supertypes.length; i++) { |
| TypeBuilder supertype = supertypes[i]; |
| TypeBuilder substed = recordSupertype(supertype.subst(substitution)); |
| if (supertype != substed) { |
| debug?.log( |
| "In ${this.classBuilder.fullNameForErrors} $supertype -> $substed"); |
| result ??= supertypes.toList(); |
| result[i] = substed; |
| } else { |
| debug?.log("In ${this.classBuilder.fullNameForErrors} " |
| "$supertype isn't substed"); |
| } |
| } |
| return result ?? supertypes; |
| } |
| |
| List<TypeBuilder> computeDefaultTypeArguments(TypeBuilder type) { |
| ClassBuilder cls = type.declaration; |
| List<TypeBuilder> result = new List<TypeBuilder>(cls.typeVariables.length); |
| for (int i = 0; i < result.length; ++i) { |
| TypeVariableBuilder tv = cls.typeVariables[i]; |
| result[i] = tv.defaultType ?? |
| cls.library.loader.computeTypeBuilder(tv.parameter.defaultType); |
| } |
| return result; |
| } |
| |
| TypeBuilder addInterface(List<TypeBuilder> interfaces, |
| List<TypeBuilder> superclasses, TypeBuilder type) { |
| ClassHierarchyNode node = hierarchy.getNodeFromType(type); |
| if (node == null) return null; |
| int depth = node.depth; |
| int myDepth = superclasses.length; |
| if (depth < myDepth && |
| superclasses[depth].declaration == node.classBuilder) { |
| // This is a potential conflict. |
| return superclasses[depth]; |
| } else { |
| for (int i = 0; i < interfaces.length; i++) { |
| // This is a quadratic algorithm, but normally, the number of |
| // interfaces is really small. |
| if (interfaces[i].declaration == type.declaration) { |
| // This is a potential conflict. |
| return interfaces[i]; |
| } |
| } |
| } |
| interfaces.add(type); |
| return null; |
| } |
| |
| MergeResult mergeInterfaces( |
| ClassHierarchyNode supernode, List<TypeBuilder> interfaces) { |
| debug?.log("mergeInterfaces($classBuilder (${this.classBuilder}) " |
| "${supernode.interfaces} ${interfaces}"); |
| List<List<Builder>> memberLists = |
| new List<List<Builder>>(interfaces.length + 1); |
| List<List<Builder>> setterLists = |
| new List<List<Builder>>(interfaces.length + 1); |
| memberLists[0] = supernode.interfaceMembers; |
| setterLists[0] = supernode.interfaceSetters; |
| for (int i = 0; i < interfaces.length; i++) { |
| ClassHierarchyNode interfaceNode = |
| hierarchy.getNodeFromType(interfaces[i]); |
| if (interfaceNode == null) { |
| memberLists[i + 1] = null; |
| setterLists[i + 1] = null; |
| } else { |
| memberLists[i + 1] = |
| interfaceNode.interfaceMembers ?? interfaceNode.classMembers; |
| setterLists[i + 1] = |
| interfaceNode.interfaceSetters ?? interfaceNode.classSetters; |
| } |
| } |
| return new MergeResult(mergeLists(memberLists, MergeKind.interfacesMembers), |
| mergeLists(setterLists, MergeKind.interfacesSetters)); |
| } |
| |
| List<Builder> mergeLists(List<List<Builder>> input, MergeKind mergeKind) { |
| // This is a k-way merge sort (where k is `input.length + 1`). We merge the |
| // lists pairwise, which reduces the number of lists to merge by half on |
| // each iteration. Consequently, we perform O(log k) merges. |
| while (input.length > 1) { |
| List<List<Builder>> output = <List<Builder>>[]; |
| for (int i = 0; i < input.length - 1; i += 2) { |
| List<Builder> first = input[i]; |
| List<Builder> second = input[i + 1]; |
| if (first == null) { |
| output.add(second); |
| } else if (second == null) { |
| output.add(first); |
| } else { |
| output.add(merge(first, second, mergeKind)); |
| } |
| } |
| if (input.length.isOdd) { |
| output.add(input.last); |
| } |
| input = output; |
| } |
| return input.single; |
| } |
| |
| /// Merge [and check] accessors. This entails copying mutable fields to |
| /// setters to simulate implied setters, and checking that setters don't |
| /// override regular methods. |
| List<Builder> mergeAccessors(List<Builder> members, List<Builder> setters) { |
| final List<Builder> mergedSetters = new List<Builder>.filled( |
| members.length + setters.length, null, |
| growable: true); |
| int storeIndex = 0; |
| int i = 0; |
| int j = 0; |
| while (i < members.length && j < setters.length) { |
| final Builder member = members[i]; |
| final Builder setter = setters[j]; |
| final int compare = compareDeclarations(member, setter); |
| if (compare == 0) { |
| mergedSetters[storeIndex++] = setter; |
| i++; |
| j++; |
| } else if (compare < 0) { |
| if (impliesSetter(member)) { |
| mergedSetters[storeIndex++] = member; |
| } |
| i++; |
| } else { |
| mergedSetters[storeIndex++] = setters[j]; |
| j++; |
| } |
| } |
| while (i < members.length) { |
| final Builder member = members[i]; |
| if (impliesSetter(member)) { |
| mergedSetters[storeIndex++] = member; |
| } |
| i++; |
| } |
| while (j < setters.length) { |
| mergedSetters[storeIndex++] = setters[j]; |
| j++; |
| } |
| |
| if (storeIndex == j) { |
| return setters; |
| } else { |
| return mergedSetters..length = storeIndex; |
| } |
| } |
| |
| void reportMissingMembers() { |
| Map<String, LocatedMessage> contextMap = <String, LocatedMessage>{}; |
| for (int i = 0; i < abstractMembers.length; i++) { |
| Builder declaration = abstractMembers[i]; |
| Member target = declaration.target; |
| if (isNameVisibleIn(target.name, classBuilder.library)) { |
| String name = declaration.fullNameForErrors; |
| String parentName = declaration.parent.fullNameForErrors; |
| String displayName = |
| declaration.isSetter ? "$parentName.$name=" : "$parentName.$name"; |
| contextMap[displayName] = templateMissingImplementationCause |
| .withArguments(displayName) |
| .withLocation( |
| declaration.fileUri, declaration.charOffset, name.length); |
| } |
| } |
| if (contextMap.isEmpty) return; |
| List<String> names = new List<String>.from(contextMap.keys)..sort(); |
| List<LocatedMessage> context = <LocatedMessage>[]; |
| for (int i = 0; i < names.length; i++) { |
| context.add(contextMap[names[i]]); |
| } |
| classBuilder.addProblem( |
| templateMissingImplementationNotAbstract.withArguments( |
| classBuilder.fullNameForErrors, names), |
| classBuilder.charOffset, |
| classBuilder.fullNameForErrors.length, |
| context: context); |
| } |
| |
| void installNsmHandlers() { |
| // TODO(ahe): Implement this. |
| } |
| |
| List<Builder> merge( |
| List<Builder> aList, List<Builder> bList, MergeKind mergeKind) { |
| final List<Builder> result = new List<Builder>.filled( |
| aList.length + bList.length, null, |
| growable: true); |
| int storeIndex = 0; |
| int i = 0; |
| int j = 0; |
| while (i < aList.length && j < bList.length) { |
| final Builder a = aList[i]; |
| final Builder b = bList[j]; |
| if ((mergeKind == MergeKind.interfacesMembers || |
| mergeKind == MergeKind.interfacesSetters) && |
| a.isStatic) { |
| i++; |
| continue; |
| } |
| if (b.isStatic) { |
| j++; |
| continue; |
| } |
| final int compare = compareDeclarations(a, b); |
| if (compare == 0) { |
| result[storeIndex++] = handleMergeConflict(a, b, mergeKind); |
| i++; |
| j++; |
| } else if (compare < 0) { |
| handleOnlyA(a, mergeKind); |
| result[storeIndex++] = a; |
| i++; |
| } else { |
| result[storeIndex++] = handleOnlyB(b, mergeKind); |
| j++; |
| } |
| } |
| while (i < aList.length) { |
| final Builder a = aList[i]; |
| if (!(mergeKind == MergeKind.interfacesMembers || |
| mergeKind == MergeKind.interfacesSetters) || |
| !a.isStatic) { |
| handleOnlyA(a, mergeKind); |
| result[storeIndex++] = a; |
| } |
| i++; |
| } |
| while (j < bList.length) { |
| final Builder b = bList[j]; |
| if (!b.isStatic) { |
| result[storeIndex++] = handleOnlyB(b, mergeKind); |
| } |
| j++; |
| } |
| if (aList.isEmpty && storeIndex == bList.length) return bList; |
| if (bList.isEmpty && storeIndex == aList.length) return aList; |
| return result..length = storeIndex; |
| } |
| |
| void inferMixinApplication() { |
| Class cls = classBuilder.cls; |
| Supertype mixedInType = cls.mixedInType; |
| if (mixedInType == null) return; |
| List<DartType> typeArguments = mixedInType.typeArguments; |
| if (typeArguments.isEmpty || typeArguments.first is! UnknownType) return; |
| new BuilderMixinInferrer( |
| classBuilder, |
| hierarchy.coreTypes, |
| new TypeBuilderConstraintGatherer( |
| hierarchy, mixedInType.classNode.typeParameters)) |
| .infer(cls); |
| List<TypeBuilder> inferredArguments = |
| new List<TypeBuilder>(typeArguments.length); |
| for (int i = 0; i < typeArguments.length; i++) { |
| inferredArguments[i] = |
| hierarchy.loader.computeTypeBuilder(typeArguments[i]); |
| } |
| NamedTypeBuilder mixedInTypeBuilder = classBuilder.mixedInType; |
| mixedInTypeBuilder.arguments = inferredArguments; |
| } |
| |
| /// The class Function from dart:core is supposed to be ignored when used as |
| /// an interface. |
| List<TypeBuilder> ignoreFunction(List<TypeBuilder> interfaces) { |
| if (interfaces == null) return null; |
| for (int i = 0; i < interfaces.length; i++) { |
| ClassBuilder classBuilder = getClass(interfaces[i]); |
| if (classBuilder != null && classBuilder.cls == hierarchy.functionClass) { |
| if (interfaces.length == 1) { |
| return null; |
| } else { |
| interfaces = interfaces.toList(); |
| interfaces.removeAt(i); |
| return ignoreFunction(interfaces); |
| } |
| } |
| } |
| return interfaces; |
| } |
| } |
| |
| class ClassHierarchyNode { |
| /// The class corresponding to this hierarchy node. |
| final ClassBuilder classBuilder; |
| |
| /// All the members of this class including [classMembers] of its |
| /// superclasses. The members are sorted by [compareDeclarations]. |
| final List<Builder> classMembers; |
| |
| /// Similar to [classMembers] but for setters. |
| final List<Builder> classSetters; |
| |
| /// All the interface members of this class including [interfaceMembers] of |
| /// its supertypes. The members are sorted by [compareDeclarations]. |
| /// |
| /// 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 List<Builder> interfaceMembers; |
| |
| /// Similar to [interfaceMembers] but for setters. |
| /// |
| /// This may be null, in which case [classSetters] is the interface setters. |
| final List<Builder> interfaceSetters; |
| |
| /// All superclasses of [classBuilder] excluding itself. The classes are |
| /// sorted by depth from the root (Object) in ascending order. |
| final List<TypeBuilder> superclasses; |
| |
| /// The list of all classes implemented by [classBuilder] and its supertypes |
| /// excluding any classes from [superclasses]. |
| final List<TypeBuilder> interfaces; |
| |
| /// The longest inheritance path from [classBuilder] to `Object`. |
| final int maxInheritancePath; |
| |
| int get depth => superclasses.length; |
| |
| final bool hasNoSuchMethod; |
| |
| ClassHierarchyNode( |
| this.classBuilder, |
| this.classMembers, |
| this.classSetters, |
| this.interfaceMembers, |
| this.interfaceSetters, |
| this.superclasses, |
| this.interfaces, |
| this.maxInheritancePath, |
| this.hasNoSuchMethod); |
| |
| /// Returns a list of all supertypes of [classBuilder], including this node. |
| List<ClassHierarchyNode> computeAllSuperNodes( |
| ClassHierarchyBuilder hierarchy) { |
| List<ClassHierarchyNode> result = new List<ClassHierarchyNode>( |
| 1 + superclasses.length + interfaces.length); |
| for (int i = 0; i < superclasses.length; i++) { |
| Builder declaration = superclasses[i].declaration; |
| if (declaration is ClassBuilder) { |
| result[i] = hierarchy.getNodeFromClass(declaration); |
| } |
| } |
| for (int i = 0; i < interfaces.length; i++) { |
| Builder declaration = interfaces[i].declaration; |
| if (declaration is ClassBuilder) { |
| result[i + superclasses.length] = |
| hierarchy.getNodeFromClass(declaration); |
| } |
| } |
| return result..last = this; |
| } |
| |
| String toString([StringBuffer sb]) { |
| sb ??= new StringBuffer(); |
| sb |
| ..write(classBuilder.fullNameForErrors) |
| ..writeln(":"); |
| if (maxInheritancePath != this.depth) { |
| sb |
| ..write(" Longest path to Object: ") |
| ..writeln(maxInheritancePath); |
| } |
| sb..writeln(" superclasses:"); |
| int depth = 0; |
| for (TypeBuilder superclass in superclasses) { |
| sb.write(" " * (depth + 2)); |
| if (depth != 0) sb.write("-> "); |
| superclass.printOn(sb); |
| sb.writeln(); |
| depth++; |
| } |
| if (interfaces != null) { |
| sb.write(" interfaces:"); |
| bool first = true; |
| for (TypeBuilder i in interfaces) { |
| if (!first) sb.write(","); |
| sb.write(" "); |
| i.printOn(sb); |
| first = false; |
| } |
| sb.writeln(); |
| } |
| printMembers(classMembers, sb, "classMembers"); |
| printMembers(classSetters, sb, "classSetters"); |
| if (interfaceMembers != null) { |
| printMembers(interfaceMembers, sb, "interfaceMembers"); |
| } |
| if (interfaceSetters != null) { |
| printMembers(interfaceSetters, sb, "interfaceSetters"); |
| } |
| return "$sb"; |
| } |
| |
| void printMembers(List<Builder> members, StringBuffer sb, String heading) { |
| sb.write(" "); |
| sb.write(heading); |
| sb.writeln(":"); |
| for (Builder member in members) { |
| sb |
| ..write(" ") |
| ..write(member.parent.fullNameForErrors) |
| ..write(".") |
| ..write(member.fullNameForErrors) |
| ..writeln(); |
| } |
| } |
| |
| Builder getInterfaceMember(Name name, bool isSetter) { |
| return findMember( |
| name, |
| isSetter |
| ? interfaceSetters ?? classSetters |
| : interfaceMembers ?? classMembers); |
| } |
| |
| Builder findMember(Name name, List<Builder> declarations) { |
| // TODO(ahe): Consider creating a map or scope. The obvious choice would be |
| // to use scopes, but they don't handle private names correctly. |
| |
| // This is a copy of `ClassHierarchy.findMemberByName`. |
| int low = 0, high = declarations.length - 1; |
| while (low <= high) { |
| int mid = low + ((high - low) >> 1); |
| Builder pivot = declarations[mid]; |
| int comparison = ClassHierarchy.compareNames(name, pivot.target.name); |
| if (comparison < 0) { |
| high = mid - 1; |
| } else if (comparison > 0) { |
| low = mid + 1; |
| } else if (high != mid) { |
| // Ensure we find the first element of the given name. |
| high = mid; |
| } else { |
| return pivot; |
| } |
| } |
| return null; |
| } |
| |
| Builder getDispatchTarget(Name name, bool isSetter) { |
| return findMember(name, isSetter ? classSetters : classMembers); |
| } |
| |
| static int compareMaxInheritancePath( |
| ClassHierarchyNode a, ClassHierarchyNode b) { |
| return b.maxInheritancePath.compareTo(a.maxInheritancePath); |
| } |
| } |
| |
| class MergeResult { |
| final List<Builder> mergedMembers; |
| |
| final List<Builder> mergedSetters; |
| |
| MergeResult(this.mergedMembers, this.mergedSetters); |
| } |
| |
| enum MergeKind { |
| /// Merging superclass members with the current class. |
| superclassMembers, |
| |
| /// Merging superclass setters with the current class. |
| superclassSetters, |
| |
| /// Merging members of two interfaces. |
| interfacesMembers, |
| |
| /// Merging setters of two interfaces. |
| interfacesSetters, |
| |
| /// Merging class members with interface members. |
| supertypesMembers, |
| |
| /// Merging class setters with interface setters. |
| supertypesSetters, |
| |
| /// Merging members with inherited setters. |
| membersWithSetters, |
| |
| /// Merging setters with inherited members. |
| settersWithMembers, |
| } |
| |
| List<LocatedMessage> inheritedConflictContext(Builder a, Builder b) { |
| return inheritedConflictContextKernel( |
| a.target, b.target, a.fullNameForErrors.length); |
| } |
| |
| List<LocatedMessage> inheritedConflictContextKernel( |
| Member a, Member b, int length) { |
| // TODO(ahe): Delete this method when it isn't used by [InterfaceResolver]. |
| int compare = "${a.fileUri}".compareTo("${b.fileUri}"); |
| if (compare == 0) { |
| compare = a.fileOffset.compareTo(b.fileOffset); |
| } |
| Member first; |
| Member second; |
| if (compare < 0) { |
| first = a; |
| second = b; |
| } else { |
| first = b; |
| second = a; |
| } |
| return <LocatedMessage>[ |
| messageInheritedMembersConflictCause1.withLocation( |
| first.fileUri, first.fileOffset, length), |
| messageInheritedMembersConflictCause2.withLocation( |
| second.fileUri, second.fileOffset, length), |
| ]; |
| } |
| |
| class BuilderMixinInferrer extends MixinInferrer { |
| final ClassBuilder cls; |
| |
| BuilderMixinInferrer( |
| this.cls, CoreTypes coreTypes, TypeBuilderConstraintGatherer gatherer) |
| : super(coreTypes, gatherer); |
| |
| Supertype asInstantiationOf(Supertype type, Class superclass) { |
| InterfaceType interfaceType = |
| gatherer.getTypeAsInstanceOf(type.asInterfaceType, superclass); |
| if (interfaceType == null) return null; |
| return new Supertype(interfaceType.classNode, interfaceType.typeArguments); |
| } |
| |
| void reportProblem(Message message, Class kernelClass) { |
| int length = cls.isMixinApplication ? 1 : cls.fullNameForErrors.length; |
| cls.addProblem(message, cls.charOffset, length); |
| } |
| } |
| |
| class TypeBuilderConstraintGatherer extends TypeConstraintGatherer |
| with StandardBounds { |
| final ClassHierarchyBuilder hierarchy; |
| |
| TypeBuilderConstraintGatherer( |
| this.hierarchy, Iterable<TypeParameter> typeParameters) |
| : super.subclassing(typeParameters); |
| |
| @override |
| Class get objectClass => hierarchy.objectClass; |
| |
| @override |
| Class get functionClass => hierarchy.functionClass; |
| |
| @override |
| Class get futureClass => hierarchy.futureClass; |
| |
| @override |
| Class get futureOrClass => hierarchy.futureOrClass; |
| |
| @override |
| Class get nullClass => hierarchy.nullClass; |
| |
| @override |
| InterfaceType get nullType => hierarchy.coreTypes.nullType; |
| |
| @override |
| InterfaceType get objectLegacyRawType => |
| hierarchy.coreTypes.objectLegacyRawType; |
| |
| @override |
| InterfaceType get functionLegacyRawType => |
| hierarchy.coreTypes.functionLegacyRawType; |
| |
| @override |
| void addLowerBound(TypeConstraint constraint, DartType lower) { |
| constraint.lower = getStandardUpperBound(constraint.lower, lower); |
| } |
| |
| @override |
| void addUpperBound(TypeConstraint constraint, DartType upper) { |
| constraint.upper = getStandardLowerBound(constraint.upper, upper); |
| } |
| |
| @override |
| Member getInterfaceMember(Class class_, Name name, {bool setter: false}) { |
| return null; |
| } |
| |
| @override |
| InterfaceType getTypeAsInstanceOf(InterfaceType type, Class superclass) { |
| return hierarchy.getKernelTypeAsInstanceOf(type, superclass); |
| } |
| |
| @override |
| InterfaceType futureType(DartType type) { |
| return new InterfaceType(hierarchy.futureClass, <DartType>[type]); |
| } |
| |
| @override |
| bool isSubtypeOf( |
| DartType subtype, DartType supertype, SubtypeCheckMode mode) { |
| return hierarchy.types.isSubtypeOfKernel(subtype, supertype, mode); |
| } |
| |
| @override |
| InterfaceType getLegacyLeastUpperBound( |
| InterfaceType type1, InterfaceType type2) { |
| return hierarchy.getKernelLegacyLeastUpperBound(type1, type2); |
| } |
| } |
| |
| class DelayedOverrideCheck { |
| final ClassBuilder classBuilder; |
| final Builder a; |
| final Builder b; |
| |
| const DelayedOverrideCheck(this.classBuilder, this.a, this.b); |
| |
| void check(ClassHierarchyBuilder hierarchy) { |
| void callback( |
| Member declaredMember, Member interfaceMember, bool isSetter) { |
| classBuilder.checkOverride( |
| hierarchy.types, declaredMember, interfaceMember, isSetter, callback, |
| isInterfaceCheck: !classBuilder.isMixinApplication); |
| } |
| |
| Builder a = this.a; |
| debug?.log("Delayed override check of ${fullName(a)} " |
| "${fullName(b)} wrt. ${classBuilder.fullNameForErrors}"); |
| if (classBuilder == a.parent) { |
| if (a is ProcedureBuilder) { |
| if (a.isGetter && !hasExplicitReturnType(a)) { |
| DartType type; |
| if (b.isGetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.returnType; |
| } else if (b.isSetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.positionalParameters.single.type; |
| } else if (b.isField) { |
| Field bTarget = b.target; |
| type = bTarget.type; |
| } |
| if (type != null) { |
| type = Substitution.fromInterfaceType( |
| hierarchy.getKernelTypeAsInstanceOf( |
| classBuilder.cls.thisType, b.target.enclosingClass)) |
| .substituteType(type); |
| if (!a.hadTypesInferred || !b.isSetter) { |
| inferReturnType( |
| classBuilder, a, type, a.hadTypesInferred, hierarchy); |
| } |
| } |
| } else if (a.isSetter && !hasExplicitlyTypedFormalParameter(a, 0)) { |
| DartType type; |
| if (b.isGetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.returnType; |
| } else if (b.isSetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.positionalParameters.single.type; |
| } else if (b.isField) { |
| Field bTarget = b.target; |
| type = bTarget.type; |
| } |
| if (type != null) { |
| type = Substitution.fromInterfaceType( |
| hierarchy.getKernelTypeAsInstanceOf( |
| classBuilder.cls.thisType, b.target.enclosingClass)) |
| .substituteType(type); |
| if (!a.hadTypesInferred || !b.isGetter) { |
| inferParameterType(classBuilder, a, a.formals.single, type, |
| a.hadTypesInferred, hierarchy); |
| } |
| } |
| } |
| a.hadTypesInferred = true; |
| } else if (a is FieldBuilder && a.type == null) { |
| DartType type; |
| if (b.isGetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.returnType; |
| } else if (b.isSetter) { |
| Procedure bTarget = b.target; |
| type = bTarget.function.positionalParameters.single.type; |
| } else if (b.isField) { |
| Field bTarget = b.target; |
| type = bTarget.type; |
| } |
| if (type != null) { |
| type = Substitution.fromInterfaceType( |
| hierarchy.getKernelTypeAsInstanceOf( |
| classBuilder.cls.thisType, b.target.enclosingClass)) |
| .substituteType(type); |
| if (type != a.field.type) { |
| if (a.hadTypesInferred) { |
| if (b.isSetter && |
| (!impliesSetter(a) || |
| hierarchy.types.isSubtypeOfKernel(type, a.field.type, |
| SubtypeCheckMode.ignoringNullabilities))) { |
| type = a.field.type; |
| } else { |
| reportCantInferFieldType(classBuilder, a); |
| type = const InvalidType(); |
| } |
| } |
| debug?.log("Inferred type ${type} for ${fullName(a)}"); |
| a.field.type = type; |
| } |
| } |
| a.hadTypesInferred = true; |
| } |
| } |
| |
| callback(a.target, b.target, a.isSetter); |
| } |
| } |
| |
| abstract class DelayedMember extends Builder { |
| /// The class which has inherited [declarations]. |
| @override |
| final ClassBuilder parent; |
| |
| /// Conflicting declarations. |
| final List<Builder> declarations; |
| |
| final bool isSetter; |
| |
| final bool modifyKernel; |
| |
| DelayedMember( |
| this.parent, this.declarations, this.isSetter, this.modifyKernel); |
| |
| void addAllDeclarationsTo(List<Builder> declarations) { |
| for (int i = 0; i < this.declarations.length; i++) { |
| addDeclarationIfDifferent(this.declarations[i], declarations); |
| } |
| assert(declarations.toSet().length == declarations.length); |
| } |
| |
| Member check(ClassHierarchyBuilder hierarchy); |
| |
| DelayedMember withParent(ClassBuilder parent); |
| |
| @override |
| Uri get fileUri => parent.fileUri; |
| |
| @override |
| int get charOffset => parent.charOffset; |
| |
| @override |
| String get fullNameForErrors => declarations.map(fullName).join("%"); |
| |
| bool get isInheritableConflict => true; |
| |
| @override |
| Member get target => declarations.first.target; |
| } |
| |
| /// This represents a concrete implementation inherited from a superclass that |
| /// has conflicts with methods inherited from an interface. The concrete |
| /// implementation is the first element of [declarations]. |
| class InheritedImplementationInterfaceConflict extends DelayedMember { |
| Member combinedMemberSignatureResult; |
| |
| @override |
| final bool isInheritableConflict; |
| |
| InheritedImplementationInterfaceConflict(ClassBuilder parent, |
| List<Builder> declarations, bool isSetter, bool modifyKernel, |
| {this.isInheritableConflict = true}) |
| : super(parent, declarations, isSetter, modifyKernel); |
| |
| @override |
| String toString() { |
| return "InheritedImplementationInterfaceConflict(" |
| "${parent.fullNameForErrors}, " |
| "[${declarations.map(fullName).join(', ')}])"; |
| } |
| |
| @override |
| Member check(ClassHierarchyBuilder hierarchy) { |
| if (combinedMemberSignatureResult != null) { |
| return combinedMemberSignatureResult; |
| } |
| if (!parent.isAbstract) { |
| Builder concreteImplementation = declarations.first; |
| for (int i = 1; i < declarations.length; i++) { |
| new DelayedOverrideCheck( |
| parent, concreteImplementation, declarations[i]) |
| .check(hierarchy); |
| } |
| } |
| return combinedMemberSignatureResult = |
| new InterfaceConflict(parent, declarations, isSetter, modifyKernel) |
| .check(hierarchy); |
| } |
| |
| @override |
| DelayedMember withParent(ClassBuilder parent) { |
| return parent == this.parent |
| ? this |
| : new InheritedImplementationInterfaceConflict( |
| parent, declarations, isSetter, modifyKernel); |
| } |
| |
| static Builder combined(ClassBuilder parent, Builder concreteImplementation, |
| Builder other, bool isSetter, bool createForwarders, |
| {bool isInheritableConflict = true}) { |
| List<Builder> declarations = <Builder>[]; |
| if (concreteImplementation is DelayedMember) { |
| concreteImplementation.addAllDeclarationsTo(declarations); |
| } else { |
| declarations.add(concreteImplementation); |
| } |
| if (other is DelayedMember) { |
| other.addAllDeclarationsTo(declarations); |
| } else { |
| addDeclarationIfDifferent(other, declarations); |
| } |
| if (declarations.length == 1) { |
| return declarations.single; |
| } else { |
| return new InheritedImplementationInterfaceConflict( |
| parent, declarations, isSetter, createForwarders, |
| isInheritableConflict: isInheritableConflict); |
| } |
| } |
| } |
| |
| class InterfaceConflict extends DelayedMember { |
| InterfaceConflict(ClassBuilder parent, List<Builder> declarations, |
| bool isSetter, bool modifyKernel) |
| : super(parent, declarations, isSetter, modifyKernel); |
| |
| Member combinedMemberSignatureResult; |
| |
| @override |
| String toString() { |
| return "InterfaceConflict(${parent.fullNameForErrors}, " |
| "[${declarations.map(fullName).join(', ')}])"; |
| } |
| |
| DartType computeMemberType( |
| ClassHierarchyBuilder hierarchy, DartType thisType, Member member) { |
| DartType type; |
| if (member is Procedure) { |
| if (member.isGetter) { |
| type = member.getterType; |
| } else if (member.isSetter) { |
| type = member.setterType; |
| } else { |
| type = member.function.functionType; |
| } |
| } else if (member is Field) { |
| type = member.type; |
| } else { |
| unhandled("${member.runtimeType}", "$member", parent.charOffset, |
| parent.fileUri); |
| } |
| return Substitution.fromInterfaceType(hierarchy.getKernelTypeAsInstanceOf( |
| thisType, member.enclosingClass)) |
| .substituteType(type); |
| } |
| |
| bool isMoreSpecific(ClassHierarchyBuilder hierarchy, DartType a, DartType b) { |
| if (isSetter) { |
| return hierarchy.types |
| .isSubtypeOfKernel(b, a, SubtypeCheckMode.ignoringNullabilities); |
| } else { |
| return hierarchy.types |
| .isSubtypeOfKernel(a, b, SubtypeCheckMode.ignoringNullabilities); |
| } |
| } |
| |
| @override |
| Member check(ClassHierarchyBuilder hierarchy) { |
| if (combinedMemberSignatureResult != null) { |
| return combinedMemberSignatureResult; |
| } |
| if (parent.library is! SourceLibraryBuilder) { |
| return combinedMemberSignatureResult = declarations.first.target; |
| } |
| DartType thisType = parent.cls.thisType; |
| Builder bestSoFar; |
| DartType bestTypeSoFar; |
| for (int i = declarations.length - 1; i >= 0; i--) { |
| Builder candidate = declarations[i]; |
| Member target = candidate.target; |
| DartType candidateType = computeMemberType(hierarchy, thisType, target); |
| if (bestSoFar == null) { |
| bestSoFar = candidate; |
| bestTypeSoFar = candidateType; |
| } else { |
| if (isMoreSpecific(hierarchy, candidateType, bestTypeSoFar)) { |
| debug?.log("Combined Member Signature: ${fullName(candidate)} " |
| "${candidateType} <: ${fullName(bestSoFar)} ${bestTypeSoFar}"); |
| bestSoFar = candidate; |
| bestTypeSoFar = candidateType; |
| } else { |
| debug?.log("Combined Member Signature: " |
| "${fullName(candidate)} !<: ${fullName(bestSoFar)}"); |
| } |
| } |
| } |
| if (bestSoFar != null) { |
| debug?.log("Combined Member Signature bestSoFar: ${fullName(bestSoFar)}"); |
| for (int i = 0; i < declarations.length; i++) { |
| Builder candidate = declarations[i]; |
| Member target = candidate.target; |
| DartType candidateType = computeMemberType(hierarchy, thisType, target); |
| if (!isMoreSpecific(hierarchy, bestTypeSoFar, candidateType)) { |
| debug?.log("Combined Member Signature: " |
| "${fullName(bestSoFar)} !<: ${fullName(candidate)}"); |
| |
| String uri = '${parent.library.uri}'; |
| if (uri == 'dart:js' && |
| parent.fileUri.pathSegments.last == 'js_dart2js.dart' || |
| uri == 'dart:_interceptors' && |
| parent.fileUri.pathSegments.last == 'js_number.dart') { |
| // TODO(johnniwinther): Fix the dart2js libraries and remove the |
| // above URIs. |
| } else { |
| bestSoFar = null; |
| bestTypeSoFar = null; |
| } |
| break; |
| } |
| } |
| } |
| if (bestSoFar == null) { |
| String name = parent.fullNameForErrors; |
| int length = parent.isAnonymousMixinApplication ? 1 : name.length; |
| List<LocatedMessage> context = declarations.map((Builder d) { |
| return messageDeclaredMemberConflictsWithInheritedMemberCause |
| .withLocation(d.fileUri, d.charOffset, d.fullNameForErrors.length); |
| }).toList(); |
| |
| parent.addProblem( |
| templateCombinedMemberSignatureFailed.withArguments( |
| parent.fullNameForErrors, declarations.first.fullNameForErrors), |
| parent.charOffset, |
| length, |
| context: context); |
| return null; |
| } |
| debug?.log("Combined Member Signature of ${fullNameForErrors}: " |
| "${fullName(bestSoFar)}"); |
| |
| ProcedureKind kind = ProcedureKind.Method; |
| if (bestSoFar.isField || bestSoFar.isSetter || bestSoFar.isGetter) { |
| kind = isSetter ? ProcedureKind.Setter : ProcedureKind.Getter; |
| } else if (bestSoFar.target is Procedure && |
| bestSoFar.target.kind == ProcedureKind.Operator) { |
| kind = ProcedureKind.Operator; |
| } |
| |
| if (modifyKernel) { |
| debug?.log("Combined Member Signature of ${fullNameForErrors}: " |
| "new ForwardingNode($parent, $bestSoFar, $declarations, $kind)"); |
| Member stub = |
| new ForwardingNode(hierarchy, parent, bestSoFar, declarations, kind) |
| .finalize(); |
| if (parent.cls == stub.enclosingClass) { |
| parent.cls.addMember(stub); |
| SourceLibraryBuilder library = parent.library; |
| if (bestSoFar.target is Procedure) { |
| library.forwardersOrigins..add(stub)..add(bestSoFar.target); |
| } |
| debug?.log("Combined Member Signature of ${fullNameForErrors}: " |
| "added stub $stub"); |
| if (parent.isMixinApplication) { |
| return combinedMemberSignatureResult = bestSoFar.target; |
| } else { |
| return combinedMemberSignatureResult = stub; |
| } |
| } |
| } |
| |
| debug?.log( |
| "Combined Member Signature of ${fullNameForErrors}: picked bestSoFar"); |
| return combinedMemberSignatureResult = bestSoFar.target; |
| } |
| |
| @override |
| DelayedMember withParent(ClassBuilder parent) { |
| return parent == this.parent |
| ? this |
| : new InterfaceConflict(parent, declarations, isSetter, modifyKernel); |
| } |
| |
| static Builder combined(ClassBuilder parent, Builder a, Builder b, |
| bool isSetter, bool createForwarders) { |
| List<Builder> declarations = <Builder>[]; |
| if (a is DelayedMember) { |
| a.addAllDeclarationsTo(declarations); |
| } else { |
| declarations.add(a); |
| } |
| if (b is DelayedMember) { |
| b.addAllDeclarationsTo(declarations); |
| } else { |
| addDeclarationIfDifferent(b, declarations); |
| } |
| if (declarations.length == 1) { |
| return declarations.single; |
| } else { |
| return new InterfaceConflict( |
| parent, declarations, isSetter, createForwarders); |
| } |
| } |
| } |
| |
| class AbstractMemberOverridingImplementation extends DelayedMember { |
| AbstractMemberOverridingImplementation( |
| ClassBuilder parent, |
| Builder abstractMember, |
| Builder concreteImplementation, |
| bool isSetter, |
| bool modifyKernel) |
| : super(parent, <Builder>[concreteImplementation, abstractMember], |
| isSetter, modifyKernel); |
| |
| Builder get concreteImplementation => declarations[0]; |
| |
| Builder get abstractMember => declarations[1]; |
| |
| Member check(ClassHierarchyBuilder hierarchy) { |
| if (!parent.isAbstract && !hierarchy.nodes[parent.cls].hasNoSuchMethod) { |
| new DelayedOverrideCheck(parent, concreteImplementation, abstractMember) |
| .check(hierarchy); |
| } |
| |
| ProcedureKind kind = ProcedureKind.Method; |
| if (abstractMember.isSetter || abstractMember.isGetter) { |
| kind = isSetter ? ProcedureKind.Setter : ProcedureKind.Getter; |
| } |
| if (modifyKernel) { |
| // This call will add a body to the abstract method if needed for |
| // isGenericCovariantImpl checks. |
| new ForwardingNode(hierarchy, parent, abstractMember, declarations, kind) |
| .finalize(); |
| } |
| return abstractMember.target; |
| } |
| |
| @override |
| DelayedMember withParent(ClassBuilder parent) { |
| return parent == this.parent |
| ? this |
| : new AbstractMemberOverridingImplementation(parent, abstractMember, |
| concreteImplementation, isSetter, modifyKernel); |
| } |
| |
| static Builder selectAbstract(Builder declaration) { |
| if (declaration is AbstractMemberOverridingImplementation) { |
| return declaration.abstractMember; |
| } else { |
| return declaration; |
| } |
| } |
| |
| static Builder selectConcrete(Builder declaration) { |
| if (declaration is AbstractMemberOverridingImplementation) { |
| return declaration.concreteImplementation; |
| } else { |
| return declaration; |
| } |
| } |
| } |
| |
| void addDeclarationIfDifferent( |
| Builder declaration, List<Builder> declarations) { |
| Member target = declaration.target; |
| if (target is Procedure) { |
| FunctionNode function = target.function; |
| for (int i = 0; i < declarations.length; i++) { |
| Member other = declarations[i].target; |
| if (other is Procedure) { |
| if (hasSameSignature(function, other.function)) return; |
| } |
| } |
| } else { |
| for (int i = 0; i < declarations.length; i++) { |
| if (declaration == declarations[i]) return; |
| } |
| } |
| declarations.add(declaration); |
| } |
| |
| String fullName(Builder declaration) { |
| String suffix = declaration.isSetter ? "=" : ""; |
| if (declaration is DelayedMember) { |
| return "${declaration.fullNameForErrors}$suffix"; |
| } |
| Builder parent = declaration.parent; |
| return parent == null |
| ? "${declaration.fullNameForErrors}$suffix" |
| : "${parent.fullNameForErrors}.${declaration.fullNameForErrors}$suffix"; |
| } |
| |
| int compareNamedParameters(VariableDeclaration a, VariableDeclaration b) { |
| return a.name.compareTo(b.name); |
| } |
| |
| bool isAbstract(Builder declaration) { |
| return declaration.target.isAbstract || declaration is InterfaceConflict; |
| } |
| |
| bool inferParameterType( |
| ClassBuilder cls, |
| ProcedureBuilder member, |
| FormalParameterBuilder parameter, |
| DartType type, |
| bool hadTypesInferred, |
| ClassHierarchyBuilder hierarchy) { |
| debug?.log("Inferred type ${type} for ${parameter}"); |
| if (type == parameter.target.type) return true; |
| bool result = true; |
| if (hadTypesInferred) { |
| reportCantInferParameterType(cls, member, parameter, hierarchy); |
| type = const InvalidType(); |
| result = false; |
| } |
| parameter.target.type = type; |
| member.hadTypesInferred = true; |
| return result; |
| } |
| |
| void reportCantInferParameterType(ClassBuilder cls, MemberBuilder member, |
| FormalParameterBuilder parameter, ClassHierarchyBuilder hierarchy) { |
| String name = parameter.name; |
| cls.addProblem( |
| templateCantInferTypeDueToInconsistentOverrides.withArguments(name), |
| parameter.charOffset, |
| name.length, |
| wasHandled: true); |
| } |
| |
| bool inferReturnType(ClassBuilder cls, ProcedureBuilder procedureBuilder, |
| DartType type, bool hadTypesInferred, ClassHierarchyBuilder hierarchy) { |
| if (type == procedureBuilder.procedure.function.returnType) return true; |
| bool result = true; |
| if (hadTypesInferred) { |
| reportCantInferReturnType(cls, procedureBuilder, hierarchy); |
| type = const InvalidType(); |
| result = false; |
| } else { |
| procedureBuilder.hadTypesInferred = true; |
| } |
| procedureBuilder.procedure.function.returnType = type; |
| return result; |
| } |
| |
| void reportCantInferReturnType( |
| ClassBuilder cls, MemberBuilder member, ClassHierarchyBuilder hierarchy) { |
| String name = member.fullNameForErrors; |
| List<LocatedMessage> context; |
| // // TODO(ahe): The following is for debugging, but could be cleaned up and |
| // // used to improve this error message in general. |
| // |
| // context = <LocatedMessage>[]; |
| // ClassHierarchyNode supernode = hierarchy.getNodeFromType(cls.supertype); |
| // // TODO(ahe): Wrong template. |
| // Template<Message Function(String)> template = |
| // templateMissingImplementationCause; |
| // if (supernode != null) { |
| // Declaration superMember = |
| // supernode.getInterfaceMember(new Name(name), false); |
| // if (superMember != null) { |
| // context.add(template |
| // .withArguments(name) |
| // .withLocation( |
| // superMember.fileUri, superMember.charOffset, name.length)); |
| // } |
| // superMember = supernode.getInterfaceMember(new Name(name), true); |
| // if (superMember != null) { |
| // context.add(template |
| // .withArguments(name) |
| // .withLocation( |
| // superMember.fileUri, superMember.charOffset, name.length)); |
| // } |
| // } |
| // List<TypeBuilder> directInterfaces = cls.interfaces; |
| // for (int i = 0; i < directInterfaces.length; i++) { |
| // ClassHierarchyNode supernode = |
| // hierarchy.getNodeFromType(directInterfaces[i]); |
| // if (supernode != null) { |
| // Declaration superMember = |
| // supernode.getInterfaceMember(new Name(name), false); |
| // if (superMember != null) { |
| // context.add(template |
| // .withArguments(name) |
| // .withLocation( |
| // superMember.fileUri, superMember.charOffset, name.length)); |
| // } |
| // superMember = supernode.getInterfaceMember(new Name(name), true); |
| // if (superMember != null) { |
| // context.add(template |
| // .withArguments(name) |
| // .withLocation( |
| // superMember.fileUri, superMember.charOffset, name.length)); |
| // } |
| // } |
| // } |
| cls.addProblem( |
| templateCantInferReturnTypeDueToInconsistentOverrides.withArguments(name), |
| member.charOffset, |
| name.length, |
| wasHandled: true, |
| context: context); |
| } |
| |
| void reportCantInferFieldType(ClassBuilder cls, FieldBuilder member) { |
| String name = member.fullNameForErrors; |
| cls.addProblem( |
| templateCantInferTypeDueToInconsistentOverrides.withArguments(name), |
| member.charOffset, |
| name.length, |
| wasHandled: true); |
| } |
| |
| ClassBuilder getClass(TypeBuilder type) { |
| Builder declaration = type.declaration; |
| return declaration is ClassBuilder ? declaration : null; |
| } |
| |
| bool hasExplicitReturnType(Builder declaration) { |
| assert(declaration is ProcedureBuilder || declaration is DillMemberBuilder, |
| "${declaration.runtimeType}"); |
| return declaration is ProcedureBuilder |
| ? declaration.returnType != null |
| : true; |
| } |
| |
| bool hasExplicitlyTypedFormalParameter(Builder declaration, int index) { |
| assert(declaration is ProcedureBuilder || declaration is DillMemberBuilder, |
| "${declaration.runtimeType}"); |
| return declaration is ProcedureBuilder |
| ? declaration.formals[index].type != null |
| : true; |
| } |