[ty] Understand classes that inherit from subscripted Protocol[] as generic (#17832)

This commit is contained in:
Alex Waygood 2025-05-09 17:39:15 +01:00 committed by GitHub
parent 2370297cde
commit d1bb10a66b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 451 additions and 183 deletions

View file

@ -59,7 +59,29 @@ type KeyDiagnosticFields = (
Severity,
);
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[];
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(8224..8254),
"Argument to this function is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(16914..16948),
"Argument to this function is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(17319..17363),
"Argument to this function is incorrect",
Severity::Error,
),
];
fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name())

View file

@ -33,7 +33,9 @@ class Shape:
reveal_type(self) # revealed: Unknown
return self
reveal_type(Shape().nested_type()) # revealed: @Todo(specialized non-generic class)
# TODO: should be `list[Shape]`
reveal_type(Shape().nested_type()) # revealed: list[Self]
reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape):

View file

@ -14,7 +14,7 @@ Ts = TypeVarTuple("Ts")
def append_int(*args: *Ts) -> tuple[*Ts, int]:
# TODO: tuple[*Ts]
reveal_type(args) # revealed: tuple
reveal_type(args) # revealed: tuple[Unknown, ...]
return (*args, 1)

View file

@ -30,24 +30,22 @@ def f(
ordered_dict_bare: typing.OrderedDict,
ordered_dict_parametrized: typing.OrderedDict[int, str],
):
# TODO: revealed: list[Unknown]
reveal_type(list_bare) # revealed: list
reveal_type(list_bare) # revealed: list[Unknown]
# TODO: revealed: list[int]
reveal_type(list_parametrized) # revealed: list
reveal_type(list_parametrized) # revealed: list[Unknown]
reveal_type(dict_bare) # revealed: dict[Unknown, Unknown]
# TODO: revealed: dict[int, str]
reveal_type(dict_parametrized) # revealed: dict[Unknown, Unknown]
# TODO: revealed: set[Unknown]
reveal_type(set_bare) # revealed: set
reveal_type(set_bare) # revealed: set[Unknown]
# TODO: revealed: set[int]
reveal_type(set_parametrized) # revealed: set
reveal_type(set_parametrized) # revealed: set[Unknown]
# TODO: revealed: frozenset[Unknown]
reveal_type(frozen_set_bare) # revealed: frozenset
reveal_type(frozen_set_bare) # revealed: frozenset[Unknown]
# TODO: revealed: frozenset[str]
reveal_type(frozen_set_parametrized) # revealed: frozenset
reveal_type(frozen_set_parametrized) # revealed: frozenset[Unknown]
reveal_type(chain_map_bare) # revealed: ChainMap[Unknown, Unknown]
# TODO: revealed: ChainMap[str, int]
@ -61,10 +59,9 @@ def f(
# TODO: revealed: defaultdict[str, int]
reveal_type(default_dict_parametrized) # revealed: defaultdict[Unknown, Unknown]
# TODO: revealed: deque[Unknown]
reveal_type(deque_bare) # revealed: deque
reveal_type(deque_bare) # revealed: deque[Unknown]
# TODO: revealed: deque[str]
reveal_type(deque_parametrized) # revealed: deque
reveal_type(deque_parametrized) # revealed: deque[Unknown]
reveal_type(ordered_dict_bare) # revealed: OrderedDict[Unknown, Unknown]
# TODO: revealed: OrderedDict[int, str]
@ -84,26 +81,23 @@ import typing
class ListSubclass(typing.List): ...
# TODO: generic protocols
# revealed: tuple[<class 'ListSubclass'>, <class 'list'>, <class 'MutableSequence'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(ListSubclass.__mro__)
class DictSubclass(typing.Dict): ...
# TODO: generic protocols
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# TODO: should not have multiple `Generic[]` elements
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(DictSubclass.__mro__)
class SetSubclass(typing.Set): ...
# TODO: generic protocols
# revealed: tuple[<class 'SetSubclass'>, <class 'set'>, <class 'MutableSet'>, <class 'AbstractSet'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(SetSubclass.__mro__)
class FrozenSetSubclass(typing.FrozenSet): ...
# TODO: generic protocols
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset'>, <class 'AbstractSet'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(FrozenSetSubclass.__mro__)
####################
@ -112,31 +106,30 @@ reveal_type(FrozenSetSubclass.__mro__)
class ChainMapSubclass(typing.ChainMap): ...
# TODO: generic protocols
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# TODO: should not have multiple `Generic[]` elements
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(ChainMapSubclass.__mro__)
class CounterSubclass(typing.Counter): ...
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
# TODO: Should have one `Generic[]` element, not three(!)
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
reveal_type(CounterSubclass.__mro__)
class DefaultDictSubclass(typing.DefaultDict): ...
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# TODO: Should not have multiple `Generic[]` elements
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(DefaultDictSubclass.__mro__)
class DequeSubclass(typing.Deque): ...
# TODO: generic protocols
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque'>, <class 'MutableSequence'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(DequeSubclass.__mro__)
class OrderedDictSubclass(typing.OrderedDict): ...
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# TODO: Should not have multiple `Generic[]` elements
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(OrderedDictSubclass.__mro__)
```

View file

@ -16,7 +16,7 @@ Alias: TypeAlias = int
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
@ -24,7 +24,7 @@ def g() -> TypeGuard[int]: ...
def h() -> TypeIs[int]: ...
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)]
return callback(42, *args, **kwargs)

View file

@ -61,7 +61,7 @@ reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
reveal_type(e) # revealed: @Todo(full tuple[...] support)
reveal_type(f) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(h) # revealed: tuple[@Todo(specialized non-generic class), @Todo(specialized non-generic class)]
reveal_type(h) # revealed: tuple[list[int], list[int]]
reveal_type(i) # revealed: tuple[str | int, str | int]
reveal_type(j) # revealed: tuple[str | int]
@ -76,7 +76,7 @@ a: tuple[()] = (1, 2)
# error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`"
b: tuple[int] = ("foo",)
# error: [invalid-assignment] "Object of type `tuple[list, Literal["foo"]]` is not assignable to `tuple[str | int, str]`"
# error: [invalid-assignment] "Object of type `tuple[list[Unknown], Literal["foo"]]` is not assignable to `tuple[str | int, str]`"
c: tuple[str | int, str] = ([], "foo")
```

View file

@ -1728,7 +1728,7 @@ reveal_type(False.real) # revealed: Literal[0]
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
```py
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: @Todo(specialized non-generic class), /) -> bytes
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
reveal_type(b"foo".join)
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
reveal_type(b"foo".endswith)

