Referring to a different instantiation whilst defining a generic class
The following generic generic class and a function to construct it type-checks fine:
from __future__ import annotations
from typing import TypeVar, Generic
T = TypeVar("T")
S = TypeVar("S")
class C(Generic[T]):
pass
def standalone(_dummy: S | None = None) -> C[S]:
ty: type[C[S]] = C
return object.__new__(ty)
But trying to move standalone into C as any kind of method breaks it:
from __future__ import annotations
from typing import TypeVar, Generic
T = TypeVar("T")
S = TypeVar("S")
class C(Generic[T]):
def meth(self, _dummy: S | None = None) -> C[S]:
ty: type[C[S]] = C
return object.__new__(ty)
@classmethod
def classmeth(cls, _dummy: S | None = None) -> C[S]:
ty: type[C[S]] = C
return object.__new__(ty)
@staticmethod
def staticmeth(_dummy: S | None = None) -> C[S]:
ty: type[C[S]] = C
return object.__new__(ty)
test.py
test.py:8:26 - error: Type "type[C[T@C]]" is not assignable to declared type "type[C[S@meth]]"
"type[C[T@C]]" is not assignable to "type[C[S@meth]]"
Type "type[C[T@C]]" is not assignable to type "type[C[S@meth]]"
Type parameter "T@C" is invariant, but "T@C" is not the same as "S@meth" (reportAssignmentType)
test.py:13:26 - error: Type "type[C[T@C]]" is not assignable to declared type "type[C[S@classmeth]]"
"type[C[T@C]]" is not assignable to "type[C[S@classmeth]]"
Type "type[C[T@C]]" is not assignable to type "type[C[S@classmeth]]"
Type parameter "T@C" is invariant, but "T@C" is not the same as "S@classmeth" (reportAssignmentType)
test.py:18:26 - error: Type "type[C[T@C]]" is not assignable to declared type "type[C[S@staticmeth]]"
"type[C[T@C]]" is not assignable to "type[C[S@staticmeth]]"
Type "type[C[T@C]]" is not assignable to type "type[C[S@staticmeth]]"
Type parameter "T@C" is invariant, but "T@C" is not the same as "S@staticmeth" (reportAssignmentType)
3 errors, 0 warnings, 0 informations
Notably, reveal_type(C) shows type[C[Unknown]] in all four contexts.
Additional context
I would like to return object.__new__(C) but then Pyright cannot infer the fact that C is used "at type S", and instead infers that we've passed a type[C[Unknown]] into object.__new__ and have gotten a C[Unknown] back, which doesn't match with the expected return type C[S].
I can work around this by first placing C into a type-annotated variable, thus
ty: type[C[S]] = C
return object.__new__(ty)
but for some reason this only works in a standalone function context (hence this bug report).
Notably, if I do return object.__new__(C[S]) this satisfies pyright, but instead makes cpython unhappy: at runtime we get TypeError: object.__new__(X): X is not a type object (_GenericAlias). Same error for return C[S].__new__(C[S])... which is a little odd, because isn't this supposed to be equivalent to C[S]()?
VS Code extension or command-line Pyright 1.1.403
Hmm, I agree this looks like a bug, but I'm curious why you're using this approach rather than simply specifying C[S]? By doing this, you indicate that you want an explicit specialization of C.
def meth(self, _dummy: S | None = None) -> C[S]:
ty = C[S]
return object.__new__(ty)
ty = C[S] return object.__new__(ty)
As I explained in additional context, this typechecks but doesn't actually run:
>>> object.__new__(C[S])
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
object.__new__(C[S])
~~~~~~~~~~~~~~^^^^^^
TypeError: object.__new__(X): X is not a type object (_GenericAlias)
Ah, I missed that in your original post. I'm surprised that C[S] doesn't work here. This might be a bug in the CPython implementation. @JelleZijlstra, any thoughts on this?
The object.__new__ approach requires an actual type (an instance of builtins.type, not a conceptual type in the type system). I think that's working correctly.
Why are you using object.__new__ in this way? It seems a bit odd and I'm not sure we should expect type checkers to support it.