Skip to content

Commit 29bc21c

Browse files
kallentuCommit Queue
authored andcommitted
[analyzer] Dot shorthands: Add DotShorthandMixin to PostfixExpression to handle null-assert expressions.
The parser now handles the `!` operator which wouldn't be captured originally because we are parsing the shorthand with SELECTOR_PRECEDENCE. Added a language test since no tests currently cover the usage of `!` and a few unit tests. Bug: #59835 Change-Id: I66bdcc9a083c98d91b16d3ce8c952b8d4010ecd9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425155 Reviewed-by: Jens Johansen <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent 8f1005f commit 29bc21c

File tree

5 files changed

+103
-6
lines changed

5 files changed

+103
-6
lines changed

pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5878,6 +5878,15 @@ class Parser {
58785878
typeArg, token, constantPatternContext,
58795879
isDotShorthand: true);
58805880

5881+
// With SELECTOR_PRECEDENCE, the `!` operator isn't parsed before
5882+
// handling the dot shorthand context. We want to capture and handle the
5883+
// null-assert before caching the context type.
5884+
Token next = token.next!;
5885+
if (next.isA(TokenType.BANG)) {
5886+
listener.handleNonNullAssertExpression(next);
5887+
token = next;
5888+
}
5889+
58815890
// The entire shorthand is parsed at this point.
58825891
listener.handleDotShorthandContext(dot);
58835892
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15530,7 +15530,10 @@ abstract final class PostfixExpression
1553015530
}
1553115531

1553215532
final class PostfixExpressionImpl extends ExpressionImpl
15533-
with NullShortableExpressionImpl, CompoundAssignmentExpressionImpl
15533+
with
15534+
NullShortableExpressionImpl,
15535+
CompoundAssignmentExpressionImpl,
15536+
DotShorthandMixin
1553415537
implements PostfixExpression {
1553515538
ExpressionImpl _operand;
1553615539

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3621,19 +3621,27 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
36213621

36223622
@override
36233623
void visitPostfixExpression(
3624-
PostfixExpression node, {
3624+
covariant PostfixExpressionImpl node, {
36253625
TypeImpl contextType = UnknownInferredType.instance,
36263626
}) {
36273627
inferenceLogWriter?.enterExpression(node, contextType);
3628+
3629+
// If [isDotShorthand] is set, cache the context type for resolution.
3630+
if (isDotShorthand(node)) {
3631+
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
3632+
}
3633+
36283634
checkUnreachableNode(node);
3629-
_postfixExpressionResolver.resolve(
3630-
node as PostfixExpressionImpl,
3631-
contextType: contextType,
3632-
);
3635+
_postfixExpressionResolver.resolve(node, contextType: contextType);
36333636
_insertImplicitCallReference(
36343637
insertGenericFunctionInstantiation(node, contextType: contextType),
36353638
contextType: contextType,
36363639
);
3640+
3641+
if (isDotShorthand(node)) {
3642+
popDotShorthandContext();
3643+
}
3644+
36373645
inferenceLogWriter?.exitExpression(node);
36383646
}
36393647

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,57 @@ DotShorthandPropertyAccess
296296
''');
297297
}
298298

299+
test_equality_nullAssert() async {
300+
await assertNoErrorsInCode(r'''
301+
class C {
302+
int x;
303+
C(this.x);
304+
static C? nullable = C(1);
305+
}
306+
307+
main() {
308+
print(C(1) == .nullable!);
309+
}
310+
''');
311+
312+
var identifier = findNode.singleDotShorthandPropertyAccess;
313+
assertResolvedNodeText(identifier, r'''
314+
DotShorthandPropertyAccess
315+
period: .
316+
propertyName: SimpleIdentifier
317+
token: nullable
318+
element: <testLibraryFragment>::@class::C::@getter::nullable#element
319+
staticType: C?
320+
staticType: C?
321+
''');
322+
}
323+
324+
test_equality_nullAssert_chain() async {
325+
await assertNoErrorsInCode(r'''
326+
class C {
327+
int x;
328+
C(this.x);
329+
static C? nullable = C(1);
330+
C? member = C(1);
331+
}
332+
333+
main() {
334+
print(C(1) == .nullable!.member!);
335+
}
336+
''');
337+
338+
var identifier = findNode.singleDotShorthandPropertyAccess;
339+
assertResolvedNodeText(identifier, r'''
340+
DotShorthandPropertyAccess
341+
period: .
342+
propertyName: SimpleIdentifier
343+
token: nullable
344+
element: <testLibraryFragment>::@class::C::@getter::nullable#element
345+
staticType: C?
346+
staticType: C?
347+
''');
348+
}
349+
299350
test_equality_pattern() async {
300351
await assertNoErrorsInCode('''
301352
enum Color { red, blue }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
// Tests for the postfix null assert operator `!`.
6+
7+
// SharedOptions=--enable-experiment=dot-shorthands
8+
9+
import "package:expect/expect.dart";
10+
11+
class C {
12+
int x;
13+
C(this.x);
14+
static C? nullable = C(1);
15+
C? member() => C(1);
16+
}
17+
18+
main() {
19+
C nullAssert = .nullable!;
20+
Expect.equals(1, nullAssert.x);
21+
22+
C nullAssertChain = .nullable!.member()!;
23+
Expect.equals(1, nullAssertChain.x);
24+
25+
bool nullAssertEq = C(1) == .nullable!.member()!;
26+
}

0 commit comments

Comments
 (0)