Skip to content

Commit fa12123

Browse files
kallentuCommit Queue
authored and
Commit Queue
committed
[analyzer] Dot shorthands: Build ASTs, add isDotShorthand flag, and handle method invocations.
With this CL, we build the `DotShorthandInvocation` AST as soon as we find a `MethodInvocation` when parsing a dot shorthand head. The context is saved as we encounter ASTs with the `isDotShorthand` flag enabled (which currently is just the invocation and property get head, more will be added later). We pop the context after resolving the dot shorthand head and using the context for resolution. The resolution of dot shorthand invocations is handled by the `MethodInvocationInferrer` where most of the logic was added in https://dart-review.googlesource.com/c/sdk/+/421560. In this CL, we're calling the `resolveDotShorthand` entry point for the very basic resolving. This is the groundwork that we'll build off of for constructor invocations, extension type invocations and other cases. Some co19 tests are crashing, as per expected, but I added resolution tests for the .shorthand invocations and ast building tests. Bug: #59835 Change-Id: I62bcb5fecb3c4fdfcf29d6c079d5d77547c4a21a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419780 Reviewed-by: Chloe Stefantsova <[email protected]> Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent 56062a9 commit fa12123

File tree

13 files changed

+276
-10
lines changed

13 files changed

+276
-10
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,18 @@ mixin TypeAnalyzer<
23452345
/// Queries whether [pattern] is a variable pattern.
23462346
bool isVariablePattern(Node pattern);
23472347

2348+
/// Pops the top of the [_dotShorthands] stack when we're finished resolving
2349+
/// the dot shorthand head that requires the recent-most context.
2350+
void popDotShorthandContext() {
2351+
_dotShorthands.removeLast();
2352+
}
2353+
2354+
/// Pushes the [context] onto the stack to use when we resolve the dot
2355+
/// shorthand head.
2356+
void pushDotShorthandContext(SharedTypeSchemaView context) {
2357+
_dotShorthands.add(context);
2358+
}
2359+
23482360
/// Returns the type of the property in [receiverType] that corresponds to
23492361
/// the name of the [field]. If the property cannot be resolved, the client
23502362
/// should report an error, and return `dynamic` for recovery.

pkg/analyzer/lib/src/dart/ast/ast.dart

+9
Original file line numberDiff line numberDiff line change
@@ -5407,6 +5407,7 @@ abstract final class DotShorthandInvocation extends InvocationExpression {
54075407
}
54085408

54095409
final class DotShorthandInvocationImpl extends InvocationExpressionImpl
5410+
with DotShorthandMixin
54105411
implements DotShorthandInvocation {
54115412
@override
54125413
final Token period;
@@ -5466,6 +5467,13 @@ final class DotShorthandInvocationImpl extends InvocationExpressionImpl
54665467
}
54675468
}
54685469

5470+
base mixin DotShorthandMixin on AstNodeImpl {
5471+
/// Whether the AST node is a dot shorthand and has a dot shorthand head
5472+
/// ([DotShorthandInvocation] or [DotShorthandPropertyAccess]) as its
5473+
/// inner-most target.
5474+
bool isDotShorthand = false;
5475+
}
5476+
54695477
/// A node that represents a dot shorthand property access of a field or a
54705478
/// static getter.
54715479
///
@@ -5483,6 +5491,7 @@ abstract final class DotShorthandPropertyAccess extends Expression {
54835491
}
54845492

