Mypy is unable to detect if a builtin class supports a generic protocol. Unexpected behavior with custom classes too.
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Python
- Sterne
- 20.6k
- Forks
- 3.3k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
Bug Report
Mypy is unable to detect if a class supports a generic protocol.
To Reproduce
Create a Wrapper[T] that supports arithmetic on its values, that is
if
A + B -> C
then
Wrapper[A] + Wrapper[B] -> Wrapper[C]
Wrapper[A] + B -> Wrapper[C]
A + Wrapper[B] -> Wrapper[C]
https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=f78c1a5896a21c3c76a9f4380a02ae69
from __future__ import annotations
from typing import TypeVar, Protocol, Generic
from typing_extensions import reveal_type
_T_contra = TypeVar('_T_contra', contravariant=True)
_T_co = TypeVar('_T_co', covariant=True)
class SupportsAdd(Protocol[_T_contra, _T_co]):
def __add__(self, other: _T_contra, /) -> _T_co: ...
class SupportsRAdd(Protocol[_T_contra, _T_co]):
def __radd__(self, other: _T_contra, /) -> _T_co: ...
_T = TypeVar('_T', covariant=True)
_T_left = TypeVar('_T_left', covariant=True)
_T_right = TypeVar('_T_right', covariant=True)
_T_out = TypeVar('_T_out', covariant=True)
class Wrapper(Generic[_T]):
val: _T
def __init__(self, val: _T):
self.val = val
def __add__(self: Wrapper[SupportsAdd[_T_right, _T_out]], right: _T_right | Wrapper[_T_right]) -> Wrapper[_T_out]:
if isinstance(right, Wrapper):
return Wrapper(self.val + right.val)
return Wrapper(self.val + right)
def __radd__(self: Wrapper[SupportsRAdd[_T_left, _T_out]], left: _T_left | Wrapper[_T_left]) -> Wrapper[_T_out]:
if isinstance(left, Wrapper):
return Wrapper(left.val + self.val)
return Wrapper(left + self.val)
def __repr__(self) -> str:
return f'Wrapper({self.val})'
class Snake:
# Snake + Legs = Lizard
def __add__(self, other: Legs) -> Lizard:
if not isinstance(other, Legs):
return NotImplemented
return Lizard()
# Legs + Snake = Dragon
def __radd__(self, other: Legs) -> Dragon:
if not isinstance(other, Legs):
return NotImplemented
return Dragon()
class Legs: ...
class Lizard: ...
class Dragon: ...
# What works as expected
reveal_type(Wrapper(10) + Wrapper(1j)) # Wrapper[complex]
reveal_type(10 + Wrapper(1j)) # Wrapper[complex]
reveal_type(Wrapper(1j) + Wrapper(10)) # Wrapper[complex]
reveal_type(Wrapper(1j) + 10) # Wrapper[complex]
reveal_type(Wrapper(10.0) + Wrapper(2)) # Wrapper[float]
reveal_type(Wrapper(10.0) + 2) # Wrapper[float]
reveal_type(Snake() + Legs()) # Lizard
reveal_type(Legs() + Snake()) # Dragon
reveal_type(Wrapper(Snake()) + Wrapper(Legs())) # Wrapper[Lizard]
reveal_type(Wrapper(Snake()) + Legs()) # Wrapper[Lizard]
reveal_type(Wrapper(Legs()) + Wrapper(Snake())) # Wrapper[Dragon]
reveal_type(Legs() + Wrapper(Snake())) # Wrapper[Dragon]
Wrapper(Snake()) + Wrapper(Snake()) # correctly gives error: error: Argument 1 to "Wrapper" has incompatible type "Snake"; expected "Legs" [arg-type]
Wrapper(10.0) + Wrapper('haha') # correctly gives error: Argument 1 to "Wrapper" has incompatible type "str"; expected "float" [arg-type]
# What doesn't work as expected
reveal_type(Wrapper(10) + 1j) # unexpected error: Unsupported operand types for + ("Wrapper[int]" and "complex") [operator]
reveal_type(1j + Wrapper(10)) # unexpected error: Unsupported operand types for + ("complex" and "Wrapper[int]") [operator]
reveal_type(Snake() + Wrapper(Legs())) # unexpected error: Unsupported operand types for + ("Snake" and "Wrapper[Legs]") [operator]
reveal_type(Wrapper(Legs()) + Snake()) # unpexpected error: Unsupported operand types for + ("Wrapper[Legs]" and "Snake") [operator]
Expected Behavior
Wrapper[int] + complex => Wrapper[complex]
complex + Wrapper[int] => Wrapper[complex]
Snake + Wrapper[Legs] => Wrapper[Lizard]
Wrapper[Legs] + Snake => Wrapper[Dragon]
Actual Behavior
error: Unsupported operand types for + ("Wrapper[int]" and "complex") [operator]
error: Unsupported operand types for + ("complex" and "Wrapper[int]") [operator]
error: Unsupported operand types for + ("Snake" and "Wrapper[Legs]") [operator]
error: Unsupported operand types for + ("Wrapper[Legs]" and "Snake") [operator]
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne mit der verknüpften mypy-play-Reproduktion und vergleiche die vier fehlschlagenden Operatorausdrücke mit den im Issue erwarteten Typen. Verfolge den Type-Checking-Pfad für generische Protokolle und reflektierte arithmetische Methoden; als abgeschlossen gilt die Aufgabe, wenn diese Ausdrücke ohne Fehler die erwarteten Wrapper- oder Ergebnistypen erzeugen und die vorhandenen gültigen und ungültigen Beispiele weiterhin korrekt bleiben.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- devtools
- Issue-Typ
- Bug
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Aktivitätsstatus
- Veraltet
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 35/100