Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Whitelist some equality checks under --strict-equality #7302

Merged
merged 2 commits into from
Aug 8, 2019
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
15 changes: 15 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from mypy import types
from mypy.sametypes import is_same_type
from mypy.erasetype import replace_meta_vars, erase_type
from mypy.maptype import map_instance_to_supertype
from mypy.messages import MessageBuilder
from mypy import message_registry
from mypy.infer import infer_type_arguments, infer_function_type_arguments
Expand Down Expand Up @@ -71,6 +72,12 @@
MAX_UNIONS = 5 # type: Final


# Types considered safe for comparisons with --strict-equality due to known behaviour of __eq__.
# NOTE: All these types are subtypes of AbstractSet.
OVERLAPPING_TYPES_WHITELIST = ['builtins.set', 'builtins.frozenset',
'typing.KeysView', 'typing.ItemsView'] # type: Final


class TooManyUnions(Exception):
"""Indicates that we need to stop splitting unions in an attempt
to match an overload in order to save performance.
Expand Down Expand Up @@ -2038,6 +2045,14 @@ def dangerous_comparison(self, left: Type, right: Type,
# We need to special case bytes, because both 97 in b'abc' and b'a' in b'abc'
# return True (and we want to show the error only if the check can _never_ be True).
return False
if isinstance(left, Instance) and isinstance(right, Instance):
# Special case some builtin implementations of AbstractSet.
if (left.type.fullname() in OVERLAPPING_TYPES_WHITELIST and
right.type.fullname() in OVERLAPPING_TYPES_WHITELIST):
abstract_set = self.chk.lookup_typeinfo('typing.AbstractSet')
left = map_instance_to_supertype(left, abstract_set)
right = map_instance_to_supertype(right, abstract_set)
return not is_overlapping_types(left.args[0], right.args[0])
return not is_overlapping_types(left, right, ignore_promotions=False)

def get_operator_method(self, op: str) -> str:
Expand Down
20 changes: 20 additions & 0 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -1385,3 +1385,23 @@ def thing(stuff: StuffDict) -> int: ...

[out]
_testNewAnalyzerTypedDictInStub_newsemanal.py:2: note: Revealed type is 'def (stuff: TypedDict('stub.StuffDict', {'foo': builtins.str, 'bar': builtins.int})) -> builtins.int'

[case testStrictEqualityWhitelist]
# mypy: strict-equality
{1} == frozenset({1})
frozenset({1}) == {1}

frozenset({1}) == [1] # Error

{1: 2}.keys() == {1}
{1: 2}.keys() == frozenset({1})
{1: 2}.items() == {(1, 2)}

{1: 2}.keys() == {'no'} # Error
{1: 2}.values() == {2} # Error
{1: 2}.keys() == [1] # Error
[out]
_testStrictEqualityWhitelist.py:5: error: Non-overlapping equality check (left operand type: "FrozenSet[int]", right operand type: "List[int]")
_testStrictEqualityWhitelist.py:11: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "Set[str]")
_testStrictEqualityWhitelist.py:12: error: Non-overlapping equality check (left operand type: "ValuesView[int]", right operand type: "Set[int]")
_testStrictEqualityWhitelist.py:13: error: Non-overlapping equality check (left operand type: "KeysView[int]", right operand type: "List[int]")