[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:
Douglas Creager 2025-12-05 08:57:21 -05:00 committed by GitHub
parent 71a7a03ad4
commit e42cdf8495
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 350 additions and 77 deletions

View File

@ -301,6 +301,7 @@ consistent with each other.
```py ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
@ -308,6 +309,11 @@ class C(Generic[T]):
def __new__(cls, x: T) -> "C[T]": def __new__(cls, x: T) -> "C[T]":
return object.__new__(cls) 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
class C(Generic[T]): class C(Generic[T]):
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
@ -343,6 +356,11 @@ class C(Generic[T]):
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
@ -362,6 +381,11 @@ class C(Generic[T]):
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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: ... 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] reveal_type(D(1)) # revealed: D[int]
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
@ -398,6 +428,11 @@ class C(Generic[T, U]):
class D(C[V, int]): class D(C[V, int]):
def __init__(self, x: V) -> None: ... 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] reveal_type(D(1)) # revealed: D[int]
``` ```
@ -405,6 +440,7 @@ reveal_type(D(1)) # revealed: D[int]
```py ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
@ -415,6 +451,11 @@ class C(Generic[T, U]):
class D(C[T, U]): class D(C[T, U]):
pass 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(C(1, "str")) # revealed: C[int, str]
reveal_type(D(1, "str")) # revealed: D[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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
@ -432,6 +474,11 @@ U = TypeVar("U")
class D(dict[T, U]): class D(dict[T, U]):
pass 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] 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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
class C(tuple[T, 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] 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 ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
S = TypeVar("S") S = TypeVar("S")
T = TypeVar("T") T = TypeVar("T")
@ -487,6 +541,11 @@ T = TypeVar("T")
class C(Generic[T]): class C(Generic[T]):
def __init__(self, x: T, y: S) -> None: ... 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, 1)) # revealed: C[int]
reveal_type(C(1, "string")) # revealed: C[int] reveal_type(C(1, "string")) # revealed: C[int]
reveal_type(C(1, True)) # revealed: C[int] reveal_type(C(1, True)) # revealed: C[int]
@ -499,6 +558,7 @@ wrong_innards: C[int] = C("five", 1)
```py ```py
from typing_extensions import overload, Generic, TypeVar from typing_extensions import overload, Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
@ -514,6 +574,11 @@ class C(Generic[T]):
def __init__(self, x: int) -> None: ... def __init__(self, x: int) -> None: ...
def __init__(self, x: str | bytes | 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("string")) # revealed: C[str]
reveal_type(C(b"bytes")) # revealed: C[bytes] reveal_type(C(b"bytes")) # revealed: C[bytes]
reveal_type(C(12)) # revealed: C[Unknown] 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, t: T, u: U) -> None: ...
def __init__(self, *args) -> 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("string")) # revealed: D[str, str]
reveal_type(D(1)) # revealed: D[str, int] reveal_type(D(1)) # revealed: D[str, int]
reveal_type(D(1, "string")) # revealed: D[int, str] reveal_type(D(1, "string")) # revealed: D[int, str]
@ -551,6 +621,7 @@ reveal_type(D(1, "string")) # revealed: D[int, str]
```py ```py
from dataclasses import dataclass from dataclasses import dataclass
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
@ -558,6 +629,11 @@ T = TypeVar("T")
class A(Generic[T]): class A(Generic[T]):
x: 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] reveal_type(A(x=1)) # revealed: A[int]
``` ```
@ -565,17 +641,28 @@ reveal_type(A(x=1)) # revealed: A[int]
```py ```py
from typing_extensions import Generic, TypeVar from typing_extensions import Generic, TypeVar
from ty_extensions import generic_context, into_callable
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U", default=T) U = TypeVar("U", default=T)
class C(Generic[T, U]): ... 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] reveal_type(C()) # revealed: C[Unknown, Unknown]
class D(Generic[T, U]): class D(Generic[T, U]):
def __init__(self) -> None: ... 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] reveal_type(D()) # revealed: D[Unknown, Unknown]
``` ```

View File

