False-positive `unsupported-operator` errors for value-constrained type variable
Summary
I'm probably doing something wrong, but I can't figure it out. Here's a simple example of what I'm trying to do
from typing import *
Scalar = TypeVar("Scalar", int,float)
def test(a:Scalar) -> Scalar:
return a * 2
reveal_type(test(10.0)) # should be float
reveal_type(test(10)) # should be int
Pyright works fine.
Mypy works, though I can't find a way to share a link to a playground.
pyrefly has no trouble with the operator, but thinks it's a bad return type. However, it's able to correctly infer the return types.
ty gives an unsupported operator error, and says the type of test(10.0) is float | int. Is there a more correct way to do what I'm trying to do?
Version
No response
Using type parameter lists does work in this case.
from typing import *
Scalar = TypeVar("Scalar", int,float)
def test[Scalar](a:Scalar) -> Scalar:
return a * 2
reveal_type(test(10.0)) # should be float
reveal_type(test(10)) # should be int
Thank you for reporting this. That is a bug.
The union float | int comes from this special case, but we should not error on the multiplication in the function body.
Using type parameter lists does work in this case.
That's not equivalent. test[Scalar](…) overrides the global TypeVar with a new-style (PEP 695) type variable without any constraints. The equivalent for the OP example would have to include the TypeVar constraints as well:
def test[Scalar: (int, float)](a:Scalar) -> Scalar:
return a * 2
reveal_type(test(10.0))
reveal_type(test(10))
And in that case, it demonstrates the same issue.
Just FYI, same thing happens to add, sub, etc.
Here's a minimal repro for the unsupported-operator bug:
def f[T: (int, float)](x: T) -> T:
x + x # error: [unsupported-operator]
return x
It's not specific to int/float -- it also repros if the TypeVar bounds are (int, str).
Adding a note here that we have an existing test for this with a TODO: https://github.com/astral-sh/ruff/blob/e177cc2a5a5d5e25c06ea6b0bbac9209f37fe756/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md?plain=1#L241-L261