Skip to content

Commit ef30ba9

Browse files
authored
Consider fallback instance promotions in is_overlapping_types() (python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy