
Pulls in the latest Salsa main branch, which supports fixpoint iteration, and uses it to handle all query cycles. With this, we no longer need to skip any corpus files to avoid panics. Latest perf results show a 6% incremental and 1% cold-check regression. This is not a "no cycles" regression, as tomllib and typeshed do trigger some definition cycles (previously handled by our old `infer_definition_types` fallback to `Unknown`). We don't currently have a benchmark we can use to measure the pure no-cycles regression, though I expect there would still be some regression; the fixpoint iteration feature in Salsa does add some overhead even for non-cyclic queries. I think this regression is within the reasonable range for this feature. We can do further optimization work later, but I don't think it's the top priority right now. So going ahead and acknowledging the regression on CodSpeed. Mypy primer is happy, so this doesn't regress anything on our currently-checked projects. I expect it probably unlocks adding a number of new projects to our ecosystem check that previously would have panicked. Fixes #13792 Fixes #14672
3.9 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]
string_annotation.py
:
class Base[T]: ...
# TODO: no error
# error: [non-subscriptable]
class Sub(Base["Sub"]): ...
reveal_type(Sub) # revealed: Literal[Sub]
bare_annotation.py
:
class Base[T]: ...
# TODO: error: [unresolved-reference]
# error: [non-subscriptable]
class Sub(Base[Sub]): ...