mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
[ty] Promote literals when inferring class specializations from constructors (#18102)
This implements the stopgap approach described in https://github.com/astral-sh/ty/issues/336#issuecomment-2880532213 for handling literal types in generic class specializations. With this approach, we will promote any literal to its instance type, but _only_ when inferring a generic class specialization from a constructor call: ```py class C[T]: def __init__(self, x: T) -> None: ... reveal_type(C("string")) # revealed: C[str] ``` If you specialize the class explicitly, we still use whatever type you provide, even if it's a literal: ```py from typing import Literal reveal_type(C[Literal[5]](5)) # revealed: C[Literal[5]] ``` And this doesn't apply at all to generic functions: ```py def f[T](x: T) -> T: return x reveal_type(f(5)) # revealed: Literal[5] ``` --- As part of making this happen, we also generalize the `TypeMapping` machinery. This provides a way to apply a function to type, returning a new type. Complicating matters is that for function literals, we have to apply the mapping lazily, since the function's signature is not created until (and if) someone calls its `signature` method. That means we have to stash away the mappings that we want to apply to the signatures parameter/return annotations once we do create it. This requires some minor `Cow` shenanigans to continue working for partial specializations.
This commit is contained in:
parent
fb589730ef
commit
ce43dbab58
12 changed files with 215 additions and 176 deletions
|
@ -90,7 +90,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp
|
|||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Generic, Literal, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@ -98,6 +98,7 @@ class C(Generic[T]):
|
|||
x: T
|
||||
|
||||
reveal_type(C[int]()) # revealed: C[int]
|
||||
reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]]
|
||||
```
|
||||
|
||||
The specialization must match the generic types:
|
||||
|
@ -229,9 +230,9 @@ class C(Generic[T]):
|
|||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -245,9 +246,9 @@ T = TypeVar("T")
|
|||
class C(Generic[T]):
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -264,9 +265,9 @@ class C(Generic[T]):
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -283,9 +284,9 @@ class C(Generic[T]):
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D(Generic[T]):
|
||||
|
@ -294,9 +295,9 @@ class D(Generic[T]):
|
|||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`"
|
||||
# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
|
@ -319,7 +320,7 @@ class C(Generic[T, U]):
|
|||
class D(C[V, int]):
|
||||
def __init__(self, x: V) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
```
|
||||
|
||||
### `__init__` is itself generic
|
||||
|
@ -333,11 +334,11 @@ T = TypeVar("T")
|
|||
class C(Generic[T]):
|
||||
def __init__(self, x: T, y: S) -> None: ...
|
||||
|
||||
reveal_type(C(1, 1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, "string")) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, True)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
|
|
|
@ -74,10 +74,13 @@ class BothGenericSyntaxes[U](Generic[T]): ...
|
|||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
reveal_type(C[int]()) # revealed: C[int]
|
||||
reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]]
|
||||
```
|
||||
|
||||
The specialization must match the generic types:
|
||||
|
@ -190,9 +193,9 @@ class C[T]:
|
|||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -202,9 +205,9 @@ wrong_innards: C[int] = C("five")
|
|||
class C[T]:
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -217,9 +220,9 @@ class C[T]:
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
|
@ -232,9 +235,9 @@ class C[T]:
|
|||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D[T]:
|
||||
|
@ -243,9 +246,9 @@ class D[T]:
|
|||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`"
|
||||
# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
|
@ -262,7 +265,7 @@ class C[T, U]:
|
|||
class D[V](C[V, int]):
|
||||
def __init__(self, x: V) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
reveal_type(D(1)) # revealed: D[int]
|
||||
```
|
||||
|
||||
### `__init__` is itself generic
|
||||
|
@ -271,11 +274,11 @@ reveal_type(D(1)) # revealed: D[Literal[1]]
|
|||
class C[T]:
|
||||
def __init__[S](self, x: T, y: S) -> None: ...
|
||||
|
||||
reveal_type(C(1, 1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, "string")) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, True)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, 1)) # revealed: C[int]
|
||||
reveal_type(C(1, "string")) # revealed: C[int]
|
||||
reveal_type(C(1, True)) # revealed: C[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue