ty icon indicating copy to clipboard operation
ty copied to clipboard

Subscripted generic classes should not be considered instances of `type`

Open AlexWaygood opened this issue 8 months ago • 3 comments

Summary

ty currently emits no error on the following code, but it should:

class Foo[T]: ...

def requires_type(x: type): ...

requires_type(Foo[int])

Foo[int] here is not an instance of type. In the long run, it'll be quite important for us to understand this, as the runtime frequently distinguishes between generic aliases and instances of types:

REPL session demonstrating several places where generic aliases are not accepted as instances of `type`
Python 3.13.1 (main, Jan  3 2025, 12:04:03) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo[T]: ...
... 
>>> isinstance(object(), Foo[int])
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    isinstance(object(), Foo[int])
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/Users/alexw/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 1375, in __instancecheck__
    return self.__subclasscheck__(type(obj))
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/alexw/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 1378, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
                    " class and instance checks")
TypeError: Subscripted generics cannot be used with class and instance checks
>>> issubclass(object, Foo[int])
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    issubclass(object, Foo[int])
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/Users/alexw/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 1378, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
                    " class and instance checks")
TypeError: Subscripted generics cannot be used with class and instance checks
>>> from functools import singledispatch
>>> @singledispatch
... def f(arg): ...
... 
>>> @f.register(list[int])
... def f(arg): ...
... 
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
    @f.register(list[int])
     ~~~~~~~~~~^^^^^^^^^^^
  File "/Users/alexw/.pyenv/versions/3.13.1/lib/python3.13/functools.py", line 893, in register
    raise TypeError(
    ...<3 lines>...
    )
TypeError: Invalid first argument to `register()`: list[int]. Use either `@register(some_class)` or plain `@register` on an annotated function.

This bug can also be seen in that both these assertions currently pass, when they should fail:

from ty_extensions import static_assert, is_subtype_of, is_assignable_to, TypeOf

class Foo[T]: ...

static_assert(is_subtype_of(TypeOf[Foo[int]], type))
static_assert(is_assignable_to(TypeOf[Foo[int]], type))

https://play.ty.dev/d373a056-b58c-451b-a136-323e76970454

Cc. @dcreager for generics!

AlexWaygood avatar Apr 22 '25 13:04 AlexWaygood

Are you sure? Pyright and MyPy have the same behaviour.

Kroppeb avatar Jun 17 '25 22:06 Kroppeb

Yes, Alex is correct here and mypy and pyright are wrong.

jelle-openai avatar Jun 18 '25 14:06 jelle-openai

Adding a note here (for the benefit of issue search) that this is how we should also emit a diagnostic on e.g. isinstance(x, list[str])

carljm avatar Dec 18 '25 20:12 carljm