blob: ac9b4211c293e5cfc0ba5626a6a9d880b410fccc [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/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/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/type_system.dart';
class InheritanceOverrideVerifier {
static const _missingOverridesKey = 'missingOverrides';
final TypeSystem _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;
for (var declaration in unit.declarations) {
if (declaration is ClassDeclaration) {
new _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) {
new _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 MixinDeclaration) {
new _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 TypeSystem typeSystem;
final TypeProvider typeProvider;
final InheritanceManager3 inheritance;
final ErrorReporter reporter;
final FeatureSet featureSet;
final LibraryElement library;
final Uri libraryUri;
final ClassElementImpl classElement;
final SimpleIdentifier classNameNode;
final List<ClassMember> members;
final ImplementsClause implementsClause;
final OnClause onClause;
final TypeName superclass;
final WithClause withClause;
/// The set of unique supertypes of the current class.
/// It is used to decide when to add a new element to [allSuperinterfaces].
final Set<InterfaceType> allSupertypes = new Set<InterfaceType>();
/// The list of all superinterfaces, collected so far.
final List<Interface> allSuperinterfaces = [];
_ClassVerifier({
this.typeSystem,
this.typeProvider,
this.inheritance,
this.reporter,
this.featureSet,
this.library,
this.classNameNode,
this.implementsClause,
this.members: const [],
this.onClause,
this.superclass,
this.withClause,
}) : libraryUri = library.source.uri,
classElement =
AbstractClassElementImpl.getImpl(classNameNode.staticElement);
void verify() {
if (_checkDirectSuperTypes()) {
return;
}
if (_checkForRecursiveInterfaceInheritance(classElement)) {
return;
}
InterfaceTypeImpl type = classElement.thisType;
// Add all superinterfaces of the direct supertype.
if (type.superclass != null) {
_addSuperinterfaces(type.superclass);
}
// 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 = type.mixins;
for (var i = 0; i < mixinTypes.length; i++) {
var mixinType = mixinTypes[i];
_checkDeclaredMembers(mixinNodes[i], mixinType);
_addSuperinterfaces(mixinType);
}
// Add all superinterfaces of the direct class interfaces.
for (var interface in type.interfaces) {
_addSuperinterfaces(interface);
}
// 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) {
FieldElement fieldElement = field.declaredElement;
_checkDeclaredMember(field.name, libraryUri, fieldElement.getter);
_checkDeclaredMember(field.name, libraryUri, fieldElement.setter);
}
} else if (member is MethodDeclaration) {
_checkDeclaredMember(member.name, libraryUri, member.declaredElement,
methodParameterNodes: member.parameters?.parameters);
}
}
// Compute the interface of the class.
var interface = inheritance.getInterface(type);
// Report conflicts between direct superinterfaces of the class.
for (var conflict in interface.conflicts) {
_reportInconsistentInheritance(classNameNode, conflict);
}
_checkForMismatchedAccessorTypes(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.
if (!typeSystem.isOverrideSubtypeOf(
concreteElement.type, interfaceElement.type)) {
reporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_OVERRIDE,
classNameNode,
[
name.name,
interfaceElement.enclosingElement.name,
interfaceElement.type.displayName,
concreteElement.enclosingElement.name,
concreteElement.type.displayName,
],
);
}
}
_reportInheritedAbstractMembers(inheritedAbstract);
}
}
void _addSuperinterfaces(InterfaceType startingType) {
var supertypes = <InterfaceType>[];
ClassElementImpl.collectAllSupertypes(supertypes, startingType, null);
for (int i = 0; i < supertypes.length; i++) {
var supertype = supertypes[i];
if (allSupertypes.add(supertype)) {
var interface = inheritance.getInterface(supertype);
allSuperinterfaces.add(interface);
}
}
}
/// Check that the given [member] is a valid override of the corresponding
/// instance members in each of [allSuperinterfaces]. The [libraryUri] is
/// the URI of the library containing the [member].
void _checkDeclaredMember(
AstNode node,
Uri libraryUri,
ExecutableElement member, {
List<AstNode> methodParameterNodes,
}) {
if (member == null) return;
if (member.isStatic) return;
var name = new Name(libraryUri, member.name);
for (var superInterface in allSuperinterfaces) {
var superMember = superInterface.declared[name];
if (superMember != null) {
// The case when members have different kinds is reported in verifier.
// TODO(scheglov) Do it here?
if (member.kind != superMember.kind) {
continue;
}
if (!typeSystem.isOverrideSubtypeOf(member.type, superMember.type)) {
reporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_OVERRIDE,
node,
[
name.name,
member.enclosingElement.name,
member.type.displayName,
superMember.enclosingElement.name,
superMember.type.displayName
],
);
}
if (methodParameterNodes != null) {
_checkForOptionalParametersDifferentDefaultValues(
superMember,
member,
methodParameterNodes,
);
}
}
}
}
/// Check that instance members of [type] are valid overrides of the
/// corresponding instance members in each of [allSuperinterfaces].
void _checkDeclaredMembers(AstNode node, InterfaceTypeImpl type) {
var libraryUri = type.element.library.source.uri;
for (var method in type.methods) {
_checkDeclaredMember(node, libraryUri, method);
}
for (var accessor in type.accessors) {
_checkDeclaredMember(node, libraryUri, accessor);
}
}
/// Verify that the given [typeName] does not extend, implement, or mixes-in
/// types such as `num` or `String`.
bool _checkDirectSuperType(TypeName typeName, ErrorCode errorCode) {
if (typeName.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.isInSystemLibrary) {
return false;
}
DartType type = typeName.type;
if (typeProvider.nonSubtypableTypes.contains(type)) {
reporter.reportErrorForNode(errorCode, typeName, [type.displayName]);
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 typeName in implementsClause.interfaces) {
if (_checkDirectSuperType(
typeName,
CompileTimeErrorCode.IMPLEMENTS_DISALLOWED_CLASS,
)) {
hasError = true;
}
}
}
if (onClause != null) {
for (var typeName in onClause.superclassConstraints) {
if (_checkDirectSuperType(
typeName,
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 typeName in withClause.mixinTypes) {
if (_checkDirectSuperType(
typeName,
CompileTimeErrorCode.MIXIN_OF_DISALLOWED_CLASS,
)) {
hasError = true;
}
}
}
return hasError;
}
void _checkForMismatchedAccessorTypes(Interface interface) {
for (var name in interface.map.keys) {
if (!name.isAccessibleFor(libraryUri)) continue;
var getter = interface.map[name];
if (getter.kind == ElementKind.GETTER) {
// TODO(scheglov) We should separate getters and setters.
var setter = interface.map[new Name(libraryUri, '${name.name}=')];
if (setter != null && setter.parameters.length == 1) {
var getterType = getter.returnType;
var setterType = setter.parameters[0].type;
if (!typeSystem.isAssignableTo(getterType, setterType,
featureSet: featureSet)) {
Element errorElement;
if (getter.enclosingElement == classElement) {
errorElement = getter;
} else if (setter.enclosingElement == classElement) {
errorElement = setter;
} else {
errorElement = classElement;
}
String getterName = getter.displayName;
if (getter.enclosingElement != classElement) {
var getterClassName = getter.enclosingElement.displayName;
getterName = '$getterClassName.$getterName';
}
String setterName = setter.displayName;
if (setter.enclosingElement != classElement) {
var setterClassName = setter.enclosingElement.displayName;
setterName = '$setterClassName.$setterName';
}
reporter.reportErrorForElement(
StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES,
errorElement,
[getterName, getterType, setterType, setterName]);
}
}
}
}
}
void _checkForOptionalParametersDifferentDefaultValues(
ExecutableElement baseExecutable,
ExecutableElement derivedExecutable,
List<AstNode> derivedParameterNodes,
) {
var derivedOptionalNodes = <AstNode>[];
var derivedOptionalElements = <ParameterElementImpl>[];
var derivedParameterElements = derivedExecutable.parameters;
for (var i = 0; i < derivedParameterElements.length; i++) {
var parameterElement = derivedParameterElements[i];
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) {
if (baseParameter is ParameterMember) {
baseParameter = (baseParameter as ParameterMember).baseElement;
}
baseOptionalElements.add(baseParameter);
}
}
// 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];
var name = derivedElement.name;
for (var j = 0; j < baseOptionalElements.length; j++) {
var baseParameter = baseOptionalElements[j];
if (name == baseParameter.name && baseParameter.initializer != null) {
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 baseElement = baseOptionalElements[i];
if (baseElement.initializer != null) {
var baseValue = baseElement.computeConstantValue();
var derivedResult = derivedOptionalElements[i].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 = new 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
InterfaceType 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;
}
/// 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) {
for (var member in members) {
if (member is MethodDeclaration) {
var name2 = member.name.name;
if (member.isSetter) {
name2 += '=';
}
if (name2 == name) {
reporter.reportErrorForNode(
StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER,
member,
[name, classElement.name]);
return true;
}
}
}
return false;
}
void _reportInconsistentInheritance(AstNode node, Conflict conflict) {
var name = conflict.name;
if (conflict.getter != null && conflict.method != null) {
reporter.reportErrorForNode(
CompileTimeErrorCode.INCONSISTENT_INHERITANCE_GETTER_AND_METHOD,
node,
[
name.name,
conflict.getter.enclosingElement.name,
conflict.method.enclosingElement.name
],
);
} else {
var candidatesStr = conflict.candidates.map((candidate) {
var className = candidate.enclosingElement.name;
return '$className.${name.name} (${candidate.displayName})';
}).join(', ');
reporter.reportErrorForNode(
CompileTimeErrorCode.INCONSISTENT_INHERITANCE,
node,
[name.name, candidatesStr],
);
}
}
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 ';
}
}
String description;
var elementName = element.displayName;
var enclosingElement = element.enclosingElement;
if (enclosingElement != null) {
var enclosingName = enclosingElement.displayName;
description = "$prefix$enclosingName.$elementName";
} else {
description = "$prefix$elementName";
}
descriptions.add(description);
}
descriptions.sort();
if (descriptions.length == 1) {
reporter.reportErrorForNode(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE,
classNameNode,
[descriptions[0]],
);
} else if (descriptions.length == 2) {
reporter.reportErrorForNode(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO,
classNameNode,
[descriptions[0], descriptions[1]],
);
} else if (descriptions.length == 3) {
reporter.reportErrorForNode(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE,
classNameNode,
[descriptions[0], descriptions[1], descriptions[2]],
);
} else if (descriptions.length == 4) {
reporter.reportErrorForNode(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR,
classNameNode,
[descriptions[0], descriptions[1], descriptions[2], descriptions[3]],
);
} else {
reporter.reportErrorForNode(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS,
classNameNode,
[
descriptions[0],
descriptions[1],
descriptions[2],
descriptions[3],
descriptions.length - 4
],
);
}
}
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 as DartObjectImpl).isEqualIgnoringTypesRecursively(y);
}
}