View file

@ -79,8 +79,7 @@ lambda x=1: reveal_type(x) # revealed: Unknown | Literal[1]
Using a variadic parameter:
```py
# TODO: should be `tuple[Unknown, ...]` (needs generics)
lambda *args: reveal_type(args) # revealed: tuple
lambda *args: reveal_type(args) # revealed: tuple[Unknown, ...]
```
Using a keyword-variadic parameter:

View file

@ -25,8 +25,8 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5,
reveal_type(f) # revealed: Literal[4]
reveal_type(g) # revealed: Unknown | Literal[5]
reveal_type(h) # revealed: Literal[6]
# TODO: should be `tuple[object, ...]` (needs generics)
reveal_type(args) # revealed: tuple
# TODO: should be `tuple[object, ...]`
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(kwargs) # revealed: dict[str, str]
```
@ -36,8 +36,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5,
```py
def g(*args, **kwargs):
# TODO: should be `tuple[Unknown, ...]` (needs generics)
reveal_type(args) # revealed: tuple
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(kwargs) # revealed: dict[str, Unknown]
```

View file

@ -399,7 +399,7 @@ In a specialized generic alias, the specialization is applied to the attributes
class.
```py
from typing import Generic, TypeVar
from typing import Generic, TypeVar, Protocol
T = TypeVar("T")
U = TypeVar("U")
@ -425,6 +425,33 @@ reveal_type(c.y) # revealed: str
reveal_type(c.method1()) # revealed: int
reveal_type(c.method2()) # revealed: str
reveal_type(c.method3()) # revealed: LinkedList[int]
class SomeProtocol(Protocol[T]):
x: T
class Foo:
x: int
class D(Generic[T, U]):
x: T
y: U
def method1(self) -> T:
return self.x
def method2(self) -> U:
return self.y
def method3(self) -> SomeProtocol[T]:
return Foo()
d = D[int, str]()
reveal_type(d.x) # revealed: int
reveal_type(d.y) # revealed: str
reveal_type(d.method1()) # revealed: int
reveal_type(d.method2()) # revealed: str
reveal_type(d.method3()) # revealed: SomeProtocol[int]
reveal_type(d.method3().x) # revealed: int
```
## Cyclic class definitions

View file

@ -38,6 +38,7 @@ T = TypeVar("T")
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
TestList = list[TypeVar("W")]
```

View file

@ -65,7 +65,7 @@ from typing import Generic, TypeVar
T = TypeVar("T")
# error: [invalid-generic-class] "Cannot both inherit from `Generic` and use PEP 695 type variables"
# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
class BothGenericSyntaxes[U](Generic[T]): ...
```

View file

@ -785,7 +785,7 @@ from subexporter import *
# TODO: Should be `list[str]`
# TODO: Should we avoid including `Unknown` for this case?
reveal_type(__all__) # revealed: Unknown | list
reveal_type(__all__) # revealed: Unknown | list[Unknown]
__all__.append("B")

View file

@ -3,5 +3,5 @@
## Empty list
```py
reveal_type([]) # revealed: list
reveal_type([]) # revealed: list[Unknown]
```

View file

@ -3,5 +3,5 @@
## Basic set
```py
reveal_type({1, 2}) # revealed: set
reveal_type({1, 2}) # revealed: set[Unknown]
```

View file

@ -48,8 +48,8 @@ alice2 = Person2(1, "Alice")
# TODO: should be an error
Person2(1)
reveal_type(alice2.id) # revealed: @Todo(GenericAlias instance)
reveal_type(alice2.name) # revealed: @Todo(GenericAlias instance)
reveal_type(alice2.id) # revealed: @Todo(functional `NamedTuple` syntax)
reveal_type(alice2.name) # revealed: @Todo(functional `NamedTuple` syntax)
```
### Multiple Inheritance

View file

