mypy icon indicating copy to clipboard operation
mypy copied to clipboard

`bool` is available even if `*constraints` of `TypeVar` is the exact types `int` and `float`

Open hyperkai opened this issue 1 month ago • 4 comments

*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.

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

hyperkai avatar Nov 28 '25 17:11 hyperkai

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

hauntsaninja avatar Nov 28 '25 20:11 hauntsaninja

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)

A5rocks avatar Nov 29 '25 16:11 A5rocks

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.

JelleZijlstra avatar Nov 29 '25 17:11 JelleZijlstra

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

hauntsaninja avatar Dec 03 '25 22:12 hauntsaninja