blob: bb1f5190a71f47f1a757ba28dc52ee8fe7b62371 [file] [log] [blame]
// Copyright (c) 2019, 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/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/correct_override.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
import 'package:meta/meta.dart';
/// Failure because of there is no most specific signature in [candidates].
class CandidatesConflict extends Conflict {
/// The list has at least two items, because the only item is always valid.
final List<ExecutableElement> candidates;
CandidatesConflict({
@required Name name,
@required this.candidates,
}) : super(name);
}
/// Failure to find a valid signature from superinterfaces.
class Conflict {
/// The name for which we failed to find a valid signature.
final Name name;
Conflict(this.name);
}
/// Failure because of a getter and a method from direct superinterfaces.
class GetterMethodConflict extends Conflict {
final ExecutableElement getter;
final ExecutableElement method;
GetterMethodConflict({
@required Name name,
@required this.getter,
@required this.method,
}) : super(name);
}
/// Manages knowledge about interface types and their members.
class InheritanceManager3 {
static final _noSuchMethodName = Name(null, 'noSuchMethod');
/// Cached instance interfaces for [ClassElement].
final Map<ClassElement, Interface> _interfaces = {};
/// The set of classes that are currently being processed, used to detect
/// self-referencing cycles.
final Set<ClassElement> _processingClasses = <ClassElement>{};
/// Combine [candidates] into a single signature in the [targetClass].
///
/// If such signature does not exist, return `null`, and if [conflicts] is
/// not `null`, add a new [Conflict] to it.
ExecutableElement combineSignatures({
@required ClassElement targetClass,
@required List<ExecutableElement> candidates,
@required bool doTopMerge,
@required Name name,
List<Conflict> conflicts,
}) {
// If just one candidate, it is always valid.
if (candidates.length == 1) {
return candidates[0];
}
// Check for a getter/method conflict.
var conflict = _checkForGetterMethodConflict(name, candidates);
if (conflict != null) {
conflicts?.add(conflict);
return null;
}
var validOverrides = <ExecutableElement>[];
for (var i = 0; i < candidates.length; i++) {
var validOverride = candidates[i];
var overrideHelper = CorrectOverrideHelper(
library: targetClass.library,
thisMember: validOverride,
);
for (var j = 0; j < candidates.length; j++) {
var candidate = candidates[j];
if (!overrideHelper.isCorrectOverrideOf(superMember: candidate)) {
validOverride = null;
break;
}
}
if (validOverride != null) {
validOverrides.add(validOverride);
}
}
if (validOverrides.isEmpty) {
conflicts?.add(
CandidatesConflict(
name: name,
candidates: candidates,
),
);
return null;
}
if (doTopMerge) {
var typeSystem = targetClass.library.typeSystem;
return _topMerge(typeSystem, targetClass, validOverrides);
} else {
return validOverrides.first;
}
}
/// Return the result of [getInherited2] with [type] substitution.
ExecutableElement getInherited(InterfaceType type, Name name) {
var rawElement = getInherited2(type.element, name);
if (rawElement == null) {
return null;
}
return ExecutableMember.from2(
rawElement,
Substitution.fromInterfaceType(type),
);
}
/// Return the most specific signature of the member with the given [name]
/// that [element] inherits from the mixins, superclasses, or interfaces;
/// or `null` if no member is inherited because the member is not declared
/// at all, or because there is no the most specific signature.
///
/// This is equivalent to `getInheritedMap2(type)[name]`.
ExecutableElement getInherited2(ClassElement element, Name name) {
return getInheritedMap2(element)[name];
}
/// Return signatures of all concrete members that the given [type] inherits
/// from the superclasses and mixins.
@Deprecated('Use getInheritedConcreteMap2')
Map<Name, ExecutableElement> getInheritedConcreteMap(InterfaceType type) {
var result = <Name, ExecutableElement>{};
var substitution = Substitution.fromInterfaceType(type);
var rawMap = getInheritedConcreteMap2(type.element);
for (var rawEntry in rawMap.entries) {
result[rawEntry.key] = ExecutableMember.from2(
rawEntry.value,
substitution,
);
}
return result;
}
/// Return signatures of all concrete members that the given [element] inherits
/// from the superclasses and mixins.
Map<Name, ExecutableElement> getInheritedConcreteMap2(ClassElement element) {
var interface = getInterface(element);
return interface._superImplemented.last;
}
/// Return the mapping from names to most specific signatures of members
/// inherited from the super-interfaces (superclasses, mixins, and
/// interfaces). If there is no most specific signature for a name, the
/// corresponding name will not be included.
@Deprecated('Use getInheritedMap2')
Map<Name, ExecutableElement> getInheritedMap(InterfaceType type) {
var result = <Name, ExecutableElement>{};
var substitution = Substitution.fromInterfaceType(type);
var rawMap = getInheritedMap2(type.element);
for (var rawEntry in rawMap.entries) {
result[rawEntry.key] = ExecutableMember.from2(
rawEntry.value,
substitution,
);
}
return result;
}
/// Return the mapping from names to most specific signatures of members
/// inherited from the super-interfaces (superclasses, mixins, and
/// interfaces). If there is no most specific signature for a name, the
/// corresponding name will not be included.
Map<Name, ExecutableElement> getInheritedMap2(ClassElement element) {
var interface = getInterface(element);
if (interface._inheritedMap == null) {
interface._inheritedMap = {};
_findMostSpecificFromNamedCandidates(
element,
interface._inheritedMap,
interface._overridden,
doTopMerge: false,
);
}
return interface._inheritedMap;
}
/// Return the interface of the given [element]. It might include
/// private members, not necessary accessible in all libraries.
Interface getInterface(ClassElement element) {
if (element == null) {
return Interface._empty;
}
var result = _interfaces[element];
if (result != null) {
return result;
}
_interfaces[element] = Interface._empty;
if (!_processingClasses.add(element)) {
return Interface._empty;
}
try {
if (element.isMixin) {
result = _getInterfaceMixin(element);
} else {
result = _getInterfaceClass(element);
}
} finally {
_processingClasses.remove(element);
}
_interfaces[element] = result;
return result;
}
/// Return the result of [getMember2] with [type] substitution.
ExecutableElement getMember(
InterfaceType type,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
var rawElement = getMember2(
type.element,
name,
concrete: concrete,
forMixinIndex: forMixinIndex,
forSuper: forSuper,
);
if (rawElement == null) {
return null;
}
var substitution = Substitution.fromInterfaceType(type);
return ExecutableMember.from2(rawElement, substitution);
}
/// Return the member with the given [name].
///
/// If [concrete] is `true`, the the concrete implementation is returned,
/// from the given [element], or its superclass.
///
/// If [forSuper] is `true`, then [concrete] is implied, and only concrete
/// members from the superclass are considered.
///
/// If [forMixinIndex] is specified, only the nominal superclass, and the
/// given number of mixins after it are considered. For example for `1` in
/// `class C extends S with M1, M2, M3`, only `S` and `M1` are considered.
ExecutableElement getMember2(
ClassElement element,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
var interface = getInterface(element);
if (forSuper) {
var superImplemented = interface._superImplemented;
if (forMixinIndex >= 0) {
return superImplemented[forMixinIndex][name];
}
if (superImplemented.isNotEmpty) {
return superImplemented.last[name];
} else {
assert(element.name == 'Object');
return null;
}
}
if (concrete) {
return interface.implemented[name];
}
return interface.map[name];
}
/// Return all members of mixins, superclasses, and interfaces that a member
/// with the given [name], defined in the [type], would override; or `null`
/// if no members would be overridden.
@Deprecated('Use getOverridden2')
List<ExecutableElement> getOverridden(InterfaceType type, Name name) {
return getOverridden2(type.element, name);
}
/// Return all members of mixins, superclasses, and interfaces that a member
/// with the given [name], defined in the [element], would override; or `null`
/// if no members would be overridden.
List<ExecutableElement> getOverridden2(ClassElement element, Name name) {
var interface = getInterface(element);
return interface._overridden[name];
}
void _addCandidates({
@required Map<Name, List<ExecutableElement>> namedCandidates,
@required Substitution substitution,
@required Interface interface,
@required bool isNonNullableByDefault,
}) {
var map = interface.map;
for (var name in map.keys) {
var candidate = map[name];
candidate = ExecutableMember.from2(candidate, substitution);
if (!isNonNullableByDefault) {
candidate = Member.legacy(candidate);
}
var candidates = namedCandidates[name];
if (candidates == null) {
candidates = <ExecutableElement>[];
namedCandidates[name] = candidates;
}
candidates.add(candidate);
}
}
void _addImplemented(
Map<Name, ExecutableElement> implemented,
ClassElement element,
) {
var libraryUri = element.librarySource.uri;
void addMember(ExecutableElement member) {
if (!member.isAbstract && !member.isStatic) {
var name = Name(libraryUri, member.name);
implemented[name] = member;
}
}
element.methods.forEach(addMember);
element.accessors.forEach(addMember);
}
void _addMixinMembers({
@required Map<Name, ExecutableElement> implemented,
@required Substitution substitution,
@required Interface mixin,
@required bool isNonNullableByDefault,
}) {
for (var entry in mixin.implemented.entries) {
var executable = entry.value;
if (executable.isAbstract) {
continue;
}
var class_ = executable.enclosingElement;
if (class_ is ClassElement && class_.isDartCoreObject) {
continue;
}
executable = ExecutableMember.from2(executable, substitution);
if (!isNonNullableByDefault) {
executable = Member.legacy(executable);
}
implemented[entry.key] = executable;
}
}
/// Check that all [candidates] for the given [name] have the same kind, all
/// getters, all methods, or all setter. If a conflict found, return the
/// new [Conflict] instance that describes it.
Conflict _checkForGetterMethodConflict(
Name name, List<ExecutableElement> candidates) {
assert(candidates.length > 1);
bool allGetters = true;
bool allMethods = true;
bool allSetters = true;
for (var candidate in candidates) {
var kind = candidate.kind;
if (kind != ElementKind.GETTER) {
allGetters = false;
}
if (kind != ElementKind.METHOD) {
allMethods = false;
}
if (kind != ElementKind.SETTER) {
allSetters = false;
}
}
if (allGetters || allMethods || allSetters) {
return null;
}
ExecutableElement getter;
ExecutableElement method;
for (var candidate in candidates) {
var kind = candidate.kind;
if (kind == ElementKind.GETTER) {
getter ??= candidate;
}
if (kind == ElementKind.METHOD) {
method ??= candidate;
}
}
return GetterMethodConflict(name: name, getter: getter, method: method);
}
/// The given [namedCandidates] maps names to candidates from direct
/// superinterfaces. Find the most specific signature, and put it into the
/// [map], if there is no one yet (from the class itself). If there is no
/// such single most specific signature (i.e. no valid override), then add a
/// new conflict description.
List<Conflict> _findMostSpecificFromNamedCandidates(
ClassElement targetClass,
Map<Name, ExecutableElement> map,
Map<Name, List<ExecutableElement>> namedCandidates, {
@required bool doTopMerge,
}) {
var conflicts = <Conflict>[];
for (var name in namedCandidates.keys) {
if (map.containsKey(name)) {
continue;
}
var candidates = namedCandidates[name];
var combinedSignature = combineSignatures(
targetClass: targetClass,
candidates: candidates,
doTopMerge: doTopMerge,
name: name,
conflicts: conflicts,
);
if (combinedSignature != null) {
map[name] = combinedSignature;
continue;
}
}
return conflicts;
}
Interface _getInterfaceClass(ClassElement element) {
var classLibrary = element.library;
var isNonNullableByDefault = classLibrary.isNonNullableByDefault;
var namedCandidates = <Name, List<ExecutableElement>>{};
var superImplemented = <Map<Name, ExecutableElement>>[];
var implemented = <Name, ExecutableElement>{};
Interface superTypeInterface;
var superType = element.supertype;
if (superType != null) {
var substitution = Substitution.fromInterfaceType(superType);
superTypeInterface = getInterface(superType.element);
_addCandidates(
namedCandidates: namedCandidates,
substitution: substitution,
interface: superTypeInterface,
isNonNullableByDefault: isNonNullableByDefault,
);
for (var entry in superTypeInterface.implemented.entries) {
var executable = entry.value;
executable = ExecutableMember.from2(executable, substitution);
if (!isNonNullableByDefault) {
executable = Member.legacy(executable);
}
implemented[entry.key] = executable;
}
superImplemented.add(implemented);
}
// TODO(scheglov) Handling of members for super and mixins is not
// optimal. We always have just one member for each name in super,
// multiple candidates happen only when we merge super and multiple
// interfaces. Consider using `Map<Name, ExecutableElement>` here.
var mixinsConflicts = <List<Conflict>>[];
for (var mixin in element.mixins) {
var mixinElement = mixin.element;
var substitution = Substitution.fromInterfaceType(mixin);
var mixinInterface = getInterface(mixinElement);
// `class X extends S with M1, M2 {}` is semantically a sequence of:
// class S&M1 extends S implements M1 {
// // declared M1 members
// }
// class S&M2 extends S&M1 implements M2 {
// // declared M2 members
// }
// class X extends S&M2 {
// // declared X members
// }
// So, each mixin always replaces members in the interface.
// And there are individual override conflicts for each mixin.
var candidatesFromSuperAndMixin = <Name, List<ExecutableElement>>{};
var mixinConflicts = <Conflict>[];
for (var name in mixinInterface.map.keys) {
var candidate = ExecutableMember.from2(
mixinInterface.map[name],
substitution,
);
var currentList = namedCandidates[name];
if (currentList == null) {
namedCandidates[name] = [
isNonNullableByDefault ? candidate : Member.legacy(candidate),
];
continue;
}
var current = currentList.single;
if (candidate.enclosingElement == mixinElement) {
namedCandidates[name] = [
isNonNullableByDefault ? candidate : Member.legacy(candidate),
];
if (current.kind != candidate.kind) {
var currentIsGetter = current.kind == ElementKind.GETTER;
mixinConflicts.add(
GetterMethodConflict(
name: name,
getter: currentIsGetter ? current : candidate,
method: currentIsGetter ? candidate : current,
),
);
}
} else {
candidatesFromSuperAndMixin[name] = [current, candidate];
}
}
// Merge members from the superclass and the mixin interface.
{
var map = <Name, ExecutableElement>{};
_findMostSpecificFromNamedCandidates(
element,
map,
candidatesFromSuperAndMixin,
doTopMerge: true,
);
for (var entry in map.entries) {
namedCandidates[entry.key] = [
isNonNullableByDefault ? entry.value : Member.legacy(entry.value),
];
}
}
mixinsConflicts.add(mixinConflicts);
implemented = Map.of(implemented);
_addMixinMembers(
implemented: implemented,
substitution: substitution,
mixin: mixinInterface,
isNonNullableByDefault: isNonNullableByDefault,
);
superImplemented.add(implemented);
}
for (var interface in element.interfaces) {
_addCandidates(
namedCandidates: namedCandidates,
substitution: Substitution.fromInterfaceType(interface),
interface: getInterface(interface.element),
isNonNullableByDefault: isNonNullableByDefault,
);
}
implemented = Map.of(implemented);
_addImplemented(implemented, element);
// If a class declaration has a member declaration, the signature of that
// member declaration becomes the signature in the interface.
var declared = _getTypeMembers(element);
// 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.
var interface = Map.of(declared);
List<Conflict> conflicts = _findMostSpecificFromNamedCandidates(
element,
interface,
namedCandidates,
doTopMerge: true,
);
var noSuchMethodForwarders = <Name>{};
if (element.isAbstract) {
if (superTypeInterface != null) {
noSuchMethodForwarders = superTypeInterface._noSuchMethodForwarders;
}
} else {
var noSuchMethod = implemented[_noSuchMethodName];
if (noSuchMethod != null && !_isDeclaredInObject(noSuchMethod)) {
var superForwarders = superTypeInterface?._noSuchMethodForwarders;
for (var name in interface.keys) {
if (!implemented.containsKey(name) ||
superForwarders != null && superForwarders.contains(name)) {
implemented[name] = interface[name];
noSuchMethodForwarders.add(name);
}
}
}
}
/// TODO(scheglov) Instead of merging conflicts we could report them on
/// the corresponding mixins applied in the class.
for (var mixinConflicts in mixinsConflicts) {
if (mixinConflicts.isNotEmpty) {
conflicts ??= [];
conflicts.addAll(mixinConflicts);
}
}
return Interface._(
interface,
declared,
implemented,
noSuchMethodForwarders,
namedCandidates,
superImplemented,
conflicts ?? const [],
);
}
Interface _getInterfaceMixin(ClassElement element) {
var classLibrary = element.library;
var isNonNullableByDefault = classLibrary.isNonNullableByDefault;
var superCandidates = <Name, List<ExecutableElement>>{};
for (var constraint in element.superclassConstraints) {
var substitution = Substitution.fromInterfaceType(constraint);
var interfaceObj = getInterface(constraint.element);
_addCandidates(
namedCandidates: superCandidates,
substitution: substitution,
interface: interfaceObj,
isNonNullableByDefault: isNonNullableByDefault,
);
}
// `mixin M on S1, S2 {}` can call using `super` any instance member
// from its superclass constraints, whether it is abstract or concrete.
var superInterface = <Name, ExecutableElement>{};
var superConflicts = _findMostSpecificFromNamedCandidates(
element,
superInterface,
superCandidates,
doTopMerge: true,
);
var interfaceCandidates = Map.of(superCandidates);
for (var interface in element.interfaces) {
_addCandidates(
namedCandidates: interfaceCandidates,
substitution: Substitution.fromInterfaceType(interface),
interface: getInterface(interface.element),
isNonNullableByDefault: isNonNullableByDefault,
);
}
var declared = _getTypeMembers(element);
var interface = Map.of(declared);
var interfaceConflicts = _findMostSpecificFromNamedCandidates(
element,
interface,
interfaceCandidates,
doTopMerge: true,
);
var implemented = <Name, ExecutableElement>{};
_addImplemented(implemented, element);
return Interface._(
interface,
declared,
implemented,
{},
interfaceCandidates,
[superInterface],
<Conflict>[...?superConflicts, ...?interfaceConflicts],
);
}
/// Given one or more [validOverrides], merge them into a single resulting
/// signature. This signature always exists.
ExecutableElement _topMerge(
TypeSystemImpl typeSystem,
ClassElement targetClass,
List<ExecutableElement> validOverrides,
) {
var first = validOverrides[0];
if (validOverrides.length == 1) {
return first;
}
if (!typeSystem.isNonNullableByDefault) {
return first;
}
var firstType = first.type;
var allTypesEqual = true;
for (var executable in validOverrides) {
if (executable.type != firstType) {
allTypesEqual = false;
break;
}
}
if (allTypesEqual) {
return first;
}
FunctionType resultType;
for (var executable in validOverrides) {
var type = executable.type;
var normalizedType = typeSystem.normalize(type);
if (resultType == null) {
resultType = normalizedType;
} else {
resultType = typeSystem.topMerge(resultType, normalizedType);
}
}
for (var executable in validOverrides) {
if (executable.type == resultType) {
return executable;
}
}
if (first is MethodElement) {
var firstMethod = first;
var result = MethodElementImpl(firstMethod.name, -1);
result.enclosingElement = targetClass;
result.typeParameters = resultType.typeFormals;
result.returnType = resultType.returnType;
result.parameters = resultType.parameters;
return result;
} else {
var firstAccessor = first as PropertyAccessorElement;
var result = PropertyAccessorElementImpl(firstAccessor.name, -1);
result.enclosingElement = targetClass;
result.isGetter = firstAccessor.isGetter;
result.returnType = resultType.returnType;
result.parameters = resultType.parameters;
return result;
}
}
static Map<Name, ExecutableElement> _getTypeMembers(ClassElement element) {
var declared = <Name, ExecutableElement>{};
var libraryUri = element.librarySource.uri;
var methods = element.methods;
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (!method.isStatic) {
var name = Name(libraryUri, method.name);
declared[name] = method;
}
}
var accessors = element.accessors;
for (var i = 0; i < accessors.length; i++) {
var accessor = accessors[i];
if (!accessor.isStatic) {
var name = Name(libraryUri, accessor.name);
declared[name] = accessor;
}
}
return declared;
}
static bool _isDeclaredInObject(ExecutableElement element) {
var enclosing = element.enclosingElement;
return enclosing is ClassElement &&
enclosing.supertype == null &&
!enclosing.isMixin;
}
}
/// The instance interface of an [InterfaceType].
class Interface {
static final _empty = Interface._(
const {},
const {},
const {},
<Name>{},
const {},
const [{}],
const [],
);
/// The map of names to their signature in the interface.
final Map<Name, ExecutableElement> map;
/// The map of declared names to their signatures.
final Map<Name, ExecutableElement> declared;
/// The map of names to their concrete implementations.
final Map<Name, ExecutableElement> implemented;
/// The set of names that are `noSuchMethod` forwarders in [implemented].
final Set<Name> _noSuchMethodForwarders;
/// The map of names to their signatures from the mixins, superclasses,
/// or interfaces.
final Map<Name, List<ExecutableElement>> _overridden;
/// Each item of this list maps names to their concrete implementations.
/// The first item of the list is the nominal superclass, next the nominal
/// superclass plus the first mixin, etc. So, for the class like
/// `class C extends S with M1, M2`, we get `[S, S&M1, S&M1&M2]`.
final List<Map<Name, ExecutableElement>> _superImplemented;
/// The list of conflicts between superinterfaces - the nominal superclass,
/// mixins, and interfaces. Does not include conflicts with the declared
/// members of the class.
final List<Conflict> conflicts;
/// The map of names to the most specific signatures from the mixins,
/// superclasses, or interfaces.
Map<Name, ExecutableElement> _inheritedMap;
Interface._(
this.map,
this.declared,
this.implemented,
this._noSuchMethodForwarders,
this._overridden,
this._superImplemented,
this.conflicts,
);
/// Return `true` if the [name] is implemented in the supertype.
bool isSuperImplemented(Name name) {
return _superImplemented.last.containsKey(name);
}
}
/// A public name, or a private name qualified by a library URI.
class Name {
/// If the name is private, the URI of the defining library.
/// Otherwise, it is `null`.
final Uri libraryUri;
/// The name of this name object.
/// If the name starts with `_`, then the name is private.
/// Names of setters end with `=`.
final String name;
/// Precomputed
final bool isPublic;
/// The cached, pre-computed hash code.
@override
final int hashCode;
factory Name(Uri libraryUri, String name) {
if (name.startsWith('_')) {
var hashCode = JenkinsSmiHash.hash2(libraryUri.hashCode, name.hashCode);
return Name._internal(libraryUri, name, false, hashCode);
} else {
return Name._internal(null, name, true, name.hashCode);
}
}
Name._internal(this.libraryUri, this.name, this.isPublic, this.hashCode);
@override
bool operator ==(Object other) {
return other is Name &&
name == other.name &&
libraryUri == other.libraryUri;
}
bool isAccessibleFor(Uri libraryUri) {
return isPublic || this.libraryUri == libraryUri;
}
@override
String toString() => libraryUri != null ? '$libraryUri::$name' : name;
}