
## Summary From #16861 This PR fixes the incorrect `ClassDef` handling of `SemanticIndexBuilder::visit_stmt`, which fixes some of the incorrect behavior of referencing the class itself in the class scope (a complete fix requires a different fix, which will be done in the another PR). --------- Co-authored-by: Carl Meyer <carl@astral.sh>
4.3 KiB
Generic classes
PEP 695 syntax
TODO: Add a red_knot_extension
function that asserts whether a function or class is generic.
This is a generic class defined using PEP 695 syntax:
class C[T]: ...
A class that inherits from a generic class, and fills its type parameters with typevars, is generic:
# TODO: no error
# error: [non-subscriptable]
class D[U](C[U]): ...
A class that inherits from a generic class, but fills its type parameters with concrete types, is not generic:
# TODO: no error
# error: [non-subscriptable]
class E(C[int]): ...
A class that inherits from a generic class, and doesn't fill its type parameters at all, implicitly
uses the default value for the typevar. In this case, that default type is Unknown
, so F
inherits from C[Unknown]
and is not itself generic.
class F(C): ...
Legacy syntax
This is a generic class defined using the legacy syntax:
from typing import Generic, TypeVar
T = TypeVar("T")
# TODO: no error
# error: [invalid-base]
class C(Generic[T]): ...
A class that inherits from a generic class, and fills its type parameters with typevars, is generic.
class D(C[T]): ...
(Examples E
and F
from above do not have analogues in the legacy syntax.)
Inferring generic class parameters
The type parameter can be specified explicitly:
class C[T]:
x: T
# TODO: no error
# TODO: revealed: C[int]
# error: [non-subscriptable]
reveal_type(C[int]()) # revealed: C
We can infer the type parameter from a type context:
c: C[int] = C()
# TODO: revealed: C[int]
reveal_type(c) # revealed: C
The typevars of a fully specialized generic class should no longer be visible:
# TODO: revealed: int
reveal_type(c.x) # revealed: T
If the type parameter is not specified explicitly, and there are no constraints that let us infer a specific type, we infer the typevar's default type:
class D[T = int]: ...
# TODO: revealed: D[int]
reveal_type(D()) # revealed: D
If a typevar does not provide a default, we use Unknown
:
# TODO: revealed: C[Unknown]
reveal_type(C()) # revealed: C
If the type of a constructor parameter is a class typevar, we can use that to infer the type parameter:
class E[T]:
def __init__(self, x: T) -> None: ...
# TODO: revealed: E[int] or E[Literal[1]]
reveal_type(E(1)) # revealed: E
The types inferred from a type context and from a constructor parameter must be consistent with each other:
# TODO: error
wrong_innards: E[int] = E("five")
Generic subclass
When a generic subclass fills its superclass's type parameter with one of its own, the actual types propagate through:
class Base[T]:
x: T | None = None
# TODO: no error
# error: [non-subscriptable]
class Sub[U](Base[U]): ...
# TODO: no error
# TODO: revealed: int | None
# error: [non-subscriptable]
reveal_type(Base[int].x) # revealed: T | None
# TODO: revealed: int | None
# error: [non-subscriptable]
reveal_type(Sub[int].x) # revealed: T | None
Cyclic class definition
A class can use itself as the type parameter of one of its superclasses. (This is also known as the curiously recurring template pattern or F-bounded quantification.)
Here, Sub
is not a generic class, since it fills its superclass's type parameter (with itself).
stub.pyi
:
class Base[T]: ...
# TODO: no error
# error: [non-subscriptable]
class Sub(Base[Sub]): ...
reveal_type(Sub) # revealed: Literal[Sub]
A similar case can work in a non-stub file, if forward references are stringified:
string_annotation.py
:
class Base[T]: ...
# TODO: no error
# error: [non-subscriptable]
class Sub(Base["Sub"]): ...
reveal_type(Sub) # revealed: Literal[Sub]
In a non-stub file, without stringified forward references, this raises a NameError
:
bare_annotation.py
:
class Base[T]: ...
# TODO: the unresolved-reference error is correct, the non-subscriptable is not
# error: [non-subscriptable]
# error: [unresolved-reference]
class Sub(Base[Sub]): ...
Another cyclic case
# TODO no error (generics)
# error: [invalid-base]
class Derived[T](list[Derived[T]]): ...