Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

add relational pattern support to unrelated_type_equality_checks #4147

Merged
merged 2 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions lib/src/rules/unrelated_type_equality_checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,10 @@ bool _hasNonComparableOperands(TypeSystem typeSystem, BinaryExpression node) {
}
return !left.isNullLiteral &&
!right.isNullLiteral &&
typesAreUnrelated(typeSystem, leftType, rightType) &&
!(_isFixNumIntX(leftType) && _isCoreInt(rightType));
_nonComparable(typeSystem, leftType, rightType);
}

bool _isCoreInt(DartType type) => type.isDartCoreInt;
bool _isCoreInt(DartType? type) => type != null && type.isDartCoreInt;

bool _isFixNumIntX(DartType type) {
// todo(pq): add tests that ensure this predicate works with fixnum >= 1.1.0-dev
Expand All @@ -159,6 +158,11 @@ bool _isFixNumIntX(DartType type) {
return uri.pathSegments.firstOrNull == 'fixnum';
}

bool _nonComparable(
TypeSystem typeSystem, DartType leftType, DartType? rightType) =>
typesAreUnrelated(typeSystem, leftType, rightType) &&
!(_isFixNumIntX(leftType) && _isCoreInt(rightType));

class UnrelatedTypeEqualityChecks extends LintRule {
static const LintCode code = LintCode('unrelated_type_equality_checks',
"Neither type of the operands of '==' is a subtype of the other.",
Expand All @@ -179,6 +183,7 @@ class UnrelatedTypeEqualityChecks extends LintRule {
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this, context.typeSystem);
registry.addBinaryExpression(this, visitor);
registry.addRelationalPattern(this, visitor);
}
}

Expand All @@ -191,14 +196,26 @@ class _Visitor extends SimpleAstVisitor<void> {
@override
void visitBinaryExpression(BinaryExpression node) {
var isDartCoreBoolean = node.staticType?.isDartCoreBool ?? false;
if (!isDartCoreBoolean ||
(node.operator.type != TokenType.EQ_EQ &&
node.operator.type != TokenType.BANG_EQ)) {
if (!isDartCoreBoolean || !_isEqualityTest(node.operator)) {
return;
}

if (_hasNonComparableOperands(typeSystem, node)) {
rule.reportLint(node);
}
}

@override
void visitRelationalPattern(RelationalPattern node) {
var valueType = node.matchedValueType;
if (valueType == null) return;
if (!_isEqualityTest(node.operator)) return;
var operandType = node.operand.staticType;
if (_nonComparable(typeSystem, valueType, operandType)) {
rule.reportLint(node);
}
}

bool _isEqualityTest(Token operator) =>
operator.type == TokenType.EQ_EQ || operator.type == TokenType.BANG_EQ;
}
3 changes: 3 additions & 0 deletions test/rules/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ import 'unnecessary_overrides_test.dart' as unnecessary_overrides;
import 'unnecessary_parenthesis_test.dart' as unnecessary_parenthesis;
import 'unnecessary_string_escapes_test.dart' as unnecessary_string_escapes;
import 'unreachable_from_main_test.dart' as unreachable_from_main;
import 'unrelated_type_equality_checks_test.dart'
as unrelated_type_equality_checks;
import 'use_build_context_synchronously_test.dart'
as use_build_context_synchronously;
import 'use_enums_test.dart' as use_enums;
Expand Down Expand Up @@ -191,6 +193,7 @@ void main() {
unnecessary_parenthesis.main();
unnecessary_string_escapes.main();
unreachable_from_main.main();
unrelated_type_equality_checks.main();
use_build_context_synchronously.main();
use_enums.main();
use_is_even_rather_than_modulo.main();
Expand Down
80 changes: 80 additions & 0 deletions test/rules/unrelated_type_equality_checks_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2023, 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:test_reflective_loader/test_reflective_loader.dart';

import '../rule_test_support.dart';

main() {
defineReflectiveSuite(() {
defineReflectiveTests(UnrelatedTypeEqualityChecksTestLanguage300);
});
}

@reflectiveTest
class UnrelatedTypeEqualityChecksTestLanguage300 extends LintRuleTest
with LanguageVersion300Mixin {
@override
bool get dumpAstOnFailures => true;

@override
String get lintRule => 'unrelated_type_equality_checks';

test_switchExpression() async {
await assertDiagnostics(r'''
const space = 32;

String f(int char) {
return switch (char) {
== 'space' => 'space',
};
}
''', [
error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 49, 6),
lint(69, 10),
]);
}

test_switchExpression_lessEq_ok() async {
await assertDiagnostics(r'''
String f(String char) {
return switch (char) {
<= 1 => 'space',
};
}
''', [
// No lint.
error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 33, 6),
error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 53, 2),
]);
}

test_switchExpression_notEq() async {
await assertDiagnostics(r'''
const space = 32;

String f(int char) {
return switch (char) {
!= 'space' => 'space',
};
}
''', [
error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 49, 6),
lint(69, 10),
]);
}

test_switchExpression_ok() async {
await assertDiagnostics(r'''
String f(String char) {
return switch (char) {
== 'space' => 'space',
};
}
''', [
// No lint.
error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 33, 6),
]);
}
}