ty icon indicating copy to clipboard operation
ty copied to clipboard

False-positive `unsupported-operator` errors for value-constrained type variable

Open varchasgopalaswamy opened this issue 3 weeks ago • 5 comments

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

varchasgopalaswamy avatar Dec 17 '25 02:12 varchasgopalaswamy

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 

Sillocan avatar Dec 17 '25 04:12 Sillocan

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.

sharkdp avatar Dec 17 '25 08:12 sharkdp

Just FYI, same thing happens to add, sub, etc.

varchasgopalaswamy avatar Dec 17 '25 13:12 varchasgopalaswamy

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

AlexWaygood avatar Dec 17 '25 14:12 AlexWaygood

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

sharkdp avatar Dec 19 '25 09:12 sharkdp