Skip to content

Commit 3f48dc7

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[analyzer] Implement static extension getters
This CL adds support for static extension getters in the CFE. The feature is observable under the `static-extensions` experimental feature flag. Only the positive support is added; the diagnostics will be added in follow-up CLs. Part of #61487 Change-Id: I6006c86d53e8ca5d20859481e7ab77840a63a1c2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457024 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 6184c44 commit 3f48dc7

18 files changed

+1109
-1
lines changed

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,6 +2327,66 @@ class ExtensionElementImpl extends InstanceElementImpl
23272327
@trackedIncludedInId
23282328
ElementKind get kind => ElementKind.EXTENSION;
23292329

2330+
/// Computes the on-declaration of the extension.
2331+
///
2332+
/// The on-declaration is computed a described in
2333+
/// https://github.com/dart-lang/language/blob/main/working/0723-static-extensions/feature-specification.md#syntax
2334+
/// In case the extension declaration doesn't have an on-declaration, `null`
2335+
/// is returned.
2336+
@trackedIndirectly
2337+
InterfaceElementImpl? get onDeclaration {
2338+
if (extendedType.nullabilitySuffix != NullabilitySuffix.question) {
2339+
switch (extendedType.element) {
2340+
// In an extension declaration of the form extension E on C {...} where C
2341+
// is an identifier (or an identifier with an import prefix) that denotes
2342+
// a class, mixin, enum, or extension type declaration, we say that the
2343+
// on-declaration of the extension is C.
2344+
2345+
// If C denotes a generic class then E is treated as extension E on C<T1
2346+
// .. Tk> {...} where T1 .. Tk are obtained by instantiation to bound.
2347+
2348+
// In an extension of the form extension E on C<T1 .. Tk> {...} where C is
2349+
// an identifier or prefixed identifier that denotes a class, mixin, enum,
2350+
// or extension type declaration, we say that the on-declaration of E is
2351+
// C.
2352+
2353+
// In an extension of the form extension E on F<T1 .. Tk> {...} where F is
2354+
// a type alias whose transitive alias expansion denotes a class, mixin,
2355+
// enum, or extension type C, we say that the on-declaration of E is C,
2356+
// and the declaration is treated as if F<T1 .. Tk> were replaced by its
2357+
// transitive alias expansion.
2358+
2359+
// Implementation note: type aliases in [extendedType] are already
2360+
// unaliased at this point, so there's no need to consider them
2361+
// separately.
2362+
2363+
// For the purpose of identifying the on-declaration of a given extension,
2364+
// the types void, dynamic, and Never are not considered to be classes,
2365+
// and neither are record types or function types.
2366+
2367+
// Also note that none of the following types are classes:
2368+
// * A type of the form T? or FutureOr<T>, for any type T.
2369+
// * A type variable.
2370+
// * An intersection type.
2371+
case ClassElementImpl() when !extendedType.isDartAsyncFutureOr:
2372+
case MixinElementImpl():
2373+
case EnumElementImpl():
2374+
case ExtensionTypeElementImpl():
2375+
return extendedType.element as InterfaceElementImpl;
2376+
default:
2377+
// In all other cases, an extension declaration does not have an
2378+
// on-declaration.
2379+
return null;
2380+
}
2381+
} else {
2382+
// Also note that none of the following types are classes:
2383+
// * A type of the form T? or FutureOr<T>, for any type T.
2384+
// * A type variable.
2385+
// * An intersection type.
2386+
return null;
2387+
}
2388+
}
2389+
23302390
@override
23312391
@trackedIndirectly
23322392
DartType get thisType => extendedType;

pkg/analyzer/lib/src/dart/resolver/applicable_extensions.dart

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ class InstantiatedExtensionWithoutMember {
6060
);
6161
}
6262

