blob: b5239e4b6cb4f987b792d7bda50ea4e821cc0ece [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:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
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/extensions.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/fine/requirements.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/element.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<ExecutableElementOrMember> candidates;
CandidatesConflict({required super.name, required this.candidates});
List<ExecutableElement> get candidates2 =>
candidates.map((e) => e.asElement2).toList();
}
/// 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({required this.name});
}
/// Failure because of a getter and a method from direct superinterfaces.
class GetterMethodConflict extends Conflict {
final ExecutableElementOrMember getter;
final ExecutableElementOrMember method;
GetterMethodConflict({
required super.name,
required this.getter,
required this.method,
});
ExecutableElement get getter2 => getter.asElement2;
ExecutableElement get method2 => method.asElement2;
}
/// The extension type has both an extension and non-extension member
/// signature with the same name.
class HasNonExtensionAndExtensionMemberConflict extends Conflict {
final List<ExecutableElementOrMember> nonExtension;
final List<ExecutableElementOrMember> extension;
HasNonExtensionAndExtensionMemberConflict({
required super.name,
required this.nonExtension,
required this.extension,
});
List<ExecutableElement> get extension2 =>
extension.map((e) => e.asElement2).toList();
List<ExecutableElement> get nonExtension2 =>
nonExtension.map((e) => e.asElement2).toList();
}
/// Manages knowledge about interface types and their members.
class InheritanceManager3 {
static final _noSuchMethodName = Name(
null,
MethodElement.NO_SUCH_METHOD_METHOD_NAME,
);
/// Cached instance interfaces for [InterfaceFragmentImpl].
final Map<InterfaceFragmentImpl, Interface> _interfaces = {};
/// Tracks signatures from superinterfaces that were combined.
/// It is used to track dependencies in manifests.
final Map<InterfaceFragmentImpl, Map<Name, List<ExecutableElement2OrMember>>>
_combinedSignatures = {};
/// The set of classes that are currently being processed, used to detect
/// self-referencing cycles.
final Set<InterfaceFragmentImpl> _processingClasses = {};
/// Combine types of [candidates] into a single most specific type.
///
/// If such signature does not exist, return `null`, and if [conflicts] is
/// not `null`, add a new [Conflict] to it.
FunctionTypeImpl? combineSignatureTypes({
required TypeSystemImpl typeSystem,
required List<ExecutableElementOrMember> candidates,
required Name name,
List<Conflict>? conflicts,
}) {
if (candidates.length == 1) {
return candidates[0].type;
}
var validOverrides = _getValidOverrides(
typeSystem: typeSystem,
candidates: candidates,
);
if (validOverrides.isEmpty) {
conflicts?.add(CandidatesConflict(name: name, candidates: candidates));
return null;
}
// Often there is one most specific signature.
var firstType = validOverrides[0].type;
if (validOverrides.length == 1) {
return firstType;
}
// Maybe more than valid, but the same type.
if (validOverrides.every((e) => e.type == firstType)) {
return firstType;
}
return _topMergeSignatureTypes(
typeSystem: typeSystem,
validOverrides: validOverrides,
);
}
/// 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(element)[name]`.
ExecutableElementOrMember? getInherited2(
InterfaceFragmentImpl element,
Name name,
) {
return getInheritedMap2(element)[name];
}
/// Returns the result of [getInherited2] with [type] substitution.
@experimental
ExecutableElement? getInherited3(InterfaceType type, Name name) {
type as InterfaceTypeImpl;
var rawElement = getInherited2(type.element3.asElement, name);
if (rawElement == null) {
return null;
}
var element = ExecutableMember.from2(
rawElement,
Substitution.fromInterfaceType(type),
);
return element.asElement2;
}
/// Returns the most specific signature of the member with the given [name]
/// that [element] inherits from the mixins, superclasses, or interfaces.
///
/// Returns `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 `getInheritedMap(type)[name]`.
// This is a replacement for `getInherited2`.
@experimental
ExecutableElement2OrMember? getInherited4(
InterfaceElement element,
Name name,
) {
element as InterfaceElementImpl; // TODO(scheglov): remove cast
var oldElement = getInherited2(element.asElement, name);
return oldElement?.asElement2;
}
/// Returns signatures of all concrete members that the given [element]
/// inherits from the superclasses and mixins.
@experimental
Map<Name, ExecutableElement> getInheritedConcreteMap(
InterfaceElement element,
) {
element as InterfaceElementImpl; // TODO(scheglov): remove cast
var fragment = element.asElement;
if (fragment is ExtensionTypeFragmentImpl) {
return const {};
}
var interface = getInterface(fragment);
if (interface.superImplemented.isEmpty) {
assert(fragment.name2 == 'Object');
return const {};
}
var map = interface.superImplemented.last;
return map.mapValue((e) => e.asElement2);
}
/// Returns 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.
@experimental
Map<Name, ExecutableElement> getInheritedMap(InterfaceElement element) {
element as InterfaceElementImpl; // TODO(scheglov): remove cast
var map = getInheritedMap2(element.asElement);
return map.mapValue((element) => element.asElement2);
}
/// 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, ExecutableElementOrMember> getInheritedMap2(
InterfaceFragmentImpl element,
) {
var interface = getInterface(element);
var inheritedMap = interface.inheritedMap;
if (inheritedMap == null) {
inheritedMap = interface.inheritedMap = {};
_findMostSpecificFromNamedCandidates(
element,
inheritedMap,
element is ExtensionTypeFragmentImpl
? interface.redeclared
: interface.overridden,
);
}
return inheritedMap;
}
/// Return the interface of the given [element]. It might include
/// private members, not necessary accessible in all libraries.
Interface getInterface(InterfaceFragmentImpl element) {
var result = _interfaces[element];
if (result != null) {
return result;
}
_interfaces[element] = Interface._empty;
if (!_processingClasses.add(element)) {
return Interface._empty;
}
try {
if (element is ExtensionTypeFragmentImpl) {
result = _getInterfaceExtensionType(element);
} else if (element is MixinFragmentImpl) {
result = _getInterfaceMixin(element);
} else {
result = _getInterfaceClass(element);
}
} finally {
_processingClasses.remove(element);
}
_interfaces[element] = result;
return result;
}
/// Returns the interface of the given [element].
///
/// The interface might include private members, not necessary accessible in
/// all libraries.
@experimental
Interface getInterface2(InterfaceElement element) {
element as InterfaceElementImpl; // TODO(scheglov): remove cast
globalResultRequirements?.record_interface_all(element: element);
return getInterface(element.asElement);
}
/// Return the result of [getMember2] with [type] substitution.
ExecutableElementOrMember? getMember(
InterfaceType type,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
type as InterfaceTypeImpl; // TODO(scheglov): remove cast
var rawElement = getMember2(
type.element3.asElement,
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 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.
ExecutableElementOrMember? getMember2(
InterfaceFragmentImpl element,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
var interface = getInterface(element);
if (forSuper) {
if (element is ExtensionTypeFragmentImpl) {
return null;
}
var superImplemented = interface.superImplemented;
if (forMixinIndex >= 0) {
return superImplemented[forMixinIndex][name];
}
if (superImplemented.isNotEmpty) {
return superImplemented.last[name];
} else {
assert(element.name2 == 'Object');
return null;
}
}
if (concrete) {
return interface.implemented[name];
}
var result = interface.map[name];
globalResultRequirements?.record_interface_getMember(
element: element.asElement2,
nameObj: name,
methodElement: result?.asElement2,
);
return result;
}
/// Returns the result of [getMember4] with [type] substitution.
// This is a replacement for `getMember`.
@experimental
ExecutableElement? getMember3(
InterfaceType type,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
var element = getMember(
type,
name,
concrete: concrete,
forMixinIndex: forMixinIndex,
forSuper: forSuper,
);
return element?.asElement2;
}
/// Returns the member with the given [name].
///
/// If [concrete] is `true`, the concrete implementation is returned, whether
/// 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.
// This is a replacement for `getMember2`.
@experimental
ExecutableElement2OrMember? getMember4(
InterfaceElement element,
Name name, {
bool concrete = false,
int forMixinIndex = -1,
bool forSuper = false,
}) {
element as InterfaceElementImpl; // TODO(scheglov): remove cast
var oldElement = getMember2(
element.asElement,
name,
concrete: concrete,
forMixinIndex: forMixinIndex,
forSuper: forSuper,
);
return oldElement?.asElement2;
}
/// 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<ExecutableElementOrMember>? getOverridden2(
InterfaceFragmentImpl element,
Name name,
) {
var interface = getInterface(element);
return interface.overridden[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>? getOverridden4(InterfaceElement element, Name name) {
var interface = getInterface2(element);
var fragments = interface.overridden[name];
return fragments?.map((fragment) => fragment.asElement2).toList();
}
/// Remove interfaces for classes defined in specified libraries.
void removeOfLibraries(Set<Uri> uriSet) {
_interfaces.removeWhere((element, _) {
return uriSet.contains(element.librarySource.uri);
});
}
void _addCandidates({
required Map<Name, List<ExecutableElementOrMember>> namedCandidates,
required MapSubstitution substitution,
required Interface interface,
}) {
var map = interface.map;
for (var entry in map.entries) {
var name = entry.key;
var candidate = entry.value;
candidate = ExecutableMember.from2(candidate, substitution);
var candidates = namedCandidates[name];
if (candidates == null) {
candidates = <ExecutableElementOrMember>[];
namedCandidates[name] = candidates;
}
candidates.add(candidate);
}
}
void _addImplemented(
Map<Name, ExecutableElementOrMember> implemented,
InterfaceFragmentImpl fragment,
InterfaceElementImpl element,
) {
var libraryUri = fragment.librarySource.uri;
void addMember(ExecutableElementOrMember member) {
if (!member.isAbstract && !member.isStatic) {
var lookupName = member.element.lookupName;
if (lookupName != null) {
var name = Name(libraryUri, lookupName);
implemented[name] = member;
}
}
}
element.methods.map((e) => e.asElement).forEach(addMember);
element.getters.map((e) => e.asElement).forEach(addMember);
element.setters.map((e) => e.asElement).forEach(addMember);
}
void _addMixinMembers({
required Map<Name, ExecutableElementOrMember> implemented,
required MapSubstitution substitution,
required Interface mixin,
}) {
for (var entry in mixin.implemented.entries) {
var executable = entry.value;
if (executable.isAbstract) {
continue;
}
var class_ = executable.asElement2.enclosingElement;
if (class_ is ClassElement && class_.isDartCoreObject) {
continue;
}
executable = ExecutableMember.from2(executable, substitution);
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<ExecutableElementOrMember> candidates,
) {
assert(candidates.length > 1);
ExecutableElementOrMember? getter;
ExecutableElementOrMember? method;
for (var candidate in candidates) {
var kind = candidate.kind;
if (kind == ElementKind.GETTER) {
getter ??= candidate;
}
if (kind == ElementKind.METHOD) {
method ??= candidate;
}
}
if (getter == null || method == null) {
return null;
} else {
return GetterMethodConflict(name: name, getter: getter, method: method);
}
}
/// 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.
ExecutableElementOrMember? _combineSignatures({
required InterfaceFragmentImpl targetClass,
required List<ExecutableElementOrMember> candidates,
required Name name,
List<Conflict>? conflicts,
}) {
// If just one candidate, it is always valid.
if (candidates.length == 1) {
return candidates[0];
}
var targetLibrary = targetClass.library;
var typeSystem = targetLibrary.typeSystem;
var validOverrides = _getValidOverrides(
candidates: candidates,
typeSystem: typeSystem,
);
if (validOverrides.isEmpty) {
conflicts?.add(CandidatesConflict(name: name, candidates: candidates));
return null;
}
(_combinedSignatures[targetClass] ??= {})[name] =
candidates.map((e) => e.asElement2).toList();
return _topMerge(typeSystem, targetClass, validOverrides);
}
/// 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(
InterfaceFragmentImpl targetClass,
Map<Name, ExecutableElementOrMember> map,
Map<Name, List<ExecutableElementOrMember>> namedCandidates,
) {
var conflicts = <Conflict>[];
for (var entry in namedCandidates.entries) {
var name = entry.key;
var candidates = entry.value;
// There is no way to resolve the getter / method conflict.
if (candidates.length > 1) {
var conflict = _checkForGetterMethodConflict(name, candidates);
if (conflict != null) {
conflicts.add(conflict);
continue;
}
}
if (map.containsKey(name)) {
continue;
}
var combinedSignature = _combineSignatures(
targetClass: targetClass,
candidates: candidates,
name: name,
conflicts: conflicts,
);
if (combinedSignature != null) {
map[name] = combinedSignature;
continue;
}
}
return conflicts;
}
Interface _getInterfaceClass(InterfaceFragmentImpl fragment) {
var element = fragment.element;
var namedCandidates = <Name, List<ExecutableElementOrMember>>{};
var superImplemented = <Map<Name, ExecutableElementOrMember>>[];
var implemented = <Name, ExecutableElementOrMember>{};
InterfaceType? superType = fragment.supertype;
Interface? superTypeInterface;
if (superType != null) {
superType as InterfaceTypeImpl;
var substitution = Substitution.fromInterfaceType(superType);
superTypeInterface = getInterface2(superType.element3);
_addCandidates(
namedCandidates: namedCandidates,
substitution: substitution,
interface: superTypeInterface,
);
for (var entry in superTypeInterface.implemented.entries) {
var executable = entry.value;
executable = ExecutableMember.from2(executable, substitution);
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.element3;
var substitution = Substitution.fromInterfaceType(mixin);
var mixinInterface = getInterface2(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<ExecutableElementOrMember>>{};
var mixinConflicts = <Conflict>[];
for (var entry in mixinInterface.map.entries) {
var name = entry.key;
var candidate = ExecutableMember.from2(entry.value, substitution);
var candidate2 = candidate.asElement2;
var currentList = namedCandidates[name];
if (currentList == null) {
namedCandidates[name] = [candidate];
continue;
}
var current = currentList.single;
if (candidate2.enclosingElement == mixinElement) {
namedCandidates[name] = [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, ExecutableElementOrMember>{};
_findMostSpecificFromNamedCandidates(
fragment,
map,
candidatesFromSuperAndMixin,
);
for (var entry in map.entries) {
namedCandidates[entry.key] = [entry.value];
}
}
mixinsConflicts.add(mixinConflicts);
implemented = Map.of(implemented);
_addMixinMembers(
implemented: implemented,
substitution: substitution,
mixin: mixinInterface,
);
superImplemented.add(implemented);
}
for (var interface in element.interfaces) {
_addCandidates(
namedCandidates: namedCandidates,
substitution: Substitution.fromInterfaceType(interface),
interface: getInterface2(interface.element3),
);
}
implemented = Map.of(implemented);
_addImplemented(implemented, fragment, element);
// If a class declaration has a member declaration, the signature of that
// member declaration becomes the signature in the interface.
var declared = _getTypeMembers(fragment, 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(
fragment,
interface,
namedCandidates,
);
var noSuchMethodForwarders = <Name>{};
if (fragment is ClassFragmentImpl && fragment.isAbstract) {
if (superTypeInterface != null) {
noSuchMethodForwarders = superTypeInterface.noSuchMethodForwarders;
}
} else {
var noSuchMethod = implemented[_noSuchMethodName];
if (noSuchMethod != null && !_isDeclaredInObject(noSuchMethod)) {
var superForwarders = superTypeInterface?.noSuchMethodForwarders;
for (var entry in interface.entries) {
var name = entry.key;
if (!implemented.containsKey(name) ||
superForwarders != null && superForwarders.contains(name)) {
implemented[name] = entry.value;
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.addAll(mixinConflicts);
}
}
implemented = implemented.map<Name, ExecutableElementOrMember>((
key,
value,
) {
var result = _inheritCovariance(fragment, namedCandidates, key, value);
return MapEntry(key, result);
});
var implemented2 = implemented.mapValue((value) {
try {
return value.asElement2;
} catch (e) {
rethrow;
}
});
var namedCandidates2 = namedCandidates.map<Name, List<ExecutableElement>>(
(key, value) => MapEntry(key, value.map((e) => e.asElement2).toList()),
);
var superImplemented2 =
superImplemented
.map(
(e) => e.map<Name, ExecutableElement>(
(key, value) => MapEntry(key, value.asElement2),
),
)
.toList();
return Interface._(
map: interface,
declared: declared,
implemented: implemented,
implemented2: implemented2,
noSuchMethodForwarders: noSuchMethodForwarders,
overridden: namedCandidates,
overridden2: namedCandidates2,
redeclared: const {},
redeclared2: const {},
superImplemented: superImplemented,
superImplemented2: superImplemented2,
conflicts: conflicts.toFixedList(),
combinedSignatures: _combinedSignatures.remove(fragment) ?? {},
);
}
/// See https://github.com/dart-lang/language
/// blob/main/accepted/future-releases/extension-types/feature-specification.md
/// #static-analysis-of-an-extension-type-member-invocation
///
/// We handle "has an extension type member" and "has a non-extension type
/// member" portions, considering redeclaration and conflicts.
Interface _getInterfaceExtensionType(ExtensionTypeFragmentImpl fragment) {
var element = fragment.element;
// Add instance members implemented by the element itself.
var declared = <Name, ExecutableElementOrMember>{};
_addImplemented(declared, fragment, element);
// Prepare precluded names.
var precludedNames = <Name>{};
var precludedMethods = <Name>{};
var precludedSetters = <Name>{};
for (var entry in declared.entries) {
var name = entry.key;
precludedNames.add(name);
switch (entry.value) {
case MethodElementOrMember():
precludedSetters.add(name.forSetter);
case PropertyAccessorElementOrMember(isSetter: true):
precludedMethods.add(name.forGetter);
}
}
// These declared members take precedence over "inherited" ones.
var implemented = Map.of(declared);
// Prepare candidates for inheritance.
var extensionCandidates = <Name, _ExtensionTypeCandidates>{};
var notExtensionCandidates = <Name, _ExtensionTypeCandidates>{};
for (var interface in element.interfaces) {
var substitution = Substitution.fromInterfaceType(interface);
for (var entry in getInterface2(interface.element3).map.entries) {
var name = entry.key;
var executable = ExecutableMember.from2(entry.value, substitution);
if (executable.isExtensionTypeMember) {
(extensionCandidates[name] ??= _ExtensionTypeCandidates(name)).add(
executable,
);
} else {
(notExtensionCandidates[name] ??= _ExtensionTypeCandidates(name)).add(
executable,
);
}
}
}
var redeclared = <Name, List<ExecutableElementOrMember>>{};
var conflicts = <Conflict>[];
// Add extension type members.
for (var entry in extensionCandidates.entries) {
var name = entry.key;
var candidates = entry.value;
(redeclared[name] ??= []).addAll(candidates.all);
var notPrecluded = candidates.notPrecluded(
precludedNames: precludedNames,
precludedMethods: precludedMethods,
precludedSetters: precludedSetters,
);
// Stop if all precluded.
if (notPrecluded.isEmpty) {
continue;
}
// If not precluded, can have either non-extension, or extension.
var nonExtensionSignatures = notExtensionCandidates[name];
if (nonExtensionSignatures != null) {
var notExtensionNotPrecluded = nonExtensionSignatures.notPrecluded(
precludedNames: precludedNames,
precludedMethods: precludedMethods,
precludedSetters: precludedSetters,
);
if (notExtensionNotPrecluded.isNotEmpty) {
conflicts.add(
HasNonExtensionAndExtensionMemberConflict(
name: name,
nonExtension: notExtensionNotPrecluded,
extension: notPrecluded,
),
);
}
continue;
}
// The inherited member must be unique.
ExecutableElementOrMember? uniqueElement;
for (var candidate in notPrecluded) {
if (uniqueElement == null) {
uniqueElement = candidate;
} else if (uniqueElement.asElement2.baseElement !=
candidate.asElement2.baseElement) {
uniqueElement = null;
break;
}
}
if (uniqueElement == null) {
conflicts.add(
NotUniqueExtensionMemberConflict(
name: name,
candidates: notPrecluded,
),
);
continue;
}
implemented[name] = uniqueElement;
}
// Add non-extension type members.
for (var entry in notExtensionCandidates.entries) {
var name = entry.key;
var candidates = entry.value;
(redeclared[name] ??= []).addAll(candidates.all);
var notPrecluded = candidates.notPrecluded(
precludedNames: precludedNames,
precludedMethods: precludedMethods,
precludedSetters: precludedSetters,
);
// Stop if all precluded.
if (notPrecluded.isEmpty) {
continue;
}
// Skip, if also has extension candidates.
// The conflict is already reported.
if (extensionCandidates.containsKey(name)) {
continue;
}
var combinedSignature = _combineSignatures(
targetClass: fragment,
candidates: notPrecluded,
name: name,
);
if (combinedSignature == null) {
conflicts.add(CandidatesConflict(name: name, candidates: notPrecluded));
continue;
}
implemented[name] = combinedSignature;
}
// Ensure unique overridden elements.
var uniqueRedeclared = <Name, List<ExecutableElementOrMember>>{};
for (var entry in redeclared.entries) {
var name = entry.key;
var elements = entry.value;
if (elements.length == 1) {
uniqueRedeclared[name] = elements;
} else {
uniqueRedeclared[name] = elements.toSet().toFixedList();
}
}
var uniqueRedeclared2 = <Name, List<ExecutableElement>>{};
for (var entry in redeclared.entries) {
var name = entry.key;
var fragments = entry.value.map((fragment) => fragment.asElement2);
if (fragments.length == 1) {
uniqueRedeclared2[name] = fragments.toFixedList();
} else {
var uniqueElements = <ExecutableElement>{};
for (var fragment in fragments) {
uniqueElements.add(fragment);
}
uniqueRedeclared2[name] = uniqueElements.toFixedList();
}
}
var implemented2 = implemented.mapValue((value) => value.asElement2);
return Interface._(
map: implemented,
declared: declared,
implemented: implemented,
implemented2: implemented2,
noSuchMethodForwarders: const {},
overridden: const {},
overridden2: const {},
redeclared: uniqueRedeclared,
redeclared2: uniqueRedeclared2,
superImplemented: const [],
superImplemented2: const [],
conflicts: conflicts.toFixedList(),
combinedSignatures: _combinedSignatures.remove(fragment) ?? {},
);
}
Interface _getInterfaceMixin(MixinFragmentImpl fragment) {
var element = fragment.element;
var superCandidates = <Name, List<ExecutableElementOrMember>>{};
for (var constraint in element.superclassConstraints) {
var substitution = Substitution.fromInterfaceType(constraint);
var interfaceObj = getInterface2(constraint.element3);
_addCandidates(
namedCandidates: superCandidates,
substitution: substitution,
interface: interfaceObj,
);
}
// `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, ExecutableElementOrMember>{};
var superConflicts = _findMostSpecificFromNamedCandidates(
fragment,
superInterface,
superCandidates,
);
var interfaceCandidates = Map.of(superCandidates);
for (var interface in element.interfaces) {
_addCandidates(
namedCandidates: interfaceCandidates,
substitution: Substitution.fromInterfaceType(interface),
interface: getInterface2(interface.element3),
);
}
var declared = _getTypeMembers(fragment, element);
var interface = Map.of(declared);
var interfaceConflicts = _findMostSpecificFromNamedCandidates(
fragment,
interface,
interfaceCandidates,
);
var implemented = <Name, ExecutableElementOrMember>{};
_addImplemented(implemented, fragment, element);
var implemented2 = implemented.mapValue((value) => value.asElement2);
var interfaceCandidates2 = interfaceCandidates.map<
Name,
List<ExecutableElement>
>((key, value) => MapEntry(key, value.map((e) => e.asElement2).toList()));
var superInterface2 = superInterface.mapValue((value) => value.asElement2);
return Interface._(
map: interface,
declared: declared,
implemented: implemented,
implemented2: implemented2,
noSuchMethodForwarders: {},
overridden: interfaceCandidates,
overridden2: interfaceCandidates2,
redeclared: const {},
redeclared2: const {},
superImplemented: [superInterface],
superImplemented2: [superInterface2],
conflicts:
<Conflict>[...superConflicts, ...interfaceConflicts].toFixedList(),
combinedSignatures: _combinedSignatures.remove(fragment) ?? {},
);
}
/// If a candidate from [namedCandidates] has covariant parameters, return
/// a copy of the [executable] with the corresponding parameters marked
/// covariant. If there are no covariant parameters, or parameters to
/// update are already covariant, return the [executable] itself.
ExecutableElementOrMember _inheritCovariance(
InterfaceFragmentImpl class_,
Map<Name, List<ExecutableElementOrMember>> namedCandidates,
Name name,
ExecutableElementOrMember executable,
) {
if (executable.asElement2.enclosingElement == class_.asElement2) {
return executable;
}
var parameters = executable.parameters;
if (parameters.isEmpty) {
return executable;
}
var candidates = namedCandidates[name];
if (candidates == null) {
return executable;
}
// Find parameters that are covariant (by declaration) in any overridden.
Set<_ParameterDesc>? covariantParameters;
for (var candidate in candidates) {
var parameters = candidate.parameters;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters[i];
if (parameter.isCovariant) {
covariantParameters ??= {};
covariantParameters.add(_ParameterDesc(i, parameter));
}
}
}
if (covariantParameters == null) {
return executable;
}
// Update covariance of the parameters of the chosen executable.
List<FormalParameterFragmentImpl>? transformedParameters;
for (var index = 0; index < parameters.length; index++) {
var parameter = parameters[index];
var shouldBeCovariant = covariantParameters.contains(
_ParameterDesc(index, parameter),
);
if (parameter.isCovariant != shouldBeCovariant) {
transformedParameters ??= [
for (var parameter in parameters) parameter.declaration,
];
transformedParameters[index] = parameter.declaration.copyWith(
isCovariant: shouldBeCovariant,
);
}
}
if (transformedParameters == null) {
return executable;
}
if (executable is MethodElementOrMember) {
var fragmentName = executable.name2 ?? '';
var elementReference = class_.element.reference!
.getChild('@method')
.getChild(fragmentName);
if (elementReference.element2 case MethodElementImpl result) {
return result.firstFragment;
}
var result = MethodFragmentImpl(name2: executable.name2, nameOffset: -1);
result.enclosingElement3 = class_;
result.isSynthetic = true;
result.parameters = transformedParameters;
result.returnType = executable.returnType;
result.typeParameters = executable.typeParameters;
var elementName = executable.asElement2.name3!;
MethodElementImpl(
name3: elementName,
reference: elementReference,
firstFragment: result,
);
return result;
}
if (executable is SetterFragmentImpl) {
var fragmentName = executable.name2 ?? '';
var setterReference = class_.element.reference!
.getChild('@setter')
.getChild(fragmentName);
if (setterReference.element2 case SetterElementImpl result) {
return result.firstFragment;
}
var result = SetterFragmentImpl(name2: executable.name2, nameOffset: -1);
result.enclosingElement3 = class_;
result.isSynthetic = true;
result.parameters = transformedParameters;
result.returnType = executable.returnType;
SetterElementImpl(setterReference, result);
var field = executable.variable2!;
var resultField = FieldFragmentImpl(name2: field.name2, nameOffset: -1);
resultField.enclosingElement3 = class_;
var elementName = executable.asElement2.name3!;
var fieldReference = class_.element.reference!
.getChild('@field')
.getChild(elementName);
assert(fieldReference.element2 == null);
FieldElementImpl(reference: fieldReference, firstFragment: resultField);
resultField.type = executable.parameters[0].type;
return result;
}
return executable;
}
/// Given one or more [validOverrides], merge them into a single resulting
/// signature. This signature always exists.
ExecutableElementOrMember _topMerge(
TypeSystemImpl typeSystem,
InterfaceFragmentImpl targetClass,
List<ExecutableElementOrMember> validOverrides,
) {
var first = validOverrides[0];
if (validOverrides.length == 1) {
return first;
}
var firstType = first.type;
if (validOverrides.every((e) => e.type == firstType)) {
return first;
}
var resultType = _topMergeSignatureTypes(
typeSystem: typeSystem,
validOverrides: validOverrides,
);
for (var executable in validOverrides) {
if (executable.type == resultType) {
return executable;
}
}
if (first is MethodElementOrMember) {
var firstElement = first.asElement2;
var fragmentName = firstElement.firstFragment.name2!;
var elementReference = targetClass.element.reference!
.getChild('@method')
.getChild(fragmentName);
if (elementReference.element2 case SetterElementImpl result) {
return result.firstFragment;
}
var result = MethodFragmentImpl(name2: fragmentName, nameOffset: -1);
result.enclosingElement3 = targetClass;
result.typeParameters = resultType.typeFormals;
result.returnType = resultType.returnType;
// TODO(scheglov): check if can type cast instead
result.parameters =
resultType.parameters
.map((e) => e.firstFragment as FormalParameterFragmentImpl)
.toList();
var elementName = first.asElement2.name3!;
MethodElementImpl(
name3: elementName,
reference: elementReference,
firstFragment: result,
);
return result;
} else {
var firstAccessor = first as PropertyAccessorElementOrMember;
var fragmentName = first.asElement2.firstFragment.name2!;
var field = FieldFragmentImpl(name2: fragmentName, nameOffset: -1);
PropertyAccessorFragmentImpl result;
if (firstAccessor.isGetter) {
var elementReference = targetClass.element.reference!
.getChild('@getter')
.getChild(fragmentName);
if (elementReference.element2 case GetterElementImpl result) {
return result.firstFragment;
}
var fragment = GetterFragmentImpl(name2: fragmentName, nameOffset: -1);
result = fragment;
var element = GetterElementImpl(elementReference, fragment);
element.returnType = resultType.returnType;
} else {
var elementReference = targetClass.element.reference!
.getChild('@setter')
.getChild(fragmentName);
if (elementReference.element2 case SetterElementImpl result) {
return result.firstFragment;
}
var fragment = SetterFragmentImpl(name2: fragmentName, nameOffset: -1);
result = fragment;
SetterElementImpl(elementReference, fragment);
}
result.enclosingElement3 = targetClass;
result.returnType = resultType.returnType;
// TODO(scheglov): check if can type cast instead
result.parameters =
resultType.parameters
.map((e) => e.firstFragment as FormalParameterFragmentImpl)
.toList();
field.enclosingElement3 = targetClass;
var elementName = first.asElement2.name3!;
var elementReference = targetClass.element.reference!
.getChild('@field')
.getChild(elementName);
assert(elementReference.element2 == null);
var fieldElement = FieldElementImpl(
reference: elementReference,
firstFragment: field,
);
result.element.variable3 = fieldElement;
if (firstAccessor.isGetter) {
field.type = result.returnType;
} else {
field.type = result.parameters[0].type;
}
return result;
}
}
static Map<Name, ExecutableElementOrMember> _getTypeMembers(
InterfaceFragmentImpl fragment,
InterfaceElementImpl element,
) {
var declared = <Name, ExecutableElementOrMember>{};
var libraryUri = fragment.librarySource.uri;
void addMember(ExecutableElementOrMember member) {
if (!member.isStatic) {
var lookupName = member.element.lookupName;
if (lookupName != null) {
var name = Name(libraryUri, lookupName);
declared[name] = member;
}
}
}
element.methods.map((e) => e.asElement).forEach(addMember);
element.getters.map((e) => e.asElement).forEach(addMember);
element.setters.map((e) => e.asElement).forEach(addMember);
return declared;
}
/// Returns executables that are valid overrides of [candidates].
static List<ExecutableElementOrMember> _getValidOverrides({
required TypeSystemImpl typeSystem,
required List<ExecutableElementOrMember> candidates,
}) {
var validOverrides = <ExecutableElementOrMember>[];
outer:
for (var i = 0; i < candidates.length; i++) {
var validOverride = candidates[i];
var validOverrideType = validOverride.type;
for (var j = 0; j < candidates.length; j++) {
var candidate = candidates[j];
if (!typeSystem.isSubtypeOf(validOverrideType, candidate.type)) {
continue outer;
}
}
validOverrides.add(validOverride);
}
return validOverrides;
}
static bool _isDeclaredInObject(ExecutableElementOrMember element) {
var enclosing = element.asElement2.enclosingElement;
return enclosing is ClassElement && enclosing.isDartCoreObject;
}
static FunctionTypeImpl _topMergeSignatureTypes({
required TypeSystemImpl typeSystem,
required List<ExecutableElementOrMember> validOverrides,
}) {
return validOverrides
.map((e) => typeSystem.normalizeFunctionType(e.type))
.reduce((previous, next) {
return typeSystem.topMerge(previous, next) as FunctionTypeImpl;
});
}
}
/// The instance interface of an [InterfaceType].
class Interface {
static final _empty = Interface._(
map: const {},
declared: const {},
implemented: const {},
implemented2: const {},
noSuchMethodForwarders: <Name>{},
overridden: const {},
overridden2: const {},
redeclared: const {},
redeclared2: const {},
superImplemented: const [{}],
superImplemented2: const [{}],
conflicts: const [],
combinedSignatures: const {},
);
/// The map of names to their signature in the interface.
final Map<Name, ExecutableElementOrMember> map;
/// The map of declared names to their signatures.
final Map<Name, ExecutableElementOrMember> declared;
/// The map of names to their concrete implementations.
final Map<Name, ExecutableElementOrMember> implemented;
/// The map of names to their concrete implementations.
final Map<Name, ExecutableElement2OrMember> implemented2;
/// 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<ExecutableElementOrMember>> overridden;
/// The map of names to their signatures from the mixins, superclasses,
/// or interfaces.
final Map<Name, List<ExecutableElement>> overridden2;
/// The map of names to the signatures from superinterfaces that a member
/// declaration in this extension type redeclares.
final Map<Name, List<ExecutableElementOrMember>> redeclared;
/// The map of names to the signatures from superinterfaces that a member
/// declaration in this extension type redeclares.
final Map<Name, List<ExecutableElement>> redeclared2;
/// 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, ExecutableElementOrMember>> superImplemented;
/// 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>> superImplemented2;
/// 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;
/// Tracks signatures from superinterfaces that were combined.
/// It is used to track dependencies in manifests.
final Map<Name, List<ExecutableElement2OrMember>> combinedSignatures;
/// The map of names to the most specific signatures from the mixins,
/// superclasses, or interfaces.
Map<Name, ExecutableElementOrMember>? inheritedMap;
Interface._({
required this.map,
required this.declared,
required this.implemented,
required this.implemented2,
required this.noSuchMethodForwarders,
required this.overridden,
required this.overridden2,
required this.redeclared,
required this.redeclared2,
required this.superImplemented,
required this.superImplemented2,
required this.conflicts,
required this.combinedSignatures,
});
/// The map of declared names to their signatures.
@experimental
Map<Name, ExecutableElement> get declared2 {
return declared.mapValue((element) => element.asElement2);
}
/// The map of names to the most specific signatures from the mixins,
/// superclasses, or interfaces.
Map<Name, ExecutableElement>? get inheritedMap2 {
if (inheritedMap == null) {
return null;
}
var inheritedMap2 = <Name, ExecutableElement>{};
for (var entry in inheritedMap!.entries) {
inheritedMap2[entry.key] = entry.value.asElement2;
}
return inheritedMap2;
}
/// The map of names to their signature in the interface.
@experimental
Map<Name, ExecutableElement2OrMember> get map2 {
return map.mapValue((element) => element.asElement2);
}
/// 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.
@AnalyzerPublicApi(message: 'Exposed by InterfaceElement2 methods')
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 = Object.hash(libraryUri, name);
return Name._internal(libraryUri, name, false, hashCode);
} else {
return Name._internal(null, name, true, name.hashCode);
}
}
factory Name.forLibrary(LibraryElement? library, String name) {
return Name(library?.uri, name);
}
Name._internal(this.libraryUri, this.name, this.isPublic, this.hashCode);
Name get forGetter {
if (name.endsWith('=')) {
var getterName = name.substring(0, name.length - 1);
return Name(libraryUri, getterName);
} else {
return this;
}
}
Name get forSetter {
if (name.endsWith('=')) {
return this;
} else {
return Name(libraryUri, '$name=');
}
}
@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;
/// Returns the name that corresponds to [element].
///
/// If the element is private, the name includes the library URI.
///
/// If the name is a setter, the name ends with `=`.
static Name? forElement(Element element) {
var name = element.lookupName;
if (name == null) {
return null;
}
if (name.startsWith('_')) {
var libraryUri = element.library2!.uri;
return Name(libraryUri, name);
} else {
return Name(null, name);
}
}
}
/// Failure because of not unique extension type member.
class NotUniqueExtensionMemberConflict extends Conflict {
final List<ExecutableElementOrMember> candidates;
NotUniqueExtensionMemberConflict({
required super.name,
required this.candidates,
});
List<ExecutableElement> get candidates2 =>
candidates.map((e) => e.asElement2).toList();
}
class _ExtensionTypeCandidates {
final Name name;
final List<MethodElementOrMember> methods = [];
final List<PropertyAccessorElementOrMember> getters = [];
final List<PropertyAccessorElementOrMember> setters = [];
_ExtensionTypeCandidates(this.name);
List<ExecutableElementOrMember> get all {
return [...methods, ...getters, ...setters];
}
void add(ExecutableElementOrMember element) {
switch (element) {
case MethodElementOrMember():
methods.add(element);
case PropertyAccessorElementOrMember(isGetter: true):
getters.add(element);
case PropertyAccessorElementOrMember(isSetter: true):
setters.add(element);
}
}
List<ExecutableElementOrMember> notPrecluded({
required Set<Name> precludedNames,
required Set<Name> precludedMethods,
required Set<Name> precludedSetters,
}) {
if (precludedNames.contains(name)) {
return const [];
}
return [
if (!precludedMethods.contains(name)) ...methods,
...getters,
if (!precludedSetters.contains(name)) ...setters,
];
}
}
class _ParameterDesc {
final int? index;
final String? name;
factory _ParameterDesc(int index, ParameterElementMixin element) {
return element.isNamed
? _ParameterDesc.name(element.name2)
: _ParameterDesc.index(index);
}
_ParameterDesc.index(this.index) : name = null;
_ParameterDesc.name(this.name) : index = null;
@override
int get hashCode {
return index?.hashCode ?? name?.hashCode ?? 0;
}
@override
bool operator ==(other) {
return other is _ParameterDesc &&
other.index == index &&
other.name == name;
}
}