[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.
This commit is contained in:
Ibraheem Ahmed
2025-12-20 11:20:53 -05:00
committed by GitHub
parent 3ec63b964c
commit ad41728204

View File

@@ -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);
}