54855493
final class DotShorthandPropertyAccessImpl extends ExpressionImpl
5494+
with DotShorthandMixin
54865495
implements DotShorthandPropertyAccess {
54875496
@override
54885497
final Token period;

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class MethodInvocationResolver with ScopeHelpers {
5656
final InvocationInferenceHelper _inferenceHelper;
5757

5858
/// The invocation being resolved.
59-
MethodInvocationImpl? _invocation;
59+
InvocationExpressionImpl? _invocation;
6060

6161
/// The [Name] object of the invocation being resolved by [resolve].
6262
Name? _currentName;
@@ -207,6 +207,8 @@ class MethodInvocationResolver with ScopeHelpers {
207207
FunctionExpressionInvocationImpl? resolveDotShorthand(
208208
DotShorthandInvocationImpl node,
209209
List<WhyNotPromotedGetter> whyNotPromotedArguments) {
210+
_invocation = node;
211+
210212
var contextType = _resolver.getDotShorthandContext().unwrapTypeSchemaView();
211213
// TODO(kallentu): Dot shorthands work - Support other context types
212214
if (contextType is InterfaceTypeImpl) {

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

+28-4
Original file line numberDiff line numberDiff line change
@@ -4066,20 +4066,44 @@ class AstBuilder extends StackListener {
40664066
);
40674067
}
40684068

4069-
// TODO(kallentu): Handle dot shorthands.
4069+
var dotShorthand = pop() as ExpressionImpl;
4070+
if (dotShorthand is DotShorthandMixin) {
4071+
(dotShorthand as DotShorthandMixin).isDotShorthand = true;
4072+
}
4073+
// TODO(kallentu): Add this assert once we've applied the DotShorthandMixin
4074+
// on all possible expressions that can be a dot shorthand.
4075+
// } else {
4076+
// assert(
4077+
// false,
4078+
// "'$dotShorthand' must be a 'DotShorthandMixin' because we "
4079+
// "should only call 'handleDotShorthandContext' after parsing "
4080+
// "expressions that have a context type we can cache.");
4081+
// }
4082+
push(dotShorthand);
40704083
}
40714084

40724085
@override
4073-
void handleDotShorthandHead(Token token) {
4086+
void handleDotShorthandHead(Token periodToken) {
40744087
debugEvent("DotShorthandHead");
40754088
if (!enabledDotShorthands) {
40764089
_reportFeatureNotEnabled(
40774090
feature: ExperimentalFeatures.dot_shorthands,
4078-
startToken: token,
4091+
startToken: periodToken,
40794092
);
40804093
}
40814094

4082-
// TODO(kallentu): Handle dot shorthands.
4095+
var operand = pop() as ExpressionImpl;
4096+
// TODO(kallentu): Handle property access case.
4097+
if (operand is MethodInvocationImpl) {
4098+
push(DotShorthandInvocationImpl(
4099+
period: periodToken,
4100+
memberName: operand.methodName,
4101+
typeArguments: operand.typeArguments,
4102+
argumentList: operand.argumentList,
4103+
));
4104+
} else {
4105+
push(operand);
4106+
}
40834107
}
40844108

40854109
@override

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

+13
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ class ElementResolver {
163163
_resolveAnnotations(node.metadata);
164164
}
165165

166+
/// Resolves the dot shorthand invocation, [node].
167+
///
168+
/// If [node] is rewritten to be a [FunctionExpressionInvocation] in the
169+
/// process, then returns that new node. Otherwise, returns `null`.
170+
FunctionExpressionInvocationImpl? visitDotShorthandInvocation(
171+
covariant DotShorthandInvocationImpl node,
172+
{List<WhyNotPromotedGetter>? whyNotPromotedArguments,
173+
required TypeImpl contextType}) {
174+
whyNotPromotedArguments ??= [];
175+
return _methodInvocationResolver.resolveDotShorthand(
176+
node, whyNotPromotedArguments);
177+
}
178+
166179
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
167180
_resolveAnnotations(node.metadata);
168181
}

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

+35-2
Original file line numberDiff line numberDiff line change
@@ -2278,9 +2278,40 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
22782278
}
22792279

22802280
@override
2281-
void visitDotShorthandInvocation(DotShorthandInvocation node,
2281+
void visitDotShorthandInvocation(covariant DotShorthandInvocationImpl node,
22822282
{TypeImpl contextType = UnknownInferredType.instance}) {
2283-
throw UnimplementedError('TODO(kallentu)');
2283+
inferenceLogWriter?.enterExpression(node, contextType);
2284+
2285+
// If [isDotShorthand] is set, cache the context type for resolution.
2286+
if (node.isDotShorthand) {
2287+
pushDotShorthandContext(SharedTypeSchemaView(contextType));
2288+
}
2289+
2290+
checkUnreachableNode(node);
2291+
var whyNotPromotedArguments =
2292+
<Map<SharedTypeView, NonPromotionReason> Function()>[];
2293+
2294+
node.typeArguments?.accept(this);
2295+
var functionRewrite = elementResolver.visitDotShorthandInvocation(node,
2296+
whyNotPromotedArguments: whyNotPromotedArguments,
2297+
contextType: contextType);
2298+
// TODO(kallentu): Handle constructors.
2299+
if (functionRewrite is FunctionExpressionInvocationImpl) {
2300+
_resolveRewrittenFunctionExpressionInvocation(
2301+
functionRewrite, whyNotPromotedArguments,
2302+
contextType: contextType);
2303+
}
2304+
var replacement =
2305+
insertGenericFunctionInstantiation(node, contextType: contextType);
2306+
checkForArgumentTypesNotAssignableInList(
2307+
node.argumentList, whyNotPromotedArguments);
2308+
_insertImplicitCallReference(replacement, contextType: contextType);
2309+
2310+
if (node.isDotShorthand) {
2311+
popDotShorthandContext();
2312+
}
2313+
2314+
inferenceLogWriter?.exitExpression(node);
22842315
}
22852316

22862317
@override
@@ -4279,6 +4310,8 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
42794310
name = nameNodeName is PrefixedIdentifier
42804311
? nameNodeName.identifier.name
42814312
: '${nameNodeName.name}.new';
4313+
} else if (nameNode is DotShorthandInvocation) {
4314+
name = nameNode.memberName.name;
42824315
} else {
42834316
throw UnimplementedError('(${nameNode.runtimeType}) $nameNode');
42844317
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class FindNode {
5959
ConstructorFieldInitializer get singleConstructorFieldInitializer =>
6060
_single();
6161

62+
DotShorthandInvocation get singleDotShorthandInvocation => _single();
63+
6264
EnumDeclaration get singleEnumDeclaration => _single();
6365

6466
ExportDirective get singleExportDirective => _single();
@@ -360,6 +362,10 @@ class FindNode {
360362
return _node(search, (n) => n is DoStatement);
361363
}
362364

365+
DotShorthandInvocation dotShorthandInvocation(String search) {
366+
return _node(search, (n) => n is DotShorthandInvocation);
367+
}
368+
363369
DoubleLiteral doubleLiteral(String search) {
364370
return _node(search, (n) => n is DoubleLiteral);
365371
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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(DotShorthandInvocationResolutionTest);
13+
defineReflectiveTests(UpdateNodeTextExpectations);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class DotShorthandInvocationResolutionTest extends PubPackageResolutionTest {
19+
test_dotShorthand_basic() async {
20+
await assertNoErrorsInCode(r'''
21+
class C {
22+
static C member() => C(1);
23+
int x;
24+
C(this.x);
25+
}
26+
27+
void main() {
28+
C c = .member();
29+
print(c);
30+
}
31+
''');
32+
33+
var node = findNode.singleDotShorthandInvocation;
34+
assertResolvedNodeText(node, r'''
35+
DotShorthandInvocation
36+
period: .
37+
memberName: SimpleIdentifier
38+
token: member
39+
element: <testLibraryFragment>::@class::C::@method::member#element
40+
staticType: C Function()
41+
argumentList: ArgumentList
42+
leftParenthesis: (
43+
rightParenthesis: )
44+
staticInvokeType: C Function()
45+
staticType: C
46+
''');
47+
}
48+
49+
test_dotShorthand_basic_generic() async {
50+
await assertNoErrorsInCode(r'''
51+
class C<T> {
52+
static C member<U>(U x) => C(x);
53+
T x;
54+
C(this.x);
55+
}
56+
57+
void main() {
58+
C c = .member<int>(1);
59+
print(c);
60+
}
61+
''');
62+
63+
var node = findNode.singleDotShorthandInvocation;
64+
assertResolvedNodeText(node, r'''
65+
DotShorthandInvocation
66+
period: .
67+
memberName: SimpleIdentifier
68+
token: member
69+
element: <testLibraryFragment>::@class::C::@method::member#element
70+
staticType: C<dynamic> Function<U>(U)
71+
typeArguments: TypeArgumentList
72+
leftBracket: <
73+
arguments
74+
NamedType
75+
name: int
76+
element2: dart:core::@class::int
77+
type: int
78+
rightBracket: >
79+
argumentList: ArgumentList
80+
leftParenthesis: (
81+
arguments
82+
IntegerLiteral
83+
literal: 1
84+
correspondingParameter: ParameterMember
85+
baseElement: <testLibraryFragment>::@class::C::@method::member::@parameter::x#element
86+
substitution: {U: int}
87+
staticType: int
88+
rightParenthesis: )
89+
staticInvokeType: C<dynamic> Function(int)
90+
staticType: C<dynamic>
91+
typeArgumentTypes
92+
int
93+
''');
94+
}
95+
96+
test_dotShorthand_basic_parameter() async {
97+
await assertNoErrorsInCode(r'''
98+
class C {
99+
static C member(int x) => C(x);
100+
int x;
101+
C(this.x);
102+
}
103+
104+
void main() {
105+
C c = .member(1);
106+
print(c);
107+
}
108+
''');
109+
110+
var node = findNode.singleDotShorthandInvocation;
111+
assertResolvedNodeText(node, r'''
112+
DotShorthandInvocation
113+
period: .
114+
memberName: SimpleIdentifier
115+
token: member
116+
element: <testLibraryFragment>::@class::C::@method::member#element
117+
staticType: C Function(int)
118+
argumentList: ArgumentList
119+
leftParenthesis: (
120+
arguments
121+
IntegerLiteral
122+
literal: 1
123+
correspondingParameter: <testLibraryFragment>::@class::C::@method::member::@parameter::x#element
124+
staticType: int
125+
rightParenthesis: )
126+
staticInvokeType: C Function(int)
127+
staticType: C
128+
''');
129+
}
130+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'constructor_field_initializer_test.dart'
2626
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;
29+
import 'dot_shorthand_invocation_test.dart' as dot_shorthand_invocation;
2930
import 'enum_test.dart' as enum_resolution;
3031
import 'extension_method_test.dart' as extension_method;
3132
import 'extension_override_test.dart' as extension_override;
@@ -139,6 +140,7 @@ main() {
139140
constructor_reference.main();
140141
constructor.main();
141142
declared_variable_pattern.main();
143+
dot_shorthand_invocation.main();
142144
enum_resolution.main();
143145
extension_method.main();
144146
extension_override.main();

pkg/analyzer/test/src/diagnostics/experiment_not_enabled_test.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ main() {
3434

3535
test_dotShorthands_disabled() async {
3636
await assertErrorsInCode(r'''
37+
// @dart = 3.8
3738
void main() {
38-
Object c = .hash;
39+
Object c = .hash(1, 2);
3940
print(c);
4041
}
4142
''', [
42-
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 27, 1),
43-
error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 28, 4),
43+
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 42, 1),
4444
]);
4545
}
4646

0 commit comments

Comments
 (0)