python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.55k stars 2.84k forks source link

Issue with TypeVar bound to a union and methods returning Self #18170

Open slanzmich opened 6 days ago

slanzmich commented 6 days ago

Bug Report

Mypy does not properly identify the return type of a method returning Self when the object is annotated with a TypeVar bound to a Union.

To Reproduce

from typing import Self, TypeVar, reveal_type

class A:
    def clone(self) -> Self:
        return self

class B:
    def clone(self) -> Self:
        return self

class C(B):
    pass

AB = TypeVar("AB", bound=A | B)

def func1(x: AB) -> AB:
    return x

def func2(x: AB) -> AB:
    # This fails with
    #   error: Incompatible return value type (got "A | B", expected "AB")  [return-value]
    return x.clone()

reveal_type(func2(A()))  # Revealed type is "__main__.A"
reveal_type(func2(B()))  # Revealed type is "__main__.B"
reveal_type(func2(C()))  # Revealed type is "__main__.C"

https://mypy-play.net/?mypy=latest&python=3.12&gist=891e2571039d82531325537a75f334f0

mypy is actually pretty close. It finds that x.clone() returns A | B, but misses that whether A or B is returned is preserved.

Using constraints instead of a bound for the TypeVar, i.e.

AB = TypeVar("AB", A, B)

makes mypy happy. However, that does not support my use case, as reveal_type(func2(C())) finds __main__.B. I would like to avoid having to list all subclasses of A and B as constraints to the TypeVar.

Expected Behavior

no error

Actual Behavior

error: Incompatible return value type (got "A | B", expected "AB")  [return-value]

Your Environment

mariafnafees commented 1 day ago

Hi there, I'll be working on resolving this bug for my university software engineering assignment.