Skip to content

Commit b086ffe

Browse files
authored
[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 astral-sh/ty#1198. This does not fix issue astral-sh/ty#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.
1 parent 537ec5f commit b086ffe

File tree

17 files changed

+444
-150
lines changed

17 files changed

+444
-150
lines changed

crates/ty_python_semantic/resources/mdtest/assignment/annotations.md

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,9 @@ type IntList = list[int]
130130
m: IntList = [1, 2, 3]
131131
reveal_type(m) # revealed: list[int]
132132

133-
# TODO: this should type-check and avoid literal promotion
134-
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
135133
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
136134
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
137135

138-
# TODO: this should type-check and avoid literal promotion
139-
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
140136
o: list[typing.LiteralString] = ["a", "b", "c"]
141137
reveal_type(o) # revealed: list[LiteralString]
142138

@@ -160,6 +156,81 @@ a: list[str] = [1, 2, 3]
160156
b: set[int] = {1, 2, "3"}
161157
```
162158

159+
## Literal annnotations are respected
160+
161+
```toml
162+
[environment]
163+
python-version = "3.12"
164+
```
165+
166+
```py
167+
from enum import Enum
168+
from typing_extensions import Literal, LiteralString
169+
170+
a: list[Literal[1]] = [1]
171+
reveal_type(a) # revealed: list[Literal[1]]
172+
173+
b: list[Literal[True]] = [True]
174+
reveal_type(b) # revealed: list[Literal[True]]
175+
176+
c: list[Literal["a"]] = ["a"]
177+
reveal_type(c) # revealed: list[Literal["a"]]
178+
179+
d: list[LiteralString] = ["a", "b", "c"]
180+
reveal_type(d) # revealed: list[LiteralString]
181+
182+
e: list[list[Literal[1]]] = [[1]]
183+
reveal_type(e) # revealed: list[list[Literal[1]]]
184+
185+
class Color(Enum):
186+
RED = "red"
187+
188+
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
189+
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
190+
191+
class X[T]:
192+
def __init__(self, value: T): ...
193+
194+
g: X[Literal[1]] = X(1)
195+
reveal_type(g) # revealed: X[Literal[1]]
196+
197+
h: X[int] = X(1)
198+
reveal_type(h) # revealed: X[int]
199+
200+
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
201+
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
202+
203+
j: list[Literal[1, 2, 3]] = [1, 2, 3]
204+
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
205+
206+
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
207+
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
208+
209+
type Y[T] = list[T]
210+
211+
l: Y[Y[Literal[1]]] = [[1]]
212+
reveal_type(l) # revealed: list[list[Literal[1]]]
213+
214+
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
215+
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
216+
217+
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
218+
reveal_type(n) # revealed: list[tuple[int, str, int]]
219+
220+
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
221+
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
222+
223+
p: list[tuple[int, ...]] = [(1, 1, 1)]
224+
reveal_type(p) # revealed: list[tuple[int, ...]]
225+
226+
# literal promotion occurs based on assignability, an exact match is not required
227+
q: list[int | Literal[1]] = [1]
228+
reveal_type(q) # revealed: list[int]
229+
230+
r: list[Literal[1, 2, 3, 4]] = [1, 2]
231+
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
232+
```
233+
163234
## PEP-604 annotations are supported
164235

165236
```py

crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,6 @@ from typing import Literal
525525
reveal_type(list((1, 2, 3))) # revealed: list[int]
526526
reveal_type(list(((1, 2, 3),))) # revealed: list[tuple[int, int, int]]
527527

528-
# TODO: we could bidirectionally infer that the user does not want literals to be promoted here,
529-
# and avoid this diagnostic
530-
#
531-
# error: [invalid-assignment] "`list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
532528
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
533529
reveal_type(x) # revealed: list[Literal[1, 2, 3]]
534530
```

0 commit comments

Comments
 (0)