Skip to content

Commit 7678f28

Browse files
authored
Whitelist some equality checks under --strict-equality (#7302)
Fixes #7275 As I mentioned in the issue, we can just whitelist some pairs of classes.
1 parent 11056f2 commit 7678f28

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

mypy/checkexpr.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from mypy import types
4040
from mypy.sametypes import is_same_type
4141
from mypy.erasetype import replace_meta_vars, erase_type
42+
from mypy.maptype import map_instance_to_supertype
4243
from mypy.messages import MessageBuilder
4344
from mypy import message_registry
4445
from mypy.infer import infer_type_arguments, infer_function_type_arguments
@@ -71,6 +72,12 @@
7172
MAX_UNIONS = 5 # type: Final
7273

7374

75+
# Types considered safe for comparisons with --strict-equality due to known behaviour of __eq__.
76+
# NOTE: All these types are subtypes of AbstractSet.
77+
OVERLAPPING_TYPES_WHITELIST = ['builtins.set', 'builtins.frozenset',
78+
'typing.KeysView', 'typing.ItemsView'] # type: Final
79+
80+
7481
class TooManyUnions(Exception):
7582
"""Indicates that we need to stop splitting unions in an attempt
7683
to match an overload in order to save performance.
@@ -2051,6 +2058,14 @@ def dangerous_comparison(self, left: Type, right: Type,
20512058
# We need to special case bytes, because both 97 in b'abc' and b'a' in b'abc'
20522059
# return True (and we want to show the error only if the check can _never_ be True).
20532060
return False
2061+
if isinstance(left, Instance) and isinstance(right, Instance):
2062+
# Special case some builtin implementations of AbstractSet.
2063+
if (left.type.fullname() in OVERLAPPING_TYPES_WHITELIST and
2064+
right.type.fullname() in OVERLAPPING_TYPES_WHITELIST):
2065+
abstract_set = self.chk.lookup_typeinfo('typing.AbstractSet')
2066+
left = map_instance_to_supertype(left, abstract_set)
2067+
right = map_instance_to_supertype(right, abstract_set)
2068+
return not is_overlapping_types(left.args[0], right.args[0])
20542069
return not is_overlapping_types(left, right, ignore_promotions=False)
20552070

20562071
def get_operator_method(self, op: str) -> str:

test-data/unit/pythoneval.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,3 +1385,23 @@ def thing(stuff: StuffDict) -> int: ...
13851385

13861386
[out]
13871387
_testNewAnalyzerTypedDictInStub_newsemanal.py:2: note: Revealed type is 'def (stuff: TypedDict('stub.StuffDict', {'foo': builtins.str, 'bar': builtins.int})) -> builtins.int'
1388+
1389+
[case testStrictEqualityWhitelist]
1390+
# mypy: strict-equality
1391+
{1} == frozenset({1})
1392+
frozenset({1}) == {1}
1393+
1394+
frozenset({1}) == [1] # Error
1395+
1396+
{1: 2}.keys() == {1}
1397+
{1: 2}.keys() == frozenset({1})
1398+
{1: 2}.items() == {(1, 2)}
1399+
1400+
{1: 2}.keys() == {'no'} # Error
1401+
{1: 2}.values() == {2} # Error
1402+
{1: 2}.keys() == [1] # Error
1403+
[out]
1404+
_testStrictEqualityWhitelist.py:5: error: Non-overlapping equality check (left operand type: "FrozenSet[int]", right operand type: "List[int]")
1405+
_testStrictEqualityWhitelist.py:11: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "Set[str]")
1406+
_testStrictEqualityWhitelist.py:12: error: Non-overlapping equality check (left operand type: "ValuesView[int]", right operand type: "Set[int]")
1407+
_testStrictEqualityWhitelist.py:13: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "List[int]")

0 commit comments

Comments
 (0)