mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-21 15:52:34 +00:00
[ty] Type-context aware literal promotion (#20776)
## Summary Avoid literal promotion when a literal type annotation is provided, e.g., ```py x: list[Literal[1]] = [1] ``` Resolves https://github.com/astral-sh/ty/issues/1198. This does not fix issue https://github.com/astral-sh/ty/issues/1284, but it does make it more relevant because after this change, it is possible to directly instantiate a generic type with a literal specialization.
This commit is contained in:
parent
537ec5f012
commit
b086ffe921
17 changed files with 445 additions and 151 deletions
|
@ -130,13 +130,9 @@ type IntList = list[int]
|
|||
m: IntList = [1, 2, 3]
|
||||
reveal_type(m) # revealed: list[int]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
|
||||
o: list[typing.LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(o) # revealed: list[LiteralString]
|
||||
|
||||
|
@ -160,6 +156,81 @@ a: list[str] = [1, 2, 3]
|
|||
b: set[int] = {1, 2, "3"}
|
||||
```
|
||||
|
||||
## Literal annnotations are respected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing_extensions import Literal, LiteralString
|
||||
|
||||
a: list[Literal[1]] = [1]
|
||||
reveal_type(a) # revealed: list[Literal[1]]
|
||||
|
||||
b: list[Literal[True]] = [True]
|
||||
reveal_type(b) # revealed: list[Literal[True]]
|
||||
|
||||
c: list[Literal["a"]] = ["a"]
|
||||
reveal_type(c) # revealed: list[Literal["a"]]
|
||||
|
||||
d: list[LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(d) # revealed: list[LiteralString]
|
||||
|
||||
e: list[list[Literal[1]]] = [[1]]
|
||||
reveal_type(e) # revealed: list[list[Literal[1]]]
|
||||
|
||||
class Color(Enum):
|
||||
RED = "red"
|
||||
|
||||
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
|
||||
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
|
||||
|
||||
class X[T]:
|
||||
def __init__(self, value: T): ...
|
||||
|
||||
g: X[Literal[1]] = X(1)
|
||||
reveal_type(g) # revealed: X[Literal[1]]
|
||||
|
||||
h: X[int] = X(1)
|
||||
reveal_type(h) # revealed: X[int]
|
||||
|
||||
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
|
||||
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
|
||||
|
||||
j: list[Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
|
||||
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
type Y[T] = list[T]
|
||||
|
||||
l: Y[Y[Literal[1]]] = [[1]]
|
||||
reveal_type(l) # revealed: list[list[Literal[1]]]
|
||||
|
||||
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
|
||||
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
|
||||
|
||||
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
|
||||
reveal_type(n) # revealed: list[tuple[int, str, int]]
|
||||
|
||||
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
|
||||
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
|
||||
|
||||
p: list[tuple[int, ...]] = [(1, 1, 1)]
|
||||
reveal_type(p) # revealed: list[tuple[int, ...]]
|
||||
|
||||
# literal promotion occurs based on assignability, an exact match is not required
|
||||
q: list[int | Literal[1]] = [1]
|
||||
reveal_type(q) # revealed: list[int]
|
||||
|
||||
r: list[Literal[1, 2, 3, 4]] = [1, 2]
|
||||
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
|
||||
```
|
||||
|
||||
## PEP-604 annotations are supported
|
||||
|
||||
```py
|
||||
|
|
|
@ -525,10 +525,6 @@ from typing import Literal
|
|||
reveal_type(list((1, 2, 3))) # revealed: list[int]
|
||||
reveal_type(list(((1, 2, 3),))) # revealed: list[tuple[int, int, int]]
|
||||
|
||||
# TODO: we could bidirectionally infer that the user does not want literals to be promoted here,
|
||||
# and avoid this diagnostic
|
||||
#
|
||||
# error: [invalid-assignment] "`list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
|
||||
reveal_type(x) # revealed: list[Literal[1, 2, 3]]
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue