Skip to content

Commit e88c97b

Browse files
committed
[ty] Pull types on synthesized Python files created by mdtest
1 parent e84406d commit e88c97b

File tree

8 files changed

+164
-134
lines changed

8 files changed

+164
-134
lines changed

Cargo.lock

Lines changed: 1 addition & 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/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

crates/ty_python_semantic/tests/corpus.rs

Lines changed: 4 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
use anyhow::{Context, anyhow};
22
use ruff_db::Upcast;
33
use ruff_db::files::{File, Files, system_path_to_file};
4-
use ruff_db::parsed::parsed_module;
54
use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem};
65
use ruff_db::vendored::VendoredFileSystem;
7-
use ruff_python_ast::visitor::source_order;
8-
use ruff_python_ast::visitor::source_order::SourceOrderVisitor;
9-
use ruff_python_ast::{
10-
self as ast, Alias, Comprehension, Expr, Parameter, ParameterWithDefault, PythonVersion, Stmt,
11-
};
6+
use ruff_python_ast::PythonVersion;
127

138
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
9+
use ty_python_semantic::pull_types::pull_types;
1410
use ty_python_semantic::{
15-
Db, HasType, Program, ProgramSettings, PythonPlatform, PythonVersionSource,
16-
PythonVersionWithSource, SearchPathSettings, SemanticModel, default_lint_registry,
11+
Program, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
12+
SearchPathSettings, default_lint_registry,
1713
};
1814