@ -133,11 +133,11 @@ match x:
case "foo" | 42 | None:
reveal_type(x) # revealed: Literal["foo", 42] | None
case "foo" | tuple():
reveal_type(x) # revealed: tuple
reveal_type(x) # revealed: tuple[Unknown, ...]
case True | False:
reveal_type(x) # revealed: bool
case 3.14 | 2.718 | 1.414:
reveal_type(x) # revealed: float & ~tuple
reveal_type(x) # revealed: float & ~tuple[Unknown, ...]
reveal_type(x) # revealed: object
```
@ -155,7 +155,7 @@ reveal_type(x) # revealed: object
match x:
case "foo" | 42 | None if reveal_type(x): # revealed: Literal["foo", 42] | None
pass
case "foo" | tuple() if reveal_type(x): # revealed: Literal["foo"] | tuple
case "foo" | tuple() if reveal_type(x): # revealed: Literal["foo"] | tuple[Unknown, ...]
pass
case True | False if reveal_type(x): # revealed: bool
pass

View file

@ -58,6 +58,7 @@ class Bar1(Protocol[T], Generic[T]):
class Bar2[T](Protocol):
x: T
# error: [invalid-generic-class] "Cannot both inherit from subscripted `typing.Protocol` and use PEP 695 type variables"
class Bar3[T](Protocol[T]):
x: T
```
@ -70,8 +71,8 @@ simultaneously:
class DuplicateBases(Protocol, Protocol[T]):
x: T
# TODO: should not have `Protocol` multiple times
# revealed: tuple[<class 'DuplicateBases'>, typing.Protocol, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# TODO: should not have `Protocol` or `Generic` multiple times
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], <class 'object'>]
reveal_type(DuplicateBases.__mro__)
```

View file

@ -13,8 +13,7 @@ reveal_type(__loader__) # revealed: LoaderProtocol | None
reveal_type(__package__) # revealed: str | None
reveal_type(__doc__) # revealed: str | None
reveal_type(__spec__) # revealed: ModuleSpec | None
reveal_type(__path__) # revealed: @Todo(specialized non-generic class)
reveal_type(__path__) # revealed: MutableSequence[str]
class X:
reveal_type(__name__) # revealed: str

View file

@ -9,13 +9,13 @@ A list can be indexed into with:
```py
x = [1, 2, 3]
reveal_type(x) # revealed: list
reveal_type(x) # revealed: list[Unknown]
# TODO reveal int
reveal_type(x[0]) # revealed: Unknown
# TODO reveal list
reveal_type(x[0:1]) # revealed: @Todo(specialized non-generic class)
# TODO reveal list[int]
reveal_type(x[0:1]) # revealed: list[Unknown]
# error: [call-non-callable]
reveal_type(x["a"]) # revealed: Unknown

View file

@ -83,9 +83,8 @@ python-version = "3.9"
```py
class A(tuple[int, str]): ...
# Runtime value: `(A, tuple, object)`
# TODO: Generics
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, @Todo(GenericAlias instance), <class 'object'>]
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(A.__mro__)
```
## `typing.Tuple`
@ -100,7 +99,7 @@ from typing import Any, Tuple
class A: ...
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
reveal_type(c) # revealed: tuple
reveal_type(c) # revealed: tuple[Unknown, ...]
reveal_type(d) # revealed: tuple[int, A]
reveal_type(e) # revealed: @Todo(full tuple[...] support)
```
@ -115,7 +114,6 @@ from typing import Tuple
class C(Tuple): ...
# TODO: generic protocols
# revealed: tuple[<class 'C'>, <class 'tuple'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
reveal_type(C.__mro__)
```

View file

