Skip to content

Commit 5de031c

Browse files
kallentuCommit Queue
authored and
Commit Queue
committed
[analyzer] Dot Shorthands: Pipe RHS == context to dot shorthand.
When there's exactly a dot shorthand of the RHS of `==`, we use the namespace of the LHS to resolve the shorthand on the RHS. There's existing CFE work done for the `case ==` case in the type analyzer, so we just have to make sure `isDotShorthand` is properly set. This will take care of the relational pattern `==` behaviour. Tested using language tests and resolution unit tests. Bug: #59835 Change-Id: I3823641d1872bca68e344bc323209b58fb5098db Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422941 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent dfa1ea5 commit 5de031c

File tree

7 files changed

+403
-26
lines changed

7 files changed

+403
-26
lines changed

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

+15-9
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,12 @@ mixin TypeAnalyzer<
271271
TypeDeclarationType extends Object,
272272
TypeDeclaration extends Object>
273273
implements TypeAnalysisNullShortingInterface<Expression, SharedTypeView> {
274-
/// Cached context types used to resolve dot shorthand heads.
275-
final _dotShorthands = <SharedTypeSchemaView>[];
274+
/// Cached context types and their respective dot shorthand nodes.
275+
///
276+
/// The [SharedTypeSchemaView] is used to resolve dot shorthand heads. We
277+
/// save the corresponding dot shorthand [Node] to make sure we aren't caching
278+
/// two context types for the same node.
279+
final _dotShorthands = <(Node, SharedTypeSchemaView)>[];
276280

277281
TypeAnalyzerErrors<Node, Statement, Expression, Variable, SharedTypeView,
278282
Pattern, Error> get errors;
@@ -538,10 +542,10 @@ mixin TypeAnalyzer<
538542
/// Saves the [context] for when we resolve the dot shorthand head.
539543
SharedTypeView analyzeDotShorthand(
540544
Expression node, SharedTypeSchemaView context) {
541-
_dotShorthands.add(context);
545+
pushDotShorthandContext(node, context);
542546
ExpressionTypeAnalysisResult analysisResult =
543547
dispatchExpression(node, context);
544-
_dotShorthands.removeLast();
548+
popDotShorthandContext();
545549
return analysisResult.type;
546550
}
547551

@@ -2167,7 +2171,7 @@ mixin TypeAnalyzer<
21672171
});
21682172

21692173
/// Returns the most recently cached dot shorthand context type.
2170-
SharedTypeSchemaView getDotShorthandContext() => _dotShorthands.last;
2174+
SharedTypeSchemaView getDotShorthandContext() => _dotShorthands.last.$2;
21712175

21722176
/// If the [element] is a map pattern entry, returns it.
21732177
MapPatternEntry<Expression, Pattern>? getMapPatternEntry(Node element);
@@ -2355,10 +2359,12 @@ mixin TypeAnalyzer<
23552359
_dotShorthands.removeLast();
23562360
}
23572361

