-
Notifications
You must be signed in to change notification settings - Fork 288
-
Let's say I have a generic Protocol and a class that implements that protocol multiple times (for different arguments) via overloads:
class Proto[T](Protocol): @type_check_only def proto(self, _: T, /) -> None: ... type One = Literal[1] type Two = Literal[2] @final class Impl[N: int]: # ensure `N` is invariant def __init__(self, n: N, /) -> None: ... @type_check_only def get_n(self, /) -> N: ... # implement Proto[N] for Impl[N] and Proto[M] for Impl[One] (for all integers N, M) @overload def proto(self, _: "Impl[N]", /) -> None: ... @overload def proto[M: int](self: "Impl[One]", _: "Impl[M]", /) -> None: ...
I can easily verify that Impl really does implement Proto the way I intended:
class check1[T]: def __init__(self, x: Proto[T], /) -> None: ... # Here it all works as expected impl1 = Impl[One](1) impl2 = Impl[Two](2) impl_ = Impl[int](3) _ = check1[Impl[One]](impl1) # ok (first overload applies) _ = check1[Impl[Two]](impl1) # ok (second overload applies) _ = check1[Impl[One]](impl2) # error (second overload doesn't apply because One != Two) _ = check1[Impl[Two]](impl2) # ok (first overload applies) _ = check1[Impl[One]](impl_) # error (second overload doesn't apply because N is invariant) _ = check1[Impl[Two]](impl_) # error (second overload doesn't apply because N is invariant) _ = check1[Impl[int]](impl_) # ok (first overload applies)
Now, let's say we wanted to write a function func that takes T and Proto[T], in any order, and just applies .proto(...) on them. Here, I'll model it as a generic class:
class func[T]: @overload def __init__(self, x: T, y: Proto[T], /) -> None: ... @overload def __init__(self, x: Proto[T], y: T, /) -> None: ... # implementation not important
If I don't specify T manually, sometimes Pyright complains:
_ = func[Impl[One]](impl2, impl1) # error (correct) _ = func[Impl[One]](impl1, impl2) # error (correct) _ = func[Impl[int]](impl_, impl1) # ok _ = func[Impl[int]](impl1, impl_) # ok _ = func[Impl[Two]](impl2, impl1) # ok _ = func[Impl[Two]](impl1, impl2) # ok reveal_type(func(impl2, impl1)) # ok, type is `func[Impl[Two]]` reveal_type(func(impl1, impl2)) # error, but why?
Code sample in pyright playground
The error message says I cannot assign impl2: Impl[Two] to y: T, because Impl[Two] is not assignable to Impl[One]: it looks like Pyright has chosen the first overload in Impl's implementation of Proto and has resolved T to be One because impl1: Impl[One] indeed implements Proto[One]. However, since this leads to an error, why has Pyright not tried the other implementation?
Side note
If I don't manually enforce the type parameter of check1, Pyright infers Impl[One]:
reveal_type(check1(impl1)) # type is `check1[Impl[One]]` # first overload applies
Is this:
- a hard limitation of Pyright (and type checker implementations in general),
- a hole (unspecified behaviour) in the typing spec,
- or the expected behaviour (and I just don't understand what's going on)?
If it is indeed a hard limitation, can I type func in a different way (without changing Impl and Proto)?
Thank you for your time and dedication to improve the soundness and expressiveness of the Python type system (and to answer all these questions from the community).
Best regards,
Riccardo Bergamaschi.
Beta Was this translation helpful? Give feedback.