Skip to content

Commit c77e27a

Browse files
authored
Fix crash caused by protocols-promote interference (#5297)
Fixes #5291
1 parent 047c2c3 commit c77e27a

File tree

3 files changed

+21
-3
lines changed

3 files changed

+21
-3
lines changed

mypy/constraints.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
312312
actual = actual.as_anonymous().fallback
313313
if isinstance(actual, Instance):
314314
instance = actual
315+
erased = erase_typevars(template)
316+
assert isinstance(erased, Instance)
315317
# We always try nominal inference if possible,
316318
# it is much faster than the structural one.
317319
if (self.direction == SUBTYPE_OF and
@@ -343,8 +345,11 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
343345
# This is a conservative way break the inference cycles.
344346
# It never produces any "false" constraints but gives up soon
345347
# on purely structural inference cycles, see #3829.
348+
# Note that we use is_protocol_implementation instead of is_subtype
349+
# because some type may be considered a subtype of a protocol
350+
# due to _promote, but still not implement the protocol.
346351
not any(is_same_type(template, t) for t in template.type.inferring) and
347-
mypy.subtypes.is_subtype(instance, erase_typevars(template))):
352+
mypy.subtypes.is_protocol_implementation(instance, erased)):
348353
template.type.inferring.append(template)
349354
self.infer_constraints_from_protocol_members(res, instance, template,
350355
original_actual, template)
@@ -353,7 +358,7 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
353358
elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and
354359
# We avoid infinite recursion for structural subtypes also here.
355360
not any(is_same_type(instance, i) for i in instance.type.inferring) and
356-
mypy.subtypes.is_subtype(erase_typevars(template), instance)):
361+
mypy.subtypes.is_protocol_implementation(erased, instance)):
357362
instance.type.inferring.append(instance)
358363
self.infer_constraints_from_protocol_members(res, instance, template,
359364
template, instance)

test-data/unit/check-type-promotion.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ y = 0.0
3737
y = 1
3838
y.x # E: "int" has no attribute "x"
3939
[builtins fixtures/primitives.pyi]
40+
41+
[case testTypePromotionsDontInterfereWithProtocols]
42+
from typing import TypeVar, Union, Protocol
43+
44+
class SupportsFloat(Protocol):
45+
def __float__(self) -> float: pass
46+
47+
T = TypeVar('T')
48+
def f(x: Union[SupportsFloat, T]) -> Union[SupportsFloat, T]: pass
49+
f(0) # should not crash
50+
[builtins fixtures/primitives.pyi]
51+
[out]

test-data/unit/fixtures/primitives.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class type:
1111

1212
class int:
1313
def __add__(self, i: int) -> int: pass
14-
class float: pass
14+
class float:
15+
def __float__(self) -> float: pass
1516
class complex: pass
1617
class bool(int): pass
1718
class str:

0 commit comments

Comments
 (0)