blob: 96d9ffa2494edec81919c84d4f8123b71862186b [file] [log] [blame]
// 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/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.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/ast.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.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/error/inference_error.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
final _missingMustBeOverridden = Expando<List<ExecutableElement>>();
final _missingOverrides = Expando<List<InternalExecutableElement>>();
class InheritanceOverrideVerifier {
final TypeSystemImpl _typeSystem;
final TypeProvider _typeProvider;
final InheritanceManager3 _inheritance;
final DiagnosticReporter _reporter;
InheritanceOverrideVerifier(
this._typeSystem,
this._inheritance,
this._reporter,
) : _typeProvider = _typeSystem.typeProvider;
void verifyUnit(CompilationUnitImpl unit) {
var library = unit.declaredFragment!.element;
for (var declaration in unit.declarations) {
_ClassVerifier verifier;
if (declaration is ClassDeclarationImpl) {
var fragment = declaration.declaredFragment!;
verifier = _ClassVerifier(
typeSystem: _typeSystem,
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
featureSet: unit.featureSet,
library: library,
classNameToken: declaration.name,
classElement: fragment.element,
implementsClause: declaration.implementsClause,
members: declaration.members,
superclass: declaration.extendsClause?.superclass,
withClause: declaration.withClause,
);
if (fragment.isAugmentation) {
verifier._checkDirectSuperTypes();
continue;
}
} else if (declaration is ClassTypeAliasImpl) {
var fragment = declaration.declaredFragment!;
verifier = _ClassVerifier(
typeSystem: _typeSystem,
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
featureSet: unit.featureSet,
library: library,
classNameToken: declaration.name,
classElement: fragment.element,
implementsClause: declaration.implementsClause,
superclass: declaration.superclass,
withClause: declaration.withClause,
);
if (fragment.isAugmentation) {
verifier._checkDirectSuperTypes();
continue;
}
} else if (declaration is EnumDeclarationImpl) {
var fragment = declaration.declaredFragment!;
verifier = _ClassVerifier(
typeSystem: _typeSystem,
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
featureSet: unit.featureSet,
library: library,
classNameToken: declaration.name,
classElement: fragment.element,
implementsClause: declaration.implementsClause,
members: declaration.members,
withClause: declaration.withClause,
);
if (fragment.isAugmentation) {
verifier._checkDirectSuperTypes();
continue;
}
} else if (declaration is MixinDeclarationImpl) {
var fragment = declaration.declaredFragment!;
verifier = _ClassVerifier(
typeSystem: _typeSystem,
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
featureSet: unit.featureSet,
library: library,
classNameToken: declaration.name,
classElement: fragment.element,
implementsClause: declaration.implementsClause,
members: declaration.members,
onClause: declaration.onClause,
);
if (fragment.isAugmentation) {
verifier._checkDirectSuperTypes();
continue;
}
} else {
continue;
}
if (verifier.verify()) {
continue;
}
verifier._verifyMustBeOverridden();
}
}
/// Returns [ExecutableElement] members that are in the interface of the
/// given class with `@mustBeOverridden`, but don't have implementations.
static List<ExecutableElement> missingMustBeOverridden(
NamedCompilationUnitMember node,
) {
return _missingMustBeOverridden[node.name] ?? const [];
}
/// Returns [ExecutableElement] members that are in the interface of the
/// given class, but don't have concrete implementations.
static List<ExecutableElement> missingOverrides(
NamedCompilationUnitMember node,
) {
return _missingOverrides[node.name] ?? const [];
}
}
class _ClassVerifier {
final TypeSystemImpl typeSystem;
final TypeProvider typeProvider;
final InheritanceManager3 inheritance;
final DiagnosticReporter reporter;
final FeatureSet featureSet;
final LibraryElementImpl library;
final Uri libraryUri;
final InterfaceElementImpl classElement;
final Token classNameToken;
final List<ClassMember> members;
final ImplementsClause? implementsClause;
final MixinOnClause? onClause;
final NamedType? superclass;
final WithClause? withClause;
final List<InterfaceType> directSuperInterfaces = [];
late final bool 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.classNameToken,
required this.classElement,
this.implementsClause,
this.members = const [],
this.onClause,
this.superclass,
this.withClause,
}) : libraryUri = library.source.uri;
/// Verify inheritance overrides, and return `true` if an error was
/// reported which should prevent follow on diagnostics from being reported.
bool verify() {
if (_checkDirectSuperTypes()) {
return true;
}
var element = classElement;
if (element is! EnumElementImpl &&
element is ClassElementImpl &&
!element.isAbstract &&
implementsDartCoreEnum) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.concreteClassHasEnumSuperinterface,
);
return true;
}
if (_checkForRecursiveInterfaceInheritance(element)) {
return true;
}
// Compute the interface of the class.
var interface = inheritance.getInterface(element);
// Report conflicts between direct superinterfaces of the class.
for (var conflict in interface.conflicts) {
_reportInconsistentInheritance(classNameToken, conflict);
}
if (element.supertype != null) {
directSuperInterfaces.add(element.supertype!);
}
if (element is MixinElementImpl) {
directSuperInterfaces.addAll(element.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 = element.mixins;
for (var i = 0; i < mixinTypes.length; i++) {
var mixinType = mixinTypes[i];
_checkDeclaredMembers(mixinNodes![i], mixinType, mixinIndex: i);
directSuperInterfaces.add(mixinType);
}
directSuperInterfaces.addAll(element.interfaces);
// Check the members of the class itself, against all the previously
// collected superinterfaces of the supertype, mixins, and interfaces.
for (var member in members) {
if (member is FieldDeclarationImpl) {
var fieldList = member.fields;
for (var field in fieldList.variables) {
var fieldElement = field.declaredFragment! as FieldFragmentImpl;
_checkDeclaredMember(
field.name,
libraryUri,
fieldElement.element.getter,
);
_checkDeclaredMember(
field.name,
libraryUri,
fieldElement.element.setter,
);
if (!member.isStatic && element is! EnumElementImpl) {
_checkIllegalEnumValuesDeclaration(field.name);
}
if (!member.isStatic) {
_checkIllegalConcreteEnumMemberDeclaration(field.name);
}
}
} else if (member is MethodDeclarationImpl) {
var hasError = _reportNoCombinedSuperSignature(member);
if (hasError) {
continue;
}
_checkDeclaredMember(
member.name,
libraryUri,
member.declaredFragment!.asElement2,
methodParameterNodes: member.parameters?.parameters,
);
if (!(member.isStatic || member.isAbstract || member.isSetter)) {
_checkIllegalConcreteEnumMemberDeclaration(member.name);
}
if (!member.isStatic && element is! EnumElementImpl) {
_checkIllegalEnumValuesDeclaration(member.name);
}
}
}
_checkIllegalConcreteEnumMemberInheritance();
_checkIllegalEnumValuesInheritance();
GetterSetterTypesVerifier(
library: library,
diagnosticReporter: reporter,
).checkInterface(element, interface);
if (element is ClassElementImpl && !element.isAbstract ||
element is EnumElementImpl) {
List<InternalExecutableElement>? 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)) {
continue;
}
if (_isNotImplementedInConcreteSuperClass(name)) {
continue;
}
// We already reported ILLEGAL_ENUM_VALUES_INHERITANCE.
if (element is EnumElementImpl &&
const {'values', 'values='}.contains(name.name)) {
continue;
}
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.
CorrectOverrideHelper(
typeSystem: typeSystem,
thisMember: concreteElement,
).verify(
superMember: interfaceElement,
diagnosticReporter: reporter,
errorNode: classNameToken,
diagnosticCode:
concreteElement is InternalSetterElement
? CompileTimeErrorCode.invalidImplementationOverrideSetter
: CompileTimeErrorCode.invalidImplementationOverride,
);
}
_reportInheritedAbstractMembers(inheritedAbstract);
}
return false;
}
/// 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(
SyntacticEntity node,
Uri libraryUri,
InternalExecutableElement? member, {
List<FormalParameter>? methodParameterNodes,
int mixinIndex = -1,
}) {
if (member == null) return;
if (member.isStatic) return;
var name = Name.forElement(member);
if (name == null) return;
var correctOverrideHelper = CorrectOverrideHelper(
typeSystem: typeSystem,
thisMember: member,
);
for (var superType in directSuperInterfaces) {
var superMember = inheritance.getMember3(
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,
diagnosticReporter: reporter,
errorNode: node,
diagnosticCode:
member is SetterElement
? CompileTimeErrorCode.invalidOverrideSetter
: CompileTimeErrorCode.invalidOverride,
);
}
if (mixinIndex == -1) {
CovariantParametersVerifier(
thisMember: member,
).verify(errorReporter: reporter, errorEntity: node);
}
}
/// Check that instance members of [type] are valid overrides of the
/// corresponding instance members in each of [directSuperInterfaces].
void _checkDeclaredMembers(
AstNode node,
InterfaceTypeImpl type, {
required int mixinIndex,
}) {
var libraryUri = type.element.library.uri;
for (var method in type.methods) {
_checkDeclaredMember(node, libraryUri, method, mixinIndex: mixinIndex);
}
for (var getter in type.getters) {
_checkDeclaredMember(node, libraryUri, getter, mixinIndex: mixinIndex);
}
for (var setter in type.setters) {
_checkDeclaredMember(node, libraryUri, setter, mixinIndex: mixinIndex);
}
}
/// If [type] cannot be subtyped, invokes a function and returns `true`.
bool _checkDirectSuperType({
required DartType type,
void Function()? hasEnum,
void Function()? notSubtypable,
}) {
// 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;
}
if (type is! InterfaceType) {
return false;
}
var typeElement = type.element;
var classElement = this.classElement;
if (typeElement is ClassElement &&
typeElement.isDartCoreEnum &&
library.featureSet.isEnabled(Feature.enhanced_enums)) {
if (classElement is ClassElementImpl && classElement.isAbstract ||
classElement is EnumElementImpl ||
classElement is MixinElementImpl) {
return false;
}
hasEnum?.call();
return true;
}
if (typeProvider.isNonSubtypableClass(typeElement)) {
notSubtypable?.call();
return true;
}
return false;
}
/// Verify that the given [namedType] does not extend, implement, or mixes-in
/// types such as `num` or `String`.
bool _checkDirectSuperTypeNode(
NamedType namedType,
DiagnosticCode diagnosticCode,
) {
if (namedType.isSynthetic) {
return false;
}
var type = namedType.typeOrThrow;
return _checkDirectSuperType(
type: type,
hasEnum: () {
reporter.atNode(
namedType,
CompileTimeErrorCode.concreteClassHasEnumSuperinterface,
);
},
notSubtypable: () {
reporter.atNode(namedType, diagnosticCode, arguments: [type]);
},
);
}
/// 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 (_checkDirectSuperTypeNode(
namedType,
CompileTimeErrorCode.implementsDisallowedClass,
)) {
hasError = true;
}
}
}
if (onClause != null) {
for (var namedType in onClause!.superclassConstraints) {
if (_checkDirectSuperTypeNode(
namedType,
CompileTimeErrorCode.mixinSuperClassConstraintDisallowedClass,
)) {
hasError = true;
}
}
}
if (superclass != null) {
if (_checkDirectSuperTypeNode(
superclass!,
CompileTimeErrorCode.extendsDisallowedClass,
)) {
hasError = true;
}
}
if (withClause != null) {
for (var namedType in withClause!.mixinTypes) {
if (_checkDirectSuperTypeNode(
namedType,
CompileTimeErrorCode.mixinOfDisallowedClass,
)) {
hasError = true;
}
if (classElement is EnumElementImpl && _checkMixinOfEnum(namedType)) {
hasError = true;
}
}
}
return hasError;
}
/// Check that [classElement] is not a superinterface to itself.
/// The [path] is a list containing the potentially cyclic implements path.
///
/// See [CompileTimeErrorCode.recursiveInterfaceInheritance],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceExtends],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceImplements],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceOn],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceWith].
bool _checkForRecursiveInterfaceInheritance(InterfaceElementImpl element) {
var cycle = element.interfaceCycle;
if (cycle == null) {
return false;
}
if (superclass case var superclass?) {
if (superclass.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceExtends,
arguments: [element.displayName],
);
return true;
}
}
if (onClause case var onClause?) {
for (var typeAnnotation in onClause.superclassConstraints) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceOn,
arguments: [element.displayName],
);
return true;
}
}
}
if (withClause case var withClause?) {
for (var typeAnnotation in withClause.mixinTypes) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceWith,
arguments: [element.displayName],
);
return true;
}
}
}
if (implementsClause case var implementsClause?) {
for (var typeAnnotation in implementsClause.interfaces) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceImplements,
arguments: [element.displayName],
);
return true;
}
}
}
reporter.atElement2(
classElement,
CompileTimeErrorCode.recursiveInterfaceInheritance,
arguments: [
element.displayName,
cycle.map((e) => e.displayName).join(', '),
],
);
return true;
}
void _checkIllegalConcreteEnumMemberDeclaration(Token name) {
if (implementsDartCoreEnum) {
var classElement = this.classElement;
if (classElement is ClassElementImpl &&
!classElement.isDartCoreEnumImpl ||
classElement is EnumElementImpl ||
classElement is MixinElementImpl) {
if (const {'index', 'hashCode', '=='}.contains(name.lexeme)) {
reporter.atToken(
name,
CompileTimeErrorCode.illegalConcreteEnumMemberDeclaration,
arguments: [name.lexeme],
);
}
}
}
}
void _checkIllegalConcreteEnumMemberInheritance() {
// We ignore mixins because they don't inherit and members.
// But to support `super.foo()` invocations we put members from superclass
// constraints into the `superImplemented` bucket, the same we look below.
if (classElement is MixinElementImpl) {
return;
}
if (implementsDartCoreEnum) {
void checkSingle(
String memberName,
bool Function(ClassElement enclosingClass) filter,
) {
var member = classElement.getInheritedConcreteMember(
Name(libraryUri, memberName),
);
if (member != null) {
var enclosingClass = member.enclosingElement;
if (enclosingClass != null) {
if (enclosingClass is! ClassElement || filter(enclosingClass)) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.illegalConcreteEnumMemberInheritance,
arguments: [memberName, enclosingClass.name!],
);
}
}
}
}
checkSingle('hashCode', (e) => !e.isDartCoreObject);
checkSingle('==', (e) => !e.isDartCoreObject);
checkSingle('index', (e) => !e.isDartCoreEnum);
}
}
void _checkIllegalEnumValuesDeclaration(Token name) {
if (implementsDartCoreEnum && name.lexeme == 'values') {
reporter.atToken(name, CompileTimeErrorCode.illegalEnumValuesDeclaration);
}
}
void _checkIllegalEnumValuesInheritance() {
if (implementsDartCoreEnum) {
var getter = inheritance.getInherited(
classElement,
Name(libraryUri, 'values'),
);
var setter = inheritance.getInherited(
classElement,
Name(libraryUri, 'values='),
);
var inherited = getter ?? setter;
if (inherited != null) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.illegalEnumValuesInheritance,
arguments: [inherited.enclosingElement!.name!],
);
}
}
}
bool _checkMixinOfEnum(NamedType namedType) {
DartType type = namedType.typeOrThrow;
if (type is! InterfaceType) {
return false;
}
var interfaceElement = type.element;
if (interfaceElement is EnumElement ||
interfaceElement is ExtensionTypeElement) {
return false;
}
if (interfaceElement.fields.every((e) => e.isStatic || e.isSynthetic)) {
return false;
}
reporter.atNode(
namedType,
CompileTimeErrorCode.enumMixinWithInstanceVariable,
);
return true;
}
/// If [name] is not implemented in the extended concrete class, the
/// issue should be fixed there, and then [classElement] will not have it too.
bool _isNotImplementedInConcreteSuperClass(Name name) {
var superElement = classElement.supertype?.element;
if (superElement is ClassElementImpl && !superElement.isAbstract) {
var superInterface = inheritance.getInterface(superElement);
return superInterface.map.containsKey(name);
}
return false;
}
/// 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,
String displayName,
) {
if (memberName == name) {
reporter.atNode(
member,
classElement is EnumElement
? CompileTimeErrorCode.enumWithAbstractMember
: CompileTimeErrorCode.concreteClassWithAbstractMember,
arguments: [displayName, classElement.name ?? ''],
);
return true;
} else {
return false;
}
}
for (var member in members) {
if (member is MethodDeclaration) {
var displayName = member.name.lexeme;
var name = displayName;
if (member.isSetter) {
name += '=';
}
if (checkMemberNameCombo(member, name, displayName)) return true;
} else if (member is FieldDeclaration) {
for (var variableDeclaration in member.fields.variables) {
var name = variableDeclaration.name.lexeme;
if (checkMemberNameCombo(member, name, name)) return true;
if (!variableDeclaration.isFinal) {
if (checkMemberNameCombo(member, '$name=', name)) return true;
}
}
}
}
return false;
}
void _reportInconsistentInheritance(Token token, 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.atToken(
token,
CompileTimeErrorCode.inconsistentInheritanceGetterAndMethod,
arguments: [
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();
return '$className.${name.name} ($typeStr)';
})
.join(', ');
reporter.atToken(
token,
CompileTimeErrorCode.inconsistentInheritance,
arguments: [name.name, candidatesStr],
);
} else {
throw StateError('${conflict.runtimeType}');
}
}
void _reportInheritedAbstractMembers(
List<InternalExecutableElement>? elements,
) {
if (elements == null) {
return;
}
_missingOverrides[classNameToken] = elements;
var descriptions = <String>[];
for (var element in elements) {
var prefix = switch (element) {
GetterElement() => 'getter ',
SetterElement() => 'setter ',
_ => '',
};
var elementName = element.displayName;
var enclosingElement = element.enclosingElement!;
var enclosingName = enclosingElement.displayString();
var description = "$prefix$enclosingName.$elementName";
descriptions.add(description);
}
descriptions.sort();
if (descriptions.length == 1) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberOne,
arguments: [descriptions[0]],
);
} else if (descriptions.length == 2) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberTwo,
arguments: [descriptions[0], descriptions[1]],
);
} else if (descriptions.length == 3) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberThree,
arguments: [descriptions[0], descriptions[1], descriptions[2]],
);
} else if (descriptions.length == 4) {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberFour,
arguments: [
descriptions[0],
descriptions[1],
descriptions[2],
descriptions[3],
],
);
} else {
reporter.atToken(
classNameToken,
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberFivePlus,
arguments: [
descriptions[0],
descriptions[1],
descriptions[2],
descriptions[3],
descriptions.length - 4,
],
);
}
}
bool _reportNoCombinedSuperSignature(MethodDeclarationImpl node) {
var fragment = node.declaredFragment;
if (fragment is MethodFragmentImpl) {
var inferenceError = fragment.element.typeInferenceError;
if (inferenceError?.kind ==
TopLevelInferenceErrorKind.overrideNoCombinedSuperSignature) {
reporter.atToken(
node.name,
CompileTimeErrorCode.noCombinedSuperSignature,
arguments: [classElement.name ?? '', inferenceError!.arguments[0]],
);
return true;
}
}
return false;
}
/// Verify that [classElement] complies with all `@mustBeOverridden`-annotated
/// members in all of its supertypes.
void _verifyMustBeOverridden() {
var classElement = this.classElement;
if (classElement is! ClassElementImpl ||
classElement.isAbstract ||
classElement.isSealed) {
// We only care about concrete classes.
return;
}
var noSuchMethodDeclaration = classElement.getMethod(
MethodElement.NO_SUCH_METHOD_METHOD_NAME,
);
if (noSuchMethodDeclaration != null &&
!noSuchMethodDeclaration.isAbstract) {
return;
}
var notOverridden = <ExecutableElement>[];
for (var supertype in classElement.allSupertypes) {
// TODO(srawlins): This looping may be expensive. Since the vast majority
// of classes will have zero elements annotated with `@mustBeOverridden`,
// we could store a bit on ClassElement (included in summaries) which
// denotes whether any declared element has been so annotated. Then the
// expensive looping is deferred until we have such a class.
for (var method in supertype.methods) {
if (method.isPrivate && method.library != classElement.library) {
continue;
}
if (method.isStatic) {
continue;
}
if (method.metadata.hasMustBeOverridden) {
var methodDeclaration = classElement.getMethod(method.name!);
if (methodDeclaration == null || methodDeclaration.isAbstract) {
notOverridden.add(method.baseElement);
}
}
}
for (var getter in supertype.getters) {
if (getter.isPrivate && getter.library != classElement.library) {
continue;
}
if (getter.isStatic) {
continue;
}
if (getter.metadata.hasMustBeOverridden ||
(getter.variable.metadata.hasMustBeOverridden)) {
var declaration = classElement.getGetter(getter.name!);
if (declaration == null || declaration.isAbstract) {
notOverridden.add(getter);
}
}
}
for (var setter in supertype.setters) {
if (setter.isPrivate && setter.library != classElement.library) {
continue;
}
if (setter.isStatic) {
continue;
}
if (setter.metadata.hasMustBeOverridden ||
(setter.variable.metadata.hasMustBeOverridden)) {
var declaration = classElement.getSetter(setter.name!);
if (declaration == null || declaration.isAbstract) {
notOverridden.add(setter);
}
}
}
}
if (notOverridden.isEmpty) {
return;
}
_missingMustBeOverridden[classNameToken] = notOverridden.toList();
var namesForError =
notOverridden
.map((e) {
var name = e.name!;
if (name.endsWith('=')) {
name = name.substring(0, name.length - 1);
}
return name;
})
.toSet()
.toList();
if (namesForError.length == 1) {
reporter.atToken(
classNameToken,
WarningCode.missingOverrideOfMustBeOverriddenOne,
arguments: namesForError,
);
} else if (namesForError.length == 2) {
reporter.atToken(
classNameToken,
WarningCode.missingOverrideOfMustBeOverriddenTwo,
arguments: namesForError,
);
} else {
reporter.atToken(
classNameToken,
WarningCode.missingOverrideOfMustBeOverriddenThreePlus,
arguments: [
namesForError[0],
namesForError[1],
(namesForError.length - 2).toString(),
],
);
}
}
}