astral-sh / astral-sh/ty

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

Open
#116 3 comments 0 reactions 0 assignees View on GitHub
bug generics type properties
Dominant language
Python
Stars
19.7k
Forks
331
Avg merge
14h 29m
Merged PRs (30d)
47

Description

### Summary

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

```py
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`

```pycon
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 "", line 1, in
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 "", line 1, in
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 "", line 1, in
@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:

```py
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!

Contributor guide

Open the contributing guide

Research direction

Start with the Python reproducer and the static_assert checks using TypeOf[Foo[int]], then compare the behavior of generic aliases with type. Done means the requires_type call is rejected and both subtype and assignability assertions fail, consistently reflecting Python's runtime distinction.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.