mirror of https://github.com/astral-sh/ruff
[ty] Carry generic context through when converting class into `Callable` (#21798)
When converting a class (whether specialized or not) into a `Callable` type, we should carry through any generic context that the constructor has. This includes both the generic context of the class itself (if it's generic) and of the constructor methods (if they are separately generic). To help test this, this also updates the `generic_context` extension function to work on `Callable` types and unions; and adds a new `into_callable` extension function that works just like `CallableTypeOf`, but on value forms instead of type forms. Pulled this out of #21551 for separate review.
This commit is contained in:
parent
71a7a03ad4
commit
e42cdf8495
|
|
@ -301,6 +301,7 @@ consistent with each other.
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -308,6 +309,11 @@ class C(Generic[T]):
|
|||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -318,12 +324,18 @@ wrong_innards: C[int] = C("five")
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -334,6 +346,7 @@ wrong_innards: C[int] = C("five")
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -343,6 +356,11 @@ class C(Generic[T]):
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -353,6 +371,7 @@ wrong_innards: C[int] = C("five")
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -362,6 +381,11 @@ class C(Generic[T]):
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -373,6 +397,11 @@ class D(Generic[T]):
|
|||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`"
|
||||
|
|
@ -386,6 +415,7 @@ to specialize the class.
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -398,6 +428,11 @@ class C(Generic[T, U]):
|
|||
class D(C[V, int]):
|
||||
def __init__(self, x: V) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[V@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[V@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
```
|
||||
|
||||
|
|
@ -405,6 +440,7 @@ reveal_type(D(1)) # revealed: D[int]
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -415,6 +451,11 @@ class C(Generic[T, U]):
|
|||
class D(C[T, U]):
|
||||
pass
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(C(1, "str")) # revealed: C[int, str]
|
||||
reveal_type(D(1, "str")) # revealed: D[int, str]
|
||||
```
|
||||
|
|
@ -425,6 +466,7 @@ This is a specific example of the above, since it was reported specifically by a
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -432,6 +474,11 @@ U = TypeVar("U")
|
|||
class D(dict[T, U]):
|
||||
pass
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(key=1)) # revealed: D[str, int]
|
||||
```
|
||||
|
||||
|
|
@ -443,12 +490,18 @@ context. But from the user's point of view, this is another example of the above
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
class C(tuple[T, U]): ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C((1, 2))) # revealed: C[int, int]
|
||||
```
|
||||
|
||||
|
|
@ -480,6 +533,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
S = TypeVar("S")
|
||||
T = TypeVar("T")
|
||||
|
|
@ -487,6 +541,11 @@ T = TypeVar("T")
|
|||
class C(Generic[T]):
|
||||
def __init__(self, x: T, y: S) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, S@__init__]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
|
@ -499,6 +558,7 @@ wrong_innards: C[int] = C("five", 1)
|
|||
|
||||
```py
|
||||
from typing_extensions import overload, Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -514,6 +574,11 @@ class C(Generic[T]):
|
|||
def __init__(self, x: int) -> None: ...
|
||||
def __init__(self, x: str | bytes | int) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C("string")) # revealed: C[str]
|
||||
reveal_type(C(b"bytes")) # revealed: C[bytes]
|
||||
reveal_type(C(12)) # revealed: C[Unknown]
|
||||
|
|
@ -541,6 +606,11 @@ class D(Generic[T, U]):
|
|||
def __init__(self, t: T, u: U) -> None: ...
|
||||
def __init__(self, *args) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D("string")) # revealed: D[str, str]
|
||||
reveal_type(D(1)) # revealed: D[str, int]
|
||||
reveal_type(D(1, "string")) # revealed: D[int, str]
|
||||
|
|
@ -551,6 +621,7 @@ reveal_type(D(1, "string")) # revealed: D[int, str]
|
|||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -558,6 +629,11 @@ T = TypeVar("T")
|
|||
class A(Generic[T]):
|
||||
x: T
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@A]
|
||||
reveal_type(generic_context(A))
|
||||
# revealed: ty_extensions.GenericContext[T@A]
|
||||
reveal_type(generic_context(into_callable(A)))
|
||||
|
||||
reveal_type(A(x=1)) # revealed: A[int]
|
||||
```
|
||||
|
||||
|
|
@ -565,17 +641,28 @@ reveal_type(A(x=1)) # revealed: A[int]
|
|||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=T)
|
||||
|
||||
class C(Generic[T, U]): ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C()) # revealed: C[Unknown, Unknown]
|
||||
|
||||
class D(Generic[T, U]):
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D()) # revealed: D[Unknown, Unknown]
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -264,12 +264,19 @@ signatures don't count towards variance).
|
|||
### `__new__` only
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -279,11 +286,18 @@ wrong_innards: C[int] = C("five")
|
|||
### `__init__` only
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -293,6 +307,8 @@ wrong_innards: C[int] = C("five")
|
|||
### Identical `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
|
|
@ -301,6 +317,11 @@ class C[T]:
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -310,6 +331,8 @@ wrong_innards: C[int] = C("five")
|
|||
### Compatible `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
|
|
@ -318,6 +341,11 @@ class C[T]:
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`"
|
||||
|
|
@ -331,6 +359,11 @@ class D[T]:
|
|||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`"
|
||||
|
|
@ -343,6 +376,8 @@ If either method comes from a generic base class, we don't currently use its inf
|
|||
to specialize the class.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T, U]:
|
||||
def __new__(cls, *args, **kwargs) -> "C[T, U]":
|
||||
return object.__new__(cls)
|
||||
|
|
@ -350,18 +385,30 @@ class C[T, U]:
|
|||
class D[V](C[V, int]):
|
||||
def __init__(self, x: V) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[V@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[V@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
```
|
||||
|
||||
### Generic class inherits `__init__` from generic base class
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T, U]:
|
||||
def __init__(self, t: T, u: U) -> None: ...
|
||||
|
||||
class D[T, U](C[T, U]):
|
||||
pass
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(C(1, "str")) # revealed: C[Literal[1], Literal["str"]]
|
||||
reveal_type(D(1, "str")) # revealed: D[Literal[1], Literal["str"]]
|
||||
```
|
||||
|
|
@ -371,9 +418,16 @@ reveal_type(D(1, "str")) # revealed: D[Literal[1], Literal["str"]]
|
|||
This is a specific example of the above, since it was reported specifically by a user.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class D[T, U](dict[T, U]):
|
||||
pass
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D(key=1)) # revealed: D[str, int]
|
||||
```
|
||||
|
||||
|
|
@ -384,8 +438,15 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher
|
|||
context. But from the user's point of view, this is another example of the above.)
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T, U](tuple[T, U]): ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C((1, 2))) # revealed: C[Literal[1], Literal[2]]
|
||||
```
|
||||
|
||||
|
|
@ -409,11 +470,18 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
|
|||
### `__init__` is itself generic
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
def __init__[S](self, x: T, y: S) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, S@__init__]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
|
@ -427,6 +495,7 @@ wrong_innards: C[int] = C("five", 1)
|
|||
```py
|
||||
from __future__ import annotations
|
||||
from typing import overload
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T]:
|
||||
# we need to use the type variable or else the class is bivariant in T, and
|
||||
|
|
@ -443,6 +512,11 @@ class C[T]:
|
|||
def __init__(self, x: int) -> None: ...
|
||||
def __init__(self, x: str | bytes | int) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C("string")) # revealed: C[str]
|
||||
reveal_type(C(b"bytes")) # revealed: C[bytes]
|
||||
reveal_type(C(12)) # revealed: C[Unknown]
|
||||
|
|
@ -470,6 +544,11 @@ class D[T, U]:
|
|||
def __init__(self, t: T, u: U) -> None: ...
|
||||
def __init__(self, *args) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D("string")) # revealed: D[str, Literal["string"]]
|
||||
reveal_type(D(1)) # revealed: D[str, Literal[1]]
|
||||
reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]]
|
||||
|
|
@ -479,24 +558,42 @@ reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]]
|
|||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
@dataclass
|
||||
class A[T]:
|
||||
x: T
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@A]
|
||||
reveal_type(generic_context(A))
|
||||
# revealed: ty_extensions.GenericContext[T@A]
|
||||
reveal_type(generic_context(into_callable(A)))
|
||||
|
||||
reveal_type(A(x=1)) # revealed: A[int]
|
||||
```
|
||||
|
||||
### Class typevar has another typevar as a default
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
class C[T, U = T]: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[T@C, U@C]
|
||||
reveal_type(generic_context(into_callable(C)))
|
||||
|
||||
reveal_type(C()) # revealed: C[Unknown, Unknown]
|
||||
|
||||
class D[T, U = T]:
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(D))
|
||||
# revealed: ty_extensions.GenericContext[T@D, U@D]
|
||||
reveal_type(generic_context(into_callable(D)))
|
||||
|
||||
reveal_type(D()) # revealed: D[Unknown, Unknown]
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -218,8 +218,8 @@ class E(A[int]):
|
|||
def method(self, x: object) -> None: ... # fine
|
||||
|
||||
class F[T](A[T]):
|
||||
# TODO: we should emit `invalid-method-override` on this:
|
||||
# `str` is not necessarily a supertype of `T`!
|
||||
# error: [invalid-method-override]
|
||||
def method(self, x: str) -> None: ...
|
||||
|
||||
class G(A[int]):
|
||||
|
|
|
|||
|
|
@ -8535,12 +8535,9 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
| TypeMapping::Materialize(_)
|
||||
| TypeMapping::ReplaceParameterDefaults
|
||||
| TypeMapping::EagerExpansion => context,
|
||||
TypeMapping::BindSelf { .. } => GenericContext::from_typevar_instances(
|
||||
db,
|
||||
context
|
||||
.variables(db)
|
||||
.filter(|var| !var.typevar(db).is_self(db)),
|
||||
),
|
||||
TypeMapping::BindSelf {
|
||||
binding_context, ..
|
||||
} => context.remove_self(db, *binding_context),
|
||||
TypeMapping::ReplaceSelf { new_upper_bound } => GenericContext::from_typevar_instances(
|
||||
db,
|
||||
context.variables(db).map(|typevar| {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ use crate::types::function::{
|
|||
use crate::types::generics::{
|
||||
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
|
||||
};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
|
||||
use crate::types::signatures::{
|
||||
CallableSignature, Parameter, ParameterForm, ParameterKind, Parameters,
|
||||
};
|
||||
use crate::types::tuple::{TupleLength, TupleType};
|
||||
use crate::types::{
|
||||
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DATACLASS_FLAGS, DataclassFlags,
|
||||
|
|
@ -788,51 +790,67 @@ impl<'db> Bindings<'db> {
|
|||
))
|
||||
};
|
||||
|
||||
let function_generic_context = |function: FunctionType<'db>| {
|
||||
let union = UnionType::from_elements(
|
||||
db,
|
||||
function
|
||||
.signature(db)
|
||||
.overloads
|
||||
.iter()
|
||||
.filter_map(|signature| signature.generic_context)
|
||||
.map(wrap_generic_context),
|
||||
);
|
||||
if union.is_never() {
|
||||
Type::none(db)
|
||||
} else {
|
||||
union
|
||||
}
|
||||
};
|
||||
let signature_generic_context =
|
||||
|signature: &CallableSignature<'db>| {
|
||||
UnionType::try_from_elements(
|
||||
db,
|
||||
signature.overloads.iter().map(|signature| {
|
||||
signature.generic_context.map(wrap_generic_context)
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
// TODO: Handle generic functions, and unions/intersections of
|
||||
// generic types
|
||||
overload.set_return_type(match ty {
|
||||
Type::ClassLiteral(class) => class
|
||||
.generic_context(db)
|
||||
.map(wrap_generic_context)
|
||||
.unwrap_or_else(|| Type::none(db)),
|
||||
let generic_context_for_simple_type = |ty: Type<'db>| match ty {
|
||||
Type::ClassLiteral(class) => {
|
||||
class.generic_context(db).map(wrap_generic_context)
|
||||
}
|
||||
|
||||
Type::FunctionLiteral(function) => {
|
||||
function_generic_context(*function)
|
||||
signature_generic_context(function.signature(db))
|
||||
}
|
||||
|
||||
Type::BoundMethod(bound_method) => {
|
||||
function_generic_context(bound_method.function(db))
|
||||
Type::BoundMethod(bound_method) => signature_generic_context(
|
||||
bound_method.function(db).signature(db),
|
||||
),
|
||||
|
||||
Type::Callable(callable) => {
|
||||
signature_generic_context(callable.signatures(db))
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(
|
||||
TypeAliasType::PEP695(alias),
|
||||
)) => alias
|
||||
.generic_context(db)
|
||||
.map(wrap_generic_context)
|
||||
.unwrap_or_else(|| Type::none(db)),
|
||||
)) => alias.generic_context(db).map(wrap_generic_context),
|
||||
|
||||
_ => Type::none(db),
|
||||
});
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let generic_context = match ty {
|
||||
Type::Union(union_type) => UnionType::try_from_elements(
|
||||
db,
|
||||
union_type
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| generic_context_for_simple_type(*ty)),
|
||||
),
|
||||
_ => generic_context_for_simple_type(*ty),
|
||||
};
|
||||
|
||||
overload.set_return_type(
|
||||
generic_context.unwrap_or_else(|| Type::none(db)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::IntoCallable) => {
|
||||
let [Some(ty)] = overload.parameter_types() else {
|
||||
continue;
|
||||
};
|
||||
let Some(callables) = ty.try_upcast_to_callable(db) else {
|
||||
continue;
|
||||
};
|
||||
overload.set_return_type(callables.into_type(db));
|
||||
}
|
||||
|
||||
Some(KnownFunction::DunderAllNames) => {
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(match ty {
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,13 @@ impl<'db> ClassType<'db> {
|
|||
/// constructor signature of this class.
|
||||
#[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
|
||||
// TODO: This mimics a lot of the logic in Type::try_call_from_constructor. Can we
|
||||
// consolidate the two? Can we invoke a class by upcasting the class into a Callable, and
|
||||
// then relying on the call binding machinery to Just Work™?
|
||||
|
||||
let (class_literal, _) = self.class_literal(db);
|
||||
let class_generic_context = class_literal.generic_context(db);
|
||||
|
||||
let self_ty = Type::from(self);
|
||||
let metaclass_dunder_call_function_symbol = self_ty
|
||||
.member_lookup_with_policy(
|
||||
|
|
@ -1206,39 +1213,58 @@ impl<'db> ClassType<'db> {
|
|||
// If the class defines an `__init__` method, then we synthesize a callable type with the
|
||||
// same parameters as the `__init__` method after it is bound, and with the return type of
|
||||
// the concrete type of `Self`.
|
||||
let synthesized_dunder_init_callable =
|
||||
if let Place::Defined(ty, _, _) = dunder_init_function_symbol {
|
||||
let signature = match ty {
|
||||
Type::FunctionLiteral(dunder_init_function) => {
|
||||
Some(dunder_init_function.signature(db))
|
||||
}
|
||||
Type::Callable(callable) => Some(callable.signatures(db)),
|
||||
_ => None,
|
||||
let synthesized_dunder_init_callable = if let Place::Defined(ty, _, _) =
|
||||
dunder_init_function_symbol
|
||||
{
|
||||
let signature = match ty {
|
||||
Type::FunctionLiteral(dunder_init_function) => {
|
||||
Some(dunder_init_function.signature(db))
|
||||
}
|
||||
Type::Callable(callable) => Some(callable.signatures(db)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(signature) = signature {
|
||||
let synthesized_signature = |signature: &Signature<'db>| {
|
||||
let self_annotation = signature
|
||||
.parameters()
|
||||
.get_positional(0)
|
||||
.and_then(Parameter::annotated_type)
|
||||
.filter(|ty| {
|
||||
ty.as_typevar()
|
||||
.is_none_or(|bound_typevar| !bound_typevar.typevar(db).is_self(db))
|
||||
});
|
||||
let return_type = self_annotation.unwrap_or(correct_return_type);
|
||||
let instance_ty = self_annotation.unwrap_or_else(|| Type::instance(db, self));
|
||||
let generic_context = GenericContext::merge_optional(
|
||||
db,
|
||||
class_generic_context,
|
||||
signature.generic_context,
|
||||
);
|
||||
Signature::new_generic(
|
||||
generic_context,
|
||||
signature.parameters().clone(),
|
||||
Some(return_type),
|
||||
)
|
||||
.with_definition(signature.definition())
|
||||
.bind_self(db, Some(instance_ty))
|
||||
};
|
||||
|
||||
if let Some(signature) = signature {
|
||||
let synthesized_signature = |signature: &Signature<'db>| {
|
||||
let instance_ty = Type::instance(db, self);
|
||||
Signature::new(signature.parameters().clone(), Some(correct_return_type))
|
||||
.with_definition(signature.definition())
|
||||
.bind_self(db, Some(instance_ty))
|
||||
};
|
||||
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
|
||||
signature.overloads.iter().map(synthesized_signature),
|
||||
);
|
||||
|
||||
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
|
||||
signature.overloads.iter().map(synthesized_signature),
|
||||
);
|
||||
|
||||
Some(CallableType::new(
|
||||
db,
|
||||
synthesized_dunder_init_signature,
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(CallableType::new(
|
||||
db,
|
||||
synthesized_dunder_init_signature,
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match (dunder_new_function, synthesized_dunder_init_callable) {
|
||||
(Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {
|
||||
|
|
@ -1261,9 +1287,13 @@ impl<'db> ClassType<'db> {
|
|||
)
|
||||
.place;
|
||||
|
||||
if let Place::Defined(Type::FunctionLiteral(new_function), _, _) =
|
||||
if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _) =
|
||||
new_function_symbol
|
||||
{
|
||||
if let Some(class_generic_context) = class_generic_context {
|
||||
new_function =
|
||||
new_function.with_inherited_generic_context(db, class_generic_context);
|
||||
}
|
||||
CallableTypes::one(
|
||||
new_function
|
||||
.into_bound_method_type(db, correct_return_type)
|
||||
|
|
@ -1273,7 +1303,11 @@ impl<'db> ClassType<'db> {
|
|||
// Fallback if no `object.__new__` is found.
|
||||
CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::empty(), Some(correct_return_type)),
|
||||
Signature::new_generic(
|
||||
class_generic_context,
|
||||
Parameters::empty(),
|
||||
Some(correct_return_type),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1339,6 +1339,8 @@ pub enum KnownFunction {
|
|||
IsSingleValued,
|
||||
/// `ty_extensions.generic_context`
|
||||
GenericContext,
|
||||
/// `ty_extensions.into_callable`
|
||||
IntoCallable,
|
||||
/// `ty_extensions.dunder_all_names`
|
||||
DunderAllNames,
|
||||
/// `ty_extensions.enum_members`
|
||||
|
|
@ -1411,6 +1413,7 @@ impl KnownFunction {
|
|||
| Self::IsSingleton
|
||||
| Self::IsSubtypeOf
|
||||
| Self::GenericContext
|
||||
| Self::IntoCallable
|
||||
| Self::DunderAllNames
|
||||
| Self::EnumMembers
|
||||
| Self::StaticAssert
|
||||
|
|
@ -1946,6 +1949,7 @@ pub(crate) mod tests {
|
|||
KnownFunction::IsSingleton
|
||||
| KnownFunction::IsSubtypeOf
|
||||
| KnownFunction::GenericContext
|
||||
| KnownFunction::IntoCallable
|
||||
| KnownFunction::DunderAllNames
|
||||
| KnownFunction::EnumMembers
|
||||
| KnownFunction::StaticAssert
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@ use crate::types::signatures::Parameters;
|
|||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarIdentity, BoundTypeVarInstance, ClassLiteral,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext,
|
||||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance,
|
||||
TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_bound_type_var_type,
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
|
||||
ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor,
|
||||
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
||||
Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
|
||||
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
||||
walk_bound_type_var_type,
|
||||
};
|
||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
||||
|
||||
|
|
@ -261,6 +262,34 @@ impl<'db> GenericContext<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn merge_optional(
|
||||
db: &'db dyn Db,
|
||||
left: Option<Self>,
|
||||
right: Option<Self>,
|
||||
) -> Option<Self> {
|
||||
match (left, right) {
|
||||
(None, None) => None,
|
||||
(Some(one), None) | (None, Some(one)) => Some(one),
|
||||
(Some(left), Some(right)) => Some(left.merge(db, right)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_self(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
binding_context: Option<BindingContext<'db>>,
|
||||
) -> Self {
|
||||
Self::from_typevar_instances(
|
||||
db,
|
||||
self.variables(db).filter(|bound_typevar| {
|
||||
!(bound_typevar.typevar(db).is_self(db)
|
||||
&& binding_context.is_none_or(|binding_context| {
|
||||
bound_typevar.binding_context(db) == binding_context
|
||||
}))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
#[derive(Default)]
|
||||
struct CollectTypeVars<'db> {
|
||||
|
|
|
|||
|
|
@ -667,10 +667,11 @@ impl<'db> Signature<'db> {
|
|||
|
||||
let mut parameters = Parameters::new(db, parameters);
|
||||
let mut return_ty = self.return_ty;
|
||||
let binding_context = self.definition.map(BindingContext::Definition);
|
||||
if let Some(self_type) = self_type {
|
||||
let self_mapping = TypeMapping::BindSelf {
|
||||
self_type,
|
||||
binding_context: self.definition.map(BindingContext::Definition),
|
||||
binding_context,
|
||||
};
|
||||
parameters = parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
|
|
@ -682,7 +683,9 @@ impl<'db> Signature<'db> {
|
|||
.map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default()));
|
||||
}
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
generic_context: self
|
||||
.generic_context
|
||||
.map(|generic_context| generic_context.remove_self(db, binding_context)),
|
||||
definition: self.definition,
|
||||
parameters,
|
||||
return_ty,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@ def is_single_valued(ty: Any) -> bool:
|
|||
# type is not generic.
|
||||
def generic_context(ty: Any) -> GenericContext | None: ...
|
||||
|
||||
# Converts a value into a `Callable`, if possible. This is the value equivalent
|
||||
# of `CallableTypeOf`, which operates on types.
|
||||
def into_callable(ty: Any) -> Any: ...
|
||||
|
||||
# Returns the `__all__` names of a module as a tuple of sorted strings, or `None` if
|
||||
# either the module does not have `__all__` or it has invalid elements.
|
||||
def dunder_all_names(module: Any) -> Any: ...
|
||||
|
|
|
|||
Loading…
Reference in New Issue