63+
class _ExtensionWithMemberWithName {
64+
final ExtensionElementImpl extension;
65+
final InternalExecutableElement member;
66+
67+
_ExtensionWithMemberWithName({required this.extension, required this.member});
68+
}
69+
6370
abstract class _NotInstantiatedExtension<R> {
6471
final ExtensionElementImpl extension;
6572

@@ -127,7 +134,7 @@ extension ExtensionsExtensions on Iterable<ExtensionElement> {
127134
}
128135

129136
/// Returns the sublist of [ExtensionElement]s that have an instance member
130-
/// named [baseName].
137+
/// with basename [baseName].
131138
List<_NotInstantiatedExtensionWithMember> havingMemberWithBaseName(
132139
Name baseName,
133140
) {
@@ -180,6 +187,54 @@ extension ExtensionsExtensions on Iterable<ExtensionElement> {
180187
}
181188
return result;
182189
}
190+
191+
/// Returns the sublist of [ExtensionElement]s that have a static member
192+
/// with name [name].
193+
List<_ExtensionWithMemberWithName> havingStaticMemberWithName(Name name) {
194+
var result = <_ExtensionWithMemberWithName>[];
195+
for (var extension in this) {
196+
if (!name.isAccessibleFor(extension.library.uri)) {
197+
continue;
198+
}
199+
200+
var getter = extension.getGetter(name.name);
201+
if (getter != null && getter.isStatic) {
202+
result.add(
203+
_ExtensionWithMemberWithName(
204+
// TODO(paulberry): eliminate this cast by changing the
205+
// extension to apply only to `Iterable<ExtensionElementImpl>`.
206+
extension: extension as ExtensionElementImpl,
207+
member: getter as GetterElementImpl,
208+
),
209+
);
210+
}
211+
212+
var setter = extension.getSetter(name.name);
213+
if (setter != null && setter.isStatic) {
214+
result.add(
215+
_ExtensionWithMemberWithName(
216+
// TODO(paulberry): eliminate this cast by changing the
217+
// extension to apply only to `Iterable<ExtensionElementImpl>`.
218+
extension: extension as ExtensionElementImpl,
219+
member: setter as SetterElementImpl,
220+
),
221+
);
222+
}
223+
224+
var method = extension.getMethod(name.name);
225+
if (method != null && method.isStatic) {
226+
result.add(
227+
_ExtensionWithMemberWithName(
228+
// TODO(paulberry): eliminate this cast by changing the
229+
// extension to apply only to `Iterable<ExtensionElementImpl>`.
230+
extension: extension as ExtensionElementImpl,
231+
member: method as MethodElementImpl,
232+
),
233+
);
234+
}
235+
}
236+
return result;
237+
}
183238
}
184239

185240
extension NotInstantiatedExtensionsExtensions<R>

pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ import 'package:analyzer/src/generated/inference_log.dart';
2424
import 'package:analyzer/src/generated/resolver.dart';
2525
import 'package:analyzer/src/utilities/extensions/string.dart';
2626

27+
/// The result of a failed attempt to resolve an identifier to the single
28+
/// element with the given name, where the result is expected to come from an
29+
/// extension. [AmbiguousStaticExtensionResolutionError] covers the case where
30+
/// multiple matching extensions were found, resulting in an ambiguity.
31+
class AmbiguousStaticExtensionResolutionError
32+
extends StaticExtensionResolutionResult {
33+
const AmbiguousStaticExtensionResolutionError();
34+
35+
@override
36+
InternalExecutableElement? get member => null;
37+
}
38+
2739
class ExtensionMemberResolver {
2840
final ResolverVisitor _resolver;
2941

@@ -146,6 +158,36 @@ class ExtensionMemberResolver {
146158
return ExtensionResolutionError.ambiguous;
147159
}
148160

161+
/// Finds extensions applicable to [declaration] for static member lookups.
162+
StaticExtensionResolutionResult findExtensionForDeclaration(
163+
InterfaceElement declaration,
164+
SyntacticEntity nameEntity,
165+
Name name,
166+
) {
167+
var extensions = [
168+
for (var extension
169+
in _resolver.libraryFragment.accessibleExtensions
170+
.havingStaticMemberWithName(name))
171+
if (extension.extension.onDeclaration == declaration) extension,
172+
];
173+
174+
if (extensions.isEmpty) {
175+
return const NoneStaticExtensionResolutionError();
176+
}
177+
178+
// When an extension is looked up for the declaration, only the exact match
179+
// matters, since there is no inheritance of the static members. No attempts
180+
// made to find the most precise match, and any ambiguity is treated as an
181+
// error.
182+
if (extensions.length == 1) {
183+
var extension = extensions[0];
184+
_resolver.libraryFragment.scope.notifyExtensionUsed(extension.extension);
185+
return SingleStaticExtensionResolutionResult(member: extension.member);
186+
}
187+
188+
return const AmbiguousStaticExtensionResolutionError();
189+
}
190+
149191
/// Resolve the [name] (without `=`) to the corresponding getter and setter
150192
/// members of the extension [node].
151193
///
@@ -506,6 +548,18 @@ enum ExtensionResolutionError implements ExtensionResolutionResult {
506548
/// result (if any) is known to come from an extension.
507549
sealed class ExtensionResolutionResult implements SimpleResolutionResult {}
508550

551+
/// The result of a failed attempt to resolve an identifier to the single
552+
/// element with the given name, where the result is expected to come from an
553+
/// extension. [NoneStaticExtensionResolutionError] covers the case where no
554+
/// matching extensions were found.
555+
class NoneStaticExtensionResolutionError
556+
extends StaticExtensionResolutionResult {
557+
const NoneStaticExtensionResolutionError();
558+
559+
@override
560+
InternalExecutableElement? get member => null;
561+
}
562+
509563
/// The result of a successful attempt to resolve an identifier to elements,
510564
/// where the result (if any) is known to come from an extension.
511565
class SingleExtensionResolutionResult extends SimpleResolutionResult
@@ -515,3 +569,25 @@ class SingleExtensionResolutionResult extends SimpleResolutionResult
515569
required super.setter2,
516570
}) : assert(getter2 != null || setter2 != null);
517571
}
572+
573+
/// The result of a successful attempt to resolve an identifier to the single
574+
/// element with the given name, where the result (if any) is known to come from
575+
/// an extension.
576+
class SingleStaticExtensionResolutionResult
577+
extends SimpleStaticExtensionResolutionResult
578+
implements StaticExtensionResolutionResult {
579+
SingleStaticExtensionResolutionResult({
580+
required InternalExecutableElement member,
581+
}) : super(member: member);
582+
583+
@override
584+
InternalExecutableElement get member => super.member!;
585+
}
586+
587+
/// The result of attempting to resolve an identifier to the single element with
588+
/// the given name, where the result (if any) is known to come from an
589+
/// extension.
590+
sealed class StaticExtensionResolutionResult
591+
extends SimpleStaticExtensionResolutionResult {
592+
const StaticExtensionResolutionResult({super.member});
593+
}

pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
66
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
7+
import 'package:analyzer/dart/analysis/features.dart';
78
import 'package:analyzer/dart/element/element.dart';
89
import 'package:analyzer/dart/element/type.dart';
910
import 'package:analyzer/error/error.dart';
@@ -798,6 +799,23 @@ class PropertyElementResolver with ScopeHelpers {
798799
}
799800
}
800801

802+
if (readElement == null) {
803+
if (_definingLibrary.featureSet.isEnabled(Feature.static_extensions)) {
804+
// When direct lookups fail, try static extension resolution.
805+
var result = _resolver.typePropertyResolver.resolveForDeclaration(
806+
declaration: typeReference,
807+
name: propertyName.name,
808+
hasRead: hasRead,
809+
hasWrite: hasWrite,
810+
propertyErrorEntity: propertyName,
811+
nameErrorEntity: propertyName,
812+
);
813+
if (result.getter2 != null) {
814+
readElement = result.getter2;
815+
}
816+
}
817+
}
818+
801819
if (readElement != null) {
802820
getType = readElement.returnType;
803821
if (_checkForStaticAccessToInstanceMember(propertyName, readElement)) {

pkg/analyzer/lib/src/dart/resolver/resolution_result.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,13 @@ class SimpleResolutionResult {
5959

6060
const SimpleResolutionResult({this.getter2, this.setter2});
6161
}
62+
63+
class SimpleStaticExtensionResolutionResult {
64+
/// Returns the single element with the given name.
65+
///
66+
/// Note that the resolution is performed on the name of the member, not on
67+
/// its basename. Hence, the result is a single member.
68+
final InternalExecutableElement? member;
69+
70+
const SimpleStaticExtensionResolutionResult({this.member});
71+
}

pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,42 @@ class TypePropertyResolver {
239239
}
240240
}
241241

242+
/// Resolve static invocations for [declaration].
243+
ResolutionResult resolveForDeclaration({
244+
required InterfaceElement declaration,
245+
required String name,
246+
required bool hasRead,
247+
required bool hasWrite,
248+
required SyntacticEntity propertyErrorEntity,
249+
required SyntacticEntity nameErrorEntity,
250+
AstNode? parentNode,
251+
}) {
252+
_name = name;
253+
_hasRead = hasRead;
254+
_hasWrite = hasWrite;
255+
_nameErrorEntity = nameErrorEntity;
256+
_resetResult();
257+
258+
var getterName = Name(_definingLibrary.uri, _name);
259+
var result = _extensionResolver.findExtensionForDeclaration(
260+
declaration,
261+
_nameErrorEntity,
262+
getterName,
263+
);
264+
// TODO(cstefantsova): Add the support for setters and methods.
265+
_reportedGetterError =
266+
result == const AmbiguousStaticExtensionResolutionError();
267+
_reportedSetterError = false;
268+
269+
if (result.member != null) {
270+
// TODO(cstefantsova): Add the support for setters and methods.
271+
_needsGetterError = false;
272+
_getterRequested = result.member;
273+
}
274+
275+
return _toResult();
276+
}
277+
242278
void _lookupExtension(TypeImpl type) {
243279
var getterName = Name(_definingLibrary.uri, _name);
244280
var result = _extensionResolver.findExtension(

0 commit comments

Comments
 (0)