Skip to content

Commit 1945bfd

Browse files
authored
[ty] Fix standalone expression type retrieval in presence of cycles (astral-sh#17849)
## Summary When entering an `infer_expression_types` cycle from `TypeInferenceBuilder::infer_standalone_expression`, we might get back a `TypeInference::cycle_fallback(…)` that doesn't actually contain any new types, but instead it contains a `cycle_fallback_type` which is set to `Some(Type::Never)`. When calling `self.extend(…)`, we therefore don't really pull in a type for the expression we're interested in. This caused us to panic if we tried to call `self.expression_type(…)` after `self.extend(…)`. The proposed fix here is to retrieve that type from the nested `TypeInferenceBuilder` directly, which will correctly fall back to `cycle_fallback_type`. ## Details I minimized the second example from astral-sh#17792 a bit further and used this example for debugging: ```py from __future__ import annotations class C: ... def f(arg: C): pass x, _ = f(1) assert x ``` This is self-referential because when we check the assignment statement `x, _ = f(1)`, we need to look up the signature of `f`. Since evaluation of annotations is deferred, we look up the public type of `C` for the `arg` parameter. The public use of `C` is visibility-constraint by "`x`" via the `assert` statement. While evaluating this constraint, we need to look up the type of `x`, which in turn leads us back to the `x, _ = f(1)` definition. The reason why this only showed up in the relatively peculiar case with unpack assignments is the code here: https://github.com/astral-sh/ruff/blob/78b4c3ccf1d6cb10613671ccec09cafba0d1de72/crates/ty_python_semantic/src/types/infer.rs#L2709-L2718 For a non-unpack assignment like `x = f(1)`, we would not try to infer the right-hand side eagerly. Instead, we would enter a `infer_definition_types` cycle that handles the situation correctly. For unpack assignments, however, we try to infer the type of `value` (`f(1)`) and therefore enter the cycle via `standalone_expression_type => infer_expression_type`. closes astral-sh#17792 ## Test Plan * New regression test * Made sure that we can now run successfully on scipy => see astral-sh#17850
1 parent a95c73d commit 1945bfd

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Regression test for https://github.com/astral-sh/ruff/issues/17792
2+
3+
from __future__ import annotations
4+
5+
6+
class C: ...
7+
8+
9+
def f(arg: C):
10+
pass
11+
12+
13+
x, _ = f(1)
14+
15+
assert x

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4002,7 +4002,12 @@ impl<'db> TypeInferenceBuilder<'db> {
40024002
let standalone_expression = self.index.expression(expression);
40034003
let types = infer_expression_types(self.db(), standalone_expression);
40044004
self.extend(types);
4005-
self.expression_type(expression)
4005+
4006+
// Instead of calling `self.expression_type(expr)` after extending here, we get
4007+
// the result from `types` directly because we might be in cycle recovery where
4008+
// `types.cycle_fallback_type` is `Some(fallback_ty)`, which we can retrieve by
4009+
// using `expression_type` on `types`:
4010+
types.expression_type(expression.scoped_expression_id(self.db(), self.scope()))
40064011
}
40074012

40084013
fn infer_expression_impl(&mut self, expression: &ast::Expr) -> Type<'db> {

0 commit comments

Comments
 (0)