[analyzer] Add correction for adding '?' before null-aware elements
Part of https://github.com/dart-lang/sdk/issues/56989
Change-Id: I487790e796a37679fd5fe1c9bc1e72ccb6248249
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405520
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_list_element.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_list_element.dart
new file mode 100644
index 0000000..523af3d
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_list_element.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class ConvertToNullAwareListElement extends ResolvedCorrectionProducer {
+ ConvertToNullAwareListElement({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ // TODO(applicability): comment on why.
+ CorrectionApplicability
+ .singleLocation;
+
+ @override
+ FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_LIST_ELEMENT;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var parent = coveringNode?.parent;
+ if (parent is ListLiteral && coveringNode is! NullAwareElement) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(coveringNode!.offset, '?');
+ });
+ }
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_map_entry.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_map_entry.dart
new file mode 100644
index 0000000..bda7c33
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_map_entry.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class ConvertToNullAwareMapEntryKey extends ResolvedCorrectionProducer {
+ ConvertToNullAwareMapEntryKey({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ // TODO(applicability): comment on why.
+ CorrectionApplicability
+ .singleLocation;
+
+ @override
+ FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_KEY;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var parent = coveringNode?.parent;
+ if (parent is MapLiteralEntry &&
+ coveringNode == parent.key &&
+ parent.keyQuestion == null) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(coveringNode!.offset, '?');
+ });
+ }
+ }
+}
+
+class ConvertToNullAwareMapEntryValue extends ResolvedCorrectionProducer {
+ ConvertToNullAwareMapEntryValue({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ // TODO(applicability): comment on why.
+ CorrectionApplicability
+ .singleLocation;
+
+ @override
+ FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_VALUE;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var parent = coveringNode?.parent;
+ if (parent is MapLiteralEntry &&
+ coveringNode == parent.value &&
+ parent.valueQuestion == null) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(coveringNode!.offset, '?');
+ });
+ }
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_set_element.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_set_element.dart
new file mode 100644
index 0000000..65a9944
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_null_aware_set_element.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class ConvertToNullAwareSetElement extends ResolvedCorrectionProducer {
+ ConvertToNullAwareSetElement({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ // TODO(applicability): comment on why.
+ CorrectionApplicability
+ .singleLocation;
+
+ @override
+ FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE_SET_ELEMENT;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var parent = coveringNode?.parent;
+ if (parent is SetOrMapLiteral &&
+ parent.isSet &&
+ coveringNode is! NullAwareElement) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(coveringNode!.offset, '?');
+ });
+ }
+ }
+}
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 e12691b..125dc93 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
@@ -1019,8 +1019,10 @@
Remove `const`.
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED:
status: hasFix
+CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ status: hasFix
CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE:
- status: noFix
+ status: needsEvaluation
CompileTimeErrorCode.MACRO_DECLARATIONS_PHASE_INTROSPECTION_CYCLE:
status: needsEvaluation
CompileTimeErrorCode.MACRO_DEFINITION_APPLICATION_SAME_LIBRARY_CYCLE:
@@ -1049,8 +1051,12 @@
status: noFix
CompileTimeErrorCode.MAP_ENTRY_NOT_IN_MAP:
status: noFix
+CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ status: hasFix
CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE:
status: noFix
+CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ status: hasFix
CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE:
status: noFix
CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL:
@@ -1444,6 +1450,8 @@
status: needsFix
notes: |-
Remove the `deferred` keyword from the import.
+CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ status: hasFix
CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE:
status: noFix
CompileTimeErrorCode.SHARED_DEFERRED_PREFIX:
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index d7932f9..ae23d25 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -577,6 +577,26 @@
DartFixKindPriority.standard,
"Convert to use '?.'",
);
+ static const CONVERT_TO_NULL_AWARE_LIST_ELEMENT = FixKind(
+ 'dart.fix.convert.toNullAwareListElement',
+ DartFixKindPriority.standard,
+ "Convert to use '?'",
+ );
+ static const CONVERT_TO_NULL_AWARE_MAP_ENTRY_KEY = FixKind(
+ 'dart.fix.convert.toNullAwareMapEntryKey',
+ DartFixKindPriority.standard,
+ "Convert to use '?'",
+ );
+ static const CONVERT_TO_NULL_AWARE_MAP_ENTRY_VALUE = FixKind(
+ 'dart.fix.convert.toNullAwareMayEntryValue',
+ DartFixKindPriority.standard,
+ "Convert to use '?'",
+ );
+ static const CONVERT_TO_NULL_AWARE_SET_ELEMENT = FixKind(
+ 'dart.fix.convert.toNullAwareSetElement',
+ DartFixKindPriority.standard,
+ "Convert to use '?'",
+ );
static const CONVERT_TO_NULL_AWARE_MULTI = FixKind(
'dart.fix.convert.toNullAware.multi',
DartFixKindPriority.inFile,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index e6fd1f8..3043784 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -69,6 +69,9 @@
import 'package:analysis_server/src/services/correction/dart/convert_to_map_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_named_arguments.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_list_element.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_map_entry.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_set_element.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware_spread.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_on_type.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_package_import.dart';
@@ -848,6 +851,15 @@
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED: [
MakeVariableNotFinal.new,
],
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
+ ConvertToNullAwareListElement.new,
+ ],
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
+ ConvertToNullAwareMapEntryKey.new,
+ ],
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
+ ConvertToNullAwareMapEntryValue.new,
+ ],
CompileTimeErrorCode.MISSING_DEFAULT_VALUE_FOR_PARAMETER: [
AddRequiredKeyword.new,
MakeVariableNullable.new,
@@ -971,6 +983,9 @@
MakeReturnTypeNullable.new,
ReplaceReturnType.new,
],
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY: [
+ ConvertToNullAwareSetElement.new,
+ ],
CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED: [
AddClassModifier.baseModifier,
AddClassModifier.finalModifier,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_list_element_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_list_element_test.dart
new file mode 100644
index 0000000..8bddb9b
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_list_element_test.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ConvertToNullAwareListElementTest);
+ });
+}
+
+@reflectiveTest
+class ConvertToNullAwareListElementTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CONVERT_TO_NULL_AWARE_LIST_ELEMENT;
+
+ Future<void> test_const_list_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f () {
+ const <String>[0];
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_const_list_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f () {
+ const String? s = null;
+ const <String>[s];
+}
+''');
+ await assertHasFix('''
+void f () {
+ const String? s = null;
+ const <String>[?s];
+}
+''', errorFilter: (error) => error.message.contains('String?'));
+ }
+
+ Future<void> test_nonConst_list_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f (int arg) {
+ <String>[arg];
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_nonConst_list_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f (String? arg) {
+ <String>[arg];
+}
+''');
+ await assertHasFix('''
+void f (String? arg) {
+ <String>[?arg];
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_map_entry_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_map_entry_test.dart
new file mode 100644
index 0000000..a19bc9e
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_map_entry_test.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ConvertToNullAwareMapEntryKeyTest);
+ defineReflectiveTests(ConvertToNullAwareMapEntryValueTest);
+ });
+}
+
+@reflectiveTest
+class ConvertToNullAwareMapEntryKeyTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_KEY;
+
+ Future<void> test_const_mapKey_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f () {
+ const <String, Symbol>{0: #value};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_const_mapKey_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f () {
+ const String? s = null;
+ const <String, double>{s: 0.1};
+}
+''');
+ await assertHasFix('''
+void f () {
+ const String? s = null;
+ const <String, double>{?s: 0.1};
+}
+''', errorFilter: (error) => error.message.contains('String?'));
+ }
+
+ Future<void> test_nonConst_mapKey_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f (int arg) {
+ <String, bool>{arg: true};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_nonConst_mapKey_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f (String? arg) {
+ <String, String>{arg: ""};
+}
+''');
+ await assertHasFix('''
+void f (String? arg) {
+ <String, String>{?arg: ""};
+}
+''');
+ }
+}
+
+@reflectiveTest
+class ConvertToNullAwareMapEntryValueTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CONVERT_TO_NULL_AWARE_MAP_ENTRY_VALUE;
+
+ Future<void> test_const_mapValue_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f () {
+ const <bool, String>{false: 0};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_const_mapValue_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f () {
+ const String? s = null;
+ const <Symbol, String>{#key: s};
+}
+''');
+ await assertHasFix('''
+void f () {
+ const String? s = null;
+ const <Symbol, String>{#key: ?s};
+}
+''', errorFilter: (error) => error.message.contains('String?'));
+ }
+
+ Future<void> test_nonConst_mapValue_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f (int arg) {
+ <int, String>{0: arg};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_nonConst_mapValue_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f (String? arg) {
+ <bool, String>{true: arg};
+}
+''');
+ await assertHasFix('''
+void f (String? arg) {
+ <bool, String>{true: ?arg};
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_set_element_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_set_element_test.dart
new file mode 100644
index 0000000..aa45008
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_null_aware_set_element_test.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2025, 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ConvertToNullAwareSetElementTest);
+ });
+}
+
+@reflectiveTest
+class ConvertToNullAwareSetElementTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.CONVERT_TO_NULL_AWARE_SET_ELEMENT;
+
+ Future<void> test_const_set_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f () {
+ const <String>{0};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_const_set_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f () {
+ const String? s = null;
+ const <String>{s};
+}
+''');
+ await assertHasFix('''
+void f () {
+ const String? s = null;
+ const <String>{?s};
+}
+''', errorFilter: (error) => error.message.contains('String?'));
+ }
+
+ Future<void> test_nonConst_set_withGeneralUnassignable() async {
+ // Check that the fix isn't suggested when the assignability issue can't be
+ // fixed by removing nullability.
+ await resolveTestCode('''
+void f (int arg) {
+ <String>{arg};
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_nonConst_set_withNullabilityUnassignable() async {
+ await resolveTestCode('''
+void f (String? arg) {
+ <String>{arg};
+}
+''');
+ await assertHasFix('''
+void f (String? arg) {
+ <String>{?arg};
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index bc7b2cc..04bf16c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -92,6 +92,12 @@
import 'convert_to_int_literal_test.dart' as convert_to_int_literal;
import 'convert_to_map_literal_test.dart' as convert_to_map_literal;
import 'convert_to_named_arguments_test.dart' as convert_to_named_arguments;
+import 'convert_to_null_aware_list_element_test.dart'
+ as convert_to_null_aware_list_element_test;
+import 'convert_to_null_aware_map_entry_test.dart'
+ as convert_to_null_aware_map_entry_test;
+import 'convert_to_null_aware_set_element_test.dart'
+ as convert_to_null_aware_set_element_test;
import 'convert_to_null_aware_spread_test.dart' as convert_to_null_aware_spread;
import 'convert_to_null_aware_test.dart' as convert_to_null_aware;
import 'convert_to_on_type_test.dart' as convert_to_on_type;
@@ -381,6 +387,9 @@
convert_to_map_literal.main();
convert_to_named_arguments.main();
convert_to_null_aware.main();
+ convert_to_null_aware_list_element_test.main();
+ convert_to_null_aware_map_entry_test.main();
+ convert_to_null_aware_set_element_test.main();
convert_to_null_aware_spread.main();
convert_to_on_type.main();
convert_to_package_import.main();
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index 27223f0..17d775e 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -1229,11 +1229,20 @@
bool _validateListExpression(
DartType listElementType, Expression expression, DartObjectImpl value) {
if (!verifier._runtimeTypeMatch(value, listElementType)) {
- verifier._errorReporter.atNode(
- expression,
- CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
- arguments: [value.type, listElementType],
- );
+ if (verifier._runtimeTypeMatch(
+ value, verifier._typeSystem.makeNullable(listElementType))) {
+ verifier._errorReporter.atNode(
+ expression,
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [value.type, listElementType],
+ );
+ } else {
+ verifier._errorReporter.atNode(
+ expression,
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ arguments: [value.type, listElementType],
+ );
+ }
return false;
}
@@ -1316,11 +1325,21 @@
}
if (!verifier._runtimeTypeMatch(keyValue, expectedKeyType)) {
- verifier._errorReporter.atNode(
- keyExpression,
- CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
- arguments: [keyType, expectedKeyType],
- );
+ if (!isKeyNullAware &&
+ verifier._runtimeTypeMatch(
+ keyValue, verifier._typeSystem.makeNullable(expectedKeyType))) {
+ verifier._errorReporter.atNode(
+ keyExpression,
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [keyType, expectedKeyType],
+ );
+ } else {
+ verifier._errorReporter.atNode(
+ keyExpression,
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
+ arguments: [keyType, expectedKeyType],
+ );
+ }
}
var featureSet = verifier._currentLibrary.featureSet;
@@ -1354,11 +1373,21 @@
}
if (valueValue is DartObjectImpl) {
if (!verifier._runtimeTypeMatch(valueValue, expectedValueType)) {
- verifier._errorReporter.atNode(
- valueExpression,
- CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
- arguments: [valueValue.type, expectedValueType],
- );
+ if (!isValueNullAware &&
+ verifier._runtimeTypeMatch(valueValue,
+ verifier._typeSystem.makeNullable(expectedValueType))) {
+ verifier._errorReporter.atNode(
+ valueExpression,
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [valueValue.type, expectedValueType],
+ );
+ } else {
+ verifier._errorReporter.atNode(
+ valueExpression,
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
+ arguments: [valueValue.type, expectedValueType],
+ );
+ }
}
}
@@ -1402,11 +1431,20 @@
DartObjectImpl value,
) {
if (!verifier._runtimeTypeMatch(value, config.elementType)) {
- verifier._errorReporter.atNode(
- expression,
- CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
- arguments: [value.type, config.elementType],
- );
+ if (verifier._runtimeTypeMatch(
+ value, verifier._typeSystem.makeNullable(config.elementType))) {
+ verifier._errorReporter.atNode(
+ expression,
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [value.type, config.elementType],
+ );
+ } else {
+ verifier._errorReporter.atNode(
+ expression,
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ arguments: [value.type, config.elementType],
+ );
+ }
return false;
}
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart
index 1d62c7f..3f9b5c4 100644
--- a/pkg/analyzer/lib/src/error/codes.g.dart
+++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -3145,6 +3145,17 @@
hasPublishedDocs: true,
);
+ /// Parameters:
+ /// 0: the actual type of the list element
+ /// 1: the expected type of the list element
+ static const CompileTimeErrorCode
+ LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY = CompileTimeErrorCode(
+ 'LIST_ELEMENT_TYPE_NOT_ASSIGNABLE',
+ "The element type '{0}' can't be assigned to the list type '{1}'.",
+ hasPublishedDocs: true,
+ uniqueName: 'LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY',
+ );
+
/// Reported when there is an issue converting a macro application argument
/// into a value. So, we cannot instantiate the macro, and run it.
/// Parameters:
@@ -3270,6 +3281,17 @@
);
/// Parameters:
+ /// 0: the type of the expression being used as a key
+ /// 1: the type of keys declared for the map
+ static const CompileTimeErrorCode MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY =
+ CompileTimeErrorCode(
+ 'MAP_KEY_TYPE_NOT_ASSIGNABLE',
+ "The element type '{0}' can't be assigned to the map key type '{1}'.",
+ hasPublishedDocs: true,
+ uniqueName: 'MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY',
+ );
+
+ /// Parameters:
/// 0: the type of the expression being used as a value
/// 1: the type of values declared for the map
static const CompileTimeErrorCode MAP_VALUE_TYPE_NOT_ASSIGNABLE =
@@ -3279,6 +3301,17 @@
hasPublishedDocs: true,
);
+ /// Parameters:
+ /// 0: the type of the expression being used as a value
+ /// 1: the type of values declared for the map
+ static const CompileTimeErrorCode MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY =
+ CompileTimeErrorCode(
+ 'MAP_VALUE_TYPE_NOT_ASSIGNABLE',
+ "The element type '{0}' can't be assigned to the map value type '{1}'.",
+ hasPublishedDocs: true,
+ uniqueName: 'MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY',
+ );
+
/// 12.1 Constants: A constant expression is ... a constant list literal.
///
/// Note: This diagnostic is never displayed to the user, so it doesn't need
@@ -4988,6 +5021,17 @@
hasPublishedDocs: true,
);
+ /// Parameters:
+ /// 0: the actual type of the set element
+ /// 1: the expected type of the set element
+ static const CompileTimeErrorCode
+ SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY = CompileTimeErrorCode(
+ 'SET_ELEMENT_TYPE_NOT_ASSIGNABLE',
+ "The element type '{0}' can't be assigned to the set type '{1}'.",
+ hasPublishedDocs: true,
+ uniqueName: 'SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY',
+ );
+
/// No parameters.
static const CompileTimeErrorCode SHARED_DEFERRED_PREFIX =
CompileTimeErrorCode(
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 467f750..94fa6ed 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -334,6 +334,7 @@
CompileTimeErrorCode.LATE_FINAL_FIELD_WITH_CONST_CONSTRUCTOR,
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED,
CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
CompileTimeErrorCode.MACRO_APPLICATION_ARGUMENT_ERROR,
CompileTimeErrorCode.MACRO_DECLARATIONS_PHASE_INTROSPECTION_CYCLE,
CompileTimeErrorCode.MACRO_DEFINITION_APPLICATION_SAME_LIBRARY_CYCLE,
@@ -346,7 +347,9 @@
CompileTimeErrorCode.MAIN_IS_NOT_FUNCTION,
CompileTimeErrorCode.MAP_ENTRY_NOT_IN_MAP,
CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY,
CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY,
CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL,
CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL,
CompileTimeErrorCode.MISSING_CONST_IN_SET_LITERAL,
@@ -506,6 +509,7 @@
CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY,
CompileTimeErrorCode.SET_ELEMENT_FROM_DEFERRED_LIBRARY,
CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
CompileTimeErrorCode.SHARED_DEFERRED_PREFIX,
CompileTimeErrorCode.SPREAD_EXPRESSION_FROM_DEFERRED_LIBRARY,
CompileTimeErrorCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
diff --git a/pkg/analyzer/lib/src/error/literal_element_verifier.dart b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
index 9efb88b..46d98d3 100644
--- a/pkg/analyzer/lib/src/error/literal_element_verifier.dart
+++ b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
@@ -56,9 +56,22 @@
if (!typeSystem.isAssignableTo(type, elementType!,
strictCasts: _strictCasts)) {
- var errorCode = forList
- ? CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
- : CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE;
+ bool assignableWhenNullable = typeSystem.isAssignableTo(
+ type, typeSystem.makeNullable(elementType),
+ strictCasts: _strictCasts);
+ var errorCode = switch ((
+ forList: forList,
+ assignableWhenNullable: assignableWhenNullable
+ )) {
+ (forList: false, assignableWhenNullable: false) =>
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ (forList: false, assignableWhenNullable: true) =>
+ CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ (forList: true, assignableWhenNullable: false) =>
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE,
+ (forList: true, assignableWhenNullable: true) =>
+ CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ };
errorReporter.atNode(
errorNode,
errorCode,
@@ -148,11 +161,22 @@
}
if (!typeSystem.isAssignableTo(keyType, mapKeyType,
strictCasts: _strictCasts)) {
- errorReporter.atNode(
- entry.key,
- CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
- arguments: [keyType, mapKeyType],
- );
+ if (entry.keyQuestion == null &&
+ typeSystem.isAssignableTo(
+ keyType, typeSystem.makeNullable(mapKeyType),
+ strictCasts: _strictCasts)) {
+ errorReporter.atNode(
+ entry.key,
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [keyType, mapKeyType],
+ );
+ } else {
+ errorReporter.atNode(
+ entry.key,
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
+ arguments: [keyType, mapKeyType],
+ );
+ }
}
var valueType = entry.value.typeOrThrow;
@@ -163,11 +187,22 @@
}
if (!typeSystem.isAssignableTo(valueType, mapValueType,
strictCasts: _strictCasts)) {
- errorReporter.atNode(
- entry.value,
- CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
- arguments: [valueType, mapValueType],
- );
+ if (entry.valueQuestion == null &&
+ typeSystem.isAssignableTo(
+ valueType, typeSystem.makeNullable(mapValueType),
+ strictCasts: _strictCasts)) {
+ errorReporter.atNode(
+ entry.value,
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ arguments: [valueType, mapValueType],
+ );
+ } else {
+ errorReporter.atNode(
+ entry.value,
+ CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
+ arguments: [valueType, mapValueType],
+ );
+ }
}
}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 926a2ee..3f5c018 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -9625,6 +9625,14 @@
return v;
}
```
+ LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ sharedName: LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
+ problemMessage: "The element type '{0}' can't be assigned to the list type '{1}'."
+ hasPublishedDocs: true
+ comment: |-
+ Parameters:
+ 0: the actual type of the list element
+ 1: the expected type of the list element
LIST_ELEMENT_TYPE_NOT_ASSIGNABLE:
problemMessage: "The element type '{0}' can't be assigned to the list type '{1}'."
hasPublishedDocs: true
@@ -9896,6 +9904,14 @@
```dart
var collection = <String>{'a', 'b'};
```
+ MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ sharedName: MAP_KEY_TYPE_NOT_ASSIGNABLE
+ problemMessage: "The element type '{0}' can't be assigned to the map key type '{1}'."
+ hasPublishedDocs: true
+ comment: |-
+ Parameters:
+ 0: the type of the expression being used as a key
+ 1: the type of keys declared for the map
MAP_KEY_TYPE_NOT_ASSIGNABLE:
problemMessage: "The element type '{0}' can't be assigned to the map key type '{1}'."
hasPublishedDocs: true
@@ -9932,6 +9948,14 @@
```dart
var m = <int, String>{2 : 'a'};
```
+ MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ sharedName: MAP_VALUE_TYPE_NOT_ASSIGNABLE
+ problemMessage: "The element type '{0}' can't be assigned to the map value type '{1}'."
+ hasPublishedDocs: true
+ comment: |-
+ Parameters:
+ 0: the type of the expression being used as a value
+ 1: the type of values declared for the map
MAP_VALUE_TYPE_NOT_ASSIGNABLE:
problemMessage: "The element type '{0}' can't be assigned to the map value type '{1}'."
hasPublishedDocs: true
@@ -15262,6 +15286,14 @@
comment: |-
Parameters:
0: the name of the sealed class being extended, implemented, or mixed in
+ SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY:
+ sharedName: SET_ELEMENT_TYPE_NOT_ASSIGNABLE
+ problemMessage: "The element type '{0}' can't be assigned to the set type '{1}'."
+ hasPublishedDocs: true
+ comment: |-
+ Parameters:
+ 0: the actual type of the set element
+ 1: the expected type of the set element
SET_ELEMENT_TYPE_NOT_ASSIGNABLE:
problemMessage: "The element type '{0}' can't be assigned to the set type '{1}'."
hasPublishedDocs: true
diff --git a/pkg/analyzer/test/generated/strong_mode_test.dart b/pkg/analyzer/test/generated/strong_mode_test.dart
index 1623128..0278ff4 100644
--- a/pkg/analyzer/test/generated/strong_mode_test.dart
+++ b/pkg/analyzer/test/generated/strong_mode_test.dart
@@ -3582,7 +3582,8 @@
List<Object> ddd = [1 as dynamic];
List<Object> eee = [new Object()];
''', [
- error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE, 73, 4),
+ error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 73, 4),
]);
expectInitializerType('aaa', 'List<Object>');
expectInitializerType('bbb', 'List<Object>');
diff --git a/pkg/analyzer/test/src/dart/resolution/type_inference/list_literal_test.dart b/pkg/analyzer/test/src/dart/resolution/type_inference/list_literal_test.dart
index 4f3dfa1..c55c0cf 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_inference/list_literal_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_inference/list_literal_test.dart
@@ -458,6 +458,16 @@
assertType(findNode.listLiteral('['), 'List<String>');
}
+ test_noContext_typeArgs_expression_conflict_nullable() async {
+ await assertErrorsInCode('''
+var a = <String>[(null as String?)];
+''', [
+ error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 17, 17),
+ ]);
+ assertType(findNode.listLiteral('['), 'List<String>');
+ }
+
test_noContext_typeArgs_expression_noConflict() async {
await assertNoErrorsInCode('''
var a = <int>[1];
diff --git a/pkg/analyzer/test/src/diagnostics/list_element_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/list_element_type_not_assignable_test.dart
index da974b0..c6868a1 100644
--- a/pkg/analyzer/test/src/diagnostics/list_element_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/list_element_type_not_assignable_test.dart
@@ -77,7 +77,8 @@
const a = null;
var v = const <int>[a];
''', [
- error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE, 36, 1),
+ error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 36, 1),
]);
}
@@ -85,7 +86,8 @@
await assertErrorsInCode('''
var v = const <int>[null];
''', [
- error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE, 20, 4),
+ error(CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 20, 4),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/map_key_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/map_key_type_not_assignable_test.dart
index fe6dbe3..6e69716 100644
--- a/pkg/analyzer/test/src/diagnostics/map_key_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/map_key_type_not_assignable_test.dart
@@ -86,7 +86,8 @@
const dynamic a = null;
var v = const <int, bool>{a : true};
''', [
- error(CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE, 50, 1),
+ error(
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY, 50, 1),
]);
}
@@ -94,7 +95,8 @@
await assertErrorsInCode('''
var v = const <int, bool>{null : true};
''', [
- error(CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE, 26, 4),
+ error(
+ CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE_NULLABILITY, 26, 4),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/map_value_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/map_value_type_not_assignable_test.dart
index 8780baf..7da431e 100644
--- a/pkg/analyzer/test/src/diagnostics/map_value_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/map_value_type_not_assignable_test.dart
@@ -86,7 +86,8 @@
const dynamic a = null;
var v = const <bool, int>{true: a};
''', [
- error(CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE, 56, 1),
+ error(CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY, 56,
+ 1),
]);
}
@@ -94,7 +95,8 @@
await assertErrorsInCode('''
var v = const <bool, int>{true: null};
''', [
- error(CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE, 32, 4),
+ error(CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE_NULLABILITY, 32,
+ 4),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/set_element_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/set_element_type_not_assignable_test.dart
index 2b95f64..d7be61c 100644
--- a/pkg/analyzer/test/src/diagnostics/set_element_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/set_element_type_not_assignable_test.dart
@@ -83,7 +83,8 @@
const a = null;
var v = const <int>{a};
''', [
- error(CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE, 36, 1),
+ error(CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 36, 1),
]);
}
@@ -91,7 +92,8 @@
await assertErrorsInCode('''
var v = const <int>{null};
''', [
- error(CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE, 20, 4),
+ error(CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE_NULLABILITY,
+ 20, 4),
]);
}