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

Cannot return concrete type within isinstance block of generic function #8354

Closed
PeterJCLaw opened this issue Feb 2, 2020 · 4 comments
Closed

Comments

@PeterJCLaw
Copy link
Contributor

I'm not sure if this is a bug or a known limitation. I think this is different to the other similar generics vs isinstance issues, but please do point me at another if this is a duplicate.

Given some function:

from typing import TypeVar

T = TypeVar('T')

def foo(val: T) -> T:
    if isinstance(val, str):
        return "abc"
    return val

I would hope that mypy can tell that, within the isinstance block, T can only be str (since that's what val is) and thus returning something which is explicitly str is fine.

Unfortunately this case seems to error:

foo.py:5: error: Incompatible return value type (got "str", expected "T")

I realise this construct looks a bit odd in the cut-down above -- my actual use-case involves walking nested list and dictionary structures to modify values, but ends up with what I believe is the same error. Just to give a sense of that a slightly larger but still very cut-down version would be:

from typing import Any, List, TypeVar, Union

T = TypeVar('T', bound=Union[str, List[Any], int])

def do_expansion(value: T) -> T:
    if isinstance(value, str):
        return value.upper()  # just using .upper() as an example
    elif isinstance(value, list):
        return [do_expansion(x) for x in value]
    else:
        return value

I realise I could use overloads to achieve a similar effect for consumers of the function, however I would also like to be able to type the implementation and get checking there.

In case it's useful, I'm using mypy version 0.761 (latest at the time of writing) on Python 3.5.2.

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Feb 2, 2020

I think the issue this runs into is:

from typing import *

class A: ...
class B(A): ...

T = TypeVar("T")

def test(x: T) -> T:
    if isinstance(x, A):
        return A()
    return x

test(B())  # you'd expect this to be a B

To make it more problematic, substitute A with object.

As you say, overloads will work for consumers, maybe for yourself try functools.singledispatch?

Edit: since, as below, that seems to make mypy quite unhappy, there's always manual dispatch...

@PeterJCLaw
Copy link
Contributor Author

Ah, yeah I'd missed that subclasses might break that.
Using functools.singledispatch as you suggest definitely looks like it could help (and maybe clarify my code a little too!) with this, once mypy is happy with how it works.

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Feb 2, 2020

Looks like mypy survives with the TypeVar for singledispatch:

from typing import *
import functools

T = TypeVar("T")

@functools.singledispatch
def foo(val: T) -> T:
    return val

@foo.register
def _(x: int) -> int:
    return x + 1

reveal_type(foo("asdf"))
z: str = foo("asdf")

But type inference may be a little lacking:

~/delete λ mypy test22.py
test22.py:14: note: Revealed type is 'T`-1'
test22.py:15: error: Incompatible types in assignment (expression has type "T", variable has type "str")
Found 1 error in 1 file (checked 1 source file)

@JukkaL
Copy link
Collaborator

JukkaL commented Feb 4, 2020

It looks like there's nothing actionable here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants