From e0f3a064b99ffefdffc89fcf512203387da9539f Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 26 Nov 2025 16:39:49 -0800 Subject: [PATCH] [ty] don't iterate over a hashset (#21649) ## Summary This caused "deterministic but chaotic" ordering of some intersection types in diagnostics. When calling a union, we infer the argument type once per matching parameter type, intersecting the inferred types for the argument expression, and we did that in an unpredictable order. We do need a hashset here for de-duplication. Sometimes we call large unions where the type for a given parameter is the same across the union, we should infer the argument once per parameter type, not once per union element. So use an `FxIndexSet` instead of an `FxHashSet`. ## Test Plan With this change, switching between `main` and https://github.com/astral-sh/ruff/pull/21646 no longer changes the ordering of the intersection type in the test in https://github.com/astral-sh/ruff/pull/21646/commits/cca3a8045df3a1038601e848b87b2163c81aebed --- crates/ty_python_semantic/src/types/infer/builder.rs | 8 ++++++-- 1 file changed, 6 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 b2f8cc5687..f5d4f36a30 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -6894,10 +6894,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Infer the type of each argument once with each distinct parameter type as type context. let parameter_types = overloads_with_binding .iter() - .filter_map(|(overload, binding)| parameter_type(overload, binding)) - .collect::>(); + .filter_map(|(overload, binding)| parameter_type(overload, binding)); + + let mut seen = FxHashSet::default(); for parameter_type in parameter_types { + if !seen.insert(parameter_type) { + continue; + } let inferred_ty = self.infer_expression(ast_argument, TypeContext::new(Some(parameter_type)));