| // 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. |
| |
| import 'package:analyzer/dart/analysis/features.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/constant/value.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_provider.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/error/correct_override.dart'; |
| import 'package:analyzer/src/error/getter_setter_types_verifier.dart'; |
| import 'package:analyzer/src/task/inference_error.dart'; |
| |
| class InheritanceOverrideVerifier { |
| static const _missingOverridesKey = 'missingOverrides'; |
| |
| final TypeSystemImpl _typeSystem; |
| final TypeProvider _typeProvider; |
| final InheritanceManager3 _inheritance; |
| final ErrorReporter _reporter; |
| |
| InheritanceOverrideVerifier( |
| this._typeSystem, this._inheritance, this._reporter) |
| : _typeProvider = _typeSystem.typeProvider; |
| |
| void verifyUnit(CompilationUnit unit) { |
| var library = unit.declaredElement!.library as LibraryElementImpl; |
| for (var declaration in unit.declarations) { |
| if (declaration is ClassDeclaration) { |
| _ClassVerifier( |
| typeSystem: _typeSystem, |
| typeProvider: _typeProvider, |
| inheritance: _inheritance, |
| reporter: _reporter, |
| featureSet: unit.featureSet, |
| library: library, |
| classNameNode: declaration.name, |
| implementsClause: declaration.implementsClause, |
| members: declaration.members, |
| superclass: declaration.extendsClause?.superclass, |
| withClause: declaration.withClause, |
| ).verify(); |
| } else if (declaration is ClassTypeAlias) { |
| _ClassVerifier( |
| typeSystem: _typeSystem, |
| typeProvider: _typeProvider, |
| inheritance: _inheritance, |
| reporter: _reporter, |
| featureSet: unit.featureSet, |
| library: library, |
| classNameNode: declaration.name, |
| implementsClause: declaration.implementsClause, |
| superclass: declaration.superclass, |
| withClause: declaration.withClause, |
| ).verify(); |
| } else if (declaration is EnumDeclaration) { |
| _ClassVerifier( |
| typeSystem: _typeSystem, |
| typeProvider: _typeProvider, |
| inheritance: _inheritance, |
| reporter: _reporter, |
| featureSet: unit.featureSet, |
| library: library, |
| classNameNode: declaration.name, |
| implementsClause: declaration.implementsClause, |
| members: declaration.members, |
| withClause: declaration.withClause, |
| ).verify(); |
| } else if (declaration is MixinDeclaration) { |
| _ClassVerifier( |
| typeSystem: _typeSystem, |
| typeProvider: _typeProvider, |
| inheritance: _inheritance, |
| reporter: _reporter, |
| featureSet: unit.featureSet, |
| library: library, |
| classNameNode: declaration.name, |
| implementsClause: declaration.implementsClause, |
| members: declaration.members, |
| onClause: declaration.onClause, |
| ).verify(); |
| } |
| } |
| } |
| |
| /// Returns [ExecutableElement] members that are in the interface of the |
| /// given class, but don't have concrete implementations. |
| static List<ExecutableElement> missingOverrides(ClassDeclaration node) { |
| return node.name.getProperty(_missingOverridesKey) ?? const []; |
| } |
| } |
| |
| class _ClassVerifier { |
| final TypeSystemImpl typeSystem; |
| final TypeProvider typeProvider; |
| final InheritanceManager3 inheritance; |
| final ErrorReporter reporter; |
| |
| final FeatureSet featureSet; |
| final LibraryElementImpl library; |
| final Uri libraryUri; |
| final AbstractClassElementImpl classElement; |
| |
| final SimpleIdentifier classNameNode; |
| final List<ClassMember> members; |
| final ImplementsClause? implementsClause; |
| final OnClause? onClause; |
| final NamedType? superclass; |
| final WithClause? withClause; |
| |
| final List<InterfaceType> directSuperInterfaces = []; |
| |
| late final implementsDartCoreEnum = |
| classElement.allSupertypes.any((e) => e.isDartCoreEnum); |
| |
| _ClassVerifier({ |
| required this.typeSystem, |
| required this.typeProvider, |
| required this.inheritance, |
| required this.reporter, |
| required this.featureSet, |
| required this.library, |
| required this.classNameNode, |
| this.implementsClause, |
| this.members = const [], |
| this.onClause, |
| this.superclass, |
| this.withClause, |
| }) : libraryUri = library.source.uri, |
| classElement = classNameNode.staticElement as AbstractClassElementImpl; |
| |
| bool get _isNonNullableByDefault => typeSystem.isNonNullableByDefault; |
| |
| void verify() { |
| if (_checkDirectSuperTypes()) { |
| return; |
| } |
| |
| if (!classElement.isEnum && |
| !classElement.isAbstract && |
| implementsDartCoreEnum) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE, |
| classNameNode, |
| ); |
| return; |
| } |
| |
| if (_checkForRecursiveInterfaceInheritance(classElement)) { |
| return; |
| } |
| |
| // Compute the interface of the class. |
| var interface = inheritance.getInterface(classElement); |
| |
| // Report conflicts between direct superinterfaces of the class. |
| for (var conflict in interface.conflicts) { |
| _reportInconsistentInheritance(classNameNode, conflict); |
| } |
| |
| if (classElement.supertype != null) { |
| directSuperInterfaces.add(classElement.supertype!); |
| } |
| directSuperInterfaces.addAll(classElement.superclassConstraints); |
| |
| // Each mixin in `class C extends S with M0, M1, M2 {}` is equivalent to: |
| // class S&M0 extends S { ...members of M0... } |
| // class S&M1 extends S&M0 { ...members of M1... } |
| // class S&M2 extends S&M1 { ...members of M2... } |
| // class C extends S&M2 { ...members of C... } |
| // So, we need to check members of each mixin against superinterfaces |
| // of `S`, and superinterfaces of all previous mixins. |
| var mixinNodes = withClause?.mixinTypes; |
| var mixinTypes = classElement.mixins; |
| for (var i = 0; i < mixinTypes.length; i++) { |
| var mixinType = mixinTypes[i]; |
| _checkDeclaredMembers(mixinNodes![i], mixinType, mixinIndex: i); |
| directSuperInterfaces.add(mixinType); |
| } |
| |
| directSuperInterfaces.addAll(classElement.interfaces); |
| |
| // Check the members if the class itself, against all the previously |
| // collected superinterfaces of the supertype, mixins, and interfaces. |
| for (var member in members) { |
| if (member is FieldDeclaration) { |
| var fieldList = member.fields; |
| for (var field in fieldList.variables) { |
| var fieldElement = field.declaredElement as FieldElement; |
| _checkDeclaredMember(field.name, libraryUri, fieldElement.getter); |
| _checkDeclaredMember(field.name, libraryUri, fieldElement.setter); |
| if (!member.isStatic && !classElement.isEnum) { |
| _checkIllegalEnumValuesDeclaration(field.name); |
| } |
| if (!member.isStatic) { |
| _checkIllegalNonAbstractEnumIndex(field.name); |
| } |
| } |
| } else if (member is MethodDeclaration) { |
| var hasError = _reportNoCombinedSuperSignature(member); |
| if (hasError) { |
| continue; |
| } |
| |
| _checkDeclaredMember(member.name, libraryUri, member.declaredElement, |
| methodParameterNodes: member.parameters?.parameters); |
| if (!member.isStatic && !classElement.isEnum) { |
| _checkIllegalEnumValuesDeclaration(member.name); |
| } |
| if (!(member.isStatic || member.isAbstract || member.isSetter)) { |
| _checkIllegalNonAbstractEnumIndex(member.name); |
| } |
| } |
| } |
| |
| _checkIllegalEnumValuesInheritance(); |
| |
| GetterSetterTypesVerifier( |
| typeSystem: typeSystem, |
| errorReporter: reporter, |
| ).checkInterface(classElement, interface); |
| |
| if (!classElement.isAbstract) { |
| List<ExecutableElement>? inheritedAbstract; |
| |
| for (var name in interface.map.keys) { |
| if (!name.isAccessibleFor(libraryUri)) { |
| continue; |
| } |
| |
| var interfaceElement = interface.map[name]!; |
| var concreteElement = interface.implemented[name]; |
| |
| // No concrete implementation of the name. |
| if (concreteElement == null) { |
| if (!_reportConcreteClassWithAbstractMember(name.name)) { |
| inheritedAbstract ??= []; |
| inheritedAbstract.add(interfaceElement); |
| } |
| continue; |
| } |
| |
| // The case when members have different kinds is reported in verifier. |
| if (concreteElement.kind != interfaceElement.kind) { |
| continue; |
| } |
| |
| // If a class declaration is not abstract, and the interface has a |
| // member declaration named `m`, then: |
| // 1. if the class contains a non-overridden member whose signature is |
| // not a valid override of the interface member signature for `m`, |
| // then it's a compile-time error. |
| // 2. if the class contains no member named `m`, and the class member |
| // for `noSuchMethod` is the one declared in `Object`, then it's a |
| // compile-time error. |
| // TODO(brianwilkerson) This code catches some cases not caught in |
| // _checkDeclaredMember, but also duplicates the diagnostic reported |
| // there in some other cases. |
| // TODO(brianwilkerson) In the case of methods inherited via mixins, the |
| // diagnostic should be reported on the name of the mixin defining the |
| // method. In other cases, it should be reported on the name of the |
| // overriding method. The classNameNode is always wrong. |
| concreteElement = library.toLegacyElementIfOptOut(concreteElement); |
| CorrectOverrideHelper( |
| library: library, |
| thisMember: concreteElement, |
| ).verify( |
| superMember: interfaceElement, |
| errorReporter: reporter, |
| errorNode: classNameNode, |
| errorCode: CompileTimeErrorCode.INVALID_IMPLEMENTATION_OVERRIDE, |
| ); |
| } |
| |
| _reportInheritedAbstractMembers(inheritedAbstract); |
| } |
| } |
| |
| /// Check that the given [member] is a valid override of the corresponding |
| /// instance members in each of [directSuperInterfaces]. The [libraryUri] is |
| /// the URI of the library containing the [member]. |
| void _checkDeclaredMember( |
| AstNode node, |
| Uri libraryUri, |
| ExecutableElement? member, { |
| List<FormalParameter>? methodParameterNodes, |
| int mixinIndex = -1, |
| }) { |
| if (member == null) return; |
| if (member.isStatic) return; |
| |
| var name = Name(libraryUri, member.name); |
| var correctOverrideHelper = CorrectOverrideHelper( |
| library: library, |
| thisMember: member, |
| ); |
| |
| for (var superType in directSuperInterfaces) { |
| var superMember = inheritance.getMember( |
| superType, |
| name, |
| forMixinIndex: mixinIndex, |
| ); |
| if (superMember == null) { |
| continue; |
| } |
| |
| // The case when members have different kinds is reported in verifier. |
| // TODO(scheglov) Do it here? |
| if (member.kind != superMember.kind) { |
| continue; |
| } |
| |
| correctOverrideHelper.verify( |
| superMember: superMember, |
| errorReporter: reporter, |
| errorNode: node, |
| ); |
| |
| if (!_isNonNullableByDefault && |
| superMember is MethodElement && |
| member is MethodElement && |
| methodParameterNodes != null) { |
| _checkForOptionalParametersDifferentDefaultValues( |
| superMember, |
| member, |
| methodParameterNodes, |
| ); |
| } |
| } |
| |
| if (mixinIndex == -1) { |
| CovariantParametersVerifier(thisMember: member).verify( |
| errorReporter: reporter, |
| errorNode: node, |
| ); |
| } |
| } |
| |
| /// Check that instance members of [type] are valid overrides of the |
| /// corresponding instance members in each of [directSuperInterfaces]. |
| void _checkDeclaredMembers(AstNode node, InterfaceType type, |
| {required int mixinIndex}) { |
| var libraryUri = type.element.library.source.uri; |
| for (var method in type.methods) { |
| _checkDeclaredMember(node, libraryUri, method, mixinIndex: mixinIndex); |
| } |
| for (var accessor in type.accessors) { |
| _checkDeclaredMember(node, libraryUri, accessor, mixinIndex: mixinIndex); |
| } |
| } |
| |
| /// Verify that the given [namedType] does not extend, implement, or mixes-in |
| /// types such as `num` or `String`. |
| bool _checkDirectSuperType(NamedType namedType, ErrorCode errorCode) { |
| if (namedType.isSynthetic) { |
| return false; |
| } |
| |
| // The SDK implementation may implement disallowed types. For example, |
| // JSNumber in dart2js and _Smi in Dart VM both implement int. |
| if (library.source.uri.isScheme('dart')) { |
| return false; |
| } |
| |
| DartType type = namedType.typeOrThrow; |
| if (type is! InterfaceType) { |
| return false; |
| } |
| |
| var interfaceElement = type.element; |
| |
| if (interfaceElement.isDartCoreEnum && |
| library.featureSet.isEnabled(Feature.enhanced_enums)) { |
| if (classElement.isAbstract) { |
| return false; |
| } |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE, |
| namedType, |
| ); |
| return true; |
| } |
| |
| if (typeProvider.isNonSubtypableClass(interfaceElement)) { |
| reporter.reportErrorForNode(errorCode, namedType, [type]); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /// Verify that direct supertypes are valid, and return `false`. If there |
| /// are direct supertypes that are not valid, report corresponding errors, |
| /// and return `true`. |
| bool _checkDirectSuperTypes() { |
| var hasError = false; |
| if (implementsClause != null) { |
| for (var namedType in implementsClause!.interfaces) { |
| if (_checkDirectSuperType( |
| namedType, |
| CompileTimeErrorCode.IMPLEMENTS_DISALLOWED_CLASS, |
| )) { |
| hasError = true; |
| } |
| } |
| } |
| if (onClause != null) { |
| for (var namedType in onClause!.superclassConstraints) { |
| if (_checkDirectSuperType( |
| namedType, |
| CompileTimeErrorCode.MIXIN_SUPER_CLASS_CONSTRAINT_DISALLOWED_CLASS, |
| )) { |
| hasError = true; |
| } |
| } |
| } |
| if (superclass != null) { |
| if (_checkDirectSuperType( |
| superclass!, |
| CompileTimeErrorCode.EXTENDS_DISALLOWED_CLASS, |
| )) { |
| hasError = true; |
| } |
| } |
| if (withClause != null) { |
| for (var namedType in withClause!.mixinTypes) { |
| if (_checkDirectSuperType( |
| namedType, |
| CompileTimeErrorCode.MIXIN_OF_DISALLOWED_CLASS, |
| )) { |
| hasError = true; |
| } |
| if (classElement.isEnum && _checkEnumMixin(namedType)) { |
| hasError = true; |
| } |
| } |
| } |
| return hasError; |
| } |
| |
| bool _checkEnumMixin(NamedType namedType) { |
| DartType type = namedType.typeOrThrow; |
| if (type is! InterfaceType) { |
| return false; |
| } |
| |
| var interfaceElement = type.element; |
| if (interfaceElement.isEnum) { |
| return false; |
| } |
| |
| if (interfaceElement.fields.every((e) => e.isStatic || e.isSynthetic)) { |
| return false; |
| } |
| |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.ENUM_MIXIN_WITH_INSTANCE_VARIABLE, |
| namedType, |
| ); |
| return true; |
| } |
| |
| void _checkForOptionalParametersDifferentDefaultValues( |
| MethodElement baseExecutable, |
| MethodElement derivedExecutable, |
| List<FormalParameter> derivedParameterNodes, |
| ) { |
| var derivedIsAbstract = derivedExecutable.isAbstract; |
| var derivedOptionalNodes = <FormalParameter>[]; |
| var derivedOptionalElements = <ParameterElementImpl>[]; |
| var derivedParameterElements = derivedExecutable.parameters; |
| for (var i = 0; i < derivedParameterElements.length; i++) { |
| var parameterElement = |
| derivedParameterElements[i] as ParameterElementImpl; |
| if (parameterElement.isOptional) { |
| derivedOptionalNodes.add(derivedParameterNodes[i]); |
| derivedOptionalElements.add(parameterElement); |
| } |
| } |
| |
| var baseOptionalElements = <ParameterElementImpl>[]; |
| var baseParameterElements = baseExecutable.parameters; |
| for (var i = 0; i < baseParameterElements.length; ++i) { |
| var baseParameter = baseParameterElements[i]; |
| if (baseParameter.isOptional) { |
| baseOptionalElements |
| .add(baseParameter.declaration as ParameterElementImpl); |
| } |
| } |
| |
| // Stop if no optional parameters. |
| if (baseOptionalElements.isEmpty || derivedOptionalElements.isEmpty) { |
| return; |
| } |
| |
| if (derivedOptionalElements[0].isNamed) { |
| for (int i = 0; i < derivedOptionalElements.length; i++) { |
| var derivedElement = derivedOptionalElements[i]; |
| if (_isNonNullableByDefault && |
| derivedIsAbstract && |
| !derivedElement.hasDefaultValue) { |
| continue; |
| } |
| var name = derivedElement.name; |
| for (var j = 0; j < baseOptionalElements.length; j++) { |
| var baseParameter = baseOptionalElements[j]; |
| if (name == baseParameter.name && baseParameter.hasDefaultValue) { |
| var baseValue = baseParameter.computeConstantValue(); |
| var derivedResult = derivedElement.evaluationResult!; |
| if (!_constantValuesEqual(derivedResult.value, baseValue)) { |
| reporter.reportErrorForNode( |
| StaticWarningCode |
| .INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_NAMED, |
| derivedOptionalNodes[i], |
| [ |
| baseExecutable.enclosingElement.displayName, |
| baseExecutable.displayName, |
| name |
| ], |
| ); |
| } |
| } |
| } |
| } |
| } else { |
| for (var i = 0; |
| i < derivedOptionalElements.length && i < baseOptionalElements.length; |
| i++) { |
| var derivedElement = derivedOptionalElements[i]; |
| if (_isNonNullableByDefault && |
| derivedIsAbstract && |
| !derivedElement.hasDefaultValue) { |
| continue; |
| } |
| var baseElement = baseOptionalElements[i]; |
| if (baseElement.hasDefaultValue) { |
| var baseValue = baseElement.computeConstantValue(); |
| var derivedResult = derivedElement.evaluationResult!; |
| if (!_constantValuesEqual(derivedResult.value, baseValue)) { |
| reporter.reportErrorForNode( |
| StaticWarningCode |
| .INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_POSITIONAL, |
| derivedOptionalNodes[i], |
| [ |
| baseExecutable.enclosingElement.displayName, |
| baseExecutable.displayName |
| ], |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Check that [classElement] is not a superinterface to itself. |
| /// The [path] is a list containing the potentially cyclic implements path. |
| /// |
| /// See [CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE], |
| /// [CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_EXTENDS], |
| /// [CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_IMPLEMENTS], |
| /// [CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_ON], |
| /// [CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_WITH]. |
| bool _checkForRecursiveInterfaceInheritance(ClassElement element, |
| [List<ClassElement>? path]) { |
| path ??= <ClassElement>[]; |
| |
| // Detect error condition. |
| int size = path.length; |
| // If this is not the base case (size > 0), and the enclosing class is the |
| // given class element then report an error. |
| if (size > 0 && classElement == element) { |
| String className = classElement.displayName; |
| if (size > 1) { |
| // Construct a string showing the cyclic implements path: |
| // "A, B, C, D, A" |
| String separator = ", "; |
| StringBuffer buffer = StringBuffer(); |
| for (int i = 0; i < size; i++) { |
| buffer.write(path[i].displayName); |
| buffer.write(separator); |
| } |
| buffer.write(element.displayName); |
| reporter.reportErrorForElement( |
| CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE, |
| classElement, |
| [className, buffer.toString()]); |
| return true; |
| } else { |
| // RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS or |
| // RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS or |
| // RECURSIVE_INTERFACE_INHERITANCE_ON or |
| // RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_WITH |
| reporter.reportErrorForElement( |
| _getRecursiveErrorCode(element), classElement, [className]); |
| return true; |
| } |
| } |
| |
| if (path.indexOf(element) > 0) { |
| return false; |
| } |
| path.add(element); |
| |
| // n-case |
| var supertype = element.supertype; |
| if (supertype != null && |
| _checkForRecursiveInterfaceInheritance(supertype.element, path)) { |
| return true; |
| } |
| |
| for (InterfaceType type in element.mixins) { |
| if (_checkForRecursiveInterfaceInheritance(type.element, path)) { |
| return true; |
| } |
| } |
| |
| for (InterfaceType type in element.superclassConstraints) { |
| if (_checkForRecursiveInterfaceInheritance(type.element, path)) { |
| return true; |
| } |
| } |
| |
| for (InterfaceType type in element.interfaces) { |
| if (_checkForRecursiveInterfaceInheritance(type.element, path)) { |
| return true; |
| } |
| } |
| |
| path.removeAt(path.length - 1); |
| return false; |
| } |
| |
| void _checkIllegalEnumValuesDeclaration(SimpleIdentifier name) { |
| if (implementsDartCoreEnum && name.name == 'values') { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_DECLARATION, |
| name, |
| ); |
| } |
| } |
| |
| void _checkIllegalEnumValuesInheritance() { |
| if (implementsDartCoreEnum) { |
| var getter = inheritance.getInherited2( |
| classElement, |
| Name(libraryUri, 'values'), |
| ); |
| var setter = inheritance.getInherited2( |
| classElement, |
| Name(libraryUri, 'values='), |
| ); |
| var inherited = getter ?? setter; |
| if (inherited != null) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_INHERITANCE, |
| classNameNode, |
| [inherited.enclosingElement.name!], |
| ); |
| } |
| } |
| } |
| |
| void _checkIllegalNonAbstractEnumIndex(SimpleIdentifier name) { |
| if (implementsDartCoreEnum && name.name == 'index') { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.ILLEGAL_NON_ABSTRACT_ENUM_INDEX, |
| name, |
| ); |
| } |
| } |
| |
| /// Return the error code that should be used when the given class [element] |
| /// references itself directly. |
| ErrorCode _getRecursiveErrorCode(ClassElement element) { |
| if (element.supertype?.element == classElement) { |
| return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_EXTENDS; |
| } |
| |
| for (InterfaceType type in element.superclassConstraints) { |
| if (type.element == classElement) { |
| return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_ON; |
| } |
| } |
| |
| for (InterfaceType type in element.mixins) { |
| if (type.element == classElement) { |
| return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_WITH; |
| } |
| } |
| |
| return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_IMPLEMENTS; |
| } |
| |
| /// We identified that the current non-abstract class does not have the |
| /// concrete implementation of a method with the given [name]. If this is |
| /// because the class itself defines an abstract method with this [name], |
| /// report the more specific error, and return `true`. |
| bool _reportConcreteClassWithAbstractMember(String name) { |
| bool checkMemberNameCombo(ClassMember member, String memberName) { |
| if (memberName == name) { |
| reporter.reportErrorForNode( |
| classElement.isEnum |
| ? CompileTimeErrorCode.ENUM_WITH_ABSTRACT_MEMBER |
| : CompileTimeErrorCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER, |
| member, |
| [name, classElement.name], |
| ); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| for (var member in members) { |
| if (member is MethodDeclaration) { |
| var name2 = member.name.name; |
| if (member.isSetter) { |
| name2 += '='; |
| } |
| if (checkMemberNameCombo(member, name2)) return true; |
| } else if (member is FieldDeclaration) { |
| for (var variableDeclaration in member.fields.variables) { |
| var name2 = variableDeclaration.name.name; |
| if (checkMemberNameCombo(member, name2)) return true; |
| if (!variableDeclaration.isFinal) { |
| name2 += '='; |
| if (checkMemberNameCombo(member, name2)) return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| void _reportInconsistentInheritance(AstNode node, Conflict conflict) { |
| var name = conflict.name; |
| |
| if (conflict is GetterMethodConflict) { |
| // Members that participate in inheritance are always enclosed in named |
| // elements so it is safe to assume that |
| // `conflict.getter.enclosingElement.name` and |
| // `conflict.method.enclosingElement.name` are both non-`null`. |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.INCONSISTENT_INHERITANCE_GETTER_AND_METHOD, |
| node, |
| [ |
| name.name, |
| conflict.getter.enclosingElement.name!, |
| conflict.method.enclosingElement.name! |
| ], |
| ); |
| } else if (conflict is CandidatesConflict) { |
| var candidatesStr = conflict.candidates.map((candidate) { |
| var className = candidate.enclosingElement.name; |
| var typeStr = candidate.type.getDisplayString( |
| withNullability: _isNonNullableByDefault, |
| ); |
| return '$className.${name.name} ($typeStr)'; |
| }).join(', '); |
| |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.INCONSISTENT_INHERITANCE, |
| node, |
| [name.name, candidatesStr], |
| ); |
| } else { |
| throw StateError('${conflict.runtimeType}'); |
| } |
| } |
| |
| void _reportInheritedAbstractMembers(List<ExecutableElement>? elements) { |
| if (elements == null) { |
| return; |
| } |
| |
| classNameNode.setProperty( |
| InheritanceOverrideVerifier._missingOverridesKey, |
| elements, |
| ); |
| |
| var descriptions = <String>[]; |
| for (ExecutableElement element in elements) { |
| String prefix = ''; |
| if (element is PropertyAccessorElement) { |
| if (element.isGetter) { |
| prefix = 'getter '; |
| } else { |
| prefix = 'setter '; |
| } |
| } |
| |
| var elementName = element.displayName; |
| var enclosingElement = element.enclosingElement; |
| var enclosingName = enclosingElement.displayName; |
| var description = "$prefix$enclosingName.$elementName"; |
| |
| descriptions.add(description); |
| } |
| descriptions.sort(); |
| |
| if (descriptions.length == 1) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE, |
| classNameNode, |
| [descriptions[0]], |
| ); |
| } else if (descriptions.length == 2) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO, |
| classNameNode, |
| [descriptions[0], descriptions[1]], |
| ); |
| } else if (descriptions.length == 3) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE, |
| classNameNode, |
| [descriptions[0], descriptions[1], descriptions[2]], |
| ); |
| } else if (descriptions.length == 4) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR, |
| classNameNode, |
| [descriptions[0], descriptions[1], descriptions[2], descriptions[3]], |
| ); |
| } else { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode |
| .NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS, |
| classNameNode, |
| [ |
| descriptions[0], |
| descriptions[1], |
| descriptions[2], |
| descriptions[3], |
| descriptions.length - 4 |
| ], |
| ); |
| } |
| } |
| |
| bool _reportNoCombinedSuperSignature(MethodDeclaration node) { |
| var element = node.declaredElement; |
| if (element is MethodElementImpl) { |
| var inferenceError = element.typeInferenceError; |
| if (inferenceError?.kind == |
| TopLevelInferenceErrorKind.overrideNoCombinedSuperSignature) { |
| reporter.reportErrorForNode( |
| CompileTimeErrorCode.NO_COMBINED_SUPER_SIGNATURE, |
| node.name, |
| [ |
| classElement.name, |
| inferenceError!.arguments[0], |
| ], |
| ); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool _constantValuesEqual(DartObject? x, DartObject? y) { |
| // If either constant value couldn't be computed due to an error, the |
| // corresponding DartObject will be `null`. Since an error has already been |
| // reported, there's no need to report another. |
| if (x == null || y == null) return true; |
| return x == y; |
| } |
| } |