Skip to content

Commit aa3c312

Browse files
authored
[ty] Fix panic when trying to pull types for subscript expressions inside Callable type expressions (#18534)
1 parent 475a02b commit aa3c312

File tree

4 files changed

+61
-17
lines changed

4 files changed

+61
-17
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from typing_extensions import TypeVar, Callable, Concatenate, ParamSpec
2+
3+
_T = TypeVar("_T")
4+
_P = ParamSpec("_P")
5+
6+
def f(self, callable: Callable[Concatenate[_T, _P], _T]) -> Callable[_P, _T]: ...

crates/ty_project/tests/check.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> {
117117
let code = std::fs::read_to_string(source)?;
118118

119119
let mut check_with_file_name = |path: &SystemPath| {
120-
if DO_NOT_ATTEMPT.contains(&&*relative_path.as_str().replace('\\', "/")) {
121-
println!("Skipping {relative_path:?} due to known stack overflow");
120+
if relative_path.file_name() == Some("types.pyi") {
121+
println!(
122+
"Skipping {relative_path:?}: paths with `types.pyi` as their final segment cause a stack overflow"
123+
);
122124
return;
123125
}
124126

@@ -301,16 +303,4 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
301303
// Fails with too-many-cycle-iterations due to a self-referential
302304
// type alias, see https://github.com/astral-sh/ty/issues/256
303305
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py", true, true),
304-
305-
// These are all "expression should belong to this TypeInference region and TypeInferenceBuilder should have inferred a type for it"
306-
("crates/ty_vendored/vendor/typeshed/stdlib/abc.pyi", true, true),
307-
("crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi", true, true),
308-
("crates/ty_vendored/vendor/typeshed/stdlib/curses/__init__.pyi", true, true),
309-
];
310-
311-
/// Attempting to check one of these files causes a stack overflow
312-
const DO_NOT_ATTEMPT: &[&str] = &[
313-
"crates/ty_vendored/vendor/typeshed/stdlib/pathlib/types.pyi",
314-
"crates/ty_vendored/vendor/typeshed/stdlib/types.pyi",
315-
"crates/ty_vendored/vendor/typeshed/stdlib/wsgiref/types.pyi",
316306
];

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,31 @@ def _(c: Callable[[Concatenate[int, str, ...], int], int]):
252252
reveal_type(c) # revealed: (...) -> int
253253
```
254254

255+
Other type expressions can be nested inside `Concatenate`:
256+
257+
```py
258+
def _(c: Callable[[Concatenate[int | str, type[str], ...], int], int]):
259+
# TODO: Should reveal the correct signature
260+
reveal_type(c) # revealed: (...) -> int
261+
```
262+
263+
But providing fewer than 2 arguments to `Concatenate` is an error:
264+
265+
```py
266+
# fmt: off
267+
268+
def _(
269+
c: Callable[Concatenate[int], int], # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 1"
270+
d: Callable[Concatenate[(int,)], int], # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 1"
271+
e: Callable[Concatenate[()], int] # error: [invalid-type-form] "Special form `typing.Concatenate` expected at least 2 parameters but got 0"
272+
):
273+
reveal_type(c) # revealed: (...) -> int
274+
reveal_type(d) # revealed: (...) -> int
275+
reveal_type(e) # revealed: (...) -> int
276+
277+
# fmt: on
278+
```
279+
255280
## Using `typing.ParamSpec`
256281

257282
```toml

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9441,8 +9441,29 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
94419441
todo_type!("`TypeGuard[]` special form")
94429442
}
94439443
SpecialFormType::Concatenate => {
9444-
self.infer_type_expression(arguments_slice);
9445-
todo_type!("`Concatenate[]` special form")
9444+
let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice {
9445+
&*tuple.elts
9446+
} else {
9447+
std::slice::from_ref(arguments_slice)
9448+
};
9449+
for argument in arguments {
9450+
self.infer_type_expression(argument);
9451+
}
9452+
let num_arguments = arguments.len();
9453+
let inferred_type = if num_arguments < 2 {
9454+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
9455+
builder.into_diagnostic(format_args!(
9456+
"Special form `{special_form}` expected at least 2 parameters but got {num_arguments}",
9457+
));
9458+
}
9459+
Type::unknown()
9460+
} else {
9461+
todo_type!("`Concatenate[]` special form")
9462+
};
9463+
if arguments_slice.is_tuple_expr() {
9464+
self.store_expression_type(arguments_slice, inferred_type);
9465+
}
9466+
inferred_type
94469467
}
94479468
SpecialFormType::Unpack => {
94489469
self.infer_type_expression(arguments_slice);
@@ -9622,7 +9643,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
96229643
}))
96239644
});
96249645
}
9625-
ast::Expr::Subscript(_) => {
9646+
ast::Expr::Subscript(subscript) => {
9647+
let value_ty = self.infer_expression(&subscript.value);
9648+
self.infer_subscript_type_expression(subscript, value_ty);
96269649
// TODO: Support `Concatenate[...]`
96279650
return Some(Parameters::todo());
96289651
}

0 commit comments

Comments
 (0)