Add CAST_FROM_NULL_ALWAYS_FAILS check
Fixes https://github.com/dart-lang/linter/issues/3605
Change-Id: I5f9a2b127173aa9f2cc6f8fcee9c93e96cb63663
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255149
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index c38174c..3b4aa26 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1247,6 +1247,8 @@
status: hasFix
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE:
status: hasFix
+HintCode.CAST_FROM_NULL_ALWAYS_FAILS:
+ status: needsEvaluation
HintCode.DEAD_CODE:
status: hasFix
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH:
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index d02d609..207bdaa 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -552,6 +552,7 @@
HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
HintCode.BODY_MIGHT_COMPLETE_NORMALLY_NULLABLE,
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE,
+ HintCode.CAST_FROM_NULL_ALWAYS_FAILS,
HintCode.DEAD_CODE,
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
index 94f9260..cada0da 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
@@ -54,6 +54,13 @@
correctionMessage: "Replace the '.' with a '?.' in the invocation.",
);
+ /// No parameters.
+ static const HintCode CAST_FROM_NULL_ALWAYS_FAILS = HintCode(
+ 'CAST_FROM_NULL_ALWAYS_FAILS',
+ "This cast will always throw an exception because the expression will "
+ "always evaluate to 'null'.",
+ );
+
/// Dead code is code that is never reached, this can happen for instance if a
/// statement follows a return statement.
///
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 15d2cf5..93f77a9 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -352,6 +352,14 @@
if (isUnnecessaryCast(node, _typeSystem)) {
_errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node);
}
+ var type = node.type.type;
+ if (_isNonNullableByDefault &&
+ type != null &&
+ _typeSystem.isNonNullable(type) &&
+ node.expression.typeOrThrow.isDartCoreNull) {
+ _errorReporter.reportErrorForNode(
+ HintCode.CAST_FROM_NULL_ALWAYS_FAILS, node);
+ }
super.visitAsExpression(node);
}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index b4e5a11..1ac7352 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -19686,6 +19686,11 @@
comment: |-
A condition in operands of a logical operator could evaluate to `null`
because it uses the null-aware '?.' operator.
+ CAST_FROM_NULL_ALWAYS_FAILS:
+ problemMessage: "This cast will always throw an exception because the expression will always evaluate to 'null'."
+ comment: |-
+ No parameters.
+ hasPublishedDocs: false
NULL_CHECK_ALWAYS_FAILS:
problemMessage: "This null-check will always throw an exception because the expression will always evaluate to 'null'."
comment: |-
diff --git a/pkg/analyzer/test/src/diagnostics/cast_from_null_always_fails_test.dart b/pkg/analyzer/test/src/diagnostics/cast_from_null_always_fails_test.dart
new file mode 100644
index 0000000..0d7369b
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/cast_from_null_always_fails_test.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(CastFromNullAlwaysFailsTest);
+ });
+}
+
+@reflectiveTest
+class CastFromNullAlwaysFailsTest extends PubPackageResolutionTest {
+ test_Null_dynamic() async {
+ await assertNoErrorsInCode('''
+void f(Null n) {
+ n as dynamic;
+}
+''');
+ }
+
+ test_Null_Never() async {
+ await assertErrorsInCode('''
+void f(Null n) {
+ n as Never;
+}
+''', [
+ error(HintCode.CAST_FROM_NULL_ALWAYS_FAILS, 19, 10),
+ ]);
+ }
+
+ test_Null_nonNullable() async {
+ await assertErrorsInCode('''
+void f(Null n) {
+ n as int;
+}
+''', [
+ error(HintCode.CAST_FROM_NULL_ALWAYS_FAILS, 19, 8),
+ ]);
+ }
+
+ test_Null_nonNullableTypeVariable() async {
+ await assertErrorsInCode('''
+void f<T extends Object>(Null n) {
+ n as T;
+}
+''', [
+ error(HintCode.CAST_FROM_NULL_ALWAYS_FAILS, 37, 6),
+ ]);
+ }
+
+ test_Null_nullable() async {
+ await assertErrorsInCode('''
+void f(Null n) {
+ n as int?;
+}
+''', [
+ error(HintCode.UNNECESSARY_CAST, 19, 9),
+ ]);
+ }
+
+ test_Null_nullableTypeVariable() async {
+ await assertNoErrorsInCode('''
+void f<T>(Null n) {
+ n as T;
+}
+''');
+ }
+
+ test_Null_preNullSafety() async {
+ await assertErrorsInCode('''
+// @dart=2.9
+
+void f(Null n) {
+ n as int;
+}
+''', [
+ error(HintCode.UNNECESSARY_CAST, 33, 8),
+ ]);
+ }
+
+ test_nullable_nonNullable() async {
+ await assertNoErrorsInCode('''
+void f(int? n) {
+ n as int;
+}
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/null_check_always_fails_test.dart b/pkg/analyzer/test/src/diagnostics/null_check_always_fails_test.dart
index 5f9267e..34b404d 100644
--- a/pkg/analyzer/test/src/diagnostics/null_check_always_fails_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/null_check_always_fails_test.dart
@@ -64,4 +64,12 @@
error(HintCode.NULL_CHECK_ALWAYS_FAILS, 19, 12),
]);
}
+
+ test_potentiallyNullableTypeVariable() async {
+ await assertNoErrorsInCode(r'''
+void f<T>(T i) {
+ i!;
+}
+''');
+ }
}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 5d6968b..e9cdc0e 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -60,6 +60,7 @@
as case_expression_type_implements_equals;
import 'case_expression_type_is_not_switch_expression_subtype_test.dart'
as case_expression_type_is_not_switch_expression_subtype;
+import 'cast_from_null_always_fails_test.dart' as cast_from_null_always_fails;
import 'cast_to_non_type_test.dart' as cast_to_non_type;
import 'class_instantiation_access_to_member_test.dart'
as class_instantiation_access_to_member;
@@ -843,6 +844,7 @@
case_block_not_terminated.main();
case_expression_type_implements_equals.main();
case_expression_type_is_not_switch_expression_subtype.main();
+ cast_from_null_always_fails.main();
cast_to_non_type.main();
class_instantiation_access_to_member.main();
concrete_class_has_enum_superinterface.main();
diff --git a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
index b4cce46..25d9dfb 100644
--- a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
@@ -19,12 +19,14 @@
@reflectiveTest
class InvalidUseOfNullValueTest extends PubPackageResolutionTest {
test_as() async {
- await assertNoErrorsInCode(r'''
+ await assertErrorsInCode(r'''
m() {
Null x;
x as int;
}
-''');
+''', [
+ error(HintCode.CAST_FROM_NULL_ALWAYS_FAILS, 18, 8),
+ ]);
}
test_await() async {