Skip to content

Commit 377eb7d

Browse files
committed
Introduce the <!-- pull-types:skip --> directive
1 parent e88c97b commit 377eb7d

File tree

9 files changed

+206
-66
lines changed

9 files changed

+206
-66
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/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_test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ty_python_semantic = { workspace = true, features = ["serde", "testing"] }
2222
ty_vendored = { workspace = true }
2323

2424
anyhow = { workspace = true }
25+
bitflags = { workspace = true }
2526
camino = { workspace = true }
2627
colored = { workspace = true }
2728
insta = { workspace = true, features = ["filters"] }

crates/ty_test/src/lib.rs

Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::config::Log;
2+
use crate::db::Db;
23
use crate::parser::{BacktickOffsets, EmbeddedFileSourceMap};
34
use camino::Utf8Path;
45
use colored::Colorize;
@@ -292,11 +293,29 @@ fn run_test(
292293
// all diagnostics. Otherwise it remains empty.
293294
let mut snapshot_diagnostics = vec![];
294295

295-
let failures: Failures = test_files
296-
.into_iter()
296+
let mut any_pull_types_failures = false;
297+
298+
let mut failures: Failures = test_files
299+
.iter()
297300
.filter_map(|test_file| {
298-
if !KNOWN_PULL_TYPES_FAILURES.contains(&&*relative_fixture_path.as_str().replace('\\', "/")) {
299-
pull_types(db, test_file.file);
301+
let pull_types_result = attempt_test(
302+
db,
303+
pull_types,
304+
test_file,
305+
"\"pull types\"",
306+
Some(
307+
"Note: either fix the panic or add the `<!-- pull-types:skip -->` \
308+
directive to this test",
309+
),
310+
);
311+
match pull_types_result {
312+
Ok(()) => {}
313+
Err(failures) => {
314+
any_pull_types_failures = true;
315+
if !test.should_skip_pulling_types() {
316+
return Some(failures);
317+
}
318+
}
300319
}
301320

302321
let parsed = parsed_module(db, test_file.file).load(db);
@@ -314,55 +333,22 @@ fn run_test(
314333
.map(|error| create_unsupported_syntax_diagnostic(test_file.file, error)),
315334
);
316335

317-
let type_diagnostics = match catch_unwind(|| check_types(db, test_file.file)) {
318-
Ok(type_diagnostics) => type_diagnostics,
319-
Err(info) => {
320-
let mut by_line = matcher::FailuresByLine::default();
321-
let mut messages = vec![];
322-
match info.location {
323-
Some(location) => messages.push(format!("panicked at {location}")),
324-
None => messages.push("panicked at unknown location".to_string()),
325-
}
326-
match info.payload.as_str() {
327-
Some(message) => messages.push(message.to_string()),
328-
// Mimic the default panic hook's rendering of the panic payload if it's
329-
// not a string.
330-
None => messages.push("Box<dyn Any>".to_string()),
331-
}
332-
if let Some(backtrace) = info.backtrace {
333-
match backtrace.status() {
334-
BacktraceStatus::Disabled => {
335-
let msg = "run with `RUST_BACKTRACE=1` environment variable to display a backtrace";
336-
messages.push(msg.to_string());
337-
}
338-
BacktraceStatus::Captured => {
339-
messages.extend(backtrace.to_string().split('\n').map(String::from));
340-
}
341-
_ => {}
342-
}
343-
}
344-
345-
if let Some(backtrace) = info.salsa_backtrace {
346-
salsa::attach(db, || {
347-
messages.extend(format!("{backtrace:#}").split('\n').map(String::from));
348-
});
349-
}
350-
351-
by_line.push(OneIndexed::from_zero_indexed(0), messages);
352-
return Some(FileFailures {
353-
backtick_offsets: test_file.backtick_offsets,
354-
by_line,
355-
});
356-
}
336+
let mdtest_result = attempt_test(db, check_types, test_file, "run mdtest", None);
337+
let type_diagnostics = match mdtest_result {
338+
Ok(diagnostics) => diagnostics,
339+
Err(failures) => return Some(failures),
357340
};
358341

359342
diagnostics.extend(type_diagnostics.into_iter().cloned());
360-
diagnostics.sort_by(|left, right|left.rendering_sort_key(db).cmp(&right.rendering_sort_key(db)));
343+
diagnostics.sort_by(|left, right| {
344+
left.rendering_sort_key(db)
345+
.cmp(&right.rendering_sort_key(db))
346+
});
361347

362348
let failure = match matcher::match_file(db, test_file.file, &diagnostics) {
363349
Ok(()) => None,
364350
Err(line_failures) => Some(FileFailures {
365-
backtick_offsets: test_file.backtick_offsets,
351+
backtick_offsets: test_file.backtick_offsets.clone(),
366352
by_line: line_failures,
367353
}),
368354
};
@@ -374,6 +360,23 @@ fn run_test(
374360
})
375361
.collect();
376362

363+
if test.should_skip_pulling_types() && !any_pull_types_failures {
364+
let mut by_line = matcher::FailuresByLine::default();
365+
by_line.push(
366+
OneIndexed::from_zero_indexed(0),
367+
vec![
368+
"Remove the `<!-- pull-types:skip -->` directive from this test: pulling types \
369+
succeeded for all files in the test."
370+
.to_string(),
371+
],
372+
);
373+
let failure = FileFailures {
374+
backtick_offsets: test_files[0].backtick_offsets.clone(),
375+
by_line,
376+
};
377+
failures.push(failure);
378+
}
379+
377380
if snapshot_diagnostics.is_empty() && test.should_snapshot_diagnostics() {
378381
panic!(
379382
"Test `{}` requested snapshotting diagnostics but it didn't produce any.",
@@ -470,10 +473,70 @@ fn create_diagnostic_snapshot(
470473
snapshot
471474
}
472475

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-
];
476+
/// Run a function over an embedded test file, catching any panics that occur in the process.
477+
///
478+
/// If no panic occurs, the result of the function is returned as an `Ok()` variant.
479+
///
480+
/// If a panic occurs, a nicely formatted [`FileFailures`] is returned as an `Err()` variant.
481+
/// This will be formatted into a diagnostic message by `ty_test`.
482+
fn attempt_test<'db, T, F>(
483+
db: &'db Db,
484+
test_fn: F,
485+
test_file: &TestFile,
486+
action: &str,
487+
clarification: Option<&str>,
488+
) -> Result<T, FileFailures>
489+
where
490+
F: FnOnce(&'db dyn ty_python_semantic::Db, File) -> T + std::panic::UnwindSafe,
491+
{
492+
catch_unwind(|| test_fn(db, test_file.file)).map_err(|info| {
493+
let mut by_line = matcher::FailuresByLine::default();
494+
let mut messages = vec![];
495+
match info.location {
496+
Some(location) => messages.push(format!(
497+
"Attempting to {action} caused a panic at {location}"
498+
)),
499+
None => messages.push(format!(
500+
"Attempting to {action} caused a panic at an unknown location",
501+
)),
502+
}
503+
if let Some(clarification) = clarification {
504+
messages.push(clarification.to_string());
505+
}
506+
messages.push(String::new());
507+
match info.payload.as_str() {
508+
Some(message) => messages.push(message.to_string()),
509+
// Mimic the default panic hook's rendering of the panic payload if it's
510+
// not a string.
511+
None => messages.push("Box<dyn Any>".to_string()),
512+
}
513+
messages.push(String::new());
514+
515+
if let Some(backtrace) = info.backtrace {
516+
match backtrace.status() {
517+
BacktraceStatus::Disabled => {
518+
let msg =
519+
"run with `RUST_BACKTRACE=1` environment variable to display a backtrace";
520+
messages.push(msg.to_string());
521+
}
522+
BacktraceStatus::Captured => {
523+
messages.extend(backtrace.to_string().split('\n').map(String::from));
524+
}
525+
_ => {}
526+
}
527+
}
528+
529+
if let Some(backtrace) = info.salsa_backtrace {
530+
salsa::attach(db, || {
531+
messages.extend(format!("{backtrace:#}").split('\n').map(String::from));
532+
});
533+
}
534+
535+
by_line.push(OneIndexed::from_zero_indexed(0), messages);
536+
537+
FileFailures {
538+
backtick_offsets: test_file.backtick_offsets.clone(),
539+
by_line,
540+
}
541+
})
542+
}

0 commit comments

Comments
 (0)