From 1af1f2ea8df261784efdc915a8eeaff36ee031f8 Mon Sep 17 00:00:00 2001 From: Brian Wilkerson Date: Sat, 17 Feb 2024 01:15:37 +0000 Subject: [PATCH] Copy lookup methods from InterfaceElement to Augmented classes Change-Id: Ie93e6aee9dc0f7a918d694303b0f640296e2df42 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352971 Reviewed-by: Konstantin Shcheglov Commit-Queue: Brian Wilkerson --- pkg/analyzer/lib/dart/element/element.dart | 33 ++ .../lib/src/dart/element/element.dart | 147 +++++++++ .../test/src/dart/element/element_test.dart | 299 ++++++++++++++++++ 3 files changed, 479 insertions(+) diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart index 41b97da8701c..8e3d7e82834e 100644 --- a/pkg/analyzer/lib/dart/element/element.dart +++ b/pkg/analyzer/lib/dart/element/element.dart @@ -143,6 +143,39 @@ abstract class AugmentedInstanceElement { /// Returns the setter from [accessors] that has the given [name]. PropertyAccessorElement? getSetter(String name); + + /// Returns the element representing the getter that results from looking up + /// the given [name] in this class with respect to the given [library], + /// or `null` if the look up fails. + /// + /// The behavior of this method is defined by the Dart Language Specification + /// in section 17.18 Lookup. + PropertyAccessorElement? lookUpGetter({ + required String name, + required LibraryElement library, + }); + + /// Returns the element representing the method that results from looking up + /// the given [name] in this class with respect to the given [library], + /// or `null` if the look up fails. + /// + /// The behavior of this method is defined by the Dart Language Specification + /// in section 17.18 Lookup. + MethodElement? lookUpMethod({ + required String name, + required LibraryElement library, + }); + + /// Returns the element representing the setter that results from looking up + /// the given [name] in this class with respect to the given [library], + /// or `null` if the look up fails. + /// + /// The behavior of this method is defined by the Dart Language Specification + /// in section 17.18 Lookup. + PropertyAccessorElement? lookUpSetter({ + required String name, + required LibraryElement library, + }); } /// The result of applying augmentations to a [InterfaceElement]. diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart index 32a947d54847..d5ea532110a7 100644 --- a/pkg/analyzer/lib/src/dart/element/element.dart +++ b/pkg/analyzer/lib/src/dart/element/element.dart @@ -941,6 +941,18 @@ class CompilationUnitElementImpl extends UriReferencedElementImpl return null; } + /// Returns the mixin defined in this compilation unit that has the given + /// [name], or `null` if this compilation unit does not define a mixin with + /// the given name. + MixinElement? getMixin(String name) { + for (final mixin in mixins) { + if (mixin.name == name) { + return mixin; + } + } + return null; + } + void setLinkedData(Reference reference, ElementLinkedData linkedData) { this.reference = reference; reference.element = this; @@ -4420,6 +4432,16 @@ class LibraryElementImpl extends LibraryOrAugmentationElementImpl return null; } + MixinElement? getMixin(String name) { + for (final unitElement in units) { + final element = unitElement.getMixin(name); + if (element != null) { + return element; + } + } + return null; + } + /// Return `true` if [reference] comes only from deprecated exports. bool isFromDeprecatedExport(ExportedReference reference) { if (reference is ExportedReferenceExported) { @@ -4981,6 +5003,131 @@ mixin MaybeAugmentedInstanceElementMixin implements AugmentedInstanceElement { } return null; } + + @override + PropertyAccessorElement? lookUpGetter({ + required String name, + required LibraryElement library, + }) { + return _implementationsOfGetter(name) + .firstWhereOrNull((getter) => getter.isAccessibleIn(library)); + } + + @override + MethodElement? lookUpMethod({ + required String name, + required LibraryElement library, + }) { + return _implementationsOfMethod(name).firstWhereOrNull( + (MethodElement method) => method.isAccessibleIn(library)); + } + + @override + PropertyAccessorElement? lookUpSetter({ + required String name, + required LibraryElement library, + }) { + return _implementationsOfSetter(name).firstWhereOrNull( + (PropertyAccessorElement setter) => setter.isAccessibleIn(library)); + } + + /// Return an iterable containing all of the implementations of a getter with + /// the given [name] that are defined in this class and any superclass of this + /// class (but not in interfaces). + /// + /// The getters that are returned are not filtered in any way. In particular, + /// they can include getters that are not visible in some context. Clients + /// must perform any necessary filtering. + /// + /// The getters are returned based on the depth of their defining class; if + /// this class contains a definition of the getter it will occur first, if + /// Object contains a definition of the getter it will occur last. + Iterable _implementationsOfGetter( + String name) sync* { + final visitedClasses = {}; + AugmentedInstanceElement? augmented = this; + while (augmented != null && visitedClasses.add(augmented)) { + var getter = augmented.getGetter(name); + if (getter != null) { + yield getter; + } + if (augmented is! AugmentedInterfaceElement) { + return; + } + for (InterfaceType mixin in augmented.mixins.reversed) { + getter = mixin.element.augmented?.getGetter(name); + if (getter != null) { + yield getter; + } + } + augmented = augmented.declaration.supertype?.element.augmented; + } + } + + /// Return an iterable containing all of the implementations of a method with + /// the given [name] that are defined in this class and any superclass of this + /// class (but not in interfaces). + /// + /// The methods that are returned are not filtered in any way. In particular, + /// they can include methods that are not visible in some context. Clients + /// must perform any necessary filtering. + /// + /// The methods are returned based on the depth of their defining class; if + /// this class contains a definition of the method it will occur first, if + /// Object contains a definition of the method it will occur last. + Iterable _implementationsOfMethod(String name) sync* { + final visitedClasses = {}; + AugmentedInstanceElement? augmented = this; + while (augmented != null && visitedClasses.add(augmented)) { + var method = augmented.getMethod(name); + if (method != null) { + yield method; + } + if (augmented is! AugmentedInterfaceElement) { + return; + } + for (InterfaceType mixin in augmented.mixins.reversed) { + method = mixin.element.augmented?.getMethod(name); + if (method != null) { + yield method; + } + } + augmented = augmented.declaration.supertype?.element.augmented; + } + } + + /// Return an iterable containing all of the implementations of a setter with + /// the given [name] that are defined in this class and any superclass of this + /// class (but not in interfaces). + /// + /// The setters that are returned are not filtered in any way. In particular, + /// they can include setters that are not visible in some context. Clients + /// must perform any necessary filtering. + /// + /// The setters are returned based on the depth of their defining class; if + /// this class contains a definition of the setter it will occur first, if + /// Object contains a definition of the setter it will occur last. + Iterable _implementationsOfSetter( + String name) sync* { + final visitedClasses = {}; + AugmentedInstanceElement? augmented = this; + while (augmented != null && visitedClasses.add(augmented)) { + var setter = augmented.getSetter(name); + if (setter != null) { + yield setter; + } + if (augmented is! AugmentedInterfaceElement) { + return; + } + for (InterfaceType mixin in augmented.mixins.reversed) { + setter = mixin.element.augmented?.getSetter(name); + if (setter != null) { + yield setter; + } + } + augmented = augmented.declaration.supertype?.element.augmented; + } + } } mixin MaybeAugmentedInterfaceElementMixin on MaybeAugmentedInstanceElementMixin diff --git a/pkg/analyzer/test/src/dart/element/element_test.dart b/pkg/analyzer/test/src/dart/element/element_test.dart index c9a24c4fba62..8710e7c62391 100644 --- a/pkg/analyzer/test/src/dart/element/element_test.dart +++ b/pkg/analyzer/test/src/dart/element/element_test.dart @@ -15,6 +15,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; import '../../../generated/type_system_base.dart'; import '../../../util/feature_sets.dart'; +import '../../summary/elements_base.dart'; import '../resolution/context_collection_resolution.dart'; import 'string_types.dart'; @@ -24,6 +25,7 @@ main() { defineReflectiveTests(FieldElementImplTest); defineReflectiveTests(FunctionTypeImplTest); defineReflectiveTests(InterfaceTypeImplTest); + defineReflectiveTests(MaybeAugmentedInstanceElementMixinTest); defineReflectiveTests(MethodElementImplTest); defineReflectiveTests(TypeParameterTypeImplTest); defineReflectiveTests(VoidTypeImplTest); @@ -1622,6 +1624,303 @@ A? } } +@reflectiveTest +class MaybeAugmentedInstanceElementMixinTest extends ElementsBaseTest { + @override + bool get keepLinkingLibraries => true; + + test_lookUpGetter_declared() async { + var library = await buildLibrary(''' +class A { + int get g {} +} +'''); + var classA = library.getClass('A')!; + var getter = classA.accessors[0]; + expect(classA.augmented?.lookUpGetter(name: 'g', library: library), + same(getter)); + } + + test_lookUpGetter_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + int get g {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +'''); + var classA = library.getClass('A')!; + var getter = classA.augmented!.accessors[0]; + expect(classA.augmented?.lookUpGetter(name: 'g', library: library), + same(getter)); + } + + test_lookUpGetter_inherited() async { + var library = await buildLibrary(''' +class A { + int get g {} +} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var getter = classA.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpGetter(name: 'g', library: library), + same(getter)); + } + + test_lookUpGetter_inherited_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + int get g {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var getter = classA.augmented!.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpGetter(name: 'g', library: library), + same(getter)); + } + + test_lookUpGetter_inherited_fromMixin() async { + var library = await buildLibrary(''' +mixin A { + int get g {} +} +class B with A {} +'''); + var mixinA = library.getMixin('A')!; + var getter = mixinA.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpGetter(name: 'g', library: library), + same(getter)); + } + + test_lookUpGetter_undeclared() async { + var library = await buildLibrary(''' +class A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpGetter(name: 'g', library: library), isNull); + } + + test_lookUpGetter_undeclared_recursive() async { + var library = await buildLibrary(''' +class A extends B {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpGetter(name: 'g', library: library), isNull); + } + + test_lookUpMethod_declared() async { + var library = await buildLibrary(''' +class A { + int m() {} +} +'''); + var classA = library.getClass('A')!; + var method = classA.methods[0]; + expect(classA.augmented?.lookUpMethod(name: 'm', library: library), + same(method)); + } + + test_lookUpMethod_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + int m() {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +'''); + var classA = library.getClass('A')!; + var method = classA.augmented!.methods[0]; + expect(classA.augmented?.lookUpMethod(name: 'm', library: library), + same(method)); + } + + test_lookUpMethod_inherited() async { + var library = await buildLibrary(''' +class A { + int m() {} +} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var method = classA.methods[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpMethod(name: 'm', library: library), + same(method)); + } + + test_lookUpMethod_inherited_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + int m() {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var method = classA.augmented!.methods[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpMethod(name: 'm', library: library), + same(method)); + } + + test_lookUpMethod_inherited_fromMixin() async { + var library = await buildLibrary(''' +mixin A { + int m() {} +} +class B with A {} +'''); + var mixinA = library.getMixin('A')!; + var method = mixinA.methods[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpMethod(name: 'm', library: library), + same(method)); + } + + test_lookUpMethod_undeclared() async { + var library = await buildLibrary(''' +class A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpMethod(name: 'm', library: library), isNull); + } + + test_lookUpMethod_undeclared_recursive() async { + var library = await buildLibrary(''' +class A extends B {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpMethod(name: 'm', library: library), isNull); + } + + test_lookUpSetter_declared() async { + var library = await buildLibrary(''' +class A { + set s(x) {} +} +'''); + var classA = library.getClass('A')!; + var setter = classA.accessors[0]; + expect(classA.augmented?.lookUpSetter(name: 's', library: library), + same(setter)); + } + + test_lookUpSetter_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + set s(x) {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +'''); + var classA = library.getClass('A')!; + var setter = classA.augmented!.accessors[0]; + expect(classA.augmented?.lookUpSetter(name: 's', library: library), + same(setter)); + } + + test_lookUpSetter_inherited() async { + var library = await buildLibrary(''' +class A { + set s(x) {} +} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var setter = classA.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpSetter(name: 's', library: library), + same(setter)); + } + + test_lookUpSetter_inherited_fromAugmentation() async { + newFile('$testPackageLibPath/a.dart', ''' +library augment 'test.dart'; + +augment class A { + set s(x) {} +} +'''); + var library = await buildLibrary(''' +import augment 'a.dart'; + +class A {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + var setter = classA.augmented!.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpSetter(name: 's', library: library), + same(setter)); + } + + test_lookUpSetter_inherited_fromMixin() async { + var library = await buildLibrary(''' +mixin A { + set s(x) {} +} +class B with A {} +'''); + var mixinA = library.getMixin('A')!; + var setter = mixinA.accessors[0]; + var classB = library.getClass('B')!; + expect(classB.augmented?.lookUpSetter(name: 's', library: library), + same(setter)); + } + + test_lookUpSetter_undeclared() async { + var library = await buildLibrary(''' +class A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpSetter(name: 's', library: library), isNull); + } + + test_lookUpSetter_undeclared_recursive() async { + var library = await buildLibrary(''' +class A extends B {} +class B extends A {} +'''); + var classA = library.getClass('A')!; + expect(classA.augmented?.lookUpSetter(name: 's', library: library), isNull); + } +} + @reflectiveTest class MethodElementImplTest extends AbstractTypeSystemTest { void test_equal() {