[ty] Infer slightly more precise types for comprehensions (#20111)
Some checks are pending
CI / mkdocs (push) Waiting to run
CI / test ruff-lsp (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This commit is contained in:
Alex Waygood 2025-08-27 13:21:47 +01:00 committed by GitHub
parent d71518b369
commit 7d0c8e045c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 111 additions and 95 deletions

View file

@ -48,6 +48,24 @@ def _(
reveal_type(h_) # revealed: Unknown reveal_type(h_) # revealed: Unknown
reveal_type(i_) # revealed: Unknown reveal_type(i_) # revealed: Unknown
reveal_type(j_) # revealed: Unknown reveal_type(j_) # revealed: Unknown
# Inspired by the conformance test suite at
# https://github.com/python/typing/blob/d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc/conformance/tests/aliases_implicit.py#L88-L122
B = [x for x in range(42)]
C = {x for x in range(42)}
D = {x: y for x, y in enumerate(range(42))}
E = (x for x in range(42))
def _(
b: B, # error: [invalid-type-form]
c: C, # error: [invalid-type-form]
d: D, # error: [invalid-type-form]
e: E, # error: [invalid-type-form]
):
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: Unknown
``` ```
## Invalid AST nodes ## Invalid AST nodes

View file

@ -75,7 +75,7 @@ a: tuple[()] = (1, 2)
# error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`" # error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`"
b: tuple[int] = ("foo",) b: tuple[int] = ("foo",)
# error: [invalid-assignment] "Object of type `tuple[list[Unknown], Literal["foo"]]` is not assignable to `tuple[str | int, str]`" # error: [invalid-assignment]
c: tuple[str | int, str] = ([], "foo") c: tuple[str | int, str] = ([], "foo")
``` ```

View file

@ -46,7 +46,7 @@ def delete():
del d # error: [unresolved-reference] "Name `d` used when not defined" del d # error: [unresolved-reference] "Name `d` used when not defined"
delete() delete()
reveal_type(d) # revealed: list[Unknown] reveal_type(d) # revealed: list[@Todo(list literal element type)]
def delete_element(): def delete_element():
# When the `del` target isn't a name, it doesn't force local resolution. # When the `del` target isn't a name, it doesn't force local resolution.
@ -62,7 +62,7 @@ def delete_global():
delete_global() delete_global()
# Again, the variable should have been removed, but we don't check it. # Again, the variable should have been removed, but we don't check it.
reveal_type(d) # revealed: list[Unknown] reveal_type(d) # revealed: list[@Todo(list literal element type)]
def delete_nonlocal(): def delete_nonlocal():
e = 2 e = 2

View file

@ -33,12 +33,6 @@ def _():
def _(): def _():
assert_never(None) # error: [type-assertion-failure] assert_never(None) # error: [type-assertion-failure]
def _():
assert_never([]) # error: [type-assertion-failure]
def _():
assert_never({}) # error: [type-assertion-failure]
def _(): def _():
assert_never(()) # error: [type-assertion-failure] assert_never(()) # error: [type-assertion-failure]

View file

@ -785,7 +785,7 @@ from subexporter import *
# TODO: Should be `list[str]` # TODO: Should be `list[str]`
# TODO: Should we avoid including `Unknown` for this case? # TODO: Should we avoid including `Unknown` for this case?
reveal_type(__all__) # revealed: Unknown | list[Unknown] reveal_type(__all__) # revealed: Unknown | list[@Todo(list literal element type)]
__all__.append("B") __all__.append("B")

View file

@ -3,5 +3,12 @@
## Empty dictionary ## Empty dictionary
```py ```py
reveal_type({}) # revealed: dict[Unknown, Unknown] reveal_type({}) # revealed: dict[@Todo(dict literal key type), @Todo(dict literal value type)]
```
## Dict comprehensions
```py
# revealed: dict[@Todo(dict comprehension key type), @Todo(dict comprehension value type)]
reveal_type({x: y for x, y in enumerate(range(42))})
``` ```

View file

@ -0,0 +1,6 @@
# Generator expressions
```py
# revealed: GeneratorType[@Todo(generator expression yield type), @Todo(generator expression send type), @Todo(generator expression return type)]
reveal_type((x for x in range(42)))
```

View file

@ -3,5 +3,11 @@
## Empty list ## Empty list
```py ```py
reveal_type([]) # revealed: list[Unknown] reveal_type([]) # revealed: list[@Todo(list literal element type)]
```
## List comprehensions
```py
reveal_type([x for x in range(42)]) # revealed: list[@Todo(list comprehension element type)]
``` ```

View file

@ -3,5 +3,11 @@
## Basic set ## Basic set
```py ```py
reveal_type({1, 2}) # revealed: set[Unknown] reveal_type({1, 2}) # revealed: set[@Todo(set literal element type)]
```
## Set comprehensions
```py
reveal_type({x for x in range(42)}) # revealed: set[@Todo(set comprehension element type)]
``` ```

View file

@ -207,7 +207,7 @@ dd[0] = 0
cm: ChainMap[int, int] = ChainMap({1: 1}, {0: 0}) cm: ChainMap[int, int] = ChainMap({1: 1}, {0: 0})
cm[0] = 0 cm[0] = 0
# TODO: should be ChainMap[int, int] # TODO: should be ChainMap[int, int]
reveal_type(cm) # revealed: ChainMap[Unknown, Unknown] reveal_type(cm) # revealed: ChainMap[@Todo(dict literal key type), @Todo(dict literal value type)]
reveal_type(l[0]) # revealed: Literal[0] reveal_type(l[0]) # revealed: Literal[0]
reveal_type(d[0]) # revealed: Literal[0] reveal_type(d[0]) # revealed: Literal[0]

View file

@ -318,7 +318,7 @@ def f(l: list[str | None]):
l: list[str | None] = [None] l: list[str | None] = [None]
def _(): def _():
# TODO: should be `str | None` # TODO: should be `str | None`
reveal_type(l[0]) # revealed: Unknown reveal_type(l[0]) # revealed: @Todo(list literal element type)
def _(): def _():
def _(): def _():

View file

@ -25,22 +25,16 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.
11 | assert_never(None) # error: [type-assertion-failure] 11 | assert_never(None) # error: [type-assertion-failure]
12 | 12 |
13 | def _(): 13 | def _():
14 | assert_never([]) # error: [type-assertion-failure] 14 | assert_never(()) # error: [type-assertion-failure]
15 | 15 |
16 | def _(): 16 | def _(flag: bool, never: Never):
17 | assert_never({}) # error: [type-assertion-failure] 17 | assert_never(1 if flag else never) # error: [type-assertion-failure]
18 | 18 |
19 | def _(): 19 | def _(any_: Any):
20 | assert_never(()) # error: [type-assertion-failure] 20 | assert_never(any_) # error: [type-assertion-failure]
21 | 21 |
22 | def _(flag: bool, never: Never): 22 | def _(unknown: Unknown):
23 | assert_never(1 if flag else never) # error: [type-assertion-failure] 23 | assert_never(unknown) # error: [type-assertion-failure]
24 |
25 | def _(any_: Any):
26 | assert_never(any_) # error: [type-assertion-failure]
27 |
28 | def _(unknown: Unknown):
29 | assert_never(unknown) # error: [type-assertion-failure]
``` ```
# Diagnostics # Diagnostics
@ -101,46 +95,12 @@ error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:14:5 --> src/mdtest_snippet.py:14:5
| |
13 | def _(): 13 | def _():
14 | assert_never([]) # error: [type-assertion-failure] 14 | assert_never(()) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^--^
| |
| Inferred type of argument is `list[Unknown]`
15 |
16 | def _():
|
info: `Never` and `list[Unknown]` are not equivalent types
info: rule `type-assertion-failure` is enabled by default
```
```
error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:17:5
|
16 | def _():
17 | assert_never({}) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^--^
| |
| Inferred type of argument is `dict[Unknown, Unknown]`
18 |
19 | def _():
|
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
info: rule `type-assertion-failure` is enabled by default
```
```
error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:20:5
|
19 | def _():
20 | assert_never(()) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^--^ | ^^^^^^^^^^^^^--^
| | | |
| Inferred type of argument is `tuple[()]` | Inferred type of argument is `tuple[()]`
21 | 15 |
22 | def _(flag: bool, never: Never): 16 | def _(flag: bool, never: Never):
| |
info: `Never` and `tuple[()]` are not equivalent types info: `Never` and `tuple[()]` are not equivalent types
info: rule `type-assertion-failure` is enabled by default info: rule `type-assertion-failure` is enabled by default
@ -149,15 +109,15 @@ info: rule `type-assertion-failure` is enabled by default
``` ```
error[type-assertion-failure]: Argument does not have asserted type `Never` error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:23:5 --> src/mdtest_snippet.py:17:5
| |
22 | def _(flag: bool, never: Never): 16 | def _(flag: bool, never: Never):
23 | assert_never(1 if flag else never) # error: [type-assertion-failure] 17 | assert_never(1 if flag else never) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^--------------------^ | ^^^^^^^^^^^^^--------------------^
| | | |
| Inferred type of argument is `Literal[1]` | Inferred type of argument is `Literal[1]`
24 | 18 |
25 | def _(any_: Any): 19 | def _(any_: Any):
| |
info: `Never` and `Literal[1]` are not equivalent types info: `Never` and `Literal[1]` are not equivalent types
info: rule `type-assertion-failure` is enabled by default info: rule `type-assertion-failure` is enabled by default
@ -166,15 +126,15 @@ info: rule `type-assertion-failure` is enabled by default
``` ```
error[type-assertion-failure]: Argument does not have asserted type `Never` error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:26:5 --> src/mdtest_snippet.py:20:5
| |
25 | def _(any_: Any): 19 | def _(any_: Any):
26 | assert_never(any_) # error: [type-assertion-failure] 20 | assert_never(any_) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^----^ | ^^^^^^^^^^^^^----^
| | | |
| Inferred type of argument is `Any` | Inferred type of argument is `Any`
27 | 21 |
28 | def _(unknown: Unknown): 22 | def _(unknown: Unknown):
| |
info: `Never` and `Any` are not equivalent types info: `Never` and `Any` are not equivalent types
info: rule `type-assertion-failure` is enabled by default info: rule `type-assertion-failure` is enabled by default
@ -183,10 +143,10 @@ info: rule `type-assertion-failure` is enabled by default
``` ```
error[type-assertion-failure]: Argument does not have asserted type `Never` error[type-assertion-failure]: Argument does not have asserted type `Never`
--> src/mdtest_snippet.py:29:5 --> src/mdtest_snippet.py:23:5
| |
28 | def _(unknown: Unknown): 22 | def _(unknown: Unknown):
29 | assert_never(unknown) # error: [type-assertion-failure] 23 | assert_never(unknown) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^-------^ | ^^^^^^^^^^^^^-------^
| | | |
| Inferred type of argument is `Unknown` | Inferred type of argument is `Unknown`

View file

@ -9,13 +9,13 @@ A list can be indexed into with:
```py ```py
x = [1, 2, 3] x = [1, 2, 3]
reveal_type(x) # revealed: list[Unknown] reveal_type(x) # revealed: list[@Todo(list literal element type)]
# TODO reveal int # TODO reveal int
reveal_type(x[0]) # revealed: Unknown reveal_type(x[0]) # revealed: @Todo(list literal element type)
# TODO reveal list[int] # TODO reveal list[int]
reveal_type(x[0:1]) # revealed: list[Unknown] reveal_type(x[0:1]) # revealed: list[@Todo(list literal element type)]
# error: [invalid-argument-type] # error: [invalid-argument-type]
reveal_type(x["a"]) # revealed: Unknown reveal_type(x["a"]) # revealed: Unknown

View file

@ -47,9 +47,9 @@ def f(x: Iterable[int], y: list[str], z: Never, aa: list[Never]):
reveal_type(tuple((1, 2))) # revealed: tuple[Literal[1], Literal[2]] reveal_type(tuple((1, 2))) # revealed: tuple[Literal[1], Literal[2]]
# TODO: should be `tuple[Literal[1], ...]` # TODO: should be `tuple[Literal[1], ...]`
reveal_type(tuple([1])) # revealed: tuple[Unknown, ...] reveal_type(tuple([1])) # revealed: tuple[@Todo(list literal element type), ...]
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[int]`, found `list[Unknown]`" # error: [invalid-argument-type]
reveal_type(tuple[int]([1])) # revealed: tuple[int] reveal_type(tuple[int]([1])) # revealed: tuple[int]
# error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[int, str]`, found `tuple[Literal[1]]`" # error: [invalid-argument-type] "Argument is incorrect: Expected `tuple[int, str]`, found `tuple[Literal[1]]`"

View file

@ -214,8 +214,8 @@ reveal_type(d) # revealed: Literal[2]
```py ```py
a, b = [1, 2] a, b = [1, 2]
# TODO: should be `int` for both `a` and `b` # TODO: should be `int` for both `a` and `b`
reveal_type(a) # revealed: Unknown reveal_type(a) # revealed: @Todo(list literal element type)
reveal_type(b) # revealed: Unknown reveal_type(b) # revealed: @Todo(list literal element type)
``` ```
### Simple unpacking ### Simple unpacking

View file

@ -5805,8 +5805,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_expression(elt); self.infer_expression(elt);
} }
// TODO generic KnownClass::List
KnownClass::List.to_instance(self.db()) .to_specialized_instance(self.db(), [todo_type!("list literal element type")])
} }
fn infer_set_expression(&mut self, set: &ast::ExprSet) -> Type<'db> { fn infer_set_expression(&mut self, set: &ast::ExprSet) -> Type<'db> {
@ -5820,8 +5820,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_expression(elt); self.infer_expression(elt);
} }
// TODO generic KnownClass::Set.to_specialized_instance(self.db(), [todo_type!("set literal element type")])
KnownClass::Set.to_instance(self.db())
} }
fn infer_dict_expression(&mut self, dict: &ast::ExprDict) -> Type<'db> { fn infer_dict_expression(&mut self, dict: &ast::ExprDict) -> Type<'db> {
@ -5836,8 +5835,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_expression(&item.value); self.infer_expression(&item.value);
} }
// TODO generic KnownClass::Dict.to_specialized_instance(
KnownClass::Dict.to_instance(self.db()) self.db(),
[
todo_type!("dict literal key type"),
todo_type!("dict literal value type"),
],
)
} }
/// Infer the type of the `iter` expression of the first comprehension. /// Infer the type of the `iter` expression of the first comprehension.
@ -5860,7 +5864,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
todo_type!("generator type") KnownClass::GeneratorType.to_specialized_instance(
self.db(),
[
todo_type!("generator expression yield type"),
todo_type!("generator expression send type"),
todo_type!("generator expression return type"),
],
)
} }
fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> { fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> {
@ -5873,7 +5884,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
todo_type!("list comprehension type") KnownClass::List
.to_specialized_instance(self.db(), [todo_type!("list comprehension element type")])
} }
fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> { fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> {
@ -5887,7 +5899,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
todo_type!("dict comprehension type") KnownClass::Dict.to_specialized_instance(
self.db(),
[
todo_type!("dict comprehension key type"),
todo_type!("dict comprehension value type"),
],
)
} }
fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> { fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> {
@ -5900,7 +5918,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
todo_type!("set comprehension type") KnownClass::Set
.to_specialized_instance(self.db(), [todo_type!("set comprehension element type")])
} }
fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) { fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) {