From ad41728204681a60e6d9761857b000cb6bfe732b Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sat, 20 Dec 2025 11:20:53 -0500 Subject: [PATCH] [ty] Avoid temporarily storing invalid multi-inference attempts (#22103) ## Summary I missed this in https://github.com/astral-sh/ruff/pull/22062. This avoids exponential runtime in the following snippet: ```py class X1: ... class X2: ... class X3: ... class X4: ... class X5: ... class X6: ... ... def f( x: list[X1 | None] | list[X2 | None] | list[X3 | None] | list[X4 | None] | list[X5 | None] | list[X6 | None] ... ): ... def g[T](x: T) -> list[T]: return [x] def id[T](x: T) -> T: return x f(id(id(id(id(g(X64())))))) ``` Eventually I want to refactor our multi-inference infrastructure (which is currently very brittle) to handle this implicitly, but this is a temporary performance fix until that happens. --- .../src/types/infer/builder.rs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 52b4e3847e..5657653548 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7458,13 +7458,23 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } }; - self.store_expression_type(expression, ty); + self.store_expression_type_impl(expression, ty, tcx); ty } #[track_caller] fn store_expression_type(&mut self, expression: &ast::Expr, ty: Type<'db>) { + self.store_expression_type_impl(expression, ty, TypeContext::default()); + } + + #[track_caller] + fn store_expression_type_impl( + &mut self, + expression: &ast::Expr, + ty: Type<'db>, + tcx: TypeContext<'db>, + ) { if self.deferred_state.in_string_annotation() || self.inner_expression_inference_state.is_get() { @@ -7493,7 +7503,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.expressions .entry(expression.into()) .and_modify(|current| { - *current = IntersectionType::from_elements(db, [*current, ty]); + // Avoid storing "failed" multi-inference attempts, which can lead to + // unnecessary union simplification overhead. + if tcx + .annotation + .is_none_or(|tcx| ty.is_assignable_to(db, tcx)) + { + *current = IntersectionType::from_elements(db, [*current, ty]); + } }) .or_insert(ty); }