Skip to content

Commit 253e715

Browse files
chloestefantsovaCommit Queue
authored andcommitted
[analyzer] Report warnings on expressions with non-nullable types
Part of #56836 Change-Id: I3fcdceff667f371fcf4b66c12bd16e2f34e6446c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391621 Reviewed-by: Keerti Parthasarathy <[email protected]> Reviewed-by: Erik Ernst <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent b7aa0fa commit 253e715

File tree

14 files changed

+343
-76
lines changed

14 files changed

+343
-76
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3375,6 +3375,12 @@ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR:
33753375
status: hasFix
33763376
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT:
33773377
status: hasFix
3378+
StaticWarningCode.INVALID_NULL_AWARE_ELEMENT:
3379+
status: needsEvaluation
3380+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY:
3381+
status: needsEvaluation
3382+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE:
3383+
status: needsEvaluation
33783384
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH:
33793385
status: hasFix
33803386
StaticWarningCode.UNNECESSARY_NON_NULL_ASSERTION:

pkg/analyzer/lib/src/error/codes.g.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6070,6 +6070,37 @@ class StaticWarningCode extends AnalyzerErrorCode {
60706070
hasPublishedDocs: true,
60716071
);
60726072

6073+
/// No parameters.
6074+
static const StaticWarningCode INVALID_NULL_AWARE_ELEMENT = StaticWarningCode(
6075+
'INVALID_NULL_AWARE_OPERATOR',
6076+
"The element can't be null, so the null-aware operator '?' is unnecessary.",
6077+
correctionMessage: "Try removing the operator '?'.",
6078+
hasPublishedDocs: true,
6079+
uniqueName: 'INVALID_NULL_AWARE_ELEMENT',
6080+
);
6081+
6082+
/// No parameters.
6083+
static const StaticWarningCode INVALID_NULL_AWARE_MAP_ENTRY_KEY =
6084+
StaticWarningCode(
6085+
'INVALID_NULL_AWARE_OPERATOR',
6086+
"The map entry key can't be null, so the null-aware operator '?' is "
6087+
"unnecessary.",
6088+
correctionMessage: "Try removing the operator '?'.",
6089+
hasPublishedDocs: true,
6090+
uniqueName: 'INVALID_NULL_AWARE_MAP_ENTRY_KEY',
6091+
);
6092+
6093+
/// No parameters.
6094+
static const StaticWarningCode INVALID_NULL_AWARE_MAP_ENTRY_VALUE =
6095+
StaticWarningCode(
6096+
'INVALID_NULL_AWARE_OPERATOR',
6097+
"The map entry value can't be null, so the null-aware operator '?' is "
6098+
"unnecessary.",
6099+
correctionMessage: "Try removing the operator '?'.",
6100+
hasPublishedDocs: true,
6101+
uniqueName: 'INVALID_NULL_AWARE_MAP_ENTRY_VALUE',
6102+
);
6103+
60736104
/// Parameters:
60746105
/// 0: the null-aware operator that is invalid
60756106
/// 1: the non-null-aware operator that can replace the invalid operator

pkg/analyzer/lib/src/error/error_code_values.g.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,9 @@ const List<ErrorCode> errorCodeValues = [
958958
ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT,
959959
ScannerErrorCode.UNTERMINATED_STRING_LITERAL,
960960
StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION,
961+
StaticWarningCode.INVALID_NULL_AWARE_ELEMENT,
962+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY,
963+
StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE,
961964
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR,
962965
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
963966
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH,

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,21 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
11571157
super.visitListLiteral(node);
11581158
}
11591159

1160+
@override
1161+
void visitMapLiteralEntry(MapLiteralEntry node) {
1162+
if (node.keyQuestion != null) {
1163+
_checkForUnnecessaryNullAware(node.key, node.keyQuestion!,
1164+
nullAwareElementOrMapEntryKind:
1165+
_NullAwareElementOrMapEntryKind.mapEntryKey);
1166+
}
1167+
if (node.valueQuestion != null) {
1168+
_checkForUnnecessaryNullAware(node.value, node.valueQuestion!,
1169+
nullAwareElementOrMapEntryKind:
1170+
_NullAwareElementOrMapEntryKind.mapEntryValue);
1171+
}
1172+
super.visitMapLiteralEntry(node);
1173+
}
1174+
11601175
@override
11611176
void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
11621177
var element = node.declaredElement!;
@@ -1296,6 +1311,14 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
12961311
super.visitNativeFunctionBody(node);
12971312
}
12981313

1314+
@override
1315+
void visitNullAwareElement(NullAwareElement node) {
1316+
_checkForUnnecessaryNullAware(node.value, node.question,
1317+
nullAwareElementOrMapEntryKind:
1318+
_NullAwareElementOrMapEntryKind.element);
1319+
super.visitNullAwareElement(node);
1320+
}
1321+
12991322
@override
13001323
void visitPatternVariableDeclarationStatement(
13011324
covariant PatternVariableDeclarationStatementImpl node,
@@ -5551,7 +5574,8 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
55515574
}
55525575
}
55535576

