Skip to content

Commit 48ee1f2

Browse files
srawlinsCommit Queue
authored andcommitted
[analyzer] new warning for nullable '==' parameter type
This rule checks that a parameter to an `operator ==` implementation has a non-nullable type. I intentionally did not enforce, in this rule, that the parameter is exactly `Object`. It is legal to narrow the parameter type to a different non-nullable type, like `int`. I can't imagine doing it, but it seems to be unrelated to whether the type should be nullable or not. Fixes https://github.com/dart-lang/linter/issues/3441 Replaces dart-archive/linter#3923 Change-Id: I61d4a7b1ab8318dc9403da1633c352de95bfac61 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277700 Reviewed-by: Mark Zhou <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 2f1372b commit 48ee1f2

File tree

14 files changed

+179
-21
lines changed

14 files changed

+179
-21
lines changed

pkg/_fe_analyzer_shared/lib/src/parser/identifier_context_impl.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ class ExtensionShowHideElementIdentifierContext extends IdentifierContext {
447447
}
448448

449449
@override
450-
bool operator ==(dynamic other) {
450+
bool operator ==(Object other) {
451451
return other is ExtensionShowHideElementIdentifierContext &&
452452
_kind == other._kind;
453453
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# - 655 "needsEvaluation"
2828
# - 282 for CompileTimeErrorCodes
2929
# - 220 for ParserErrorCodes
30-
# - 92 "needsFix"
30+
# - 93 "needsFix"
3131
# - 310 "hasFix"
3232
# - 94 "noFix"
3333

@@ -2751,6 +2751,11 @@ StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT:
27512751
status: hasFix
27522752
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH:
27532753
status: hasFix
2754+
StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER:
2755+
status: needsFix
2756+
notes: |-
2757+
The fix is to remove the question mark if the parameter has one (if the
2758+
written type is an alias, it may not).
27542759
StaticWarningCode.SDK_VERSION_AS_EXPRESSION_IN_CONST_CONTEXT:
27552760
status: hasFix
27562761
StaticWarningCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE:

pkg/analysis_server/test/src/services/correction/fix/remove_comparison_test.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ class Person {
281281
final String name = '';
282282
283283
@override
284-
operator ==(Object? other) =>
284+
operator ==(Object other) =>
285285
other != null &&
286286
other is Person &&
287287
name == other.name;
@@ -292,7 +292,7 @@ class Person {
292292
final String name = '';
293293
294294
@override
295-
operator ==(Object? other) =>
295+
operator ==(Object other) =>
296296
other is Person &&
297297
name == other.name;
298298
}
@@ -305,7 +305,7 @@ class Person {
305305
final String name = '';
306306
307307
@override
308-
operator ==(Object? other) {
308+
operator ==(Object other) {
309309
return other != null &&
310310
other is Person &&
311311
name == other.name;
@@ -317,7 +317,7 @@ class Person {
317317
final String name = '';
318318
319319
@override
320-
operator ==(Object? other) {
320+
operator ==(Object other) {
321321
return other is Person &&
322322
name == other.name;
323323
}
@@ -331,7 +331,7 @@ class Person {
331331
final String name = '';
332332
333333
@override
334-
operator ==(Object? other) {
334+
operator ==(Object other) {
335335
if (other is! Person) return false;
336336
other ??= Person();
337337
return other.name == name;
@@ -349,7 +349,7 @@ class Person {
349349
final String name = '';
350350
351351
@override
352-
operator ==(Object? other) {
352+
operator ==(Object other) {
353353
if (other is! Person) return false;
354354
final toCompare = other ?? Person();
355355
return toCompare.name == name;
@@ -362,7 +362,7 @@ class Person {
362362
final String name = '';
363363
364364
@override
365-
operator ==(Object? other) {
365+
operator ==(Object other) {
366366
if (other is! Person) return false;
367367
final toCompare = other;
368368
return toCompare.name == name;
@@ -381,7 +381,7 @@ class Person {
381381
final String name = '';
382382
383383
@override
384-
operator ==(Object? other) {
384+
operator ==(Object other) {
385385
if (other == null) return false;
386386
return other is Person &&
387387
name == other.name;
@@ -393,7 +393,7 @@ class Person {
393393
final String name = '';
394394
395395
@override
396-
operator ==(Object? other) {
396+
operator ==(Object other) {
397397
return other is Person &&
398398
name == other.name;
399399
}

pkg/analysis_server/tool/code_completion/metrics_util.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ class Place {
458458
int get rank => _numerator;
459459

460460
@override
461-
bool operator ==(dynamic other) =>
461+
bool operator ==(Object other) =>
462462
other is Place &&
463463
_numerator == other._numerator &&
464464
_denominator == other._denominator;

pkg/analyzer/lib/src/error/best_practices_verifier.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
683683
_checkForMissingReturn(node.body, node);
684684
_mustCallSuperVerifier.checkMethodDeclaration(node);
685685
_checkForUnnecessaryNoSuchMethod(node);
686+
_checkForNullableEqualsParameterType(node);
686687

687688
var name = Name(_currentLibrary.source.uri, element.name);
688689
var elementIsOverride = element is ClassMemberElement &&
@@ -1390,6 +1391,44 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
13901391
}
13911392
}
13921393

1394+
void _checkForNullableEqualsParameterType(MethodDeclaration node) {
1395+
if (!_typeSystem.isNonNullableByDefault) {
1396+
// Cannot specify non-nullable types before null safety.
1397+
return;
1398+
}
1399+
1400+
if (node.name.type != TokenType.EQ_EQ) {
1401+
return;
1402+
}
1403+
1404+
var parameters = node.parameters;
1405+
if (parameters == null) {
1406+
return;
1407+
}
1408+
1409+
if (parameters.parameters.length != 1) {
1410+
return;
1411+
}
1412+
1413+
var parameter = parameters.parameters.first;
1414+
var parameterElement = parameter.declaredElement;
1415+
if (parameterElement == null) {
1416+
return;
1417+
}
1418+
1419+
var type = parameterElement.type;
1420+
if (!type.isDartCoreObject && !type.isDynamic) {
1421+
// There is no legal way to define a nullable parameter type, which is not
1422+
// `dynamic` or `Object?`, so avoid double reporting here.
1423+
return;
1424+
}
1425+
1426+
if (_typeSystem.isNullable(parameterElement.type)) {
1427+
_errorReporter.reportErrorForToken(
1428+
StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER, node.name);
1429+
}
1430+
}
1431+
13931432
void _checkForNullableTypeInCatchClause(CatchClause node) {
13941433
if (!_isNonNullableByDefault) {
13951434
return;

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5645,6 +5645,14 @@ class StaticWarningCode extends AnalyzerErrorCode {
56455645
hasPublishedDocs: true,
56465646
);
56475647

5648+
/// No parameters.
5649+
static const StaticWarningCode NON_NULLABLE_EQUALS_PARAMETER =
5650+
StaticWarningCode(
5651+
'NON_NULLABLE_EQUALS_PARAMETER',
5652+
"The parameter type of '==' operators should be non-nullable.",
5653+
correctionMessage: "Try using a non-nullable type.",
5654+
);
5655+
56485656
/// Parameters:
56495657
/// 0: the name of the class
56505658
static const StaticWarningCode SDK_VERSION_ASYNC_EXPORTED_FROM_CORE =

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,7 @@ const List<ErrorCode> errorCodeValues = [
964964
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR,
965965
StaticWarningCode.INVALID_NULL_AWARE_OPERATOR_AFTER_SHORT_CIRCUIT,
966966
StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH,
967+
StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER,
967968
StaticWarningCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE,
968969
StaticWarningCode.SDK_VERSION_AS_EXPRESSION_IN_CONST_CONTEXT,
969970
StaticWarningCode.SDK_VERSION_BOOL_OPERATOR_IN_CONST_CONTEXT,

pkg/analyzer/messages.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5635,7 +5635,7 @@ CompileTimeErrorCode:
56355635

56365636
```dart
56375637
mixin M on Enum {
5638-
bool operator [!==!](Object? other) => false;
5638+
bool operator [!==!](Object other) => false;
56395639
}
56405640
```
56415641

@@ -22789,6 +22789,11 @@ StaticWarningCode:
2278922789
```
2279022790
TODO(brianwilkerson) This documentation will need to be updated when NNBD
2279122791
ships.
22792+
NON_NULLABLE_EQUALS_PARAMETER:
22793+
problemMessage: "The parameter type of '==' operators should be non-nullable."
22794+
correctionMessage: Try using a non-nullable type.
22795+
hasPublishedDocs: false
22796+
comment: No parameters.
2279222797
SDK_VERSION_ASYNC_EXPORTED_FROM_CORE:
2279322798
problemMessage: "The class '{0}' wasn't exported from 'dart:core' until version 2.1, but this code is required to be able to run on earlier versions."
2279422799
correctionMessage: "Try either importing 'dart:async' or updating the SDK constraints."

pkg/analyzer/test/src/diagnostics/extension_declares_member_of_object_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ extension E on String {
2828
test_instance_sameKind() async {
2929
await assertErrorsInCode('''
3030
extension E on String {
31-
bool operator==(_) => false;
31+
bool operator==(Object _) => false;
3232
int get hashCode => 0;
3333
String toString() => '';
3434
dynamic get runtimeType => null;
3535
dynamic noSuchMethod(_) => null;
3636
}
3737
''', [
3838
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 39, 2),
39-
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 65, 8),
40-
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 89, 8),
41-
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 121, 11),
42-
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 152, 12),
39+
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 72, 8),
40+
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 96, 8),
41+
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 128, 11),
42+
error(CompileTimeErrorCode.EXTENSION_DECLARES_MEMBER_OF_OBJECT, 159, 12),
4343
]);
4444
}
4545

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) 2019, 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:analyzer/src/utilities/legacy.dart';
7+
import 'package:test_reflective_loader/test_reflective_loader.dart';
8+
9+
import '../dart/resolution/context_collection_resolution.dart';
10+
11+
main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(NonNullableEqualsParameterTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class NonNullableEqualsParameterTest extends PubPackageResolutionTest {
19+
test_dynamic() async {
20+
await assertErrorsInCode(r'''
21+
class C {
22+
@override
23+
bool operator ==(dynamic other) => false;
24+
}
25+
''', [
26+
error(StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER, 38, 2),
27+
]);
28+
}
29+
30+
test_inheritedDynamic() async {
31+
await assertErrorsInCode(r'''
32+
class C {
33+
@override
34+
bool operator ==(dynamic other) => false;
35+
}
36+
class D extends C {
37+
@override
38+
bool operator ==(other) => false;
39+
}
40+
''', [
41+
error(StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER, 38, 2),
42+
error(StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER, 116, 2),
43+
]);
44+
}
45+
46+
test_inheritedFromObject() async {
47+
await assertNoErrorsInCode(r'''
48+
class C {
49+
@override
50+
bool operator ==(other) => false;
51+
}
52+
''');
53+
}
54+
55+
test_inheritedFromObject_preNullSafe() async {
56+
try {
57+
noSoundNullSafety = false;
58+
await assertNoErrorsInCode(r'''
59+
// @dart=2.9
60+
class C {
61+
@override
62+
bool operator ==(other) => false;
63+
}
64+
''');
65+
} finally {
66+
noSoundNullSafety = true;
67+
}
68+
}
69+
70+
test_int() async {
71+
await assertNoErrorsInCode(r'''
72+
class C {
73+
@override
74+
bool operator ==(covariant int other) => false;
75+
}
76+
''');
77+
}
78+
79+
test_nullableObject() async {
80+
await assertErrorsInCode(r'''
81+
class C {
82+
@override
83+
bool operator ==(Object? other) => false;
84+
}
85+
''', [
86+
error(StaticWarningCode.NON_NULLABLE_EQUALS_PARAMETER, 38, 2),
87+
]);
88+
}
89+
90+
test_object() async {
91+
await assertNoErrorsInCode(r'''
92+
class C {
93+
@override
94+
bool operator ==(Object other) => false;
95+
}
96+
''');
97+
}
98+
}

0 commit comments

Comments
 (0)