blob: fbbb768841ed60f55fb1c1dde7379c360a683979 [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/analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/type_system.dart';
class InheritanceOverrideVerifier {
final StrongTypeSystemImpl _typeSystem;
final ErrorReporter _reporter;
/// Cached instance interfaces for [InterfaceType].
final Map<InterfaceType, _Interface> _interfaces = {};
InheritanceOverrideVerifier(this._typeSystem, this._reporter);
void verifyUnit(CompilationUnit unit) {
for (var declaration in unit.declarations) {
if (declaration is ClassDeclaration) {
_verifyClass(declaration.name,
withClause: declaration.withClause, members: declaration.members);
} else if (declaration is ClassTypeAlias) {
_verifyClass(declaration.name, withClause: declaration.withClause);
} else if (declaration is MixinDeclaration) {
_verifyClass(declaration.name, members: declaration.members);
}
}
}
/// Check that the given [member] is a valid override of the corresponding
/// instance members in each of [allSuperinterfaces].
void _checkDeclaredMember(
List<InterfaceType> allSuperinterfaces,
AstNode node,
ExecutableElement member,
) {
if (member == null) return;
if (member.isStatic) return;
var name = member.name;
for (var supertype in allSuperinterfaces) {
var superMember = _getInstanceMember(supertype, name);
if (superMember != null && superMember.isAccessibleIn(member.library)) {
// The case when members have different kinds is reported in verifier.
if (member.kind != superMember.kind) {
continue;
}
if (!_typeSystem.isOverrideSubtypeOf(member.type, superMember.type)) {
_reporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_OVERRIDE,
node,
[
name,
member.enclosingElement.name,
member.type.displayName,
superMember.enclosingElement.name,
superMember.type.displayName
],
);
}
}
}
}
/// Check that instance members of [type] are valid overrides of the
/// corresponding instance members in each of [allSuperinterfaces].
void _checkDeclaredMembers(
List<InterfaceType> allSuperinterfaces,
AstNode node,
InterfaceTypeImpl type,
) {
for (var method in type.methods) {
_checkDeclaredMember(allSuperinterfaces, node, method);
}
for (var accessor in type.accessors) {
_checkDeclaredMember(allSuperinterfaces, node, accessor);
}
}
/// Return the instance member given the [name], defined in the [type],
/// or `null` if the [type] does not define a member with the [name], or
/// if it is not an instance member.
ExecutableElement _getInstanceMember(InterfaceType type, String name) {
ExecutableElement result;
if (name.endsWith('=')) {
name = name.substring(0, name.length - 1);
result = type.getSetter(name);
} else {
result = type.getMethod(name) ?? type.getGetter(name);
}
if (result != null && result.isStatic) {
result = null;
}
return result;
}
/// Return the interface of the given [type], for the [consumerLibrary].
_Interface _getInterface(InterfaceType type, LibraryElement consumerLibrary) {
if (type == null) return new _Interface({}, []);
var result = _interfaces[type];
if (result != null) return result;
var map = <String, FunctionType>{};
var conflicts = <_Conflict>[];
_interfaces[type] = new _Interface(map, conflicts);
// If a class declaration has a member declaration, the signature of that
// member declaration becomes the signature in the interface.
{
void addTypeMember(ExecutableElement member) {
if (member.isAccessibleIn(consumerLibrary) && !member.isStatic) {
map[member.name] = member.type;
}
}
type.methods.forEach(addTypeMember);
type.accessors.forEach(addTypeMember);
}
var inheritedCandidates = <String, List<FunctionType>>{};
void addSuperinterfaceMember(String name, FunctionType candidate) {
// If name is in the [map], then it is defined in the [type] itself.
// Don't consider candidates from direct superinterfaces.
// The version defined in the type might be invalid, we check elsewhere.
if (map.containsKey(name)) return;
var candidates = inheritedCandidates[name];
if (candidates == null) {
candidates = <FunctionType>[];
inheritedCandidates[name] = candidates;
}
candidates.add(candidate);
}
var library = type.element.library;
void addSuperinterfaceMembers(InterfaceType superinterface) {
_getInterface(superinterface, library)
.map
.forEach(addSuperinterfaceMember);
}
// Fill candidates for each instance name.
addSuperinterfaceMembers(type.superclass);
type.superclassConstraints.forEach(addSuperinterfaceMembers);
type.mixins.forEach(addSuperinterfaceMembers);
type.interfaces.forEach(addSuperinterfaceMembers);
// If a class declaration does not have a member declaration with a
// particular name, but some super-interfaces do have a member with that
// name, it's a compile-time error if there is no signature among the
// super-interfaces that is a valid override of all the other
// super-interface signatures with the same name. That "most specific"
// signature becomes the signature of the class's interface.
for (var name in inheritedCandidates.keys) {
var candidates = inheritedCandidates[name];
bool allGetters = true;
bool allMethods = true;
bool allSetters = true;
for (var candidate in candidates) {
var kind = candidate.element.kind;
if (kind != ElementKind.GETTER) {
allGetters = false;
}
if (kind != ElementKind.METHOD) {
allMethods = false;
}
if (kind != ElementKind.SETTER) {
allSetters = false;
}
}
if (allSetters) {
// OK, setters don't conflict with anything.
} else if (!(allGetters || allMethods)) {
FunctionType getterType;
FunctionType methodType;
for (var candidate in candidates) {
var kind = candidate.element.kind;
if (kind == ElementKind.GETTER) {
getterType ??= candidate;
}
if (kind == ElementKind.METHOD) {
methodType ??= candidate;
}
}
conflicts.add(new _Conflict(name, candidates, getterType, methodType));
continue;
}
FunctionType validOverride;
for (var i = 0; i < candidates.length; i++) {
validOverride = candidates[i];
for (var j = 0; j < candidates.length; j++) {
var candidate = candidates[j];
if (!_typeSystem.isOverrideSubtypeOf(validOverride, candidate)) {
validOverride = null;
break;
}
}
if (validOverride != null) {
break;
}
}
if (validOverride != null) {
map[name] = validOverride;
} else {
conflicts.add(new _Conflict(name, candidates));
}
}
return new _Interface(map, conflicts);
}
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,
conflict.getter.element.enclosingElement.name,
conflict.method.element.enclosingElement.name
],
);
} else {
var candidatesStr = conflict.candidates.map((candidate) {
var className = candidate.element.enclosingElement.name;
return '$className.$name (${candidate.displayName})';
}).join(', ');
_reporter.reportErrorForNode(
CompileTimeErrorCode.INCONSISTENT_INHERITANCE,
node,
[name, candidatesStr],
);
}
}
void _verifyClass(SimpleIdentifier classNameNode,
{List<ClassMember> members: const [], WithClause withClause}) {
ClassElementImpl element = classNameNode.staticElement;
LibraryElement library = element.library;
InterfaceTypeImpl type = element.type;
var allSuperinterfaces = <InterfaceType>[];
// Add all superinterfaces of the direct supertype.
if (type.superclass != null) {
ClassElementImpl.collectAllSupertypes(
allSuperinterfaces, type.superclass, null);
}
// 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++) {
_checkDeclaredMembers(allSuperinterfaces, mixinNodes[i], mixinTypes[i]);
ClassElementImpl.collectAllSupertypes(
allSuperinterfaces, mixinTypes[i], null);
}
// Add all superinterfaces of the direct class interfaces.
for (var interface in type.interfaces) {
ClassElementImpl.collectAllSupertypes(
allSuperinterfaces, interface, null);
}
// 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(
allSuperinterfaces, fieldList, fieldElement.getter);
_checkDeclaredMember(
allSuperinterfaces, fieldList, fieldElement.setter);
}
} else if (member is MethodDeclaration) {
_checkDeclaredMember(
allSuperinterfaces, member, member.declaredElement);
}
}
// Compute the interface of the class.
var interfaceMembers = _getInterface(type, element.library);
// Report conflicts between direct superinterfaces of the class.
for (var conflict in interfaceMembers.conflicts) {
_reportInconsistentInheritance(classNameNode, conflict);
}
// TODO(scheglov) isMixin must be also isAbstract.
if (!element.isAbstract && !element.isMixin) {
for (var name in interfaceMembers.map.keys) {
var concreteElement = type.lookUpInheritedMember(name, library,
concrete: true, thisType: true, setter: name.endsWith('='));
// TODO(scheglov) handle here instead of ErrorVerifier?
if (concreteElement == null) {
continue;
}
// TODO(scheglov) Why InterfaceType even returns statics?
if (concreteElement.isStatic) {
continue;
}
var concreteType = concreteElement.type;
var interfaceType = interfaceMembers.map[name];
// The case when members have different kinds is reported in verifier.
if (concreteType.element.kind != interfaceType.element.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(scheglov) implement this
if (!_typeSystem.isOverrideSubtypeOf(concreteType, interfaceType)) {
_reporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_OVERRIDE,
classNameNode,
[
name,
concreteElement.enclosingElement.name,
concreteType.displayName,
interfaceType.element.enclosingElement.name,
interfaceType.displayName
],
);
}
}
}
}
}
/// Description of a failure to find a valid override from superinterfaces.
class _Conflict {
/// The name of an instance member for which we failed to find a valid
/// override.
final String name;
/// The list of candidates for a valid override for a member [name]. It has
/// at least two items, because otherwise the only candidate is always valid.
final List<FunctionType> candidates;
final FunctionType getter;
final FunctionType method;
_Conflict(this.name, this.candidates, [this.getter, this.method]);
}
/// The instance interface of an [InterfaceType].
class _Interface {
final Map<String, FunctionType> map;
final List<_Conflict> conflicts;
_Interface(this.map, this.conflicts);
}