Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d4ac706

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Improve diagnostic message when short circuiting (issue 42599)
Change-Id: I456bc50792dfdb30c281578d0d54ffa9e2af6474 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/154161 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
1 parent 7960ad5 commit d4ac706

File tree

7 files changed

+156
-24
lines changed

7 files changed

+156
-24
lines changed

pkg/analyzer/lib/error/error.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ const List<ErrorCode> errorCodeValues = [
765765
StaticWarningCode.IMPORT_OF_NON_LIBRARY,
766766
StaticWarningCode.INSTANTIATE_ABSTRACT_CLASS,
767767
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR,
768+
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
768769
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_NAMED,
769770
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_POSITIONAL,
770771
StaticWarningCode.INVALID_USE_OF_NULL_VALUE,

pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/token.dart';
67
import 'package:analyzer/dart/element/element.dart';
78
import 'package:analyzer/diagnostic/diagnostic.dart';
89
import 'package:analyzer/error/error.dart';
@@ -47,6 +48,25 @@ class DiagnosticFactory {
4748
]);
4849
}
4950

51+
/// Return a diagnostic indicating that the [duplicateKey] (in a constant map)
52+
/// is a duplicate of the [originalKey].
53+
AnalysisError invalidNullAwareAfterShortCircuit(Source source, int offset,
54+
int length, List<Object> arguments, Token previousToken) {
55+
var lexeme = previousToken.lexeme;
56+
return AnalysisError(
57+
source,
58+
offset,
59+
length,
60+
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
61+
arguments, [
62+
DiagnosticMessageImpl(
63+
filePath: source.fullName,
64+
message: "The operator '$lexeme' is causing the short circuiting.",
65+
offset: previousToken.offset,
66+
length: previousToken.length)
67+
]);
68+
}
69+
5070
/// Return a diagnostic indicating that the given [identifier] was referenced
5171
/// before it was declared.
5272
AnalysisError referencedBeforeDeclaration(

pkg/analyzer/lib/src/error/codes.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9343,6 +9343,22 @@ class StaticWarningCode extends AnalyzerErrorCode {
93439343
errorSeverity: ErrorSeverity.WARNING,
93449344
hasPublishedDocs: true);
93459345

9346+
/**
9347+
* Parameters:
9348+
* 0: The null-aware operator that is invalid
9349+
* 1: The non-null-aware operator that can replace the invalid operator
9350+
*/
9351+
static const StaticWarningCode
9352+
INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT =
9353+
StaticWarningCodeWithUniqueName(
9354+
'INVALID_NULL_AWARE_OPERATOR',
9355+
'INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT',
9356+
"The target expression can't be null because of short-circuiting, so "
9357+
"the null-aware operator '{0}' can't be used.",
9358+
correction: "Try replace the operator '{0}' with '{1}'.",
9359+
errorSeverity: ErrorSeverity.WARNING,
9360+
hasPublishedDocs: true);
9361+
93469362
/**
93479363
* 7.1 Instance Methods: It is a static warning if an instance method
93489364
* <i>m1</i> overrides an instance member <i>m2</i>, the signature of
@@ -10632,10 +10648,12 @@ class StaticWarningCodeWithUniqueName extends StaticWarningCode {
1063210648
const StaticWarningCodeWithUniqueName(
1063310649
String name, this.uniqueName, String message,
1063410650
{String correction,
10651+
ErrorSeverity errorSeverity = ErrorSeverity.ERROR,
1063510652
bool hasPublishedDocs,
1063610653
bool isUnresolvedIdentifier = false})
1063710654
: super(name, message,
1063810655
correction: correction,
10656+
errorSeverity: errorSeverity,
1063910657
hasPublishedDocs: hasPublishedDocs,
1064010658
isUnresolvedIdentifier: isUnresolvedIdentifier);
1064110659
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4203,7 +4203,54 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
42034203
return;
42044204
}
42054205

4206+
/// If the operator is not valid because the target already makes use of a
4207+
/// null aware operator, return the null aware operator from the target.
4208+
Token previousShortCircuitingOperator(Expression target) {
4209+
if (target is PropertyAccess) {
4210+
var operator = target.operator;
4211+
var type = operator.type;
4212+
if (type == TokenType.QUESTION_PERIOD) {
4213+
var realTarget = target.realTarget;
4214+
if (_isExpressionWithType(realTarget)) {
4215+
return previousShortCircuitingOperator(realTarget) ?? operator;
4216+
}
4217+
}
4218+
} else if (target is IndexExpression) {
4219+
if (target.question != null) {
4220+
var realTarget = target.realTarget;
4221+
if (_isExpressionWithType(realTarget)) {
4222+
return previousShortCircuitingOperator(realTarget) ??
4223+
target.question;
4224+
}
4225+
}
4226+
} else if (target is MethodInvocation) {
4227+
var operator = target.operator;
4228+
var type = operator.type;
4229+
if (type == TokenType.QUESTION_PERIOD) {
4230+
var realTarget = target.realTarget;
4231+
if (_isExpressionWithType(realTarget)) {
4232+
return previousShortCircuitingOperator(realTarget) ?? operator;
4233+
}
4234+
return operator;
4235+
}
4236+
}
4237+
return null;
4238+
}
4239+
42064240
if (_typeSystem.isStrictlyNonNullable(target.staticType)) {
4241+
if (errorCode == StaticWarningCode.INVALID_NULL_AWARE_OPERATOR) {
4242+
var previousOperator = previousShortCircuitingOperator(target);
4243+
if (previousOperator != null) {
4244+
_errorReporter.reportError(DiagnosticFactory()
4245+
.invalidNullAwareAfterShortCircuit(
4246+
_errorReporter.source,
4247+
operator.offset,
4248+
endToken.end - operator.offset,
4249+
arguments,
4250+
previousOperator));
4251+
return;
4252+
}
4253+
}
42074254
_errorReporter.reportErrorForOffset(
42084255
errorCode,
42094256
operator.offset,

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

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ class EqualElementsInConstSetTest extends DriverResolutionTest {
2424
var c = const {1, 2, 1};
2525
''', [
2626
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 21, 1,
27-
contextMessages: [
28-
message(resourceProvider.convertPath('/test/lib/test.dart'), 15, 1)
29-
]),
27+
contextMessages: [message('/test/lib/test.dart', 15, 1)]),
3028
]);
3129
}
3230

@@ -38,12 +36,7 @@ var c = const {1, if (1 < 0) 2 else 1};
3836
analysisOptions.experimentStatus.constant_update_2018
3937
? [
4038
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 36, 1,
41-
contextMessages: [
42-
message(
43-
resourceProvider.convertPath('/test/lib/test.dart'),
44-
15,
45-
1)
46-
]),
39+
contextMessages: [message('/test/lib/test.dart', 15, 1)]),
4740
]
4841
: [
4942
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 19),
@@ -106,12 +99,7 @@ var c = const {1, if (0 < 1) 1};
10699
analysisOptions.experimentStatus.constant_update_2018
107100
? [
108101
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 29, 1,
109-
contextMessages: [
110-
message(
111-
resourceProvider.convertPath('/test/lib/test.dart'),
112-
15,
113-
1)
114-
]),
102+
contextMessages: [message('/test/lib/test.dart', 15, 1)]),
115103
]
116104
: [
117105
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 12),
@@ -127,9 +115,7 @@ class A<T> {
127115
var c = const {const A<int>(), const A<int>()};
128116
''', [
129117
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 60, 14,
130-
contextMessages: [
131-
message(resourceProvider.convertPath('/test/lib/test.dart'), 44, 14)
132-
]),
118+
contextMessages: [message('/test/lib/test.dart', 44, 14)]),
133119
]);
134120
}
135121

@@ -164,12 +150,7 @@ var c = const {1, ...{1}};
164150
analysisOptions.experimentStatus.constant_update_2018
165151
? [
166152
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 21, 3,
167-
contextMessages: [
168-
message(
169-
resourceProvider.convertPath('/test/lib/test.dart'),
170-
15,
171-
1)
172-
]),
153+
contextMessages: [message('/test/lib/test.dart', 15, 1)]),
173154
]
174155
: [
175156
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 6),

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,72 @@ import '../dart/resolution/driver_resolution.dart';
1212

1313
main() {
1414
defineReflectiveSuite(() {
15+
defineReflectiveTests(InvalidNullAwareOperatorAfterShortCircuitTest);
1516
defineReflectiveTests(InvalidNullAwareOperatorTest);
1617
});
1718
}
1819

20+
@reflectiveTest
21+
class InvalidNullAwareOperatorAfterShortCircuitTest
22+
extends DriverResolutionTest {
23+
@override
24+
AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
25+
..contextFeatures = FeatureSet.fromEnableFlags(
26+
[EnableString.non_nullable],
27+
);
28+
29+
Future<void> test_getter_previousTarget() async {
30+
await assertErrorsInCode('''
31+
void f(String? s) {
32+
s?.length?.isEven;
33+
}
34+
''', [
35+
error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
36+
31, 2,
37+
contextMessages: [message('/test/lib/test.dart', 23, 2)]),
38+
]);
39+
}
40+
41+
Future<void> test_index_previousTarget() async {
42+
await assertErrorsInCode('''
43+
void f(String? s) {
44+
s?[4]?.length;
45+
}
46+
''', [
47+
error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
48+
27, 2,
49+
contextMessages: [message('/test/lib/test.dart', 23, 1)]),
50+
]);
51+
}
52+
53+
Future<void> test_methodInvocation_previousTarget() async {
54+
await assertErrorsInCode('''
55+
void f(String? s) {
56+
s?.substring(0, 5)?.length;
57+
}
58+
''', [
59+
error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
60+
40, 2,
61+
contextMessages: [message('/test/lib/test.dart', 23, 2)]),
62+
]);
63+
}
64+
65+
Future<void> test_methodInvocation_previousTwoTargets() async {
66+
await assertErrorsInCode('''
67+
void f(String? s) {
68+
s?.substring(0, 5)?.toLowerCase()?.length;
69+
}
70+
''', [
71+
error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
72+
40, 2,
73+
contextMessages: [message('/test/lib/test.dart', 23, 2)]),
74+
error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
75+
55, 2,
76+
contextMessages: [message('/test/lib/test.dart', 23, 2)]),
77+
]);
78+
}
79+
}
80+
1981
@reflectiveTest
2082
class InvalidNullAwareOperatorTest extends DriverResolutionTest {
2183
@override

pkg/analyzer/tool/diagnostics/diagnostics.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3439,6 +3439,9 @@ var x;
34393439

34403440
### invalid_null_aware_operator
34413441

3442+
_The target expression can't be null because of short-circuiting, so the
3443+
null-aware operator '{0}' can't be used._
3444+
34423445
_The target expression can't be null, so the null-aware operator '{0}' can't be
34433446
used._
34443447

0 commit comments

Comments
 (0)