`bool` is available even if `*constraints` of `TypeVar` is the exact types `int` and `float`
*Memo:
-
mypy --strict test.py - mypy 1.18.2
- Python 3.14.0
- Windows 11
The doc says 'Must be exactly str or bytes' about *constraints to make difference between bound and *constraints as shown below:
*Memo:
- From my understanding:
- Setting
bound, the type and the subtypes are available. - Setting
*constraints, only the exact types are available but not the subtypes.
- Setting
T = TypeVar('T') # Can be anything S = TypeVar('S', bound=str) # Can be any subtype of str A = TypeVar('A', str, bytes) # Must be exactly str or bytes
But bool is available even if *constraints is the exact types int and float as shown below:
<With TypeVar (Old syntax)>:
from typing import TypeVar, Generic
T = TypeVar('T', int, float)
class MyCls(Generic[T]):
num: T
def __init__(self, x: T) -> None:
self.num = x
# ↓↓↓↓↓↓↓↓↓↓↓↓
mycls = MyCls[bool](True)
# No error
<Without TypeVar (New syntax)>:
class MyCls[T: (int, float)]:
num: T
def __init__(self, x: T) -> None:
self.num = x
# ↓↓↓↓↓↓↓↓↓↓↓↓
mycls = MyCls[bool](True)
# No error
Note it you reveal_type(mycls), you see mypy has solved it to MyCls[int]. Other type checkers behave similarly on this example. So there is no incorrect behaviour introduced, but it could be reasonable to want a lint for this
Bool is a subtype of int, this is fine.
The docs say:
Subtypes of types constrained by a type variable should be treated as their respective explicitly listed base types in the context of the type variable.
And while that's in reference to runtime behavior, I don't think this is necessarily bad.
(But yeah maybe a lint? Not convinced this is a problem though)
I do think the behavior here is wrong; explicit specialization should only allow the exact constraint types. The bit @A5rocks quotes is about inference, not explicit specialization.
The bit that is necessary for soundness is that reveal_type(mycls) reveals MyCls[int].
I think a lint on the specialisation seems worthwhile, but if we think the current behaviour is "wrong", maybe we should make that explicit in the spec, because both mypy and pyright have the same behaviour on this example
For completeness:
- pyrefly and ty are both actually wrong, they reveal the wrong type and don't complain on the specialisation
- zuban has the behaviour proposed here, ie. reveals correctly and complains on the specialisation