Skip to content

Checking Generic Type at Runtime #13053

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

Closed
samuelstevens opened this issue Jul 1, 2022 · 5 comments
Closed

Checking Generic Type at Runtime #13053

samuelstevens opened this issue Jul 1, 2022 · 5 comments
Labels
bug mypy got something wrong topic-type-variables

Comments

@samuelstevens
Copy link

Bug Report

When checking the type of a generic, mypy reports an error when (as far as I understand), I am still following the type contract I set.

To Reproduce

from typing import TypeVar

T = TypeVar("T")

def increment(obj: T) -> T:
    if isinstance(obj, int):
        return obj + 1
    else:
        return obj
$ mypy --strict scratch.py
scratch.py:12: error: Incompatible return value type (got "int", expected "T")
Found 1 error in 1 file (checked 1 source file)

Expected Behavior

I expect there to be no error because if obj is an int, then returning an int is the correct behavior.

Actual Behavior

Mypy reports an error.

Your Environment

  • Mypy version used: 0.961
  • Mypy command-line flags: --strict
  • Mypy configuration options from mypy.ini (and other config files): no other config
  • Python version used: 3.9.7
  • Operating system and version: Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)
@samuelstevens samuelstevens added the bug mypy got something wrong label Jul 1, 2022
@erictraut
Copy link

This behavior is correct because isinstance(obj, int) does not guarantee that obj is an int; it simply guarantees that it's a subtype of int. However, the __add__ method for int always returns an int, not a subtype of int.

class IntSubclass(int): ...

i1 = IntSubclass(0)
i2 = increment(i1)

# At runtime, i2 is an instance of 'int', not 'IntSubclass'.
print(type(i2)) # <class 'int'>

@samuelstevens
Copy link
Author

That makes sense. Thank you for explainging!

My actual use case is more complicated, however. Instead of int, I have some objects of a class that call some methods. Something like:

from typing import TypeVar

T = TypeVar("T")


class Dummy:
    def __init__(self):
        self.state: int = 0

    def mutate_state(self):
        self.state += 1


def handle(obj: T) -> T:
    if isinstance(obj, Dummy):
        obj.mutate_state()
        return obj
    else:
        return obj

I get the same error:

$ mypy --strict scratch.py
scratch.py:17: error: Incompatible return value type (got "Dummy", expected "T")
Found 1 error in 1 file (checked 1 source file)

Even though obj might just be a subclass of Dummy, I am still returning obj directly which is still of type T. So shouldn't this be error-free?

@erictraut
Copy link

Yes, your revised sample is type safe. I don't think mypy should emit an error in this case. By comparison, pyright does not.

Here's a workaround for mypy:

@overload
def handle(obj: Dummy) -> Dummy: ...
@overload
def handle(obj: T) -> T: ...

def handle(obj: T | Dummy) -> T | Dummy:
    if isinstance(obj, Dummy):
        obj.mutate_state()
        return obj
    else:
        return obj

@samuelstevens
Copy link
Author

samuelstevens commented Jul 2, 2022

Thanks for the suggestion; I will try it in my full case but I was hoping to avoid all the extra boilerplate! (I have half a dozen classes that have special isinstance cases.)

I'll leave this open as a bug in mypy. If I have time, maybe I will be able to look into why this happens (probably not).

@ilevkivskyi
Copy link
Member

Yeah, this is not a bug. The revised example is going to be fixed in #19183

@ilevkivskyi ilevkivskyi closed this as not planned Won't fix, can't repro, duplicate, stale May 31, 2025
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

4 participants