@ -264,12 +264,19 @@ signatures don't count towards variance).
### `__new__` only ### `__new__` only
```py ```py
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
x: T x: T
def __new__(cls, x: T) -> "C[T]": def __new__(cls, x: T) -> "C[T]":
return object.__new__(cls) 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ### `__init__` only
```py ```py
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
x: T x: T
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ### Identical `__new__` and `__init__` signatures
```py ```py
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
x: T x: T
@ -301,6 +317,11 @@ class C[T]:
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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 ### Compatible `__new__` and `__init__` signatures
```py ```py
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
x: T x: T
@ -318,6 +341,11 @@ class C[T]:
def __init__(self, x: T) -> None: ... 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] reveal_type(C(1)) # revealed: C[int]
# error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `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: ... 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] reveal_type(D(1)) # revealed: D[int]
# error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `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. to specialize the class.
```py ```py
from ty_extensions import generic_context, into_callable
class C[T, U]: class C[T, U]:
def __new__(cls, *args, **kwargs) -> "C[T, U]": def __new__(cls, *args, **kwargs) -> "C[T, U]":
return object.__new__(cls) return object.__new__(cls)
@ -350,18 +385,30 @@ class C[T, U]:
class D[V](C[V, int]): class D[V](C[V, int]):
def __init__(self, x: V) -> None: ... 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]] reveal_type(D(1)) # revealed: D[Literal[1]]
``` ```
### Generic class inherits `__init__` from generic base class ### Generic class inherits `__init__` from generic base class
```py ```py
from ty_extensions import generic_context, into_callable
class C[T, U]: class C[T, U]:
def __init__(self, t: T, u: U) -> None: ... def __init__(self, t: T, u: U) -> None: ...
class D[T, U](C[T, U]): class D[T, U](C[T, U]):
pass 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(C(1, "str")) # revealed: C[Literal[1], Literal["str"]]
reveal_type(D(1, "str")) # revealed: D[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. This is a specific example of the above, since it was reported specifically by a user.
```py ```py
from ty_extensions import generic_context, into_callable
class D[T, U](dict[T, U]): class D[T, U](dict[T, U]):
pass 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] 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.) context. But from the user's point of view, this is another example of the above.)
```py ```py
from ty_extensions import generic_context, into_callable
class C[T, U](tuple[T, U]): ... 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]] 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 ### `__init__` is itself generic
```py ```py
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
x: T x: T
def __init__[S](self, x: T, y: S) -> None: ... 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, 1)) # revealed: C[int]
reveal_type(C(1, "string")) # revealed: C[int] reveal_type(C(1, "string")) # revealed: C[int]
reveal_type(C(1, True)) # revealed: C[int] reveal_type(C(1, True)) # revealed: C[int]
@ -427,6 +495,7 @@ wrong_innards: C[int] = C("five", 1)
```py ```py
from __future__ import annotations from __future__ import annotations
from typing import overload from typing import overload
from ty_extensions import generic_context, into_callable
class C[T]: class C[T]:
# we need to use the type variable or else the class is bivariant in T, and # 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: int) -> None: ...
def __init__(self, x: str | bytes | 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("string")) # revealed: C[str]
reveal_type(C(b"bytes")) # revealed: C[bytes] reveal_type(C(b"bytes")) # revealed: C[bytes]
reveal_type(C(12)) # revealed: C[Unknown] 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, t: T, u: U) -> None: ...
def __init__(self, *args) -> 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("string")) # revealed: D[str, Literal["string"]]
reveal_type(D(1)) # revealed: D[str, Literal[1]] reveal_type(D(1)) # revealed: D[str, Literal[1]]
reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]] 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 ```py
from dataclasses import dataclass from dataclasses import dataclass
from ty_extensions import generic_context, into_callable
@dataclass @dataclass
class A[T]: class A[T]:
x: 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] reveal_type(A(x=1)) # revealed: A[int]
``` ```
### Class typevar has another typevar as a default ### Class typevar has another typevar as a default
```py ```py
from ty_extensions import generic_context, into_callable
class C[T, U = T]: ... 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] reveal_type(C()) # revealed: C[Unknown, Unknown]
class D[T, U = T]: class D[T, U = T]:
def __init__(self) -> None: ... 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] reveal_type(D()) # revealed: D[Unknown, Unknown]
``` ```

View File

@ -218,8 +218,8 @@ class E(A[int]):
def method(self, x: object) -> None: ... # fine def method(self, x: object) -> None: ... # fine
class F[T](A[T]): class F[T](A[T]):
# TODO: we should emit `invalid-method-override` on this:
# `str` is not necessarily a supertype of `T`! # `str` is not necessarily a supertype of `T`!
# error: [invalid-method-override]
def method(self, x: str) -> None: ... def method(self, x: str) -> None: ...
class G(A[int]): class G(A[int]):

View File

@ -8535,12 +8535,9 @@ impl<'db> TypeMapping<'_, 'db> {
| TypeMapping::Materialize(_) | TypeMapping::Materialize(_)
| TypeMapping::ReplaceParameterDefaults | TypeMapping::ReplaceParameterDefaults
| TypeMapping::EagerExpansion => context, | TypeMapping::EagerExpansion => context,
TypeMapping::BindSelf { .. } => GenericContext::from_typevar_instances( TypeMapping::BindSelf {
db, binding_context, ..
context } => context.remove_self(db, *binding_context),
.variables(db)
.filter(|var| !var.typevar(db).is_self(db)),
),
TypeMapping::ReplaceSelf { new_upper_bound } => GenericContext::from_typevar_instances( TypeMapping::ReplaceSelf { new_upper_bound } => GenericContext::from_typevar_instances(
db, db,
context.variables(db).map(|typevar| { context.variables(db).map(|typevar| {

View File

@ -32,7 +32,9 @@ use crate::types::function::{
use crate::types::generics::{ use crate::types::generics::{
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError, 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::tuple::{TupleLength, TupleType};
use crate::types::{ use crate::types::{
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DATACLASS_FLAGS, DataclassFlags, BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DATACLASS_FLAGS, DataclassFlags,
@ -788,51 +790,67 @@ impl<'db> Bindings<'db> {
)) ))
}; };
let function_generic_context = |function: FunctionType<'db>| { let signature_generic_context =
let union = UnionType::from_elements( |signature: &CallableSignature<'db>| {
UnionType::try_from_elements(
db, db,
function signature.overloads.iter().map(|signature| {
.signature(db) signature.generic_context.map(wrap_generic_context)
.overloads }),
.iter() )
.filter_map(|signature| signature.generic_context)
.map(wrap_generic_context),
);
if union.is_never() {
Type::none(db)
} else {
union
}
}; };
// TODO: Handle generic functions, and unions/intersections of let generic_context_for_simple_type = |ty: Type<'db>| match ty {
// generic types Type::ClassLiteral(class) => {
overload.set_return_type(match ty { class.generic_context(db).map(wrap_generic_context)
Type::ClassLiteral(class) => class
.generic_context(db)
.map(wrap_generic_context)
.unwrap_or_else(|| Type::none(db)),
Type::FunctionLiteral(function) => {
function_generic_context(*function)
} }
Type::BoundMethod(bound_method) => { Type::FunctionLiteral(function) => {
function_generic_context(bound_method.function(db)) signature_generic_context(function.signature(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( Type::KnownInstance(KnownInstanceType::TypeAliasType(
TypeAliasType::PEP695(alias), TypeAliasType::PEP695(alias),
)) => alias )) => alias.generic_context(db).map(wrap_generic_context),
.generic_context(db)
.map(wrap_generic_context)
.unwrap_or_else(|| Type::none(db)),
_ => 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) => { Some(KnownFunction::DunderAllNames) => {
if let [Some(ty)] = overload.parameter_types() { if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(match ty { overload.set_return_type(match ty {

View File

@ -1133,6 +1133,13 @@ impl<'db> ClassType<'db> {
/// constructor signature of this class. /// constructor signature of this class.
#[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)] #[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> { 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 self_ty = Type::from(self);
let metaclass_dunder_call_function_symbol = self_ty let metaclass_dunder_call_function_symbol = self_ty
.member_lookup_with_policy( .member_lookup_with_policy(
@ -1206,8 +1213,9 @@ impl<'db> ClassType<'db> {
// If the class defines an `__init__` method, then we synthesize a callable type with the // 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 // same parameters as the `__init__` method after it is bound, and with the return type of
// the concrete type of `Self`. // the concrete type of `Self`.
let synthesized_dunder_init_callable = let synthesized_dunder_init_callable = if let Place::Defined(ty, _, _) =
if let Place::Defined(ty, _, _) = dunder_init_function_symbol { dunder_init_function_symbol
{
let signature = match ty { let signature = match ty {
Type::FunctionLiteral(dunder_init_function) => { Type::FunctionLiteral(dunder_init_function) => {
Some(dunder_init_function.signature(db)) Some(dunder_init_function.signature(db))
@ -1218,8 +1226,26 @@ impl<'db> ClassType<'db> {
if let Some(signature) = signature { if let Some(signature) = signature {
let synthesized_signature = |signature: &Signature<'db>| { let synthesized_signature = |signature: &Signature<'db>| {
let instance_ty = Type::instance(db, self); let self_annotation = signature
Signature::new(signature.parameters().clone(), Some(correct_return_type)) .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()) .with_definition(signature.definition())
.bind_self(db, Some(instance_ty)) .bind_self(db, Some(instance_ty))
}; };
@ -1261,9 +1287,13 @@ impl<'db> ClassType<'db> {
) )
.place; .place;
if let Place::Defined(Type::FunctionLiteral(new_function), _, _) = if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _) =
new_function_symbol 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( CallableTypes::one(
new_function new_function
.into_bound_method_type(db, correct_return_type) .into_bound_method_type(db, correct_return_type)
@ -1273,7 +1303,11 @@ impl<'db> ClassType<'db> {
// Fallback if no `object.__new__` is found. // Fallback if no `object.__new__` is found.
CallableTypes::one(CallableType::single( CallableTypes::one(CallableType::single(
db, db,
Signature::new(Parameters::empty(), Some(correct_return_type)), Signature::new_generic(
class_generic_context,
Parameters::empty(),
Some(correct_return_type),
),
)) ))
} }
} }

View File

@ -1339,6 +1339,8 @@ pub enum KnownFunction {
IsSingleValued, IsSingleValued,
/// `ty_extensions.generic_context` /// `ty_extensions.generic_context`
GenericContext, GenericContext,
/// `ty_extensions.into_callable`
IntoCallable,
/// `ty_extensions.dunder_all_names` /// `ty_extensions.dunder_all_names`
DunderAllNames, DunderAllNames,
/// `ty_extensions.enum_members` /// `ty_extensions.enum_members`
@ -1411,6 +1413,7 @@ impl KnownFunction {
| Self::IsSingleton | Self::IsSingleton
| Self::IsSubtypeOf | Self::IsSubtypeOf
| Self::GenericContext | Self::GenericContext
| Self::IntoCallable
| Self::DunderAllNames | Self::DunderAllNames
| Self::EnumMembers | Self::EnumMembers
| Self::StaticAssert | Self::StaticAssert
@ -1946,6 +1949,7 @@ pub(crate) mod tests {
KnownFunction::IsSingleton KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf | KnownFunction::IsSubtypeOf
| KnownFunction::GenericContext | KnownFunction::GenericContext
| KnownFunction::IntoCallable
| KnownFunction::DunderAllNames | KnownFunction::DunderAllNames
| KnownFunction::EnumMembers | KnownFunction::EnumMembers
| KnownFunction::StaticAssert | KnownFunction::StaticAssert

View File

@ -17,11 +17,12 @@ use crate::types::signatures::Parameters;
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarIdentity, BoundTypeVarInstance, ClassLiteral, ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor,
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_bound_type_var_type, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
walk_bound_type_var_type,
}; };
use crate::{Db, FxOrderMap, FxOrderSet}; 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> { pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
#[derive(Default)] #[derive(Default)]
struct CollectTypeVars<'db> { struct CollectTypeVars<'db> {

View File

@ -667,10 +667,11 @@ impl<'db> Signature<'db> {
let mut parameters = Parameters::new(db, parameters); let mut parameters = Parameters::new(db, parameters);
let mut return_ty = self.return_ty; let mut return_ty = self.return_ty;
let binding_context = self.definition.map(BindingContext::Definition);
if let Some(self_type) = self_type { if let Some(self_type) = self_type {
let self_mapping = TypeMapping::BindSelf { let self_mapping = TypeMapping::BindSelf {
self_type, self_type,
binding_context: self.definition.map(BindingContext::Definition), binding_context,
}; };
parameters = parameters.apply_type_mapping_impl( parameters = parameters.apply_type_mapping_impl(
db, db,
@ -682,7 +683,9 @@ impl<'db> Signature<'db> {
.map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default())); .map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default()));
} }
Self { Self {
generic_context: self.generic_context, generic_context: self
.generic_context
.map(|generic_context| generic_context.remove_self(db, binding_context)),
definition: self.definition, definition: self.definition,
parameters, parameters,
return_ty, return_ty,

View File

@ -147,6 +147,10 @@ def is_single_valued(ty: Any) -> bool:
# type is not generic. # type is not generic.
def generic_context(ty: Any) -> GenericContext | None: ... 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 # 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. # either the module does not have `__all__` or it has invalid elements.
def dunder_all_names(module: Any) -> Any: ... def dunder_all_names(module: Any) -> Any: ...