Skip to content

Commit 2e13754

Browse files
committed
improve generic call expression inference
1 parent bb40c34 commit 2e13754

File tree

12 files changed

+618
-250
lines changed

12 files changed

+618
-250
lines changed

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

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,14 +427,13 @@ a = f("a")
427427
reveal_type(a) # revealed: list[Literal["a"]]
428428

429429
b: list[int | Literal["a"]] = f("a")
430-
reveal_type(b) # revealed: list[Literal["a"] | int]
430+
reveal_type(b) # revealed: list[int | Literal["a"]]
431431

432432
c: list[int | str] = f("a")
433-
reveal_type(c) # revealed: list[str | int]
433+
reveal_type(c) # revealed: list[int | str]
434434

435435
d: list[int | tuple[int, int]] = f((1, 2))
436-
# TODO: We could avoid reordering the union elements here.
437-
reveal_type(d) # revealed: list[tuple[int, int] | int]
436+
reveal_type(d) # revealed: list[int | tuple[int, int]]
438437

439438
e: list[int] = f(True)
440439
reveal_type(e) # revealed: list[int]
@@ -455,7 +454,81 @@ j: int | str = f2(True)
455454
reveal_type(j) # revealed: Literal[True]
456455
```
457456

458-
Types are not widened unnecessarily:
457+
The function arguments are also inferred using the type context:
458+
459+
```py
460+
from typing import TypedDict
461+
462+
class TD(TypedDict):
463+
x: int
464+
465+
def f[T](x: list[T]) -> T:
466+
return x[0]
467+
468+
a: TD = f([{"x": 0}, {"x": 1}])
469+
# TODO: This should reveal `TD` after typed dict assignability is implemented.
470+
reveal_type(a) # revealed: Unknown | dict[Unknown | str, Unknown | int]
471+
472+
b: TD | None = f([{"x": 0}, {"x": 1}])
473+
# TODO: This should reveal `TD` after typed dict assignability is implemented.
474+
reveal_type(b) # revealed: Unknown | dict[Unknown | str, Unknown | int]
475+
476+
# TODO: This should error after typed dict assignability is implemented.
477+
# TODO: error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
478+
# TODO: error: [invalid-key] "Invalid key for TypedDict `TD`: Unknown key "y""
479+
c: TD = f([{"y": 0}, {"x": 1}])
480+
481+
# TODO: This should error after typed dict assignability is implemented.
482+
c: TD | None = f([{"y": 0}, {"x": 1}])
483+
```
484+
485+
## Prefer the declared type of generic classes
486+
487+
```toml
488+
[environment]
489+
python-version = "3.12"
490+
```
491+
492+
```py
493+
from typing import Any
494+
495+
def f[T](x: T) -> list[T]:
496+
return [x]
497+
498+
def f2[T](x: T) -> list[T] | None:
499+
return [x]
500+
501+
def f3[T](x: T) -> list[T] | dict[T, T]:
502+
return [x]
503+
504+
a = f(1)
505+
reveal_type(a) # revealed: list[Literal[1]]
506+
507+
b: list[Any] = f(1)
508+
reveal_type(b) # revealed: list[Any]
509+
510+
c: list[Any] = [1]
511+
reveal_type(c) # revealed: list[Any]
512+
513+
d: list[Any] | None = f(1)
514+
reveal_type(d) # revealed: list[Any]
515+
516+
e: list[Any] | None = [1]
517+
reveal_type(e) # revealed: list[Any]
518+
519+
f: list[Any] | None = f2(1)
520+
reveal_type(f) # revealed: list[Any] | None
521+
522+
g: list[Any] | dict[Any, Any] = f3(1)
523+
reveal_type(g) # revealed: list[Any] | dict[Any, Any]
524+
```
525+
526+
## Prefer the inferred type of non-generic classes
527+
528+
```toml
529+
[environment]
530+
python-version = "3.12"
531+
```
459532

460533
```py
461534
def id[T](x: T) -> T:
@@ -476,10 +549,8 @@ def _(i: int):
476549
b: list[int | None] | None = id([i])
477550
c: list[int | None] | int | None = id([i])
478551
reveal_type(a) # revealed: list[int | None]
479-
# TODO: these should reveal `list[int | None]`
480-
# we currently do not use the call expression annotation as type context for argument inference
481-
reveal_type(b) # revealed: list[Unknown | int]
482-
reveal_type(c) # revealed: list[Unknown | int]
552+
reveal_type(b) # revealed: list[int | None]
553+
reveal_type(c) # revealed: list[int | None]
483554

484555
a: list[int | None] | None = [i]
485556
b: list[int | None] | None = lst(i)
@@ -494,4 +565,23 @@ def _(i: int):
494565
reveal_type(a) # revealed: list[Unknown]
495566
reveal_type(b) # revealed: list[Unknown]
496567
reveal_type(c) # revealed: list[Unknown]
568+
569+
def f[T](x: list[T]) -> T:
570+
return x[0]
571+
572+
def _(a: int, b: int | str):
573+
x1: int = f(lst(a))
574+
reveal_type(x1) # revealed: int
575+
576+
x2: int | str = f(lst(a))
577+
reveal_type(x2) # revealed: int
578+
579+
x3: int | None = f(lst(a))
580+
reveal_type(x3) # revealed: int
581+
582+
x4: int | str = f(lst(b))
583+
reveal_type(x4) # revealed: int | str
584+
585+
x5: int | str | None = f(lst(b))
586+
reveal_type(x5) # revealed: int | str
497587
```

crates/ty_python_semantic/resources/mdtest/bidirectional.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ def _(l: list[int] | None = None):
5050
def f[T](x: T, cond: bool) -> T | list[T]:
5151
return x if cond else [x]
5252

53-
# TODO: no error
54-
# error: [invalid-assignment] "Object of type `Literal[1] | list[Literal[1]]` is not assignable to `int | list[int]`"
5553
l5: int | list[int] = f(1, True)
5654
```
5755

crates/ty_python_semantic/resources/mdtest/class/super.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,7 @@ class BuilderMeta2(type):
206206
) -> BuilderMeta2:
207207
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
208208
s = reveal_type(super())
209-
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
210-
# revealed: Unknown
209+
# revealed: BuilderMeta2
211210
return reveal_type(s.__new__(cls, name, bases, dct))
212211

213212
class Foo[T]:

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Data:
3737
content: list[int] = field(default_factory=list)
3838
timestamp: datetime = field(default_factory=datetime.now, init=False)
3939

40-
# revealed: (self: Data, content: list[int] = Unknown) -> None
40+
# revealed: (self: Data, content: list[int] = list[int]) -> None
4141
reveal_type(Data.__init__)
4242

4343
data = Data([1, 2, 3])
@@ -63,7 +63,6 @@ class Person:
6363
age: int | None = field(default=None, kw_only=True)
6464
role: str = field(default="user", kw_only=True)
6565

66-
# TODO: this would ideally show a default value of `None` for `age`
6766
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = Literal["user"]) -> None
6867
reveal_type(Person.__init__)
6968

0 commit comments

Comments
 (0)