5554-
void _checkForUnnecessaryNullAware(Expression target, Token operator) {
5577+
void _checkForUnnecessaryNullAware(Expression target, Token operator,
5578+
{_NullAwareElementOrMapEntryKind? nullAwareElementOrMapEntryKind}) {
55555579
if (target is SuperExpression) {
55565580
return;
55575581
}
@@ -5560,9 +5584,20 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
55605584
Token endToken = operator;
55615585
List<Object> arguments = const [];
55625586
if (operator.type == TokenType.QUESTION) {
5563-
errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR;
5564-
endToken = operator.next!;
5565-
arguments = ['?[', '['];
5587+
if (nullAwareElementOrMapEntryKind == null) {
5588+
errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR;
5589+
endToken = operator.next!;
5590+
arguments = ['?[', '['];
5591+
} else {
5592+
switch (nullAwareElementOrMapEntryKind) {
5593+
case _NullAwareElementOrMapEntryKind.element:
5594+
errorCode = StaticWarningCode.INVALID_NULL_AWARE_ELEMENT;
5595+
case _NullAwareElementOrMapEntryKind.mapEntryKey:
5596+
errorCode = StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY;
5597+
case _NullAwareElementOrMapEntryKind.mapEntryValue:
5598+
errorCode = StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE;
5599+
}
5600+
}
55665601
} else if (operator.type == TokenType.QUESTION_PERIOD) {
55675602
errorCode = StaticWarningCode.INVALID_NULL_AWARE_OPERATOR;
55685603
arguments = [operator.lexeme, '.'];
@@ -7164,6 +7199,10 @@ class _MacroTypeAnnotationLocationConverter {
71647199
}
71657200
}
71667201

7202+
/// Signals the kind of the null-aware element or entry observed in list, set,
7203+
/// or map literals.
7204+
enum _NullAwareElementOrMapEntryKind { element, mapEntryKey, mapEntryValue }
7205+
71677206
/// Recursively visits a type annotation, looking uninstantiated bounds.
71687207
class _UninstantiatedBoundChecker extends RecursiveAstVisitor<void> {
71697208
final ErrorReporter _errorReporter;

pkg/analyzer/messages.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22673,6 +22673,14 @@ StaticWarningCode:
2267322673
has the value `null`, the cast will fail and the invocation of `length`
2267422674
will not happen.
2267522675

22676+
The following code produces this diagnostic because `s` can't be `null`:
22677+
22678+
```dart
22679+
List<String> makeSingletonList(String s) {
22680+
return <String>[[!?!]s];
22681+
}
22682+
```
22683+
2267622684
#### Common fixes
2267722685

2267822686
Replace the null-aware operator with a non-null-aware equivalent; for
@@ -22695,6 +22703,24 @@ StaticWarningCode:
2269522703
Parameters:
2269622704
0: the null-aware operator that is invalid
2269722705
1: the non-null-aware operator that can replace the invalid operator
22706+
INVALID_NULL_AWARE_ELEMENT:
22707+
sharedName: INVALID_NULL_AWARE_OPERATOR
22708+
problemMessage: "The element can't be null, so the null-aware operator '?' is unnecessary."
22709+
correctionMessage: "Try removing the operator '?'."
22710+
hasPublishedDocs: true
22711+
comment: No parameters.
22712+
INVALID_NULL_AWARE_MAP_ENTRY_KEY:
22713+
sharedName: INVALID_NULL_AWARE_OPERATOR
22714+
problemMessage: "The map entry key can't be null, so the null-aware operator '?' is unnecessary."
22715+
correctionMessage: "Try removing the operator '?'."
22716+
hasPublishedDocs: true
22717+
comment: No parameters.
22718+
INVALID_NULL_AWARE_MAP_ENTRY_VALUE:
22719+
sharedName: INVALID_NULL_AWARE_OPERATOR
22720+
problemMessage: "The map entry value can't be null, so the null-aware operator '?' is unnecessary."
22721+
correctionMessage: "Try removing the operator '?'."
22722+
hasPublishedDocs: true
22723+
comment: No parameters.
2269822724
MISSING_ENUM_CONSTANT_IN_SWITCH:
2269922725
problemMessage: "Missing case clause for '{0}'."
2270022726
correctionMessage: Try adding a case clause for the missing constant, or adding a default clause.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/src/error/codes.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import '../dart/resolution/context_collection_resolution.dart';
9+
10+
main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(InvalidNullAwareElementsErrorTest);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class InvalidNullAwareElementsErrorTest extends PubPackageResolutionTest {
18+
test_invalid_null_aware_element_in_list() async {
19+
await assertErrorsInCode('''
20+
const stringConst = "";
21+
const list = [0, ?stringConst];
22+
''', [
23+
error(StaticWarningCode.INVALID_NULL_AWARE_ELEMENT, 41, 1),
24+
]);
25+
}
26+
27+
test_invalid_null_aware_element_in_set() async {
28+
await assertErrorsInCode('''
29+
const stringConst = "";
30+
const set = {0, ?stringConst};
31+
''', [
32+
error(StaticWarningCode.INVALID_NULL_AWARE_ELEMENT, 40, 1),
33+
]);
34+
}
35+
36+
test_invalid_null_aware_key_in_map() async {
37+
await assertErrorsInCode('''
38+
const intConst = 0;
39+
const map = {?0: intConst};
40+
''', [
41+
error(StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_KEY, 33, 1),
42+
]);
43+
}
44+
45+
test_invalid_null_aware_value_in_map() async {
46+
await assertErrorsInCode('''
47+
const intConst = 0;
48+
const map = {0: ?intConst};
49+
''', [
50+
error(StaticWarningCode.INVALID_NULL_AWARE_MAP_ENTRY_VALUE, 36, 1),
51+
]);
52+
}
53+
}

0 commit comments

Comments
 (0)