Skip to content

Commit 63ce623

Browse files
committed
improve literal promotion heuristics
1 parent 2a2b719 commit 63ce623

File tree

13 files changed

+566
-255
lines changed

13 files changed

+566
-255
lines changed

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

Lines changed: 20 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -229,87 +229,6 @@ a: list[str] = [1, 2, 3]
229229
b: set[int] = {1, 2, "3"}
230230
```
231231

232-
## Literal annnotations are respected
233-
234-
```toml
235-
[environment]
236-
python-version = "3.12"
237-
```
238-
239-
```py
240-
from enum import Enum
241-
from typing_extensions import Literal, LiteralString
242-
243-
a: list[Literal[1]] = [1]
244-
reveal_type(a) # revealed: list[Literal[1]]
245-
246-
b: list[Literal[True]] = [True]
247-
reveal_type(b) # revealed: list[Literal[True]]
248-
249-
c: list[Literal["a"]] = ["a"]
250-
reveal_type(c) # revealed: list[Literal["a"]]
251-
252-
d: list[LiteralString] = ["a", "b", "c"]
253-
reveal_type(d) # revealed: list[LiteralString]
254-
255-
e: list[list[Literal[1]]] = [[1]]
256-
reveal_type(e) # revealed: list[list[Literal[1]]]
257-
258-
class Color(Enum):
259-
RED = "red"
260-
261-
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
262-
reveal_type(f) # revealed: dict[list[Literal[1]], list[Color]]
263-
264-
class X[T]:
265-
def __init__(self, value: T): ...
266-
267-
g: X[Literal[1]] = X(1)
268-
reveal_type(g) # revealed: X[Literal[1]]
269-
270-
h: X[int] = X(1)
271-
reveal_type(h) # revealed: X[int]
272-
273-
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
274-
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
275-
276-
j: list[Literal[1, 2, 3]] = [1, 2, 3]
277-
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
278-
279-
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
280-
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
281-
282-
type Y[T] = list[T]
283-
284-
l: Y[Y[Literal[1]]] = [[1]]
285-
reveal_type(l) # revealed: list[Y[Literal[1]]]
286-
287-
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
288-
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
289-
290-
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
291-
reveal_type(n) # revealed: list[tuple[int, str, int]]
292-
293-
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
294-
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
295-
296-
p: list[tuple[int, ...]] = [(1, 1, 1)]
297-
reveal_type(p) # revealed: list[tuple[int, ...]]
298-
299-
# literal promotion occurs based on assignability, an exact match is not required
300-
q: list[int | Literal[1]] = [1]
301-
reveal_type(q) # revealed: list[int]
302-
303-
r: list[Literal[1, 2, 3, 4]] = [1, 2]
304-
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
305-
306-
s: list[Literal[1]]
307-
s = [1]
308-
reveal_type(s) # revealed: list[Literal[1]]
309-
(s := [1])
310-
reveal_type(s) # revealed: list[Literal[1]]
311-
```
312-
313232
## Generic constructor annotations are understood
314233

315234
```toml
@@ -352,17 +271,25 @@ from dataclasses import dataclass
352271
class Y[T]:
353272
value: T
354273

355-
y1: Y[Any] = Y(value=1)
356-
reveal_type(y1) # revealed: Y[Any]
274+
y1 = Y(value=1)
275+
reveal_type(y1) # revealed: Y[int]
276+
277+
y2: Y[Any] = Y(value=1)
278+
reveal_type(y2) # revealed: Y[Any]
357279
```
358280

359281
```py
360282
class Z[T]:
283+
value: T
284+
361285
def __new__(cls, value: T):
362286
return super().__new__(cls)
363287

