Extension types. Support in InheritanceManager3.
Change-Id: I49e30138f810247d1e20fa983f33f32d99a29c62
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316845
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/extensions.dart b/pkg/analyzer/lib/src/dart/element/extensions.dart
index 725ef73..4ab7e2a3 100644
--- a/pkg/analyzer/lib/src/dart/element/extensions.dart
+++ b/pkg/analyzer/lib/src/dart/element/extensions.dart
@@ -104,6 +104,12 @@
bool get isEnumConstructor {
return this is ConstructorElement && enclosingElement2 is EnumElementImpl;
}
+
+ /// Whether the enclosing element is the class `Object`.
+ bool get isObjectMember {
+ final enclosing = enclosingElement2;
+ return enclosing is ClassElement && enclosing.isDartCoreObject;
+ }
}
extension ExecutableElementExtensionQuestion on ExecutableElement? {
diff --git a/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart b/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
index b4d417a..be92357 100644
--- a/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
+++ b/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
@@ -17,9 +17,9 @@
final List<ExecutableElement> candidates;
CandidatesConflict({
- required Name name,
+ required super.name,
required this.candidates,
- }) : super(name);
+ });
}
/// Failure to find a valid signature from superinterfaces.
@@ -27,7 +27,9 @@
/// The name for which we failed to find a valid signature.
final Name name;
- Conflict(this.name);
+ Conflict({
+ required this.name,
+ });
}
/// Failure because of a getter and a method from direct superinterfaces.
@@ -36,10 +38,10 @@
final ExecutableElement method;
GetterMethodConflict({
- required Name name,
+ required super.name,
required this.getter,
required this.method,
- }) : super(name);
+ });
}
/// Manages knowledge about interface types and their members.
@@ -160,7 +162,7 @@
Map<Name, ExecutableElement> getInheritedConcreteMap2(
InterfaceElement element) {
var interface = getInterface(element);
- return interface._superImplemented.last;
+ return interface.superImplemented.last;
}
/// Return the mapping from names to most specific signatures of members
@@ -189,13 +191,13 @@
/// corresponding name will not be included.
Map<Name, ExecutableElement> getInheritedMap2(InterfaceElement element) {
var interface = getInterface(element);
- var inheritedMap = interface._inheritedMap;
+ var inheritedMap = interface.inheritedMap;
if (inheritedMap == null) {
- inheritedMap = interface._inheritedMap = {};
+ inheritedMap = interface.inheritedMap = {};
_findMostSpecificFromNamedCandidates(
element,
inheritedMap,
- interface._overridden,
+ interface.overridden,
doTopMerge: false,
);
}
@@ -216,7 +218,9 @@
}
try {
- if (element is MixinElement) {
+ if (element is ExtensionTypeElement) {
+ result = _getInterfaceExtensionType(element);
+ } else if (element is MixinElement) {
result = _getInterfaceMixin(element);
} else {
result = _getInterfaceClass(element);
@@ -272,7 +276,10 @@
}) {
var interface = getInterface(element);
if (forSuper) {
- var superImplemented = interface._superImplemented;
+ if (element is ExtensionTypeElement) {
+ return null;
+ }
+ var superImplemented = interface.superImplemented;
if (forMixinIndex >= 0) {
return superImplemented[forMixinIndex][name];
}
@@ -302,7 +309,7 @@
/// if no members would be overridden.
List<ExecutableElement>? getOverridden2(InterfaceElement element, Name name) {
var interface = getInterface(element);
- return interface._overridden[name];
+ return interface.overridden[name];
}
/// Remove interfaces for classes defined in specified libraries.
@@ -608,12 +615,12 @@
var noSuchMethodForwarders = <Name>{};
if (element is ClassElement && element.isAbstract) {
if (superTypeInterface != null) {
- noSuchMethodForwarders = superTypeInterface._noSuchMethodForwarders;
+ noSuchMethodForwarders = superTypeInterface.noSuchMethodForwarders;
}
} else {
var noSuchMethod = implemented[_noSuchMethodName];
if (noSuchMethod != null && !_isDeclaredInObject(noSuchMethod)) {
- var superForwarders = superTypeInterface?._noSuchMethodForwarders;
+ var superForwarders = superTypeInterface?.noSuchMethodForwarders;
for (var entry in interface.entries) {
var name = entry.key;
if (!implemented.containsKey(name) ||
@@ -649,6 +656,126 @@
);
}
+ /// 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(ExtensionTypeElement element) {
+ final augmented = element.augmentedOfDeclaration;
+
+ // Add instance members implemented by the element itself.
+ final declared = <Name, ExecutableElement>{};
+ _addImplemented(declared, element, augmented);
+
+ // These declared members take precedence over "inherited" ones.
+ final implemented = Map.of(declared);
+
+ // Prepare candidates for inheritance.
+ final extensionCandidates = <Name, List<ExecutableElement>>{};
+ final notExtensionCandidates = <Name, List<ExecutableElement>>{};
+ for (final interface in augmented.interfaces) {
+ for (final entry in getInterface(interface.element).map.entries) {
+ final name = entry.key;
+ if (interface.element is ExtensionTypeElement) {
+ (extensionCandidates[name] ??= []).add(entry.value);
+ } else {
+ (notExtensionCandidates[name] ??= []).add(entry.value);
+ }
+ }
+ }
+
+ final overridden = <Name, List<ExecutableElement>>{};
+ final conflicts = <Conflict>[];
+
+ // Add extension type members.
+ for (final entry in extensionCandidates.entries) {
+ final name = entry.key;
+ final candidates = entry.value;
+ overridden[name] = candidates;
+
+ // Stop if redeclared.
+ if (implemented.containsKey(name)) {
+ continue;
+ }
+
+ // The inherited member must be unique.
+ ExecutableElement? uniqueElement;
+ for (final candidate in candidates) {
+ if (uniqueElement == null) {
+ uniqueElement = candidate;
+ } else if (uniqueElement.declaration != candidate.declaration) {
+ uniqueElement = null;
+ break;
+ }
+ }
+
+ if (uniqueElement != null) {
+ implemented[name] = uniqueElement;
+ } else {
+ conflicts.add(
+ NotUniqueExtensionMemberConflict(
+ name: name,
+ candidates: candidates,
+ ),
+ );
+ }
+ }
+
+ // Add non-extension type members.
+ for (final entry in notExtensionCandidates.entries) {
+ final name = entry.key;
+ final candidates = entry.value;
+ (overridden[name] ??= []).addAll(candidates);
+
+ // Stop if redeclared.
+ if (implemented.containsKey(name)) {
+ continue;
+ }
+
+ final combinedSignature = combineSignatures(
+ targetClass: element,
+ candidates: candidates,
+ doTopMerge: true,
+ name: name,
+ );
+
+ if (combinedSignature != null) {
+ implemented[name] = combinedSignature;
+ } else {
+ conflicts.add(
+ CandidatesConflict(
+ name: name,
+ candidates: candidates,
+ ),
+ );
+ }
+ }
+
+ // Ensure unique overridden elements.
+ final overridden2 = <Name, List<ExecutableElement>>{};
+ for (final entry in overridden.entries) {
+ final name = entry.key;
+ final elements = entry.value;
+ if (elements.length == 1) {
+ overridden2[name] = elements;
+ } else {
+ overridden2[name] = elements.toSet().toFixedList();
+ }
+ }
+
+ return Interface._(
+ implemented,
+ declared,
+ implemented,
+ const {},
+ overridden2,
+ const [],
+ conflicts.toFixedList(),
+ );
+ }
+
Interface _getInterfaceMixin(MixinElement element) {
final augmented = element.augmentedOfDeclaration;
var classLibrary = element.library;
@@ -941,17 +1068,17 @@
final Map<Name, ExecutableElement> implemented;
/// The set of names that are `noSuchMethod` forwarders in [implemented].
- final Set<Name> _noSuchMethodForwarders;
+ final Set<Name> noSuchMethodForwarders;
/// The map of names to their signatures from the mixins, superclasses,
/// or interfaces.
- final Map<Name, List<ExecutableElement>> _overridden;
+ 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;
+ 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
@@ -960,21 +1087,21 @@
/// The map of names to the most specific signatures from the mixins,
/// superclasses, or interfaces.
- Map<Name, ExecutableElement>? _inheritedMap;
+ Map<Name, ExecutableElement>? inheritedMap;
Interface._(
this.map,
this.declared,
this.implemented,
- this._noSuchMethodForwarders,
- this._overridden,
- this._superImplemented,
+ 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);
+ return superImplemented.last.containsKey(name);
}
}
@@ -1022,6 +1149,16 @@
String toString() => libraryUri != null ? '$libraryUri::$name' : name;
}
+/// Failure because of not unique extension type member.
+class NotUniqueExtensionMemberConflict extends Conflict {
+ final List<ExecutableElement> candidates;
+
+ NotUniqueExtensionMemberConflict({
+ required super.name,
+ required this.candidates,
+ });
+}
+
class _ParameterDesc {
final int? index;
final String? name;
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 75ad09a..ab09464 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -341,6 +341,9 @@
@override
void beginExtensionTypeDeclaration(Token extensionKeyword, Token name) {
+ assert(optional('extension', extensionKeyword));
+ assert(_classLikeBuilder == null);
+
final typeParameters = pop() as TypeParameterListImpl?;
final metadata = pop() as List<AnnotationImpl>?;
final comment = _findComment(metadata, extensionKeyword);
@@ -1669,6 +1672,8 @@
implementsClause: implementsClause,
),
);
+
+ _classLikeBuilder = null;
}
@override
diff --git a/pkg/analyzer/lib/src/summary2/types_builder.dart b/pkg/analyzer/lib/src/summary2/types_builder.dart
index d344889..0f0f555 100644
--- a/pkg/analyzer/lib/src/summary2/types_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/types_builder.dart
@@ -262,17 +262,21 @@
final element = node.declaredElement!;
final typeSystem = element.library.typeSystem;
- final interfaces = node.implementsClause?.interfaces
+ final type = node.representation.fieldType.typeOrThrow;
+ element.representation.type = type;
+
+ var interfaces = node.implementsClause?.interfaces
.map((e) => e.type)
.whereType<InterfaceType>()
.where(typeSystem.isValidExtensionTypeSuperinterface)
.toFixedList();
- if (interfaces != null) {
- element.interfaces = interfaces;
+ if (interfaces == null || interfaces.isEmpty) {
+ final superInterface = typeSystem.isNonNullable(type)
+ ? typeSystem.objectNone
+ : typeSystem.objectQuestion;
+ interfaces = [superInterface];
}
-
- final type = node.representation.fieldType.typeOrThrow;
- element.representation.type = type;
+ element.interfaces = interfaces;
final primaryConstructor = element.constructors.first;
final primaryFormalParameter = primaryConstructor.parameters.first;
diff --git a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
index ab961fa..b8d6c7e 100644
--- a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
+++ b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
@@ -5,16 +5,24 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.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/inheritance_manager3.dart';
+import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../../../util/element_printer.dart';
+import '../../../util/tree_string_sink.dart';
+import '../../summary/elements_base.dart';
import '../resolution/context_collection_resolution.dart';
+import '../resolution/node_text_expectations.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(InheritanceManager3Test);
defineReflectiveTests(InheritanceManager3WithoutNullSafetyTest);
+ defineReflectiveTests(InheritanceManager3Test_ExtensionType);
});
}
@@ -401,6 +409,585 @@
}
@reflectiveTest
+class InheritanceManager3Test_ExtensionType extends ElementsBaseTest {
+ final printerConfiguration = _InstancePrinterConfiguration();
+
+ @override
+ bool get keepLinkingLibraries => true;
+
+ void assertInterfaceText(InterfaceElementImpl element, String expected) {
+ final actual = _interfaceText(element);
+ if (actual != expected) {
+ print('-------- Actual --------');
+ print('$actual------------------------');
+ NodeTextExpectationsCollector.add(actual);
+ }
+ expect(actual, expected);
+ }
+
+ @override
+ void setUp() {
+ super.setUp();
+ printerConfiguration.withoutIdenticalImplemented = true;
+ }
+
+ test_declareGetter() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ int get foo => 0;
+}
+''');
+
+ final element = library.extensionType('A');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::A::@getter::foo
+ it: self::@extensionType::A::@getter::it
+declared
+ foo: self::@extensionType::A::@getter::foo
+ it: self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_declareMethod() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('A');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::A::@method::foo
+ it: self::@extensionType::A::@getter::it
+declared
+ foo: self::@extensionType::A::@method::foo
+ it: self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_declareMethod_implementClass_method2_wouldConflict() async {
+ final library = await buildLibrary(r'''
+class A {
+ int foo() => 0;
+}
+
+class B {
+ String foo() => '0';
+}
+
+extension type C(Object it) implements A, B {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+ self::@class::B::@method::foo
+''');
+ }
+
+ test_declareMethod_implementClass_noOverride() async {
+ final library = await buildLibrary(r'''
+class A {}
+
+class B extends A {
+ void foo() {}
+}
+
+extension type C(B it) implements A {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+''');
+ }
+
+ test_declareMethod_implementClass_override() async {
+ final library = await buildLibrary(r'''
+class A {
+ void foo() {}
+}
+
+class B extends A {
+ void bar() {}
+}
+
+extension type C(B it) implements A {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+''');
+ }
+
+ test_declareMethod_implementClass_override_getter() async {
+ final library = await buildLibrary(r'''
+class A {
+ int get foo => 0;
+}
+
+class B extends A {
+ void bar() {}
+}
+
+extension type C(B it) implements A {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ foo: self::@extensionType::C::@method::foo
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@getter::foo
+''');
+ }
+
+ test_declareMethod_implementExtensionType_method2_wouldConflict() async {
+ final library = await buildLibrary(r'''
+extension type A1(int it) {
+ void foo() {}
+}
+
+extension type A2(int it) {
+ void foo() {}
+}
+
+extension type B(int it) implements A1, A2 {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('B');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+declared
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+overridden
+ foo
+ self::@extensionType::A1::@method::foo
+ self::@extensionType::A2::@method::foo
+ it
+ self::@extensionType::A1::@getter::it
+ self::@extensionType::A2::@getter::it
+''');
+ }
+
+ test_declareMethod_implementExtensionType_override() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ void foo() {}
+}
+
+extension type B(int it) implements A {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('B');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+declared
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+overridden
+ foo
+ self::@extensionType::A::@method::foo
+ it
+ self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_declareMethod_implementExtensionType_override_getter() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ int get foo => 0;
+}
+
+extension type B(int it) implements A {
+ void foo() {}
+}
+''');
+
+ final element = library.extensionType('B');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+declared
+ foo: self::@extensionType::B::@method::foo
+ it: self::@extensionType::B::@getter::it
+overridden
+ foo
+ self::@extensionType::A::@getter::foo
+ it
+ self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_declareMethod_static() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ static void foo() {}
+}
+''');
+
+ final element = library.extensionType('A');
+ assertInterfaceText(element, r'''
+map
+ it: self::@extensionType::A::@getter::it
+declared
+ it: self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_noDeclaration_implementClass_implementExtensionType() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ void foo() {}
+}
+
+class B {
+ void foo() {}
+}
+
+class C extends B {}
+
+extension type D(C it) implements B, A {}
+''');
+
+ final element = library.extensionType('D');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::A::@method::foo
+ it: self::@extensionType::D::@getter::it
+declared
+ it: self::@extensionType::D::@getter::it
+overridden
+ foo
+ self::@extensionType::A::@method::foo
+ self::@class::B::@method::foo
+ it
+ self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_noDeclaration_implementClass_method() async {
+ final library = await buildLibrary(r'''
+class A {
+ void foo() {}
+}
+
+class B extends A {}
+
+extension type C(B it) implements A {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@class::A::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+''');
+ }
+
+ test_noDeclaration_implementClass_method2_hasConflict() async {
+ final library = await buildLibrary(r'''
+class A {
+ int foo() => 0;
+}
+
+class B {
+ String foo() => '0';
+}
+
+extension type C(Object it) implements A, B {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+ self::@class::B::@method::foo
+conflicts
+ CandidatesConflict
+ self::@class::A::@method::foo
+ self::@class::B::@method::foo
+''');
+ }
+
+ test_noDeclaration_implementClass_method2_noConflict() async {
+ final library = await buildLibrary(r'''
+class A {
+ int foo() => 0;
+}
+
+class B {
+ num foo() => 0;
+}
+
+extension type C(Object it) implements A, B {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@class::A::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+ self::@class::B::@method::foo
+''');
+ }
+
+ test_noDeclaration_implementClass_method2_noConflict2() async {
+ final library = await buildLibrary(r'''
+class A {
+ int foo() => 0;
+}
+
+class B1 extends A {}
+
+class B2 extends A {}
+
+extension type C(Object it) implements B1, B2 {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@class::A::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@class::A::@method::foo
+''');
+ }
+
+ test_noDeclaration_implementClass_setter() async {
+ final library = await buildLibrary(r'''
+class A {
+ set foo(int _) {}
+}
+
+class B extends A {}
+
+extension type C(B it) implements A {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo=: self::@class::A::@setter::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo=
+ self::@class::A::@setter::foo
+''');
+ }
+
+ test_noDeclaration_implementExtensionType_method() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ void foo() {}
+}
+
+extension type B(int it) implements A {}
+''');
+
+ final element = library.extensionType('B');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::A::@method::foo
+ it: self::@extensionType::B::@getter::it
+declared
+ it: self::@extensionType::B::@getter::it
+overridden
+ foo
+ self::@extensionType::A::@method::foo
+ it
+ self::@extensionType::A::@getter::it
+''');
+ }
+
+ test_noDeclaration_implementExtensionType_method2_hasConflict() async {
+ final library = await buildLibrary(r'''
+extension type A1(int it) {
+ void foo() {}
+}
+
+extension type A2(int it) {
+ void foo() {}
+}
+
+extension type B(int it) implements A1, A2 {}
+''');
+
+ final element = library.extensionType('B');
+ assertInterfaceText(element, r'''
+map
+ it: self::@extensionType::B::@getter::it
+declared
+ it: self::@extensionType::B::@getter::it
+overridden
+ foo
+ self::@extensionType::A1::@method::foo
+ self::@extensionType::A2::@method::foo
+ it
+ self::@extensionType::A1::@getter::it
+ self::@extensionType::A2::@getter::it
+conflicts
+ NotUniqueExtensionMemberConflict
+ self::@extensionType::A1::@method::foo
+ self::@extensionType::A2::@method::foo
+''');
+ }
+
+ test_noDeclaration_implementExtensionType_method2_noConflict() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {
+ void foo() {}
+}
+
+extension type B1(int it) implements A {}
+
+extension type B2(int it) implements A {}
+
+extension type C(int it) implements B1, B2 {}
+''');
+
+ final element = library.extensionType('C');
+ assertInterfaceText(element, r'''
+map
+ foo: self::@extensionType::A::@method::foo
+ it: self::@extensionType::C::@getter::it
+declared
+ it: self::@extensionType::C::@getter::it
+overridden
+ foo
+ self::@extensionType::A::@method::foo
+ it
+ self::@extensionType::B1::@getter::it
+ self::@extensionType::B2::@getter::it
+''');
+ }
+
+ test_withObjectMembers() async {
+ final library = await buildLibrary(r'''
+extension type A(int it) {}
+''');
+
+ final element = library.extensionType('A');
+ printerConfiguration.withObjectMembers = true;
+ assertInterfaceText(element, r'''
+map
+ ==: dart:core::@class::Object::@method::==
+ hashCode: dart:core::@class::Object::@getter::hashCode
+ it: self::@extensionType::A::@getter::it
+ noSuchMethod: dart:core::@class::Object::@method::noSuchMethod
+ runtimeType: dart:core::@class::Object::@getter::runtimeType
+ toString: dart:core::@class::Object::@method::toString
+declared
+ it: self::@extensionType::A::@getter::it
+overridden
+ ==
+ dart:core::@class::Object::@method::==
+ hashCode
+ dart:core::@class::Object::@getter::hashCode
+ noSuchMethod
+ dart:core::@class::Object::@method::noSuchMethod
+ runtimeType
+ dart:core::@class::Object::@getter::runtimeType
+ toString
+ dart:core::@class::Object::@method::toString
+''');
+ }
+
+ String _interfaceText(InterfaceElementImpl element) {
+ final library = element.library;
+ final inheritance = library.session.inheritanceManager;
+ final interface = inheritance.getInterface(element);
+
+ final buffer = StringBuffer();
+ final sink = TreeStringSink(
+ sink: buffer,
+ indent: '',
+ );
+ final elementPrinter = ElementPrinter(
+ sink: sink,
+ configuration: ElementPrinterConfiguration(),
+ selfUriStr: '${library.source.uri}',
+ );
+
+ _InterfacePrinter(
+ sink: sink,
+ elementPrinter: elementPrinter,
+ configuration: printerConfiguration,
+ ).write(interface);
+
+ return buffer.toString();
+ }
+}
+
+@reflectiveTest
class InheritanceManager3WithoutNullSafetyTest
extends _InheritanceManager3Base {
test_getInherited_closestSuper() async {
@@ -1626,3 +2213,140 @@
expect(actual, expected);
}
}
+
+class _InstancePrinterConfiguration {
+ bool withObjectMembers = false;
+ bool withoutIdenticalImplemented = false;
+}
+
+class _InterfacePrinter {
+ final TreeStringSink _sink;
+ final ElementPrinter _elementPrinter;
+ final _InstancePrinterConfiguration _configuration;
+
+ _InterfacePrinter({
+ required TreeStringSink sink,
+ required ElementPrinter elementPrinter,
+ required _InstancePrinterConfiguration configuration,
+ }) : _sink = sink,
+ _elementPrinter = elementPrinter,
+ _configuration = configuration;
+
+ void write(Interface interface) {
+ _writeNameToMap('map', interface.map);
+ _writeNameToMap('declared', interface.declared);
+
+ if (_configuration.withoutIdenticalImplemented) {
+ expect(interface.implemented, same(interface.map));
+ } else {
+ _writeNameToMap('implemented', interface.implemented);
+ }
+
+ _writeNameToListMap('overridden', interface.overridden);
+ _writeListOfMaps('superImplemented', interface.superImplemented);
+ _writeNameToMap('inheritedMap', interface.inheritedMap ?? {});
+ _writeConflicts(interface.conflicts);
+ }
+
+ String _nameObjStr(Name nameObj) {
+ return nameObj.name;
+ }
+
+ bool _shouldWrite(ExecutableElement element) {
+ return _configuration.withObjectMembers || !element.isObjectMember;
+ }
+
+ List<MapEntry<Name, T>> _sortedEntries<T>(
+ Iterable<MapEntry<Name, T>> entries,
+ ) {
+ return entries.sortedBy(
+ (e) => '${e.key.name} ${e.key.libraryUri}',
+ );
+ }
+
+ List<ExecutableElement> _withoutObject(List<ExecutableElement> elements) {
+ return elements.where(_shouldWrite).toList();
+ }
+
+ void _writeConflicts(List<Conflict> conflicts) {
+ if (conflicts.isEmpty) return;
+
+ _sink.writelnWithIndent('conflicts');
+ _sink.withIndent(() {
+ for (final conflict in conflicts) {
+ switch (conflict) {
+ case CandidatesConflict _:
+ _elementPrinter.writeElementList(
+ 'CandidatesConflict',
+ conflict.candidates,
+ );
+ case NotUniqueExtensionMemberConflict _:
+ _elementPrinter.writeElementList(
+ 'NotUniqueExtensionMemberConflict',
+ conflict.candidates,
+ );
+ default:
+ fail('Not implemented: ${conflict.runtimeType}');
+ }
+ }
+ });
+ }
+
+ void _writeListOfMaps(
+ String name,
+ List<Map<Name, ExecutableElement>> listOfMaps,
+ ) {
+ if (listOfMaps.isEmpty) return;
+
+ _sink.writelnWithIndent(name);
+ _sink.withIndent(() {
+ listOfMaps.forEachIndexed((index, map) {
+ _writeNameToMap('$index', map);
+ });
+ });
+ }
+
+ void _writeNameToListMap(
+ String name,
+ Map<Name, List<ExecutableElement>> map,
+ ) {
+ final isEmpty = map.values.expand((elements) => elements).where((element) {
+ if (_configuration.withObjectMembers) return true;
+ return !element.isObjectMember;
+ }).isEmpty;
+ if (isEmpty) return;
+
+ _sink.writelnWithIndent(name);
+ _sink.withIndent(() {
+ for (final entry in _sortedEntries(map.entries)) {
+ final name = _nameObjStr(entry.key);
+ final elements = _withoutObject(entry.value);
+ _elementPrinter.writeElementList(name, elements);
+ }
+ });
+ }
+
+ void _writeNameToMap(String name, Map<Name, ExecutableElement> map) {
+ final isEmpty = map.values.none(_shouldWrite);
+ if (isEmpty) return;
+
+ _sink.writelnWithIndent(name);
+ _sink.withIndent(() {
+ for (final entry in _sortedEntries(map.entries)) {
+ final name = _nameObjStr(entry.key);
+ final element = entry.value;
+ if (_shouldWrite(element)) {
+ _elementPrinter.writeNamedElement(name, element);
+ }
+ }
+ });
+ }
+}
+
+extension on LibraryElementImpl {
+ ExtensionTypeElementImpl extensionType(String name) {
+ return topLevelElements
+ .whereType<ExtensionTypeElementImpl>()
+ .singleWhere((e) => e.name == name);
+ }
+}
diff --git a/pkg/analyzer/test/src/summary/elements_test.dart b/pkg/analyzer/test/src/summary/elements_test.dart
index 6d5a699..eb228c6 100644
--- a/pkg/analyzer/test/src/summary/elements_test.dart
+++ b/pkg/analyzer/test/src/summary/elements_test.dart
@@ -46508,6 +46508,8 @@
extensionTypes
A @21
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @27
type: int
@@ -46537,6 +46539,8 @@
codeOffset: 0
codeLength: 33
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @27
codeOffset: 23
@@ -46574,6 +46578,8 @@
codeOffset: 0
codeLength: 27
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
codeOffset: 17
@@ -46609,6 +46615,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46641,6 +46649,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46673,6 +46683,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46705,6 +46717,8 @@
extensionTypes
A @32
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @43
metadata
@@ -46742,6 +46756,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46802,6 +46818,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: num
@@ -46844,6 +46862,29 @@
''');
}
+ test_interfaces_implicitObjectQuestion() async {
+ var library = await buildLibrary(r'''
+extension type X(int? it) {}
+''');
+
+ configuration.withConstructors = false;
+ checkElementText(library, r'''
+library
+ definingUnit
+ extensionTypes
+ X @15
+ representation: self::@extensionType::X::@field::it
+ interfaces
+ Object?
+ fields
+ final it @22
+ type: int?
+ accessors
+ synthetic get it @-1
+ returnType: int?
+''');
+ }
+
test_interfaces_void() async {
var library = await buildLibrary(r'''
typedef A = void;
@@ -46898,6 +46939,8 @@
staticType: null
element: package:test/a.dart::@getter::foo
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @43
type: int
@@ -46927,6 +46970,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46956,6 +47001,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -46989,6 +47036,8 @@
codeOffset: 0
codeLength: 21
representation: self::@extensionType::A::@field::<empty>
+ interfaces
+ Object?
fields
final <empty> @17
codeOffset: 17
@@ -47024,6 +47073,8 @@
extensionTypes
A @15
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @21
type: int
@@ -47055,6 +47106,8 @@
bound: num
covariant U @32
representation: self::@extensionType::A::@field::it
+ interfaces
+ Object
fields
final it @45
type: Map<T, U>