1915
fn get_cargo_workspace_root() -> anyhow::Result<SystemPathBuf> {
@@ -174,129 +170,6 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> {
174170
Ok(())
175171
}
176172

177-
fn pull_types(db: &dyn Db, file: File) {
178-
let mut visitor = PullTypesVisitor::new(db, file);
179-
180-
let ast = parsed_module(db.upcast(), file).load(db.upcast());
181-
182-
visitor.visit_body(ast.suite());
183-
}
184-
185-
struct PullTypesVisitor<'db> {
186-
model: SemanticModel<'db>,
187-
}
188-
189-
impl<'db> PullTypesVisitor<'db> {
190-
fn new(db: &'db dyn Db, file: File) -> Self {
191-
Self {
192-
model: SemanticModel::new(db, file),
193-
}
194-
}
195-
196-
fn visit_target(&mut self, target: &Expr) {
197-
match target {
198-
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
199-
for element in elts {
200-
self.visit_target(element);
201-
}
202-
}
203-
_ => self.visit_expr(target),
204-
}
205-
}
206-
}
207-
208-
impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
209-
fn visit_stmt(&mut self, stmt: &Stmt) {
210-
match stmt {
211-
Stmt::FunctionDef(function) => {
212-
let _ty = function.inferred_type(&self.model);
213-
}
214-
Stmt::ClassDef(class) => {
215-
let _ty = class.inferred_type(&self.model);
216-
}
217-
Stmt::Assign(assign) => {
218-
for target in &assign.targets {
219-
self.visit_target(target);
220-
}
221-
self.visit_expr(&assign.value);
222-
return;
223-
}
224-
Stmt::For(for_stmt) => {
225-
self.visit_target(&for_stmt.target);
226-
self.visit_expr(&for_stmt.iter);
227-
self.visit_body(&for_stmt.body);
228-
self.visit_body(&for_stmt.orelse);
229-
return;
230-
}
231-
Stmt::With(with_stmt) => {
232-
for item in &with_stmt.items {
233-
if let Some(target) = &item.optional_vars {
234-
self.visit_target(target);
235-
}
236-
self.visit_expr(&item.context_expr);
237-
}
238-
239-
self.visit_body(&with_stmt.body);
240-
return;
241-
}
242-
Stmt::AnnAssign(_)
243-
| Stmt::Return(_)
244-
| Stmt::Delete(_)
245-
| Stmt::AugAssign(_)
246-
| Stmt::TypeAlias(_)
247-
| Stmt::While(_)
248-
| Stmt::If(_)
249-
| Stmt::Match(_)
250-
| Stmt::Raise(_)
251-
| Stmt::Try(_)
252-
| Stmt::Assert(_)
253-
| Stmt::Import(_)
254-
| Stmt::ImportFrom(_)
255-
| Stmt::Global(_)
256-
| Stmt::Nonlocal(_)
257-
| Stmt::Expr(_)
258-
| Stmt::Pass(_)
259-
| Stmt::Break(_)
260-
| Stmt::Continue(_)
261-
| Stmt::IpyEscapeCommand(_) => {}
262-
}
263-
264-
source_order::walk_stmt(self, stmt);
265-
}
266-
267-
fn visit_expr(&mut self, expr: &Expr) {
268-
let _ty = expr.inferred_type(&self.model);
269-
270-
source_order::walk_expr(self, expr);
271-
}
272-
273-
fn visit_comprehension(&mut self, comprehension: &Comprehension) {
274-
self.visit_expr(&comprehension.iter);
275-
self.visit_target(&comprehension.target);
276-
for if_expr in &comprehension.ifs {
277-
self.visit_expr(if_expr);
278-
}
279-
}
280-
281-
fn visit_parameter(&mut self, parameter: &Parameter) {
282-
let _ty = parameter.inferred_type(&self.model);
283-
284-
source_order::walk_parameter(self, parameter);
285-
}
286-
287-
fn visit_parameter_with_default(&mut self, parameter_with_default: &ParameterWithDefault) {
288-
let _ty = parameter_with_default.inferred_type(&self.model);
289-
290-
source_order::walk_parameter_with_default(self, parameter_with_default);
291-
}
292-
293-
fn visit_alias(&mut self, alias: &Alias) {
294-
let _ty = alias.inferred_type(&self.model);
295-
296-
source_order::walk_alias(self, alias);
297-
}
298-
}
299-
300173
/// Whether or not the .py/.pyi version of this file is expected to fail
301174
#[rustfmt::skip]
302175
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[

crates/ty_test/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ruff_python_trivia = { workspace = true }
1818
ruff_source_file = { workspace = true }
1919
ruff_text_size = { workspace = true }
2020
ruff_python_ast = { workspace = true }
21-
ty_python_semantic = { workspace = true, features = ["serde"] }
21+
ty_python_semantic = { workspace = true, features = ["serde", "testing"] }
2222
ty_vendored = { workspace = true }
2323

2424
anyhow = { workspace = true }

crates/ty_test/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use ruff_db::testing::{setup_logging, setup_logging_with_filter};
1717
use ruff_source_file::{LineIndex, OneIndexed};
1818
use std::backtrace::BacktraceStatus;
1919
use std::fmt::Write;
20+
use ty_python_semantic::pull_types::pull_types;
2021
use ty_python_semantic::types::check_types;
2122
use ty_python_semantic::{
2223
Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource,
@@ -294,6 +295,10 @@ fn run_test(
294295
let failures: Failures = test_files
295296
.into_iter()
296297
.filter_map(|test_file| {
298+
if !KNOWN_PULL_TYPES_FAILURES.contains(&&*relative_fixture_path.as_str().replace('\\', "/")) {
299+
pull_types(db, test_file.file);
300+
}
301+
297302
let parsed = parsed_module(db, test_file.file).load(db);
298303

299304
let mut diagnostics: Vec<Diagnostic> = parsed
@@ -350,6 +355,7 @@ fn run_test(
350355
});
351356
}
352357
};
358+
353359
diagnostics.extend(type_diagnostics.into_iter().cloned());
354360
diagnostics.sort_by(|left, right|left.rendering_sort_key(db).cmp(&right.rendering_sort_key(db)));
355361

@@ -363,6 +369,7 @@ fn run_test(
363369
if test.should_snapshot_diagnostics() {
364370
snapshot_diagnostics.extend(diagnostics);
365371
}
372+
366373
failure
367374
})
368375
.collect();
@@ -462,3 +469,11 @@ fn create_diagnostic_snapshot(
462469
}
463470
snapshot
464471
}
472+
473+
const KNOWN_PULL_TYPES_FAILURES: &[&str] = &[
474+
"crates/ty_python_semantic/resources/mdtest/annotations/any.md",
475+
"crates/ty_python_semantic/resources/mdtest/annotations/callable.md",
476+
"crates/ty_python_semantic/resources/mdtest/type_api.md",
477+
"crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md",
478+
"crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md",
479+
];

0 commit comments

Comments
 (0)