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>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 82811a4..3dfbaf5 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -765,6 +765,7 @@
StaticWarningCode.IMPORT_OF_NON_LIBRARY,
StaticWarningCode.INSTANTIATE_ABSTRACT_CLASS,
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR,
+ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_NAMED,
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_POSITIONAL,
StaticWarningCode.INVALID_USE_OF_NULL_VALUE,
diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
index 63533de..14c09b7 100644
--- a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
+++ b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
@@ -47,6 +48,25 @@
]);
}
+ /// Return a diagnostic indicating that the [duplicateKey] (in a constant map)
+ /// is a duplicate of the [originalKey].
+ AnalysisError invalidNullAwareAfterShortCircuit(Source source, int offset,
+ int length, List<Object> arguments, Token previousToken) {
+ var lexeme = previousToken.lexeme;
+ return AnalysisError(
+ source,
+ offset,
+ length,
+ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ arguments, [
+ DiagnosticMessageImpl(
+ filePath: source.fullName,
+ message: "The operator '$lexeme' is causing the short circuiting.",
+ offset: previousToken.offset,
+ length: previousToken.length)
+ ]);
+ }
+
/// Return a diagnostic indicating that the given [identifier] was referenced
/// before it was declared.
AnalysisError referencedBeforeDeclaration(
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index bd4b447..89ebfc4 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -9344,6 +9344,22 @@
hasPublishedDocs: true);
/**
+ * Parameters:
+ * 0: The null-aware operator that is invalid
+ * 1: The non-null-aware operator that can replace the invalid operator
+ */
+ static const StaticWarningCode
+ INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT =
+ StaticWarningCodeWithUniqueName(
+ 'INVALID_NULL_AWARE_OPERATOR',
+ 'INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT',
+ "The target expression can't be null because of short-circuiting, so "
+ "the null-aware operator '{0}' can't be used.",
+ correction: "Try replace the operator '{0}' with '{1}'.",
+ errorSeverity: ErrorSeverity.WARNING,
+ hasPublishedDocs: true);
+
+ /**
* 7.1 Instance Methods: It is a static warning if an instance method
* <i>m1</i> overrides an instance member <i>m2</i>, the signature of
* <i>m2</i> explicitly specifies a default value for a formal parameter
@@ -10632,10 +10648,12 @@
const StaticWarningCodeWithUniqueName(
String name, this.uniqueName, String message,
{String correction,
+ ErrorSeverity errorSeverity = ErrorSeverity.ERROR,
bool hasPublishedDocs,
bool isUnresolvedIdentifier = false})
: super(name, message,
correction: correction,
+ errorSeverity: errorSeverity,
hasPublishedDocs: hasPublishedDocs,
isUnresolvedIdentifier: isUnresolvedIdentifier);
}
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index cf31354..e98b03d2e 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -4203,7 +4203,54 @@
return;
}
+ /// If the operator is not valid because the target already makes use of a
+ /// null aware operator, return the null aware operator from the target.
+ Token previousShortCircuitingOperator(Expression target) {
+ if (target is PropertyAccess) {
+ var operator = target.operator;
+ var type = operator.type;
+ if (type == TokenType.QUESTION_PERIOD) {
+ var realTarget = target.realTarget;
+ if (_isExpressionWithType(realTarget)) {
+ return previousShortCircuitingOperator(realTarget) ?? operator;
+ }
+ }
+ } else if (target is IndexExpression) {
+ if (target.question != null) {
+ var realTarget = target.realTarget;
+ if (_isExpressionWithType(realTarget)) {
+ return previousShortCircuitingOperator(realTarget) ??
+ target.question;
+ }
+ }
+ } else if (target is MethodInvocation) {
+ var operator = target.operator;
+ var type = operator.type;
+ if (type == TokenType.QUESTION_PERIOD) {
+ var realTarget = target.realTarget;
+ if (_isExpressionWithType(realTarget)) {
+ return previousShortCircuitingOperator(realTarget) ?? operator;
+ }
+ return operator;
+ }
+ }
+ return null;
+ }
+
if (_typeSystem.isStrictlyNonNullable(target.staticType)) {
+ if (errorCode == StaticWarningCode.INVALID_NULL_AWARE_OPERATOR) {
+ var previousOperator = previousShortCircuitingOperator(target);
+ if (previousOperator != null) {
+ _errorReporter.reportError(DiagnosticFactory()
+ .invalidNullAwareAfterShortCircuit(
+ _errorReporter.source,
+ operator.offset,
+ endToken.end - operator.offset,
+ arguments,
+ previousOperator));
+ return;
+ }
+ }
_errorReporter.reportErrorForOffset(
errorCode,
operator.offset,
diff --git a/pkg/analyzer/test/src/diagnostics/equal_elements_in_const_set_test.dart b/pkg/analyzer/test/src/diagnostics/equal_elements_in_const_set_test.dart
index 69b5728..eeb763d 100644
--- a/pkg/analyzer/test/src/diagnostics/equal_elements_in_const_set_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/equal_elements_in_const_set_test.dart
@@ -24,9 +24,7 @@
var c = const {1, 2, 1};
''', [
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 21, 1,
- contextMessages: [
- message(resourceProvider.convertPath('/test/lib/test.dart'), 15, 1)
- ]),
+ contextMessages: [message('/test/lib/test.dart', 15, 1)]),
]);
}
@@ -38,12 +36,7 @@
analysisOptions.experimentStatus.constant_update_2018
? [
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 36, 1,
- contextMessages: [
- message(
- resourceProvider.convertPath('/test/lib/test.dart'),
- 15,
- 1)
- ]),
+ contextMessages: [message('/test/lib/test.dart', 15, 1)]),
]
: [
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 19),
@@ -106,12 +99,7 @@
analysisOptions.experimentStatus.constant_update_2018
? [
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 29, 1,
- contextMessages: [
- message(
- resourceProvider.convertPath('/test/lib/test.dart'),
- 15,
- 1)
- ]),
+ contextMessages: [message('/test/lib/test.dart', 15, 1)]),
]
: [
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 12),
@@ -127,9 +115,7 @@
var c = const {const A<int>(), const A<int>()};
''', [
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 60, 14,
- contextMessages: [
- message(resourceProvider.convertPath('/test/lib/test.dart'), 44, 14)
- ]),
+ contextMessages: [message('/test/lib/test.dart', 44, 14)]),
]);
}
@@ -164,12 +150,7 @@
analysisOptions.experimentStatus.constant_update_2018
? [
error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 21, 3,
- contextMessages: [
- message(
- resourceProvider.convertPath('/test/lib/test.dart'),
- 15,
- 1)
- ]),
+ contextMessages: [message('/test/lib/test.dart', 15, 1)]),
]
: [
error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 18, 6),
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
index 96ce4f9..01890ce 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
@@ -12,11 +12,73 @@
main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(InvalidNullAwareOperatorAfterShortCircuitTest);
defineReflectiveTests(InvalidNullAwareOperatorTest);
});
}
@reflectiveTest
+class InvalidNullAwareOperatorAfterShortCircuitTest
+ extends DriverResolutionTest {
+ @override
+ AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
+ ..contextFeatures = FeatureSet.fromEnableFlags(
+ [EnableString.non_nullable],
+ );
+
+ Future<void> test_getter_previousTarget() async {
+ await assertErrorsInCode('''
+void f(String? s) {
+ s?.length?.isEven;
+}
+''', [
+ error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ 31, 2,
+ contextMessages: [message('/test/lib/test.dart', 23, 2)]),
+ ]);
+ }
+
+ Future<void> test_index_previousTarget() async {
+ await assertErrorsInCode('''
+void f(String? s) {
+ s?[4]?.length;
+}
+''', [
+ error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ 27, 2,
+ contextMessages: [message('/test/lib/test.dart', 23, 1)]),
+ ]);
+ }
+
+ Future<void> test_methodInvocation_previousTarget() async {
+ await assertErrorsInCode('''
+void f(String? s) {
+ s?.substring(0, 5)?.length;
+}
+''', [
+ error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ 40, 2,
+ contextMessages: [message('/test/lib/test.dart', 23, 2)]),
+ ]);
+ }
+
+ Future<void> test_methodInvocation_previousTwoTargets() async {
+ await assertErrorsInCode('''
+void f(String? s) {
+ s?.substring(0, 5)?.toLowerCase()?.length;
+}
+''', [
+ error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ 40, 2,
+ contextMessages: [message('/test/lib/test.dart', 23, 2)]),
+ error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
+ 55, 2,
+ contextMessages: [message('/test/lib/test.dart', 23, 2)]),
+ ]);
+ }
+}
+
+@reflectiveTest
class InvalidNullAwareOperatorTest extends DriverResolutionTest {
@override
AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index 88b7aee..490a45c 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -3439,6 +3439,9 @@
### invalid_null_aware_operator
+_The target expression can't be null because of short-circuiting, so the
+null-aware operator '{0}' can't be used._
+
_The target expression can't be null, so the null-aware operator '{0}' can't be
used._