`ParamSpec` inferred overly wide from protocol
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Python
- Sterne
- 20.6k
- Forks
- 3.3k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
Bug Report
When implementing a protocol generic wrt a ParamSpec type inference ends up inferring Any for the param spec in certain cases. Writing code around the way the type checker works we can force it to unify against a properly inferred param spec, but this limits our ability to write nice APIs.
To Reproduce
from typing import Protocol, reveal_type
class Context: pass
class Namer[**P](Protocol):
def name_for(self, *args: P.args, **kwargs: P.kwargs) -> str: ...
def execute_on(
self, ctx: Context, *args: P.args, **kwargs: P.kwargs
) -> None: ...
class Impl0:
@staticmethod
def name_for(x: int, y: str) -> str:
return 'Test'
@staticmethod
def execute_on(ctx: Context, x: int, y: str):
pass
class Impl1:
@staticmethod
def name_for(y: str) -> str:
return 'Test'
@staticmethod
def execute_on(ctx: Context, x: int, y: str):
pass
class UseImplFirst[**P, T]:
def __init__(self, impl: T, *args: P.args, **kwargs: P.kwargs) -> None:
self.impl = impl
self.args = args
self.kwargs = kwargs
def __call__(self: UseImplFirst[P, Namer[P]]) -> None:
pass
def useImplSecond[**P](impl: Namer[P], *args: P.args, **kwargs: P.kwargs):
pass
def useImplThird[**P](wrapper: UseImplFirst[P, Namer[P]]) -> None:
pass
__testImpl0: Namer[[int, str]] = Impl0
UseImplFirst(Impl0, 0, '0')()
useImplSecond(Impl0, 0, '0')
useImplThird(UseImplFirst(Impl0, 0, '0'))
__testImpl1: Namer[[int, str]] = Impl1 # Should fail (Fails)
UseImplFirst(Impl1, 1, '1')() # Should fail (Succeeds)
useImplSecond(Impl1, 1, '1') # Should fail (Succeeds)
useImplThird(UseImplFirst(Impl1, 1, '1')) # Should fail (Succeeds)
Expected Behavior / Actual Behavior
See the snippet above.
Woraround
The following forces mypy to infer the ParamSpec first and unify against the protocol implementation later:
class UseImpl[**P]:
def __init__(self, *args: P.args, **kwargs: P.kwargs):
self.args = args
self.kwargs = kwargs
def __call__(self, impl: Namer[P]) -> None:
pass
__testImpl0: Namer[[int, str]] = Impl0
UseImpl(0, '0')(Impl0)
__testImpl1: Namer[[int, str]] = Impl1 # Should fail (Fails)
UseImpl(1, '1')(Impl1) # Should fail (fails)
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 damit, den Reproducer aus dem Issue auszuführen und die Playgrounds von mypy 1.12.0 und 1.20.2 zu vergleichen. Verwende die erwarteten Fehler für Impl1 und den funktionierenden Workaround als Abschlusskriterien und füge Regressionstests in den relevanten Tests zur Typinferenz hinzu, sobald der Implementierungsbereich identifiziert ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- compilers
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Ruhig
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 45/100