364-
z1: Z[Any] = Z(1)
365-
reveal_type(z1) # revealed: Z[Any]
288+
z1 = Z(1)
289+
reveal_type(z1) # revealed: Z[int]
290+
291+
z2: Z[Any] = Z(1)
292+
reveal_type(z2) # revealed: Z[Any]
366293
```
367294

368295
## PEP-604 annotations are supported
@@ -481,7 +408,7 @@ def f[T](x: T) -> list[T]:
481408
return [x]
482409

483410
a = f("a")
484-
reveal_type(a) # revealed: list[Literal["a"]]
411+
reveal_type(a) # revealed: list[str]
485412

486413
b: list[int | Literal["a"]] = f("a")
487414
reveal_type(b) # revealed: list[int | Literal["a"]]
@@ -495,10 +422,10 @@ reveal_type(d) # revealed: list[int | tuple[int, int]]
495422
e: list[int] = f(True)
496423
reveal_type(e) # revealed: list[int]
497424

498-
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `list[int]`"
425+
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `list[int]`"
499426
g: list[int] = f("a")
500427

501-
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `tuple[int]`"
428+
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
502429
h: tuple[int] = f("a")
503430

504431
def f2[T: int](x: T) -> T:
@@ -603,7 +530,7 @@ def f3[T](x: T) -> list[T] | dict[T, T]:
603530
return [x]
604531

605532
a = f(1)
606-
reveal_type(a) # revealed: list[Literal[1]]
533+
reveal_type(a) # revealed: list[int]
607534

608535
b: list[Any] = f(1)
609536
reveal_type(b) # revealed: list[Any]
@@ -619,11 +546,11 @@ reveal_type(e) # revealed: list[Any]
619546

620547
f: list[Any] | None = f2(1)
621548
# TODO: Better constraint solver.
622-
reveal_type(f) # revealed: list[Literal[1]] | None
549+
reveal_type(f) # revealed: list[int] | None
623550

624551
g: list[Any] | dict[Any, Any] = f3(1)
625552
# TODO: Better constraint solver.
626-
reveal_type(g) # revealed: list[Literal[1]] | dict[Literal[1], Literal[1]]
553+
reveal_type(g) # revealed: list[int] | dict[int, int]
627554
```
628555

629556
We currently prefer the generic declared type regardless of its variance:
@@ -662,8 +589,8 @@ x4 = invariant(1)
662589

663590
reveal_type(x1) # revealed: Bivariant[Literal[1]]
664591
reveal_type(x2) # revealed: Covariant[Literal[1]]
665-
reveal_type(x3) # revealed: Contravariant[Literal[1]]
666-
reveal_type(x4) # revealed: Invariant[Literal[1]]
592+
reveal_type(x3) # revealed: Contravariant[int]
593+
reveal_type(x4) # revealed: Invariant[int]
667594

668595
x5: Bivariant[Any] = bivariant(1)
669596
x6: Covariant[Any] = covariant(1)

crates/ty_python_semantic/resources/mdtest/bidirectional.md

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,19 @@ python-version = "3.12"
1616
```
1717

1818
```py
19+
from typing import Literal
20+
1921
def list1[T](x: T) -> list[T]:
2022
return [x]
2123

22-
l1 = list1(1)
24+
l1: list[Literal[1]] = list1(1)
2325
reveal_type(l1) # revealed: list[Literal[1]]
24-
l2: list[int] = list1(1)
25-
reveal_type(l2) # revealed: list[int]
2626

27-
# `list[Literal[1]]` and `list[int]` are incompatible, since `list[T]` is invariant in `T`.
28-
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
29-
l2 = l1
30-
31-
intermediate = list1(1)
32-
# TODO: the error will not occur if we can infer the type of `intermediate` to be `list[int]`
33-
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
34-
l3: list[int] = intermediate
35-
# TODO: it would be nice if this were `list[int]`
36-
reveal_type(intermediate) # revealed: list[Literal[1]]
37-
reveal_type(l3) # revealed: list[int]
27+
l2 = list1(1)
28+
reveal_type(l2) # revealed: list[int]
3829

39-
l4: list[int | str] | None = list1(1)
40-
reveal_type(l4) # revealed: list[int | str]
30+
l3: list[int | str] | None = list1(1)
31+
reveal_type(l3) # revealed: list[int | str]
4132

