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 {