Skip to content

Commit 3dd7e22

Browse files
kallentuCommit Queue
authored and
Commit Queue
committed
[analyzer] Dot shorthands: Resolve DotShorthandPropertyAccess.
Allows the resolution of `.new` constructor references and static property accesses, using the existing `PropertyElementResolver` resolution logic. Testing includes an AST builder test, and the `DotShorthandPropertyAccess` resolution tests. Some property access language tests are now passing. Bug: #59835 Change-Id: Idb576ef7fa0866bc4b2d155bbf867886ae2b4df6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421964 Commit-Queue: Kallen Tu <[email protected]> Reviewed-by: Chloe Stefantsova <[email protected]>
1 parent 3915216 commit 3dd7e22

File tree

13 files changed

+218
-21
lines changed

13 files changed

+218
-21
lines changed

pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,6 +2332,10 @@ mixin TypeAnalyzer<
23322332
/// Queries whether [node] is a dot shorthand.
23332333
bool isDotShorthand(Expression node);
23342334

2335+
/// Queries whether the [_dotShorthands] stack is empty, meaning that we have
2336+
/// no cached context types.
2337+
bool get isDotShorthandContextEmpty => _dotShorthands.isEmpty;
2338+
23352339
/// Queries whether the switch statement or expression represented by [node]
23362340
/// was exhaustive. [expressionType] is the static type of the scrutinee.
23372341
///

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,44 @@ class PropertyElementResolver with ScopeHelpers {
3838

3939
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
4040

41+
PropertyElementResolverResult resolveDotShorthand(
42+
DotShorthandPropertyAccessImpl node) {
43+
if (_resolver.isDotShorthandContextEmpty) {
44+
// TODO(kallentu): Produce an error here for not being able to find a
45+
// context type.
46+
return PropertyElementResolverResult();
47+
}
48+
TypeImpl context =
49+
_resolver.getDotShorthandContext().unwrapTypeSchemaView();
50+
// TODO(kallentu): Support other context types
51+
if (context is InterfaceTypeImpl) {
52+
var identifier = node.propertyName;
53+
if (identifier.name == 'new') {
54+
var element =
55+
context.lookUpConstructor2(identifier.name, _definingLibrary);
56+
if (element != null) {
57+
return PropertyElementResolverResult(
58+
readElementRequested2: element,
59+
getType: element.returnType,
60+
);
61+
}
62+
} else {
63+
var contextElement = context.element3;
64+
return _resolveTargetInterfaceElement(
65+
typeReference: contextElement,
66+
isCascaded: false,
67+
propertyName: identifier,
68+
hasRead: true,
69+
hasWrite: false,
70+
);
71+
}
72+
}
73+
74+
// TODO(kallentu): Produce an error here for not being able to find a
75+
// property.
76+
return PropertyElementResolverResult();
77+
}
78+
4179
PropertyElementResolverResult resolveIndexExpression({
4280
required IndexExpressionImpl node,
4381
required bool hasRead,

pkg/analyzer/lib/src/fasta/ast_builder.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,8 +4093,12 @@ class AstBuilder extends StackListener {
40934093
}
40944094

40954095
var operand = pop() as ExpressionImpl;
4096-
// TODO(kallentu): Handle property access case.
4097-
if (operand is MethodInvocationImpl) {
4096+
if (operand is SimpleIdentifierImpl) {
4097+
push(DotShorthandPropertyAccessImpl(
4098+
period: periodToken,
4099+
propertyName: operand,
4100+
));
4101+
} else if (operand is MethodInvocationImpl) {
40984102
push(DotShorthandInvocationImpl(
40994103
period: periodToken,
41004104
memberName: operand.methodName,

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5378,6 +5378,11 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
53785378
/// [CompileTimeErrorCode.UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER].
53795379
void _checkForUnqualifiedReferenceToNonLocalStaticMember(
53805380
SimpleIdentifier name) {
5381+
if (name.parent is DotShorthandPropertyAccessImpl ||
5382+
name.parent is DotShorthandInvocationImpl) {
5383+
return;
5384+
}
5385+
53815386
var element = name.writeOrReadElement2;
53825387
if (element == null || element is TypeParameterElement2) {
53835388
return;

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,9 +2315,26 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
23152315
}
23162316

23172317
@override
2318-
void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node,
2318+
void visitDotShorthandPropertyAccess(
2319+
covariant DotShorthandPropertyAccessImpl node,
23192320
{TypeImpl contextType = UnknownInferredType.instance}) {
2320-
throw UnimplementedError('TODO(kallentu)');
2321+
inferenceLogWriter?.enterExpression(node, contextType);
2322+
2323+
// If [isDotShorthand] is set, cache the context type for resolution.
2324+
if (node.isDotShorthand) {
2325+
pushDotShorthandContext(SharedTypeSchemaView(contextType));
2326+
}
2327+
2328+
checkUnreachableNode(node);
2329+
var result = _propertyElementResolver.resolveDotShorthand(node);
2330+
_resolvePropertyAccessRhs_common(
2331+
result, node, node.propertyName, contextType);
2332+
2333+
if (node.isDotShorthand) {
2334+
popDotShorthandContext();
2335+
}
2336+
2337+
inferenceLogWriter?.exitExpression(node);
23212338
}
23222339

23232340
@override
@@ -3982,21 +3999,34 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
39823999
hasWrite: false,
39834000
);
39844001

3985-
var element = result.readElement2;
4002+
_resolvePropertyAccessRhs_common(
4003+
result, node, node.propertyName, contextType);
4004+
nullSafetyDeadCodeVerifier.verifyPropertyAccess(node);
4005+
}
4006+
4007+
/// Common logic for resolving dot shorthands property accesses and
4008+
/// [_resolvePropertyAccessRhs].
4009+
void _resolvePropertyAccessRhs_common(
4010+
PropertyElementResolverResult resolverResult,
4011+
ExpressionImpl node,
4012+
SimpleIdentifierImpl propertyName,
4013+
TypeImpl contextType) {
4014+
var element = resolverResult.readElement2;
39864015

3987-
var propertyName = node.propertyName;
39884016
propertyName.element = element;
39894017

39904018
DartType type;
39914019
if (element is MethodElement2) {
39924020
type = element.type;
4021+
} else if (element is ConstructorElementImpl2) {
4022+
type = element.type;
39934023
} else if (element is GetterElement) {
3994-
type = result.getType!;
3995-
} else if (result.functionTypeCallType != null) {
3996-
type = result.functionTypeCallType!;
3997-
} else if (result.recordField != null) {
3998-
type = result.recordField!.type;
3999-
} else if (result.atDynamicTarget) {
4024+
type = resolverResult.getType!;
4025+
} else if (resolverResult.functionTypeCallType != null) {
4026+
type = resolverResult.functionTypeCallType!;
4027+
} else if (resolverResult.recordField != null) {
4028+
type = resolverResult.recordField!.type;
4029+
} else if (resolverResult.atDynamicTarget) {
40004030
type = DynamicTypeImpl.instance;
40014031
} else {
40024032
type = InvalidTypeImpl.instance;
@@ -4020,7 +4050,6 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
40204050

40214051
nullShortingTermination(node, rewrittenExpression: replacement);
40224052
_insertImplicitCallReference(replacement, contextType: contextType);
4023-
nullSafetyDeadCodeVerifier.verifyPropertyAccess(node);
40244053
}
40254054

40264055
/// Continues resolution of a [FunctionExpressionInvocation] that was created

pkg/analyzer/lib/src/test_utilities/find_node.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class FindNode {
6161

6262
DotShorthandInvocation get singleDotShorthandInvocation => _single();
6363

64+
DotShorthandPropertyAccess get singleDotShorthandPropertyAccess => _single();
65+
6466
EnumDeclaration get singleEnumDeclaration => _single();
6567

6668
ExportDirective get singleExportDirective => _single();
@@ -366,6 +368,10 @@ class FindNode {
366368
return _node(search, (n) => n is DotShorthandInvocation);
367369
}
368370

371+
DotShorthandPropertyAccess dotShorthandPropertyAccess(String search) {
372+
return _node(search, (n) => n is DotShorthandPropertyAccess);
373+
}
374+
369375
DoubleLiteral doubleLiteral(String search) {
370376
return _node(search, (n) => n is DoubleLiteral);
371377
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:test_reflective_loader/test_reflective_loader.dart';
6+
7+
import 'context_collection_resolution.dart';
8+
import 'node_text_expectations.dart';
9+
10+
main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(DotShorthandPropertyAccessResolutionTest);
13+
defineReflectiveTests(UpdateNodeTextExpectations);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class DotShorthandPropertyAccessResolutionTest
19+
extends PubPackageResolutionTest {
20+
test_class_basic() async {
21+
await assertNoErrorsInCode('''
22+
class C {
23+
static C get member => C(1);
24+
int x;
25+
C(this.x);
26+
}
27+
28+
void main() {
29+
C c = .member;
30+
print(c);
31+
}
32+
''');
33+
34+
var identifier = findNode.singleDotShorthandPropertyAccess;
35+
assertResolvedNodeText(identifier, r'''
36+
DotShorthandPropertyAccess
37+
period: .
38+
propertyName: SimpleIdentifier
39+
token: member
40+
element: <testLibraryFragment>::@class::C::@getter::member#element
41+
staticType: C
42+
staticType: C
43+
''');
44+
}
45+
46+
test_enum_basic() async {
47+
await assertNoErrorsInCode('''
48+
enum C { red }
49+
50+
void main() {
51+
C c = .red;
52+
print(c);
53+
}
54+
''');
55+
56+
var identifier = findNode.singleDotShorthandPropertyAccess;
57+
assertResolvedNodeText(identifier, r'''
58+
DotShorthandPropertyAccess
59+
period: .
60+
propertyName: SimpleIdentifier
61+
token: red
62+
element: <testLibraryFragment>::@enum::C::@getter::red#element
63+
staticType: C
64+
staticType: C
65+
''');
66+
}
67+
68+
test_object_new() async {
69+
await assertNoErrorsInCode('''
70+
void main() {
71+
Object o = .new;
72+
print(o);
73+
}
74+
''');
75+
76+
var identifier = findNode.singleDotShorthandPropertyAccess;
77+
assertResolvedNodeText(identifier, r'''
78+
DotShorthandPropertyAccess
79+
period: .
80+
propertyName: SimpleIdentifier
81+
token: new
82+
element: dart:core::<fragment>::@class::Object::@constructor::new#element
83+
staticType: Object Function()
84+
staticType: Object Function()
85+
''');
86+
}
87+
}

pkg/analyzer/test/src/dart/resolution/test_all.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import 'constructor_reference_test.dart' as constructor_reference;
2727
import 'constructor_test.dart' as constructor;
2828
import 'declared_variable_pattern_test.dart' as declared_variable_pattern;
2929
import 'dot_shorthand_invocation_test.dart' as dot_shorthand_invocation;
30+
import 'dot_shorthand_property_access_test.dart'
31+
as dot_shorthand_property_access;
3032
import 'enum_test.dart' as enum_resolution;
3133
import 'extension_method_test.dart' as extension_method;
3234
import 'extension_override_test.dart' as extension_override;
@@ -141,6 +143,7 @@ main() {
141143
constructor.main();
142144
declared_variable_pattern.main();
143145
dot_shorthand_invocation.main();
146+
dot_shorthand_property_access.main();
144147
enum_resolution.main();
145148
extension_method.main();
146149
extension_override.main();

pkg/analyzer/test/src/fasta/ast_builder_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,25 @@ DotShorthandInvocation
925925
''');
926926
}
927927

928+
void test_dotShorthand_propertyAccess() {
929+
var parseResult = parseStringWithErrors(r'''
930+
enum E { a }
931+
932+
void main() {
933+
E e = .a;
934+
}
935+
''');
936+
parseResult.assertNoErrors();
937+
938+
var node = parseResult.findNode.dotShorthandPropertyAccess('.a');
939+
assertParsedNodeText(node, r'''
940+
DotShorthandPropertyAccess
941+
period: .
942+
propertyName: SimpleIdentifier
943+
token: a
944+
''');
945+
}
946+
928947
void test_enum_base() {
929948
var parseResult = parseStringWithErrors(r'''
930949
base enum E { v }

pkg/analyzer/test/src/summary/resolved_ast_printer.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,16 @@ class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
477477
});
478478
}
479479

480+
@override
481+
void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
482+
_sink.writeln('DotShorthandPropertyAccess');
483+
_sink.withIndent(() {
484+
_writeNamedChildEntities(node);
485+
_writeParameterElement(node);
486+
_writeType('staticType', node.staticType);
487+
});
488+
}
489+
480490
@override
481491
void visitDottedName(DottedName node) {
482492
_sink.writeln('DottedName');

tests/language/dot_shorthands/feature_disabled_error_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,4 @@ void main() {
1313
// ^
1414
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
1515
// [cfe] This requires the experimental 'dot-shorthands' language feature to be enabled.
16-
// ^^^^
17-
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
1816
}

tests/language/dot_shorthands/feature_enabled_error_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,4 @@ void main() {
1212
// ^
1313
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
1414
// [cfe] This requires the experimental 'dot-shorthands' language feature to be enabled.
15-
// ^^^^
16-
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
1715
}

tests/language/explicit_type_instantiation_parsing_test.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,6 @@ void main() {
250250
// ^
251251
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
252252
// [cfe] This requires the experimental 'dot-shorthands' language feature to be enabled.
253-
// ^^^^^^^^
254-
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
255253

256254
X<2>.any;
257255
// ^
@@ -260,8 +258,6 @@ void main() {
260258
// ^
261259
// [analyzer] SYNTACTIC_ERROR.EXPERIMENT_NOT_ENABLED
262260
// [cfe] This requires the experimental 'dot-shorthands' language feature to be enabled.
263-
// ^^^
264-
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
265261

266262
// This would be invalid even if `X` had an `any` member. See next.
267263
X<X>.any; // Invalid, Class does not have any static `any` member.

0 commit comments

Comments
 (0)