@ -151,7 +151,8 @@ static_assert(not is_subtype_of(tuple[B1, B2], tuple[()]))
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1]))
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1, A2, Unrelated]))
static_assert(is_subtype_of(tuple[int], tuple))
# TODO: should pass
static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error]
```
## Union types

View file

@ -6,14 +6,20 @@ cpython # access to field whilst being initialized, too many cycle iterations
discord.py # some kind of hang, only when multi-threaded?
freqtrade # hangs
hydpy # too many iterations
ibis # too many iterations
jax # too many iterations
packaging # too many iterations
pandas # slow
pandas-stubs # hangs/slow, or else https://github.com/salsa-rs/salsa/issues/831
pandera # stack overflow
pip # vendors packaging, see above
prefect # slow
pylint # cycle panics (self-recursive type alias)
pyodide # too many cycle iterations
pywin32 # bad use-def map (binding with definitely-visible unbound)
schemathesis # https://github.com/salsa-rs/salsa/issues/831
scikit-learn # success, but mypy-primer hangs processing the output
setuptools # vendors packaging, see above
spack # success, but mypy-primer hangs processing the output
spark # too many iterations
steam.py # hangs

View file

@ -42,13 +42,11 @@ git-revise
graphql-core
httpx-caching
hydra-zen
ibis
ignite
imagehash
isort
itsdangerous
janus
jax
jinja
koda-validate
kopf
@ -70,11 +68,9 @@ openlibrary
operator
optuna
paasta
packaging
paroxython
parso
pegen
pip
poetry
porcupine
ppb-vector
@ -86,7 +82,6 @@ pydantic
pyinstrument
pyjwt
pylox
pyodide
pyp
pyppeteer
pytest
@ -100,7 +95,6 @@ rotki
schema_salad
scipy
scrapy
setuptools
sockeye
speedrun.com_global_scoreboard_webapp
sphinx

View file

@ -662,7 +662,7 @@ impl<'db> Type<'db> {
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
match self {
Self::Dynamic(DynamicType::Todo(_) | DynamicType::SubscriptedProtocol) => true,
Self::Dynamic(DynamicType::Todo(_)) => true,
Self::AlwaysFalsy
| Self::AlwaysTruthy
@ -703,9 +703,7 @@ impl<'db> Type<'db> {
}
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
SubclassOfInner::Dynamic(
DynamicType::Todo(_) | DynamicType::SubscriptedProtocol,
) => true,
SubclassOfInner::Dynamic(DynamicType::Todo(_)) => true,
SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
SubclassOfInner::Class(_) => false,
},
@ -722,12 +720,10 @@ impl<'db> Type<'db> {
Self::BoundSuper(bound_super) => {
matches!(
bound_super.pivot_class(db),
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::SubscriptedProtocol)
ClassBase::Dynamic(DynamicType::Todo(_))
) || matches!(
bound_super.owner(db),
SuperOwnerKind::Dynamic(
DynamicType::Todo(_) | DynamicType::SubscriptedProtocol
)
SuperOwnerKind::Dynamic(DynamicType::Todo(_))
)
}
@ -3939,6 +3935,9 @@ impl<'db> Type<'db> {
);
Signatures::single(signature)
}
Some(KnownClass::NamedTuple) => {
Signatures::single(CallableSignature::todo("functional `NamedTuple` syntax"))
}
Some(KnownClass::Object) => {
// ```py
// class object:
@ -4328,6 +4327,12 @@ impl<'db> Type<'db> {
return Ok(UnionType::from_elements(db, tuple_type.elements(db)));
}
if let Type::GenericAlias(alias) = self {
if alias.origin(db).is_known(db, KnownClass::Tuple) {
return Ok(todo_type!("*tuple[] annotations"));
}
}
let try_call_dunder_getitem = || {
self.try_call_dunder(
db,
@ -4826,7 +4831,7 @@ impl<'db> Type<'db> {
KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")),
KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")),
KnownInstanceType::Protocol => Err(InvalidTypeExpressionError {
KnownInstanceType::Protocol(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
fallback_type: Type::unknown(),
}),
@ -4931,9 +4936,6 @@ impl<'db> Type<'db> {
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
Some(KnownClass::NamedTuple) => Ok(todo_type!(
"Support for functional `typing.NamedTuple` syntax"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self
@ -5093,6 +5095,10 @@ impl<'db> Type<'db> {
instance.apply_type_mapping(db, type_mapping),
),
Type::ProtocolInstance(instance) => {
Type::ProtocolInstance(instance.apply_specialization(db, type_mapping))
}
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(
function.apply_type_mapping(db, type_mapping),
@ -5176,8 +5182,6 @@ impl<'db> Type<'db> {
| Type::StringLiteral(_)
| Type::BytesLiteral(_)
| Type::BoundSuper(_)
// Same for `ProtocolInstance`
| Type::ProtocolInstance(_)
| Type::KnownInstance(_) => self,
}
}
@ -5498,9 +5502,6 @@ pub enum DynamicType {
///
/// This variant should be created with the `todo_type!` macro.
Todo(TodoType),
/// Temporary type until we support generic protocols.
/// We use a separate variant (instead of `Todo(…)`) in order to be able to match on them explicitly.
SubscriptedProtocol,
}
impl std::fmt::Display for DynamicType {
@ -5511,11 +5512,6 @@ impl std::fmt::Display for DynamicType {
// `DynamicType::Todo`'s display should be explicit that is not a valid display of
// any other type
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
DynamicType::SubscriptedProtocol => f.write_str(if cfg!(debug_assertions) {
"@Todo(`Protocol[]` subscript)"
} else {
"@Todo"
}),
}
}
}

View file

@ -312,7 +312,7 @@ impl<'db> ClassType<'db> {
ClassBase::Dynamic(_) => false,
// Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol | ClassBase::Generic(_) => false,
ClassBase::Protocol(_) | ClassBase::Generic(_) => false,
ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
@ -350,7 +350,7 @@ impl<'db> ClassType<'db> {
ClassBase::Dynamic(_) => false,
// Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol | ClassBase::Generic(_) => false,
ClassBase::Protocol(_) | ClassBase::Generic(_) => false,
ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
@ -536,7 +536,10 @@ impl<'db> ClassLiteral<'db> {
pub(crate) fn legacy_generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
self.explicit_bases(db).iter().find_map(|base| match base {
Type::KnownInstance(KnownInstanceType::Generic(generic_context)) => *generic_context,
Type::KnownInstance(
KnownInstanceType::Generic(generic_context)
| KnownInstanceType::Protocol(generic_context),
) => *generic_context,
_ => None,
})
}
@ -608,6 +611,17 @@ impl<'db> ClassLiteral<'db> {
}
}
/// Returns a specialization of this class with a `@Todo`-type
pub(crate) fn todo_specialization(self, db: &'db dyn Db, todo: &'static str) -> ClassType<'db> {
match self.generic_context(db) {
None => ClassType::NonGeneric(self),
Some(generic_context) => {
let specialization = generic_context.todo_specialization(db, todo);
ClassType::Generic(GenericAlias::new(db, self, specialization))
}
}
}
/// Returns the unknown specialization of this class. For non-generic classes, the class is
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
/// maps each of the class's typevars to `Unknown`.
@ -678,13 +692,11 @@ impl<'db> ClassLiteral<'db> {
// - OR be the last-but-one base (with the final base being `Generic[]` or `object`)
// - OR be the last-but-two base (with the penultimate base being `Generic[]`
// and the final base being `object`)
self.explicit_bases(db).iter().rev().take(3).any(|base| {
matches!(
base,
Type::KnownInstance(KnownInstanceType::Protocol)
| Type::Dynamic(DynamicType::SubscriptedProtocol)
)
})
self.explicit_bases(db)
.iter()
.rev()
.take(3)
.any(|base| matches!(base, Type::KnownInstance(KnownInstanceType::Protocol(_))))
})
}
@ -1011,12 +1023,8 @@ impl<'db> ClassLiteral<'db> {
for superclass in mro_iter {
match superclass {
ClassBase::Dynamic(DynamicType::SubscriptedProtocol)
| ClassBase::Generic(_)
| ClassBase::Protocol => {
// TODO: We currently skip `Protocol` when looking up class members, in order to
// avoid creating many dynamic types in our test suite that would otherwise
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
ClassBase::Generic(_) | ClassBase::Protocol(_) => {
// Skip over these very special class bases that aren't really classes.
}
ClassBase::Dynamic(_) => {
// Note: calling `Type::from(superclass).member()` would be incorrect here.
@ -1354,12 +1362,8 @@ impl<'db> ClassLiteral<'db> {
for superclass in self.iter_mro(db, specialization) {
match superclass {
ClassBase::Dynamic(DynamicType::SubscriptedProtocol)
| ClassBase::Generic(_)
| ClassBase::Protocol => {
// TODO: We currently skip these when looking up instance members, in order to
// avoid creating many dynamic types in our test suite that would otherwise
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
ClassBase::Generic(_) | ClassBase::Protocol(_) => {
// Skip over these very special class bases that aren't really classes.
}
ClassBase::Dynamic(_) => {
return SymbolAndQualifiers::todo(

View file

@ -18,7 +18,7 @@ pub enum ClassBase<'db> {
Class(ClassType<'db>),
/// Although `Protocol` is not a class in typeshed's stubs, it is at runtime,
/// and can appear in the MRO of a class.
Protocol,
Protocol(Option<GenericContext<'db>>),
/// Bare `Generic` cannot be subclassed directly in user code,
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
/// `Protocol[T]`, or bare `Protocol`.
@ -50,11 +50,17 @@ impl<'db> ClassBase<'db> {
ClassBase::Class(ClassType::Generic(alias)) => {
write!(f, "<class '{}'>", alias.display(self.db))
}
ClassBase::Protocol => f.write_str("typing.Protocol"),
ClassBase::Protocol(generic_context) => {
f.write_str("typing.Protocol")?;
if let Some(generic_context) = generic_context {
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}
ClassBase::Generic(generic_context) => {
f.write_str("typing.Generic")?;
if let Some(generic_context) = generic_context {
write!(f, "{}", generic_context.display(self.db))?;
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}
@ -71,9 +77,7 @@ impl<'db> ClassBase<'db> {
ClassBase::Dynamic(DynamicType::Any) => "Any",
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
ClassBase::Dynamic(DynamicType::Todo(_)) => "@Todo",
ClassBase::Protocol | ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => {
"Protocol"
}
ClassBase::Protocol(_) => "Protocol",
ClassBase::Generic(_) => "Generic",
}
}
@ -199,7 +203,9 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::Callable => {
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
}
KnownInstanceType::Protocol => Some(ClassBase::Protocol),
KnownInstanceType::Protocol(generic_context) => {
Some(ClassBase::Protocol(generic_context))
}
KnownInstanceType::Generic(generic_context) => {
Some(ClassBase::Generic(generic_context))
}
@ -210,14 +216,14 @@ impl<'db> ClassBase<'db> {
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
match self {
Self::Class(class) => Some(class),
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => None,
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None,
}
}
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
match self {
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => self,
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self,
}
}
@ -241,7 +247,7 @@ impl<'db> ClassBase<'db> {
.try_mro(db, specialization)
.is_err_and(MroError::is_cycle)
}
ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol => false,
ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false,
}
}
@ -252,11 +258,8 @@ impl<'db> ClassBase<'db> {
additional_specialization: Option<Specialization<'db>>,
) -> impl Iterator<Item = ClassBase<'db>> {
match self {
ClassBase::Protocol => {
ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(None))
}
ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => {
ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(None))
ClassBase::Protocol(context) => {
ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context))
}
ClassBase::Dynamic(_) | ClassBase::Generic(_) => {
ClassBaseMroIterator::length_2(db, self)
@ -279,7 +282,9 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
match value {
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
ClassBase::Class(class) => class.into(),
ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol),
ClassBase::Protocol(generic_context) => {
Type::KnownInstance(KnownInstanceType::Protocol(generic_context))
}
ClassBase::Generic(generic_context) => {
Type::KnownInstance(KnownInstanceType::Generic(generic_context))
}

View file

@ -174,7 +174,9 @@ impl Display for DisplayRepresentation<'_> {
function = function.name(self.db),
specialization = if let Some(specialization) = function.specialization(self.db)
{
specialization.display_short(self.db).to_string()
specialization
.display_short(self.db, TupleSpecialization::No)
.to_string()
} else {
String::new()
},
@ -187,7 +189,9 @@ impl Display for DisplayRepresentation<'_> {
function = function.name(self.db),
specialization = if let Some(specialization) = function.specialization(self.db)
{
specialization.display_short(self.db).to_string()
specialization
.display_short(self.db, TupleSpecialization::No)
.to_string()
} else {
String::new()
},
@ -274,7 +278,10 @@ impl Display for DisplayGenericAlias<'_> {
f,
"{origin}{specialization}",
origin = self.origin.name(self.db),
specialization = self.specialization.display_short(self.db),
specialization = self.specialization.display_short(
self.db,
TupleSpecialization::from_class(self.db, self.origin)
),
)
}
}
@ -327,22 +334,32 @@ impl Display for DisplayGenericContext<'_> {
impl<'db> Specialization<'db> {
/// Renders the specialization in full, e.g. `{T = int, U = str}`.
pub fn display(&'db self, db: &'db dyn Db) -> DisplaySpecialization<'db> {
pub fn display(
&'db self,
db: &'db dyn Db,
tuple_specialization: TupleSpecialization,
) -> DisplaySpecialization<'db> {
DisplaySpecialization {
typevars: self.generic_context(db).variables(db),
types: self.types(db),
db,
full: true,
tuple_specialization,
}
}
/// Renders the specialization as it would appear in a subscript expression, e.g. `[int, str]`.
pub fn display_short(&'db self, db: &'db dyn Db) -> DisplaySpecialization<'db> {
pub fn display_short(
&'db self,
db: &'db dyn Db,
tuple_specialization: TupleSpecialization,
) -> DisplaySpecialization<'db> {
DisplaySpecialization {
typevars: self.generic_context(db).variables(db),
types: self.types(db),
db,
full: false,
tuple_specialization,
}
}
}
@ -352,6 +369,7 @@ pub struct DisplaySpecialization<'db> {
types: &'db [Type<'db>],
db: &'db dyn Db,
full: bool,
tuple_specialization: TupleSpecialization,
}
impl Display for DisplaySpecialization<'_> {
@ -373,11 +391,34 @@ impl Display for DisplaySpecialization<'_> {
}
ty.display(self.db).fmt(f)?;
}
if self.tuple_specialization.is_yes() {
f.write_str(", ...")?;
}
f.write_char(']')
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TupleSpecialization {
Yes,
No,
}
impl TupleSpecialization {
const fn is_yes(self) -> bool {
matches!(self, Self::Yes)
}
fn from_class(db: &dyn Db, class: ClassLiteral) -> Self {
if class.is_known(db, KnownClass::Tuple) {
Self::Yes
} else {
Self::No
}
}
}
impl<'db> CallableType<'db> {
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayCallableType<'db> {
DisplayCallableType {

View file

@ -4,8 +4,8 @@ use rustc_hash::FxHashMap;
use crate::semantic_index::SemanticIndex;
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{
declaration_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarVariance, UnionType,
declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarVariance, UnionType,
};
use crate::{Db, FxOrderSet};
@ -17,6 +17,7 @@ use crate::{Db, FxOrderSet};
pub struct GenericContext<'db> {
#[returns(ref)]
pub(crate) variables: FxOrderSet<TypeVarInstance<'db>>,
pub(crate) origin: GenericContextOrigin,
}
impl<'db> GenericContext<'db> {
@ -30,7 +31,7 @@ impl<'db> GenericContext<'db> {
.iter()
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
.collect();
Self::new(db, variables)
Self::new(db, variables, GenericContextOrigin::TypeParameterList)
}
fn variable_from_type_param(
@ -76,7 +77,11 @@ impl<'db> GenericContext<'db> {
if variables.is_empty() {
return None;
}
Some(Self::new(db, variables))
Some(Self::new(
db,
variables,
GenericContextOrigin::LegacyGenericFunction,
))
}
/// Creates a generic context from the legacy `TypeVar`s that appear in class's base class
@ -92,7 +97,7 @@ impl<'db> GenericContext<'db> {
if variables.is_empty() {
return None;
}
Some(Self::new(db, variables))
Some(Self::new(db, variables, GenericContextOrigin::Inherited))
}
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
@ -133,6 +138,20 @@ impl<'db> GenericContext<'db> {
self.specialize_partial(db, &vec![None; self.variables(db).len()])
}
#[allow(unused_variables)] // Only unused in release builds
pub(crate) fn todo_specialization(
self,
db: &'db dyn Db,
todo: &'static str,
) -> Specialization<'db> {
let types = self
.variables(db)
.iter()
.map(|typevar| typevar.default_ty(db).unwrap_or(todo_type!(todo)))
.collect();
self.specialize(db, types)
}
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
let types = self
.variables(db)
@ -209,6 +228,58 @@ impl<'db> GenericContext<'db> {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum GenericContextOrigin {
LegacyBase(LegacyGenericBase),
Inherited,
LegacyGenericFunction,
TypeParameterList,
}
impl GenericContextOrigin {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::LegacyBase(base) => base.as_str(),
Self::Inherited => "inherited",
Self::LegacyGenericFunction => "legacy generic function",
Self::TypeParameterList => "type parameter list",
}
}
}
impl std::fmt::Display for GenericContextOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum LegacyGenericBase {
Generic,
Protocol,
}
impl LegacyGenericBase {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Generic => "`typing.Generic`",
Self::Protocol => "subscripted `typing.Protocol`",
}
}
}
impl std::fmt::Display for LegacyGenericBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl From<LegacyGenericBase> for GenericContextOrigin {
fn from(base: LegacyGenericBase) -> Self {
Self::LegacyBase(base)
}
}
/// An assignment of a specific type to each type variable in a generic scope.
///
/// TODO: Handle nested specializations better, with actual parent links to the specialization of

View file

@ -107,6 +107,7 @@ use super::diagnostic::{
report_unresolved_reference, INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL,
REDUNDANT_CAST, STATIC_ASSERT_ERROR, SUBCLASS_OF_FINAL_CLASS, TYPE_ASSERTION_FAILURE,
};
use super::generics::{GenericContextOrigin, LegacyGenericBase};
use super::slots::check_class_slots;
use super::string_annotation::{
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION,
@ -1020,14 +1021,16 @@ impl<'db> TypeInferenceBuilder<'db> {
// (5) Check that a generic class does not have invalid or conflicting generic
// contexts.
if class.pep695_generic_context(self.db()).is_some()
&& class.legacy_generic_context(self.db()).is_some()
{
if let Some(builder) = self.context.report_lint(&INVALID_GENERIC_CLASS, class_node)
{
builder.into_diagnostic(
"Cannot both inherit from `Generic` and use PEP 695 type variables",
);
if class.pep695_generic_context(self.db()).is_some() {
if let Some(legacy_context) = class.legacy_generic_context(self.db()) {
if let Some(builder) =
self.context.report_lint(&INVALID_GENERIC_CLASS, class_node)
{
builder.into_diagnostic(format_args!(
"Cannot both inherit from {} and use PEP 695 type variables",
legacy_context.origin(self.db())
));
}
}
}
@ -4734,6 +4737,7 @@ impl<'db> TypeInferenceBuilder<'db> {
| KnownClass::Property
| KnownClass::Super
| KnownClass::TypeVar
| KnownClass::NamedTuple
)
)
// temporary special-casing for all subclasses of `enum.Enum`
@ -5795,8 +5799,6 @@ impl<'db> TypeInferenceBuilder<'db> {
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
(todo @ Type::Dynamic(DynamicType::Todo(_)), _, _)
| (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo),
(todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _, _)
| (_, todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _) => Some(todo),
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some(
@ -6884,6 +6886,13 @@ impl<'db> TypeInferenceBuilder<'db> {
// special cases, too.
let value_ty = self.infer_expression(value);
if let Type::ClassLiteral(class) = value_ty {
if class.is_known(self.db(), KnownClass::Tuple) {
self.infer_expression(slice);
// TODO heterogeneous and homogeneous tuples in value expressions
return Type::from(
class.todo_specialization(self.db(), "Generic tuple specializations"),
);
}
if let Some(generic_context) = class.generic_context(self.db()) {
return self.infer_explicit_class_specialization(
subscript,
@ -7072,14 +7081,44 @@ impl<'db> TypeInferenceBuilder<'db> {
value_ty,
Type::IntLiteral(i64::from(bool)),
),
(Type::KnownInstance(KnownInstanceType::Protocol), _, _) => {
Type::Dynamic(DynamicType::SubscriptedProtocol)
(Type::KnownInstance(KnownInstanceType::Protocol(None)), Type::Tuple(typevars), _) => {
self.legacy_generic_class_context(
value_node,
typevars.elements(self.db()),
LegacyGenericBase::Protocol,
)
.map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context))))
.unwrap_or_else(Type::unknown)
}
(Type::KnownInstance(KnownInstanceType::Protocol(None)), typevar, _) => self
.legacy_generic_class_context(
value_node,
std::slice::from_ref(&typevar),
LegacyGenericBase::Protocol,
)
.map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context))))
.unwrap_or_else(Type::unknown),
(Type::KnownInstance(KnownInstanceType::Protocol(Some(_))), _, _) => {
// TODO: emit a diagnostic
todo_type!("doubly-specialized typing.Protocol")
}
(Type::KnownInstance(KnownInstanceType::Generic(None)), Type::Tuple(typevars), _) => {
self.infer_subscript_legacy_generic_class(value_node, typevars.elements(self.db()))
self.legacy_generic_class_context(
value_node,
typevars.elements(self.db()),
LegacyGenericBase::Generic,
)
.map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context))))
.unwrap_or_else(Type::unknown)
}
(Type::KnownInstance(KnownInstanceType::Generic(None)), typevar, _) => self
.infer_subscript_legacy_generic_class(value_node, std::slice::from_ref(&typevar)),
.legacy_generic_class_context(
value_node,
std::slice::from_ref(&typevar),
LegacyGenericBase::Generic,
)
.map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context))))
.unwrap_or_else(Type::unknown),
(Type::KnownInstance(KnownInstanceType::Generic(Some(_))), _, _) => {
// TODO: emit a diagnostic
todo_type!("doubly-specialized typing.Generic")
@ -7238,11 +7277,12 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_subscript_legacy_generic_class(
fn legacy_generic_class_context(
&mut self,
value_node: &ast::Expr,
typevars: &[Type<'db>],
) -> Type<'db> {
origin: LegacyGenericBase,
) -> Option<GenericContext<'db>> {
let typevars: Option<FxOrderSet<_>> = typevars
.iter()
.map(|typevar| match typevar {
@ -7252,7 +7292,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
{
builder.into_diagnostic(format_args!(
"`{}` is not a valid argument to `typing.Generic`",
"`{}` is not a valid argument to {origin}",
typevar.display(self.db()),
));
}
@ -7260,11 +7300,9 @@ impl<'db> TypeInferenceBuilder<'db> {
}
})
.collect();
let Some(typevars) = typevars else {
return Type::unknown();
};
let generic_context = GenericContext::new(self.db(), typevars);
Type::KnownInstance(KnownInstanceType::Generic(Some(generic_context)))
typevars.map(|typevars| {
GenericContext::new(self.db(), typevars, GenericContextOrigin::from(origin))
})
}
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {
@ -8420,9 +8458,14 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_type_expression(arguments_slice);
todo_type!("`Unpack[]` special form")
}
KnownInstanceType::Protocol => {
KnownInstanceType::Protocol(_) => {
self.infer_type_expression(arguments_slice);
Type::Dynamic(DynamicType::SubscriptedProtocol)
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`typing.Protocol` is not allowed in type expressions",
));
}
Type::unknown()
}
KnownInstanceType::Generic(_) => {
self.infer_type_expression(arguments_slice);

View file

@ -260,6 +260,21 @@ impl<'db> ProtocolInstanceType<'db> {
.unwrap_or_else(|| KnownClass::Object.to_instance(db).instance_member(db, name)),
}
}
pub(super) fn apply_specialization<'a>(
self,
db: &'db dyn Db,
type_mapping: TypeMapping<'a, 'db>,
) -> Self {
match self.0 {
Protocol::FromClass(class) => Self(Protocol::FromClass(
class.apply_type_mapping(db, type_mapping),
)),
Protocol::Synthesized(synthesized) => Self(Protocol::Synthesized(
synthesized.apply_type_mapping(db, type_mapping),
)),
}
}
}
/// An enumeration of the two kinds of protocol types: those that originate from a class
@ -287,6 +302,7 @@ impl<'db> Protocol<'db> {
mod synthesized_protocol {
use crate::db::Db;
use crate::types::generics::TypeMapping;
use crate::types::protocol_class::ProtocolInterface;
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
@ -306,6 +322,14 @@ mod synthesized_protocol {
Self(interface.normalized(db))
}
pub(super) fn apply_type_mapping<'a>(
self,
db: &'db dyn Db,
type_mapping: TypeMapping<'a, 'db>,
) -> Self {
Self(self.0.specialized_and_normalized(db, type_mapping))
}
pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> {
self.0
}

View file

@ -60,7 +60,7 @@ pub enum KnownInstanceType<'db> {
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
OrderedDict,
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
Protocol,
Protocol(Option<GenericContext<'db>>),
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
Generic(Option<GenericContext<'db>>),
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
@ -146,7 +146,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::Deque
| Self::ChainMap
| Self::OrderedDict
| Self::Protocol
| Self::Protocol(_)
| Self::Generic(_)
| Self::ReadOnly
| Self::TypeAliasType(_)
@ -203,7 +203,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Deque => KnownClass::StdlibAlias,
Self::ChainMap => KnownClass::StdlibAlias,
Self::OrderedDict => KnownClass::StdlibAlias,
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
Self::Protocol(_) => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
Self::Generic(_) => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
@ -249,7 +249,7 @@ impl<'db> KnownInstanceType<'db> {
"ChainMap" => Self::ChainMap,
"OrderedDict" => Self::OrderedDict,
"Generic" => Self::Generic(None),
"Protocol" => Self::Protocol,
"Protocol" => Self::Protocol(None),
"Optional" => Self::Optional,
"Union" => Self::Union,
"NoReturn" => Self::NoReturn,
@ -311,7 +311,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::Generic(_)
| Self::Callable => module.is_typing(),
Self::Annotated
| Self::Protocol
| Self::Protocol(_)
| Self::Literal
| Self::LiteralString
| Self::Never
@ -384,11 +384,17 @@ impl Display for KnownInstanceRepr<'_> {
KnownInstanceType::Deque => f.write_str("typing.Deque"),
KnownInstanceType::ChainMap => f.write_str("typing.ChainMap"),
KnownInstanceType::OrderedDict => f.write_str("typing.OrderedDict"),
KnownInstanceType::Protocol => f.write_str("typing.Protocol"),
KnownInstanceType::Protocol(generic_context) => {
f.write_str("typing.Protocol")?;
if let Some(generic_context) = generic_context {
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}
KnownInstanceType::Generic(generic_context) => {
f.write_str("typing.Generic")?;
if let Some(generic_context) = generic_context {
write!(f, "{}", generic_context.display(self.db))?;
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}

View file

@ -174,7 +174,9 @@ impl<'db> Mro<'db> {
continue;
}
match base {
ClassBase::Class(_) | ClassBase::Generic(_) | ClassBase::Protocol => {
ClassBase::Class(_)
| ClassBase::Generic(_)
| ClassBase::Protocol(_) => {
errors.push(DuplicateBaseError {
duplicate_base: base,
first_index: *first_index,

View file

@ -8,7 +8,7 @@ use crate::{
db::Db,
semantic_index::{symbol_table, use_def_map},
symbol::{symbol_from_bindings, symbol_from_declarations},
types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeQualifiers},
types::{ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers},
};
impl<'db> ClassLiteral<'db> {
@ -146,6 +146,29 @@ impl<'db> ProtocolInterface<'db> {
Self::SelfReference => Self::SelfReference,
}
}
pub(super) fn specialized_and_normalized<'a>(
self,
db: &'db dyn Db,
type_mapping: TypeMapping<'a, 'db>,
) -> Self {
match self {
Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new(
db,
members
.inner(db)
.iter()
.map(|(name, data)| {
(
name.clone(),
data.apply_type_mapping(db, type_mapping).normalized(db),
)
})
.collect::<BTreeMap<_, _>>(),
)),
Self::SelfReference => Self::SelfReference,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update)]
@ -161,6 +184,13 @@ impl<'db> ProtocolMemberData<'db> {
qualifiers: self.qualifiers,
}
}
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
Self {
ty: self.ty.apply_type_mapping(db, type_mapping),
qualifiers: self.qualifiers,
}
}
}
/// A single member of a protocol interface.

View file

@ -153,11 +153,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right),
(ClassBase::Class(_), _) => Ordering::Less,
(_, ClassBase::Class(_)) => Ordering::Greater,
(ClassBase::Protocol, _) => Ordering::Less,
(_, ClassBase::Protocol) => Ordering::Greater,
(ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right),
(ClassBase::Protocol(_), _) => Ordering::Less,
(_, ClassBase::Protocol(_)) => Ordering::Greater,
(ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right),
(ClassBase::Generic(_), _) => Ordering::Less,
(_, ClassBase::Generic(_)) => Ordering::Greater,
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}
@ -253,8 +257,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(KnownInstanceType::Generic(_), _) => Ordering::Less,
(_, KnownInstanceType::Generic(_)) => Ordering::Greater,
(KnownInstanceType::Protocol, _) => Ordering::Less,
(_, KnownInstanceType::Protocol) => Ordering::Greater,
(KnownInstanceType::Protocol(left), KnownInstanceType::Protocol(right)) => {
left.cmp(right)
}
(KnownInstanceType::Protocol(_), _) => Ordering::Less,
(_, KnownInstanceType::Protocol(_)) => Ordering::Greater,
(KnownInstanceType::NoReturn, _) => Ordering::Less,
(_, KnownInstanceType::NoReturn) => Ordering::Greater,
@ -379,8 +386,5 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
#[cfg(not(debug_assertions))]
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
(DynamicType::SubscriptedProtocol, _) => Ordering::Less,
(_, DynamicType::SubscriptedProtocol) => Ordering::Greater,
}
}