2358-
/// Pushes the [context] onto the stack to use when we resolve the dot
2359-
/// shorthand head.
2360-
void pushDotShorthandContext(SharedTypeSchemaView context) {
2361-
_dotShorthands.add(context);
2362+
/// Pushes the [node] and [context] onto the stack to use when we resolve the
2363+
/// dot shorthand head.
2364+
void pushDotShorthandContext(Node node, SharedTypeSchemaView context) {
2365+
if (_dotShorthands.isEmpty || _dotShorthands.last.$1 != node) {
2366+
_dotShorthands.add((node, context));
2367+
}
23622368
}
23632369

23642370
/// Returns the type of the property in [receiverType] that corresponds to

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

+9
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ class BinaryExpressionResolver {
106106
leftInfo = flow?.equalityOperand_end(left);
107107
}
108108

109+
// When evaluating exactly a dot shorthand in the RHS, we save the LHS type
110+
// to provide the context type for the shorthand.
111+
if (node.rightOperand is DotShorthandMixin) {
112+
_resolver.pushDotShorthandContext(
113+
node.rightOperand,
114+
SharedTypeSchemaView(left.typeOrThrow),
115+
);
116+
}
117+
109118
_resolver.analyzeExpression(
110119
node.rightOperand,
111120
SharedTypeSchemaView(UnknownInferredType.instance),

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

+18-8
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,26 @@ class InstanceCreationExpressionResolver {
5656
}
5757

5858
/// Resolves a [DotShorthandConstructorInvocation] node.
59-
void resolveDotShorthand(DotShorthandConstructorInvocationImpl node) {
60-
TypeImpl contextType =
59+
void resolveDotShorthand(
60+
DotShorthandConstructorInvocationImpl node, {
61+
required TypeImpl contextType,
62+
}) {
63+
TypeImpl dotShorthandContextType =
6164
_resolver.getDotShorthandContext().unwrapTypeSchemaView();
6265

6366
// The static namespace denoted by `S` is also the namespace denoted by
6467
// `FutureOr<S>`.
65-
contextType = _resolver.typeSystem.futureOrBase(contextType);
68+
dotShorthandContextType = _resolver.typeSystem.futureOrBase(
69+
dotShorthandContextType,
70+
);
6671

6772
// TODO(kallentu): Support other context types
68-
if (contextType is InterfaceTypeImpl) {
69-
_resolveDotShorthandConstructorInvocation(node, contextType: contextType);
73+
if (dotShorthandContextType is InterfaceTypeImpl) {
74+
_resolveDotShorthandConstructorInvocation(
75+
node,
76+
contextType: contextType,
77+
dotShorthandContextType: dotShorthandContextType,
78+
);
7079
}
7180

7281
// TODO(kallentu): Report error.
@@ -75,15 +84,16 @@ class InstanceCreationExpressionResolver {
7584
void _resolveDotShorthandConstructorInvocation(
7685
DotShorthandConstructorInvocationImpl node, {
7786
required TypeImpl contextType,
87+
required TypeImpl dotShorthandContextType,
7888
}) {
7989
var whyNotPromotedArguments = <WhyNotPromotedGetter>[];
8090
_resolver.elementResolver.visitDotShorthandConstructorInvocation(node);
8191
var elementToInfer = _resolver.inferenceHelper.constructorElementToInfer(
82-
typeElement: contextType.element3,
92+
typeElement: dotShorthandContextType.element3,
8393
constructorName: node.constructorName,
8494
definingLibrary: _resolver.definingLibrary,
8595
);
86-
DotShorthandConstructorInvocationInferrer(
96+
var returnType = DotShorthandConstructorInvocationInferrer(
8797
resolver: _resolver,
8898
node: node,
8999
argumentList: node.argumentList,
@@ -94,7 +104,7 @@ class InstanceCreationExpressionResolver {
94104
// `ConstructorElementToInfer.asType`.
95105
rawType: elementToInfer?.asType as FunctionTypeImpl?,
96106
);
97-
node.recordStaticType(contextType, resolver: _resolver);
107+
node.recordStaticType(returnType, resolver: _resolver);
98108
_resolver.checkForArgumentTypesNotAssignableInList(
99109
node.argumentList,
100110
whyNotPromotedArguments,

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

+10-7
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,9 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
12401240

12411241
@override
12421242
bool isDotShorthand(ExpressionImpl node) {
1243-
// TODO(kallentu): Implement this for dot shorthand equality implementation.
1243+
if (node is DotShorthandMixin) {
1244+
return (node as DotShorthandMixin).isDotShorthand;
1245+
}
12441246
return false;
12451247
}
12461248

@@ -2416,8 +2418,8 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
24162418
inferenceLogWriter?.enterExpression(node, contextType);
24172419

24182420
// If [isDotShorthand] is set, cache the context type for resolution.
2419-
if (node.isDotShorthand) {
2420-
pushDotShorthandContext(SharedTypeSchemaView(contextType));
2421+
if (isDotShorthand(node)) {
2422+
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
24212423
}
24222424

24232425
checkUnreachableNode(node);
@@ -2454,10 +2456,11 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
24542456
case DotShorthandConstructorInvocationImpl():
24552457
_instanceCreationExpressionResolver.resolveDotShorthand(
24562458
rewrittenExpression,
2459+
contextType: contextType,
24572460
);
24582461
}
24592462

2460-
if (node.isDotShorthand) {
2463+
if (isDotShorthand(node)) {
24612464
popDotShorthandContext();
24622465
}
24632466

@@ -2472,8 +2475,8 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
24722475
inferenceLogWriter?.enterExpression(node, contextType);
24732476

24742477
// If [isDotShorthand] is set, cache the context type for resolution.
2475-
if (node.isDotShorthand) {
2476-
pushDotShorthandContext(SharedTypeSchemaView(contextType));
2478+
if (isDotShorthand(node)) {
2479+
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
24772480
}
24782481

24792482
checkUnreachableNode(node);
@@ -2485,7 +2488,7 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
24852488
contextType,
24862489
);
24872490

2488-
if (node.isDotShorthand) {
2491+
if (isDotShorthand(node)) {
24892492
popDotShorthandContext();
24902493
}
24912494

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

+199-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,205 @@ DotShorthandConstructorInvocation
8585
substitution: {T: dynamic}
8686
staticType: int
8787
rightParenthesis: )
88-
staticType: C<dynamic>?
88+
staticType: C<dynamic>
89+
''');
90+
}
91+
92+
test_equality() async {
93+
await assertNoErrorsInCode(r'''
94+
class C {
95+
int x;
96+
C.named(this.x);
97+
}
98+
99+
void main() {
100+
C lhs = C.named(2);
101+
bool b = lhs == .named(1);
102+
print(b);
103+
}
104+
''');
105+
106+
var identifier = findNode.singleDotShorthandConstructorInvocation;
107+
assertResolvedNodeText(identifier, r'''
108+
DotShorthandConstructorInvocation
109+
period: .
110+
constructorName: SimpleIdentifier
111+
token: named
112+
element: <testLibraryFragment>::@class::C::@constructor::named#element
113+
staticType: null
114+
argumentList: ArgumentList
115+
leftParenthesis: (
116+
arguments
117+
IntegerLiteral
118+
literal: 1
119+
correspondingParameter: <testLibraryFragment>::@class::C::@constructor::named::@parameter::x#element
120+
staticType: int
121+
rightParenthesis: )
122+
correspondingParameter: dart:core::<fragment>::@class::Object::@method::==::@parameter::other#element
123+
staticType: C
124+
''');
125+
}
126+
127+
test_equality_inferTypeParameters() async {
128+
await assertNoErrorsInCode('''
129+
void main() {
130+
bool x = <int>[] == .filled(2, '2');
131+
print(x);
132+
}
133+
''');
134+
135+
var identifier = findNode.singleDotShorthandConstructorInvocation;
136+
assertResolvedNodeText(identifier, r'''
137+
DotShorthandConstructorInvocation
138+
period: .
139+
constructorName: SimpleIdentifier
140+
token: filled
141+
element: ConstructorMember
142+
baseElement: dart:core::<fragment>::@class::List::@constructor::filled#element
143+
substitution: {E: String}
144+
staticType: null
145+
argumentList: ArgumentList
146+
leftParenthesis: (
147+
arguments
148+
IntegerLiteral
149+
literal: 2
150+
correspondingParameter: ParameterMember
151+
baseElement: dart:core::<fragment>::@class::List::@constructor::filled::@parameter::length#element
152+
substitution: {E: String}
153+
staticType: int
154+
SimpleStringLiteral
155+
literal: '2'
156+
rightParenthesis: )
157+
correspondingParameter: dart:core::<fragment>::@class::Object::@method::==::@parameter::other#element
158+
staticType: List<String>
159+
''');
160+
}
161+
162+
@FailingTest(
163+
issue: 'https://github.com/dart-lang/sdk/issues/59835',
164+
reason:
165+
'Constant evaluation for dot shorthand constructor invocations needs '
166+
'to be implemented.',
167+
)
168+
test_equality_pattern() async {
169+
await assertNoErrorsInCode(r'''
170+
class C {
171+
int x;
172+
C.named(this.x);
173+
}
174+
175+
void main() {
176+
C c = C.named(1);
177+
if (c case == .named(2)) print('ok');
178+
}
179+
''');
180+
181+
var identifier = findNode.singleDotShorthandConstructorInvocation;
182+
assertResolvedNodeText(identifier, r'''
183+
DotShorthandConstructorInvocation
184+
period: .
185+
constructorName: SimpleIdentifier
186+
token: named
187+
element: <testLibraryFragment>::@class::C::@constructor::named#element
188+
staticType: null
189+
argumentList: ArgumentList
190+
leftParenthesis: (
191+
arguments
192+
IntegerLiteral
193+
literal: 1
194+
correspondingParameter: <testLibraryFragment>::@class::C::@constructor::named::@parameter::x#element
195+
staticType: int
196+
rightParenthesis: )
197+
correspondingParameter: dart:core::<fragment>::@class::Object::@method::==::@parameter::other#element
198+
staticType: C
199+
''');
200+
}
201+
202+
test_nested_invocation() async {
203+
await assertNoErrorsInCode(r'''
204+
class C<T> {
205+
static C member() => C(1);
206+
T x;
207+
C(this.x);
208+
}
209+
210+
void main() {
211+
C<C> c = .new(.member());
212+
print(c);
213+
}
214+
''');
215+
216+
var node = findNode.singleDotShorthandConstructorInvocation;
217+
assertResolvedNodeText(node, r'''
218+
DotShorthandConstructorInvocation
219+
period: .
220+
constructorName: SimpleIdentifier
221+
token: new
222+
element: ConstructorMember
223+
baseElement: <testLibraryFragment>::@class::C::@constructor::new#element
224+
substitution: {T: C<dynamic>}
225+
staticType: null
226+
argumentList: ArgumentList
227+
leftParenthesis: (
228+
arguments
229+
DotShorthandInvocation
230+
period: .
231+
memberName: SimpleIdentifier
232+
token: member
233+
element: <testLibraryFragment>::@class::C::@method::member#element
234+
staticType: C<dynamic> Function()
235+
argumentList: ArgumentList
236+
leftParenthesis: (
237+
rightParenthesis: )
238+
correspondingParameter: FieldFormalParameterMember
239+
baseElement: <testLibraryFragment>::@class::C::@constructor::new::@parameter::x#element
240+
substitution: {T: C<dynamic>}
241+
staticInvokeType: C<dynamic> Function()
242+
staticType: C<dynamic>
243+
rightParenthesis: )
244+
staticType: C<C<dynamic>>
245+
''');
246+
}
247+
248+
test_nested_property() async {
249+
await assertNoErrorsInCode(r'''
250+
class C<T> {
251+
static C get member => C(1);
252+
T x;
253+
C(this.x);
254+
}
255+
256+
void main() {
257+
C<C> c = .new(.member);
258+
print(c);
259+
}
260+
''');
261+
262+
var node = findNode.singleDotShorthandConstructorInvocation;
263+
assertResolvedNodeText(node, r'''
264+
DotShorthandConstructorInvocation
265+
period: .
266+
constructorName: SimpleIdentifier
267+
token: new
268+
element: ConstructorMember
269+
baseElement: <testLibraryFragment>::@class::C::@constructor::new#element
270+
substitution: {T: C<dynamic>}
271+
staticType: null
272+
argumentList: ArgumentList
273+
leftParenthesis: (
274+
arguments
275+
DotShorthandPropertyAccess
276+
period: .
277+
propertyName: SimpleIdentifier
278+
token: member
279+
element: <testLibraryFragment>::@class::C::@getter::member#element
280+
staticType: C<dynamic>
281+
correspondingParameter: FieldFormalParameterMember
282+
baseElement: <testLibraryFragment>::@class::C::@constructor::new::@parameter::x#element
283+
substitution: {T: C<dynamic>}
284+
staticType: C<dynamic>
285+
rightParenthesis: )
286+
staticType: C<C<dynamic>>
89287
''');
90288
}
91289

0 commit comments

Comments
 (0)