Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeVar bound to Union in if isinstance(...) branch has incompatible return type #12800

Closed
headtr1ck opened this issue May 16, 2022 · 9 comments
Labels
bug mypy got something wrong topic-type-variables

Comments

@headtr1ck
Copy link

Bug Report

A TypeVar bound to Union[x, y] will resolve to x after an if isinstance(..., x) is used which is incompatible with the return type.

I'm not sure on how to describe exactly what is going on, better check the Example.
Also mypy experts, please forgive me if that is intended behavior and not a bug (I would appreciate a working solution in this case :)

To Reproduce

Example:

from typing import TypeVar, Union

T = TypeVar("T", bound=Union[str, int])

def a(x: T) -> T:
    if isinstance(x, str):
        return x
    return x

Expected Behavior

I expect that this definition is correct, since whatever input is given, the output is the same.

Actual Behavior

mypy raises error: Incompatible return value type (got "str", expected "T").

Further Info

  • I want to have the str -> str, int -> int mapping, so simply using Union[str, int] won't work.
  • This function is called sometimes with variables of type Union[str, int], so using TypeVar("T", str, int) won't work either.

Your Environment

  • Mypy version used: 0.950
  • Mypy command-line flags: --strict
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.9
  • Operating system and version: win10
@headtr1ck headtr1ck added the bug mypy got something wrong label May 16, 2022
@headtr1ck
Copy link
Author

As a side note: a reveal_type(x) in the else branch returns "T`-1". Maybe a more expressive type would be useful?

@AlexWaygood
Copy link
Member

This one looks like a bug to me :)

For a (horrible) workaround, you can use overloads instead:

from typing import overload

@overload
def a(x: str) -> str: ...

@overload
def a(x: int) -> int: ...

@overload
def a(x: int | str) -> int | str: ...

def a(x: int | str) -> int | str:
    if isinstance(x, str):
        return x
    return x

b: str
c: int
d: int | str

reveal_type(a(b))  # Revealed type is "builtins.str"
reveal_type(a(c))  # Revealed type is "builtins.int"
reveal_type(a(d))  # Revealed type is "Union[builtins.int, builtins.str]"

@sterliakov
Copy link
Collaborator

sterliakov commented May 17, 2022

I don't know what does it mean for debugging, but this is also a bit odd:

from typing import TypeVar, Union

T = TypeVar("T", bound=Union[str, int])

def a(x: T) -> T:
    reveal_type(x)  # T`-1
    if isinstance(x, str):
        reveal_type(x)  # str
        return x
    reveal_type(x)  # T`-1
    return x

Gives T`-1, str and T`-1 revealed (in order) and error on line 9 (1st return). However, the following:

from typing import TypeVar, Union

T = TypeVar("T", bound=Union[str, int])

def a(x: T) -> T:
    reveal_type(x)  # T`-1
    if isinstance(x, str | None):
        reveal_type(x)  # T`-1
        return x
    reveal_type(x)  # T`-1
    return x

Gives T`-1 three times and no error. But I just add None which is not a member of T bounds, so it shouldn't affect inference, right?.. Say, the following ignores None in similar case:

x: str
if isinstance(x, str | None):
    reveal_type(x)  # str

But... Maybe it should be a separate issue, given this weird result with mypy from master and python 3.10:

x: str | int
if isinstance(x, str | None):
    reveal_type(x)  # N: Revealed type is "Union[builtins.str, builtins.int]"

@AlexWaygood
Copy link
Member

@headtr1ck, a slightly simpler and less horrible workaround: just use a cast():

from typing import TypeVar, Union, cast

T = TypeVar("T", bound=Union[str, int])

def a(x: T) -> T:
    if isinstance(x, str):
        return cast(T, x)
    return x

@headtr1ck
Copy link
Author

@headtr1ck, a slightly simpler and less horrible workaround: just use a cast():

This is what I ended up doing in the moment.

@gschaffner
Copy link
Contributor

Is this a duplicate of #10817?

@hauntsaninja
Copy link
Collaborator

Closing as duplicate of #10817

@hauntsaninja hauntsaninja closed this as not planned Won't fix, can't repro, duplicate, stale Jan 10, 2023
@moranabadie
Copy link

Closing as duplicate of #10817

#10817 is closed but the problem persists in python 1.10.1

@AlexWaygood
Copy link
Member

#10817 is closed

not true, it is still open

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-variables
Projects
None yet
Development

No branches or pull requests

6 participants