Skip to content

Commit 324e5cb

Browse files
authored
[ty] Pull types on synthesized Python files created by mdtest (#18539)
1 parent e6fe2af commit 324e5cb

File tree

14 files changed

+361
-191
lines changed

14 files changed

+361
-191
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ty_python_semantic/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ strum_macros = { workspace = true }
5050
[dev-dependencies]
5151
ruff_db = { workspace = true, features = ["testing", "os"] }
5252
ruff_python_parser = { workspace = true }
53+
ty_python_semantic = { workspace = true, features = ["testing"] }
5354
ty_test = { workspace = true }
5455
ty_vendored = { workspace = true }
5556

@@ -63,6 +64,7 @@ quickcheck_macros = { version = "1.0.0" }
6364

6465
[features]
6566
serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"]
67+
testing = []
6668

6769
[lints]
6870
workspace = true

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ x: int = MagicMock()
139139

140140
## Invalid
141141

142+
<!-- pull-types:skip -->
143+
142144
`Any` cannot be parameterized:
143145

144146
```py

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ def _(c: Callable[[int, 42, str, False], None]):
5858

5959
### Missing return type
6060

61+
<!-- pull-types:skip -->
62+
6163
Using a parameter list:
6264

6365
```py

crates/ty_python_semantic/resources/mdtest/type_api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ directly.
1414

1515
### Negation
1616

17+
<!-- pull-types:skip -->
18+
1719
```py
1820
from typing import Literal
1921
from ty_extensions import Not, static_assert
@@ -371,6 +373,8 @@ static_assert(not is_single_valued(Literal["a"] | Literal["b"]))
371373

372374
## `TypeOf`
373375

376+
<!-- pull-types:skip -->
377+
374378
We use `TypeOf` to get the inferred type of an expression. This is useful when we want to refer to
375379
it in a type expression. For example, if we want to make sure that the class literal type `str` is a
376380
subtype of `type[str]`, we can not use `is_subtype_of(str, type[str])`, as that would test if the
@@ -412,6 +416,8 @@ def f(x: TypeOf) -> None:
412416

413417
## `CallableTypeOf`
414418

419+
<!-- pull-types:skip -->
420+
415421
The `CallableTypeOf` special form can be used to extract the `Callable` structural type inhabited by
416422
a given callable object. This can be used to get the externally visibly signature of the object,
417423
which can then be used to test various type properties.

crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ d.a = 2
8484

8585
## Too many arguments
8686

87+
<!-- pull-types:skip -->
88+
8789
```py
8890
from typing import ClassVar
8991

crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ reveal_type(FINAL_E) # revealed: int
4545

4646
## Too many arguments
4747

48+
<!-- pull-types:skip -->
49+
4850
```py
4951
from typing import Final
5052

crates/ty_python_semantic/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub mod types;
3535
mod unpack;
3636
mod util;
3737

38+
#[cfg(feature = "testing")]
39+
pub mod pull_types;
40+
3841
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
3942

4043
/// Returns the default registry with all known semantic lints.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//! A utility visitor for testing, which attempts to "pull a type" for ever sub-node in a given AST.
2+
//!
3+
//! This is used in the "corpus" and (indirectly) the "mdtest" integration tests for this crate.
4+
//! (Mdtest uses the `pull_types` function via the `ty_test` crate.)
5+
6+
use crate::{Db, HasType, SemanticModel};
7+
use ruff_db::{files::File, parsed::parsed_module};
8+
use ruff_python_ast::{
9+
self as ast, visitor::source_order, visitor::source_order::SourceOrderVisitor,
10+
};
11+
12+
pub fn pull_types(db: &dyn Db, file: File) {
13+
let mut visitor = PullTypesVisitor::new(db, file);
14+
15+
let ast = parsed_module(db.upcast(), file).load(db.upcast());
16+
17+
visitor.visit_body(ast.suite());
18+
}
19+
20+
struct PullTypesVisitor<'db> {
21+
model: SemanticModel<'db>,
22+
}
23+
24+
impl<'db> PullTypesVisitor<'db> {
25+
fn new(db: &'db dyn Db, file: File) -> Self {
26+
Self {
27+
model: SemanticModel::new(db, file),
28+
}
29+
}
30+
31+
fn visit_target(&mut self, target: &ast::Expr) {
32+
match target {
33+
ast::Expr::List(ast::ExprList { elts, .. })
34+
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
35+
for element in elts {
36+
self.visit_target(element);
37+
}
38+
}
39+
_ => self.visit_expr(target),
40+
}
41+
}
42+
}
43+
44+
impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
45+
fn visit_stmt(&mut self, stmt: &ast::Stmt) {
46+
match stmt {
47+
ast::Stmt::FunctionDef(function) => {
48+
let _ty = function.inferred_type(&self.model);
49+
}
50+
ast::Stmt::ClassDef(class) => {
51+
let _ty = class.inferred_type(&self.model);
52+
}
53+
ast::Stmt::Assign(assign) => {
54+
for target in &assign.targets {
55+
self.visit_target(target);
56+
}
57+
self.visit_expr(&assign.value);
58+
return;
59+
}
60+
ast::Stmt::For(for_stmt) => {
61+
self.visit_target(&for_stmt.target);
62+
self.visit_expr(&for_stmt.iter);
63+
self.visit_body(&for_stmt.body);
64+
self.visit_body(&for_stmt.orelse);
65+
return;
66+
}
67+
ast::Stmt::With(with_stmt) => {
68+
for item in &with_stmt.items {
69+
if let Some(target) = &item.optional_vars {
70+
self.visit_target(target);
71+
}
72+
self.visit_expr(&item.context_expr);
73+
}
74+
75+
self.visit_body(&with_stmt.body);
76+
return;
77+
}
78+
ast::Stmt::AnnAssign(_)
79+
| ast::Stmt::Return(_)
80+
| ast::Stmt::Delete(_)
81+
| ast::Stmt::AugAssign(_)
82+
| ast::Stmt::TypeAlias(_)
83+
| ast::Stmt::While(_)
84+
| ast::Stmt::If(_)
85+
| ast::Stmt::Match(_)
86+
| ast::Stmt::Raise(_)
87+
| ast::Stmt::Try(_)
88+
| ast::Stmt::Assert(_)
89+
| ast::Stmt::Import(_)
90+
| ast::Stmt::ImportFrom(_)
91+
| ast::Stmt::Global(_)
92+
| ast::Stmt::Nonlocal(_)
93+
| ast::Stmt::Expr(_)
94+
| ast::Stmt::Pass(_)
95+
| ast::Stmt::Break(_)
96+
| ast::Stmt::Continue(_)
97+
| ast::Stmt::IpyEscapeCommand(_) => {}
98+
}
99+
100+
source_order::walk_stmt(self, stmt);
101+
}
102+
103+
fn visit_expr(&mut self, expr: &ast::Expr) {
104+
let _ty = expr.inferred_type(&self.model);
105+
106+
source_order::walk_expr(self, expr);
107+
}
108+
109+
fn visit_comprehension(&mut self, comprehension: &ast::Comprehension) {
110+
self.visit_expr(&comprehension.iter);
111+
self.visit_target(&comprehension.target);
112+
for if_expr in &comprehension.ifs {
113+
self.visit_expr(if_expr);
114+
}
115+
}
116+
117+
fn visit_parameter(&mut self, parameter: &ast::Parameter) {
118+
let _ty = parameter.inferred_type(&self.model);
119+
120+
source_order::walk_parameter(self, parameter);
121+
}
122+
123+
fn visit_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) {
124+
let _ty = parameter_with_default.inferred_type(&self.model);
125+
126+
source_order::walk_parameter_with_default(self, parameter_with_default);
127+
}
128+
129+
fn visit_alias(&mut self, alias: &ast::Alias) {
130+
let _ty = alias.inferred_type(&self.model);
131+
132+
source_order::walk_alias(self, alias);
133+
}
134+
}

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,10 @@ impl<'db> TypeInference<'db> {
471471
#[track_caller]
472472
pub(crate) fn expression_type(&self, expression: ScopedExpressionId) -> Type<'db> {
473473
self.try_expression_type(expression).expect(
474-
"expression should belong to this TypeInference region and \
475-
TypeInferenceBuilder should have inferred a type for it",
474+
"Failed to retrieve the inferred type for an `ast::Expr` node \
475+
passed to `TypeInference::expression_type()`. The `TypeInferenceBuilder` \
476+
should infer and store types for all `ast::Expr` nodes in any `TypeInference` \
477+
region it analyzes.",
476478
)
477479
}
478480

0 commit comments

Comments
 (0)