Skip to content

Commit ef30ba9

Browse files
authored
Consider fallback instance promotions in is_overlapping_types() (#7358)
Using this opportunity I also made a tiny refactoring to avoid code duplication (that would grow even bigger if I would also add logic for promotions).
1 parent 5b8ca1f commit ef30ba9

File tree

3 files changed

+26
-14
lines changed

3 files changed

+26
-14
lines changed

mypy/meet.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType,
1111
ProperType, get_proper_type, get_proper_types
1212
)
13-
from mypy.subtypes import (
14-
is_equivalent, is_subtype, is_protocol_implementation, is_callable_compatible,
15-
is_proper_subtype,
16-
)
13+
from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype
1714
from mypy.erasetype import erase_type
1815
from mypy.maptype import map_instance_to_supertype
1916
from mypy.typeops import tuple_fallback
@@ -276,7 +273,13 @@ def _type_object_overlap(left: ProperType, right: ProperType) -> bool:
276273
right = right.fallback
277274

278275
if isinstance(left, LiteralType) and isinstance(right, LiteralType):
279-
return left == right
276+
if left.value == right.value:
277+
# If values are the same, we still need to check if fallbacks are overlapping,
278+
# this is done below.
279+
left = left.fallback
280+
right = right.fallback
281+
else:
282+
return False
280283
elif isinstance(left, LiteralType):
281284
left = left.fallback
282285
elif isinstance(right, LiteralType):
@@ -285,15 +288,13 @@ def _type_object_overlap(left: ProperType, right: ProperType) -> bool:
285288
# Finally, we handle the case where left and right are instances.
286289

287290
if isinstance(left, Instance) and isinstance(right, Instance):
288-
if left.type.is_protocol and is_protocol_implementation(right, left):
289-
return True
290-
if right.type.is_protocol and is_protocol_implementation(left, right):
291+
# First we need to handle promotions and structural compatibility for instances
292+
# that came as fallbacks, so simply call is_subtype() to avoid code duplication.
293+
if (is_subtype(left, right, ignore_promotions=ignore_promotions)
294+
or is_subtype(right, left, ignore_promotions=ignore_promotions)):
291295
return True
292296

293297
# Two unrelated types cannot be partially overlapping: they're disjoint.
294-
# We don't need to handle promotions because they've already been handled
295-
# by the calls to `is_subtype(...)` up above (and promotable types never
296-
# have any generic arguments we need to recurse on).
297298
if left.type.has_base(right.type.fullname()):
298299
left = map_instance_to_supertype(left, right.type)
299300
elif right.type.has_base(left.type.fullname()):
@@ -302,9 +303,6 @@ def _type_object_overlap(left: ProperType, right: ProperType) -> bool:
302303
return False
303304

304305
if len(left.args) == len(right.args):
305-
if not left.args:
306-
# We can get here if the instance is in fact a fallback from another type.
307-
return True
308306
# Note: we don't really care about variance here, since the overlapping check
309307
# is symmetric and since we want to return 'True' even for partial overlaps.
310308
#

test-data/unit/check-expressions.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,6 +2303,18 @@ if f == 0: # E: Non-overlapping equality check (left operand type: "FileId", ri
23032303
...
23042304
[builtins fixtures/bool.pyi]
23052305

2306+
[case testStrictEqualityPromotionsLiterals]
2307+
# flags: --strict-equality --py2
2308+
from typing import Final
2309+
2310+
U_FOO = u'foo' # type: Final
2311+
2312+
if str() == U_FOO:
2313+
pass
2314+
assert u'foo' == 'foo'
2315+
assert u'foo' == u'bar' # E: Non-overlapping equality check (left operand type: "Literal[u'foo']", right operand type: "Literal[u'bar']")
2316+
[builtins_py2 fixtures/python2.pyi]
2317+
23062318
[case testUnimportedHintAny]
23072319
def f(x: Any) -> None: # E: Name 'Any' is not defined \
23082320
# N: Did you forget to import it from "typing"? (Suggestion: "from typing import Any")

test-data/unit/fixtures/python2.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ from typing import Generic, Iterable, TypeVar
22

33
class object:
44
def __init__(self) -> None: pass
5+
def __eq__(self, other: object) -> bool: pass
6+
def __ne__(self, other: object) -> bool: pass
57

68
class type:
79
def __init__(self, x) -> None: pass

0 commit comments

Comments
 (0)