4233
def _(l: list[int] | None = None):
4334
l1 = l or list()
@@ -50,8 +41,6 @@ def _(l: list[int] | None = None):
5041
def f[T](x: T, cond: bool) -> T | list[T]:
5142
return x if cond else [x]
5243

53-
# TODO: Better constraint solver.
54-
# error: [invalid-assignment]
5544
l5: int | list[int] = f(1, True)
5645
```
5746

@@ -233,6 +222,9 @@ def _(flag: bool):
233222

234223
def _(c: C):
235224
c.x = lst(1)
225+
226+
# TODO: Use the parameter type of `__set__` as type context to avoid this error.
227+
# error: [invalid-assignment]
236228
C.x = lst(1)
237229
```
238230

@@ -296,7 +288,7 @@ def f[T](x: T) -> list[T]:
296288

297289
def _(flag: bool):
298290
x1 = f(1) if flag else f(2)
299-
reveal_type(x1) # revealed: list[Literal[1]] | list[Literal[2]]
291+
reveal_type(x1) # revealed: list[int]
300292

301293
x2: list[int | None] = f(1) if flag else f(2)
302294
reveal_type(x2) # revealed: list[int | None]

crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -978,10 +978,10 @@ reveal_type(ParentDataclass.__init__)
978978
reveal_type(ChildOfParentDataclass.__init__)
979979

980980
result_int = uses_dataclass(42)
981-
reveal_type(result_int) # revealed: ChildOfParentDataclass[Literal[42]]
981+
reveal_type(result_int) # revealed: ChildOfParentDataclass[int]
982982

983983
result_str = uses_dataclass("hello")
984-
reveal_type(result_str) # revealed: ChildOfParentDataclass[Literal["hello"]]
984+
reveal_type(result_str) # revealed: ChildOfParentDataclass[str]
985985
```
986986

987987
## Descriptor-typed fields

crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ class C[T, U]:
322322
class D[V](C[V, int]):
323323
def __init__(self, x: V) -> None: ...
324324

325-
reveal_type(D(1)) # revealed: D[int]
325+
reveal_type(D(1)) # revealed: D[Literal[1]]
326326
```
327327

328328
### Generic class inherits `__init__` from generic base class
@@ -334,8 +334,8 @@ class C[T, U]:
334334
class D[T, U](C[T, U]):
335335
pass
336336

337-
reveal_type(C(1, "str")) # revealed: C[int, str]
338-
reveal_type(D(1, "str")) # revealed: D[int, str]
337+
reveal_type(C(1, "str")) # revealed: C[Literal[1], Literal["str"]]
338+
reveal_type(D(1, "str")) # revealed: D[Literal[1], Literal["str"]]
339339
```
340340

341341
### Generic class inherits `__init__` from `dict`
@@ -358,7 +358,7 @@ context. But from the user's point of view, this is another example of the above
358358
```py
359359
class C[T, U](tuple[T, U]): ...
360360

361-
reveal_type(C((1, 2))) # revealed: C[int, int]
361+
reveal_type(C((1, 2))) # revealed: C[Literal[1], Literal[2]]
362362
```
363363

364364
### Upcasting a `tuple` to its `Sequence` supertype
@@ -442,9 +442,9 @@ class D[T, U]:
442442
def __init__(self, t: T, u: U) -> None: ...
443443
def __init__(self, *args) -> None: ...
444444

445-
reveal_type(D("string")) # revealed: D[str, str]
446-
reveal_type(D(1)) # revealed: D[str, int]
447-
reveal_type(D(1, "string")) # revealed: D[int, str]
445+
reveal_type(D("string")) # revealed: D[str, Literal["string"]]
446+
reveal_type(D(1)) # revealed: D[str, Literal[1]]
447+
reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]]
448448
```
449449

450450
### Synthesized methods with dataclasses

0 commit comments

Comments
 (0)