From 253e715f838a859b15a15d7fa4ec7c59ad8f933b Mon Sep 17 00:00:00 2001 From: Chloe Stefantsova Date: Fri, 25 Oct 2024 10:08:04 +0000 Subject: [PATCH] [analyzer] Report warnings on expressions with non-nullable types Part of https://github.com/dart-lang/sdk/issues/56836 Change-Id: I3fcdceff667f371fcf4b66c12bd16e2f34e6446c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391621 Reviewed-by: Keerti Parthasarathy Reviewed-by: Erik Ernst Commit-Queue: Chloe Stefantsova --- .../services/correction/error_fix_status.yaml | 6 + pkg/analyzer/lib/src/error/codes.g.dart | 31 ++++ .../lib/src/error/error_code_values.g.dart | 3 + .../lib/src/generated/error_verifier.dart | 47 +++++- pkg/analyzer/messages.yaml | 26 ++++ ...nvalid_null_aware_elements_error_test.dart | 53 +++++++ ...re_elements_const_literals_error_test.dart | 144 +++++++++--------- .../test/src/diagnostics/test_all.dart | 3 + pkg/analyzer/tool/diagnostics/diagnostics.md | 16 ++ pkg/test_runner/lib/src/command_output.dart | 1 + .../const_literals_error_test.dart | 22 +++ .../flow_analysis_test.dart | 5 + .../type_inference_simple_error_test.dart | 24 +++ .../warnings_simple_test.dart | 38 +++++ 14 files changed, 343 insertions(+), 76 deletions(-) create mode 100644 pkg/analyzer/test/src/diagnostics/invalid_null_aware_elements_error_test.dart create mode 100644 tests/language/null_aware_elements/warnings_simple_test.dart 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 741ce0eca650..354bb585d022 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 @@ -3375,6 +3375,12 @@ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR: status: hasFix StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT: status: hasFix +StaticWarningCode.INVALID_NULL_AWARE_ELEMENT: + status: needsEvaluation +StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY: + status: needsEvaluation +StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE: + status: needsEvaluation StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH: status: hasFix StaticWarningCode.UNNECESSARY_NON_NULL_ASSERTION: diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index ec1258260812..5cd75ffa9807 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart @@ -6070,6 +6070,37 @@ class StaticWarningCode extends AnalyzerErrorCode { hasPublishedDocs: true, ); + /// No parameters. + static const StaticWarningCode INVALID_NULL_AWARE_ELEMENT = StaticWarningCode( + 'INVALID_NULL_AWARE_OPERATOR', + "The element can't be null, so the null-aware operator '?' is unnecessary.", + correctionMessage: "Try removing the operator '?'.", + hasPublishedDocs: true, + uniqueName: 'INVALID_NULL_AWARE_ELEMENT', + ); + + /// No parameters. + static const StaticWarningCode INVALID_NULL_AWARE_MAP_ENTRY_KEY = + StaticWarningCode( + 'INVALID_NULL_AWARE_OPERATOR', + "The map entry key can't be null, so the null-aware operator '?' is " + "unnecessary.", + correctionMessage: "Try removing the operator '?'.", + hasPublishedDocs: true, + uniqueName: 'INVALID_NULL_AWARE_MAP_ENTRY_KEY', + ); + + /// No parameters. + static const StaticWarningCode INVALID_NULL_AWARE_MAP_ENTRY_VALUE = + StaticWarningCode( + 'INVALID_NULL_AWARE_OPERATOR', + "The map entry value can't be null, so the null-aware operator '?' is " + "unnecessary.", + correctionMessage: "Try removing the operator '?'.", + hasPublishedDocs: true, + uniqueName: 'INVALID_NULL_AWARE_MAP_ENTRY_VALUE', + ); + /// Parameters: /// 0: the null-aware operator that is invalid /// 1: the non-null-aware operator that can replace the invalid operator diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart index 407af9ca1868..249399bee4a3 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -958,6 +958,9 @@ const List errorCodeValues = [ ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT, ScannerErrorCode.UNTERMINATED_STRING_LITERAL, StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, + StaticWarningCode.INVALID_NULL_AWARE_ELEMENT, + StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY, + StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE, StaticWarningCode.INVALID_NULL_AWARE_OPERATOR, StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT, StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index 12060a5b7396..6385d3ae0110 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart @@ -1157,6 +1157,21 @@ class ErrorVerifier extends RecursiveAstVisitor super.visitListLiteral(node); } + @override + void visitMapLiteralEntry(MapLiteralEntry node) { + if (node.keyQuestion != null) { + _checkForUnnecessaryNullAware(node.key, node.keyQuestion!, + nullAwareElementOrMapEntryKind: + _NullAwareElementOrMapEntryKind.mapEntryKey); + } + if (node.valueQuestion != null) { + _checkForUnnecessaryNullAware(node.value, node.valueQuestion!, + nullAwareElementOrMapEntryKind: + _NullAwareElementOrMapEntryKind.mapEntryValue); + } + super.visitMapLiteralEntry(node); + } + @override void visitMethodDeclaration(covariant MethodDeclarationImpl node) { var element = node.declaredElement!; @@ -1296,6 +1311,14 @@ class ErrorVerifier extends RecursiveAstVisitor super.visitNativeFunctionBody(node); } + @override + void visitNullAwareElement(NullAwareElement node) { + _checkForUnnecessaryNullAware(node.value, node.question, + nullAwareElementOrMapEntryKind: + _NullAwareElementOrMapEntryKind.element); + super.visitNullAwareElement(node); + } + @override void visitPatternVariableDeclarationStatement( covariant PatternVariableDeclarationStatementImpl node, @@ -5551,7 +5574,8 @@ class ErrorVerifier extends RecursiveAstVisitor } } - void _checkForUnnecessaryNullAware(Expression target, Token operator) { + void _checkForUnnecessaryNullAware(Expression target, Token operator, + {_NullAwareElementOrMapEntryKind? nullAwareElementOrMapEntryKind}) { if (target is SuperExpression) { return; } @@ -5560,9 +5584,20 @@ class ErrorVerifier extends RecursiveAstVisitor Token endToken = operator; List arguments = const []; if (operator.type == TokenType.QUESTION) { - errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR; - endToken = operator.next!; - arguments = ['?[', '[']; + if (nullAwareElementOrMapEntryKind == null) { + errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR; + endToken = operator.next!; + arguments = ['?[', '[']; + } else { + switch (nullAwareElementOrMapEntryKind) { + case _NullAwareElementOrMapEntryKind.element: + errorCode = StaticWarningCode.INVALID_NULL_AWARE_ELEMENT; + case _NullAwareElementOrMapEntryKind.mapEntryKey: + errorCode = StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY; + case _NullAwareElementOrMapEntryKind.mapEntryValue: + errorCode = StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE; + } + } } else if (operator.type == TokenType.QUESTION_PERIOD) { errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR; arguments = [operator.lexeme, '.']; @@ -7164,6 +7199,10 @@ class _MacroTypeAnnotationLocationConverter { } } +/// Signals the kind of the null-aware element or entry observed in list, set, +/// or map literals. +enum _NullAwareElementOrMapEntryKind { element, mapEntryKey, mapEntryValue } + /// Recursively visits a type annotation, looking uninstantiated bounds. class _UninstantiatedBoundChecker extends RecursiveAstVisitor { final ErrorReporter _errorReporter; diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index a2edd39ecb84..4347c317d112 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -22673,6 +22673,14 @@ StaticWarningCode: has the value `null`, the cast will fail and the invocation of `length` will not happen. + The following code produces this diagnostic because `s` can't be `null`: + + ```dart + List makeSingletonList(String s) { + return [[!?!]s]; + } + ``` + #### Common fixes Replace the null-aware operator with a non-null-aware equivalent; for @@ -22695,6 +22703,24 @@ StaticWarningCode: Parameters: 0: the null-aware operator that is invalid 1: the non-null-aware operator that can replace the invalid operator + INVALID_NULL_AWARE_ELEMENT: + sharedName: INVALID_NULL_AWARE_OPERATOR + problemMessage: "The element can't be null, so the null-aware operator '?' is unnecessary." + correctionMessage: "Try removing the operator '?'." + hasPublishedDocs: true + comment: No parameters. + INVALID_NULL_AWARE_MAP_ENTRY_KEY: + sharedName: INVALID_NULL_AWARE_OPERATOR + problemMessage: "The map entry key can't be null, so the null-aware operator '?' is unnecessary." + correctionMessage: "Try removing the operator '?'." + hasPublishedDocs: true + comment: No parameters. + INVALID_NULL_AWARE_MAP_ENTRY_VALUE: + sharedName: INVALID_NULL_AWARE_OPERATOR + problemMessage: "The map entry value can't be null, so the null-aware operator '?' is unnecessary." + correctionMessage: "Try removing the operator '?'." + hasPublishedDocs: true + comment: No parameters. MISSING_ENUM_CONSTANT_IN_SWITCH: problemMessage: "Missing case clause for '{0}'." correctionMessage: Try adding a case clause for the missing constant, or adding a default clause. diff --git a/pkg/analyzer/test/src/diagnostics/invalid_null_aware_elements_error_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_elements_error_test.dart new file mode 100644 index 000000000000..75abfb3c319d --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_elements_error_test.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2024, 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(InvalidNullAwareElementsErrorTest); + }); +} + +@reflectiveTest +class InvalidNullAwareElementsErrorTest extends PubPackageResolutionTest { + test_invalid_null_aware_element_in_list() async { + await assertErrorsInCode(''' +const stringConst = ""; +const list = [0, ?stringConst]; +''', [ + error(StaticWarningCode.INVALID_NULL_AWARE_ELEMENT, 41, 1), + ]); + } + + test_invalid_null_aware_element_in_set() async { + await assertErrorsInCode(''' +const stringConst = ""; +const set = {0, ?stringConst}; +''', [ + error(StaticWarningCode.INVALID_NULL_AWARE_ELEMENT, 40, 1), + ]); + } + + test_invalid_null_aware_key_in_map() async { + await assertErrorsInCode(''' +const intConst = 0; +const map = {?0: intConst}; +''', [ + error(StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY, 33, 1), + ]); + } + + test_invalid_null_aware_value_in_map() async { + await assertErrorsInCode(''' +const intConst = 0; +const map = {0: ?intConst}; +''', [ + error(StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE, 36, 1), + ]); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/null_aware_elements_const_literals_error_test.dart b/pkg/analyzer/test/src/diagnostics/null_aware_elements_const_literals_error_test.dart index 8df61f8e3dc2..985fbe53ecc2 100644 --- a/pkg/analyzer/test/src/diagnostics/null_aware_elements_const_literals_error_test.dart +++ b/pkg/analyzer/test/src/diagnostics/null_aware_elements_const_literals_error_test.dart @@ -17,105 +17,105 @@ main() { class NullAwareElementsConstLiteralsErrorTest extends PubPackageResolutionTest { test_duplicated_in_key_named_null_aware_key_in_map() async { await assertErrorsInCode(''' -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const map = {intConst: null, 0: ?intConst, null: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 73, 1, - contextMessages: [message(testFile, 57, 8)]), + error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 86, 1, + contextMessages: [message(testFile, 70, 8)]), ]); } test_duplicated_int_in_set() async { await assertErrorsInCode(''' -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const set = {0, ?intConst, stringConst}; ''', [ - error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 61, 8, - contextMessages: [message(testFile, 57, 1)]), + error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 74, 8, + contextMessages: [message(testFile, 70, 1)]), ]); } test_duplicated_int_key_null_aware_key_in_map() async { await assertErrorsInCode(''' -const intConst = 0; -const stringConst = ""; -const map = {intConst: null, ?0: intConst, null: 1, stringConst: 1}; +const int? intConst = 0; +const String? stringConst = ""; +const map = {intConst: null, ?(0 as int?): intConst, null: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 74, 1, - contextMessages: [message(testFile, 57, 8)]), + error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 87, 11, + contextMessages: [message(testFile, 70, 8)]), ]); } test_duplicated_null_in_set() async { await assertErrorsInCode(''' -const nullConst = null; -const intConst = 0; -const stringConst = ""; +const int? nullConst = null; +const int? intConst = 0; +const String? stringConst = ""; const set = {nullConst, null, intConst, stringConst}; ''', [ - error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 92, 4, - contextMessages: [message(testFile, 81, 9)]), + error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 110, 4, + contextMessages: [message(testFile, 99, 9)]), ]); } test_duplicated_null_key_in_map() async { await assertErrorsInCode(''' const nullConst = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const map = {null: 1, nullConst: 1, intConst: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 90, 9, - contextMessages: [message(testFile, 81, 4)]), + error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 103, 9, + contextMessages: [message(testFile, 94, 4)]), ]); } test_duplicated_null_key_null_aware_value_in_map() async { await assertErrorsInCode(''' const nullConst = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const map = {null: 1, nullConst: ?intConst, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 90, 9, - contextMessages: [message(testFile, 81, 4)]), + error(CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP, 103, 9, + contextMessages: [message(testFile, 94, 4)]), ]); } test_duplicated_string_in_set() async { await assertErrorsInCode(''' -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const set = {null, intConst, "", ?stringConst}; ''', [ - error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 78, 11, - contextMessages: [message(testFile, 73, 2)]), + error(CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET, 91, 11, + contextMessages: [message(testFile, 86, 2)]), ]); } test_non_const_and_null_under_question_in_list() async { await assertErrorsInCode(''' var nullVar = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const list = [?null, ?nullVar, intConst, stringConst]; ''', [ - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 86, + error(CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT, 99, 7), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 99, 7), - error(CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT, 86, 7), ]); } test_non_const_in_key_under_question_in_map() async { await assertErrorsInCode(''' -const stringConst = ""; -var intVar = 0; +const String? stringConst = ""; +int? intVar = 0; const map = {null: 1, ?intVar: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 63, 6), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 63, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 72, 6), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 72, 6), ]); } @@ -123,24 +123,24 @@ const map = {null: 1, ?intVar: 1, stringConst: 1}; test_non_const_int_under_question_in_set() async { await assertErrorsInCode(''' const nullConst = null; -const stringConst = ""; -var intVar = 0; +const String? stringConst = ""; +int? intVar = 0; const set = {nullConst, ?intVar, stringConst}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 89, 6), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 89, + error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 98, 6), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 98, 6), ]); } test_non_const_int_value_under_question_map() async { await assertErrorsInCode(''' -const stringConst = ""; -var intVar = 0; +const String? stringConst = ""; +int? intVar = 0; const map = {null: 1, 0: ?intVar, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 66, 6), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 66, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 75, 6), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 75, 6), ]); } @@ -148,12 +148,12 @@ const map = {null: 1, 0: ?intVar, stringConst: 1}; test_non_const_null_key_under_question_in_map() async { await assertErrorsInCode(''' var nullVar = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const map = {?nullVar: 1, intConst: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 78, 7), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 78, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 91, 7), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 91, 7), ]); } @@ -161,12 +161,12 @@ const map = {?nullVar: 1, intConst: 1, stringConst: 1}; test_non_const_null_under_question_in_set() async { await assertErrorsInCode(''' var nullVar = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const set = {?nullVar, intConst, stringConst}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 78, 7), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 78, + error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 91, 7), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 91, 7), ]); } @@ -174,23 +174,23 @@ const set = {?nullVar, intConst, stringConst}; test_non_const_null_value_under_question_in_map() async { await assertErrorsInCode(''' var nullVar = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const map = {null: ?nullVar, intConst: 1, stringConst: 1}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 84, 7), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 84, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 97, 7), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 97, 7), ]); } test_non_const_string_key_under_question_in_map() async { await assertErrorsInCode(''' -var stringVar = ""; +String? stringVar = ""; const map = {null: 1, 0: 1, ?stringVar: 1}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 49, 9), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 49, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, 53, 9), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 53, 9), ]); } @@ -198,23 +198,23 @@ const map = {null: 1, 0: 1, ?stringVar: 1}; test_non_const_string_under_question_in_set() async { await assertErrorsInCode(''' const nullConst = null; -const intConst = 0; -var stringVar = ""; +const int? intConst = 0; +String? stringVar = ""; const set = {nullConst, intConst, ?stringVar}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 99, 9), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 99, + error(CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, 108, 9), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 108, 9), ]); } test_non_const_string_value_under_question_in_map() async { await assertErrorsInCode(''' -var stringVar = ""; +String? stringVar = ""; const map = {null: 1, 0: 1, "": ?stringVar}; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 53, 9), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 53, + error(CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, 57, 9), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 57, 9), ]); } @@ -222,12 +222,12 @@ const map = {null: 1, 0: 1, "": ?stringVar}; test_non_const_under_question_in_list() async { await assertErrorsInCode(''' var nullVar = null; -const intConst = 0; -const stringConst = ""; +const int? intConst = 0; +const String? stringConst = ""; const list = [?nullVar, intConst, stringConst]; ''', [ - error(CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT, 79, 7), - error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 79, + error(CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT, 92, 7), + error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 92, 7), ]); } diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index 29c80870cb3f..324969bb59fc 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -451,6 +451,8 @@ import 'invalid_modifier_on_constructor_test.dart' import 'invalid_modifier_on_setter_test.dart' as invalid_modifier_on_setter; import 'invalid_non_virtual_annotation_test.dart' as invalid_non_virtual_annotation; +import 'invalid_null_aware_elements_error_test.dart' + as invalid_null_aware_elements_error; import 'invalid_null_aware_operator_test.dart' as invalid_null_aware_operator; import 'invalid_override_of_non_virtual_member_test.dart' as invalid_override_of_non_virtual_member; @@ -1215,6 +1217,7 @@ main() { invalid_modifier_on_constructor.main(); invalid_modifier_on_setter.main(); invalid_non_virtual_annotation.main(); + invalid_null_aware_elements_error.main(); invalid_null_aware_operator.main(); invalid_override_of_non_virtual_member.main(); invalid_override.main(); diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md index ff8791244ab2..fe3137dbcf5b 100644 --- a/pkg/analyzer/tool/diagnostics/diagnostics.md +++ b/pkg/analyzer/tool/diagnostics/diagnostics.md @@ -10709,6 +10709,14 @@ abstract class C { ### invalid_null_aware_operator +_The element can't be null, so the null-aware operator '?' is unnecessary._ + +_The map entry key can't be null, so the null-aware operator '?' is +unnecessary._ + +_The map entry value can't be null, so the null-aware operator '?' is +unnecessary._ + _The receiver can't be 'null' because of short-circuiting, so the null-aware operator '{0}' can't be used._ @@ -10769,6 +10777,14 @@ because of the cast to `String`, which is a non-nullable type. If `o` ever has the value `null`, the cast will fail and the invocation of `length` will not happen. +The following code produces this diagnostic because `s` can't be `null`: + +```dart +List makeSingletonList(String s) { + return [[!?!]s]; +} +``` + #### Common fixes Replace the null-aware operator with a non-null-aware equivalent; for diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart index fac66a1b7a49..c26f46c64e99 100644 --- a/pkg/test_runner/lib/src/command_output.dart +++ b/pkg/test_runner/lib/src/command_output.dart @@ -439,6 +439,7 @@ class AnalyzerError implements Comparable { static const Set _specifiedWarnings = { 'dead_null_aware_expression', 'invalid_null_aware_operator', + 'invalid_null_aware_element_or_map_entry', 'missing_enum_constant_in_switch', 'unnecessary_non_null_assertion', 'unnecessary_null_assert_pattern', diff --git a/tests/language/null_aware_elements/const_literals_error_test.dart b/tests/language/null_aware_elements/const_literals_error_test.dart index 34a5278bc31a..7d56396631d6 100644 --- a/tests/language/null_aware_elements/const_literals_error_test.dart +++ b/tests/language/null_aware_elements/const_literals_error_test.dart @@ -44,12 +44,16 @@ const set1 = {nullConst, null, intConst, stringConst}; const set2 = {0, ?intConst, stringConst}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.EQUAL_ELEMENTS_IN_CONST_SET const set3 = {null, intConst, "", ?stringConst}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.EQUAL_ELEMENTS_IN_CONST_SET @@ -64,6 +68,8 @@ const set4 = {?nullVar, intConst, stringConst}; const set5 = {nullConst, ?intVar, stringConst}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_SET_ELEMENT @@ -72,6 +78,8 @@ const set5 = {nullConst, ?intVar, stringConst}; const set6 = {nullConst, intConst, ?stringVar}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_SET_ELEMENT @@ -102,6 +110,8 @@ const map3 = {null: ?nullVar, intConst: 1, stringConst: 1}; const map4 = {null: 1, ?intVar: 1, stringConst: 1}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_MAP_KEY @@ -110,6 +120,8 @@ const map4 = {null: 1, ?intVar: 1, stringConst: 1}; const map5 = {null: 1, 0: ?intVar, stringConst: 1}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_MAP_VALUE @@ -118,6 +130,8 @@ const map5 = {null: 1, 0: ?intVar, stringConst: 1}; const map6 = {null: 1, 0: 1, ?stringVar: 1}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_MAP_KEY @@ -126,6 +140,8 @@ const map6 = {null: 1, 0: 1, ?stringVar: 1}; const map7 = {null: 1, 0: 1, "": ?stringVar}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE // [analyzer] COMPILE_TIME_ERROR.NON_CONSTANT_MAP_VALUE @@ -136,10 +152,14 @@ const map8 = {null: 1, nullConst: ?intConst, stringConst: 1}; // [cfe] Constant evaluation error: // ^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.EQUAL_KEYS_IN_CONST_MAP +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR const map9 = {intConst: null, ?0: intConst, null: 1, stringConst: 1}; // ^ // [cfe] Constant evaluation error: +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^ // [analyzer] COMPILE_TIME_ERROR.EQUAL_KEYS_IN_CONST_MAP @@ -148,5 +168,7 @@ const map10 = {intConst: null, 0: ?intConst, null: 1, stringConst: 1}; // [cfe] Constant evaluation error: // ^ // [analyzer] COMPILE_TIME_ERROR.EQUAL_KEYS_IN_CONST_MAP +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR main() {} diff --git a/tests/language/null_aware_elements/flow_analysis_test.dart b/tests/language/null_aware_elements/flow_analysis_test.dart index 4aaa4bbb5fa5..5fca9775e640 100644 --- a/tests/language/null_aware_elements/flow_analysis_test.dart +++ b/tests/language/null_aware_elements/flow_analysis_test.dart @@ -8,6 +8,8 @@ import '../static_type_helper.dart'; test1(String a, num x) { {?a: (x as int)}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR x..expectStaticType>(); } @@ -21,6 +23,9 @@ test3(String a, bool b, num x) { x as int; } else { {?a: (throw 0)}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR + // Unreachable. } x..expectStaticType>(); diff --git a/tests/language/null_aware_elements/type_inference_simple_error_test.dart b/tests/language/null_aware_elements/type_inference_simple_error_test.dart index 7b1f40319545..b414c2c1c059 100644 --- a/tests/language/null_aware_elements/type_inference_simple_error_test.dart +++ b/tests/language/null_aware_elements/type_inference_simple_error_test.dart @@ -10,8 +10,12 @@ String? stringQuestion() => null; main() { [?""]; // Ok. + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR [?""]; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^ // [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE // ^ @@ -42,6 +46,8 @@ main() { // [cfe] Expected ']' before this. [?"": 0]; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^ // [analyzer] SYNTACTIC_ERROR.EXPECTED_TOKEN // [cfe] Expected ']' before this. @@ -52,8 +58,12 @@ main() { // [cfe] Expected ']' before this. {?""}; // Ok. + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR {?""}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^ // [analyzer] COMPILE_TIME_ERROR.SET_ELEMENT_TYPE_NOT_ASSIGNABLE // ^ @@ -72,6 +82,8 @@ main() { // [analyzer] COMPILE_TIME_ERROR.MAP_ENTRY_NOT_IN_MAP // ^ // [cfe] Expected ',' before this. + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR {0: ?stringQuestion()}; // ^^^^^^^^^^^^^^^^^^^^ @@ -80,6 +92,8 @@ main() { // [cfe] Expected ',' before this. {?"": 0}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^^^^ // [analyzer] COMPILE_TIME_ERROR.MAP_ENTRY_NOT_IN_MAP // ^ @@ -92,8 +106,12 @@ main() { // [cfe] Expected ',' before this. {?"": #foo}; // Ok. + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR {?"": #foo}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^ // [analyzer] COMPILE_TIME_ERROR.MAP_KEY_TYPE_NOT_ASSIGNABLE // [cfe] A value of type 'String' can't be assigned to a variable of type 'num?'. @@ -106,8 +124,12 @@ main() { // [cfe] A value of type 'String?' can't be assigned to a variable of type 'num?'. {0: ?""}; // Ok. + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR {0: ?""}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^ // [analyzer] COMPILE_TIME_ERROR.MAP_VALUE_TYPE_NOT_ASSIGNABLE // [cfe] A value of type 'String' can't be assigned to a variable of type 'num?'. @@ -120,6 +142,8 @@ main() { // [cfe] A value of type 'String?' can't be assigned to a variable of type 'num?'. {0: "", ?""}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR // ^^^ // [analyzer] COMPILE_TIME_ERROR.EXPRESSION_IN_MAP // [cfe] Expected ':' after this. diff --git a/tests/language/null_aware_elements/warnings_simple_test.dart b/tests/language/null_aware_elements/warnings_simple_test.dart new file mode 100644 index 000000000000..42ec7203b38e --- /dev/null +++ b/tests/language/null_aware_elements/warnings_simple_test.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2024, 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. + +// SharedOptions=--enable-experiment=null-aware-elements + +import 'package:expect/expect.dart'; + +List testList(int x) { + return [?x]; +// ^ +// [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR +} + +Set testSet(int x) { + return {?x}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR +} + +Map testMapKey(int x) { + return {?x: ""}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR +} + +Map testMapValue(int x) { + return {"": ?x}; + // ^ + // [analyzer] STATIC_WARNING.INVALID_NULL_AWARE_OPERATOR +} + +main() { + Expect.listEquals(testList(0), [0]); + Expect.setEquals(testSet(0), {0}); + Expect.mapEquals(testMapKey(0), {0: ""}); + Expect.mapEquals(testMapValue(0), {"": 0}); +}