(args: &Py<PyTuple>, vm: &VirtualMachine)
| 286 | } |
| 287 | |
| 288 | fn dedup_and_flatten_args(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyResult<UnionComponents> { |
| 289 | let args = flatten_args(args, vm); |
| 290 | |
| 291 | // Use set-based deduplication like CPython: |
| 292 | // - For hashable elements: use Python's set semantics (hash + equality) |
| 293 | // - For unhashable elements: use equality comparison |
| 294 | // |
| 295 | // This avoids calling __eq__ when hashes differ, so `int | BadType` |
| 296 | // doesn't raise even if BadType.__eq__ raises. |
| 297 | |
| 298 | let mut new_args: Vec<PyObjectRef> = Vec::with_capacity(args.len()); |
| 299 | |
| 300 | // Track hashable elements using a Python set (uses hash + equality) |
| 301 | let hashable_set = PySet::default().into_ref(&vm.ctx); |
| 302 | let mut hashable_list: Vec<PyObjectRef> = Vec::new(); |
| 303 | let mut unhashable_list: Vec<PyObjectRef> = Vec::new(); |
| 304 | |
| 305 | for arg in &*args { |
| 306 | // Try to hash the element first |
| 307 | match arg.hash(vm) { |
| 308 | Ok(_) => { |
| 309 | // Element is hashable - use set for deduplication |
| 310 | // Set membership uses hash first, then equality only if hashes match |
| 311 | let contains = vm |
| 312 | .call_method(hashable_set.as_ref(), "__contains__", (arg.clone(),)) |
| 313 | .and_then(|r| r.try_to_bool(vm))?; |
| 314 | if !contains { |
| 315 | hashable_set.add(arg.clone(), vm)?; |
| 316 | hashable_list.push(arg.clone()); |
| 317 | new_args.push(arg.clone()); |
| 318 | } |
| 319 | } |
| 320 | Err(_) => { |
| 321 | // Element is unhashable - use equality comparison |
| 322 | let mut is_duplicate = false; |
| 323 | for existing in &unhashable_list { |
| 324 | match existing.rich_compare_bool(arg, PyComparisonOp::Eq, vm) { |
| 325 | Ok(true) => { |
| 326 | is_duplicate = true; |
| 327 | break; |
| 328 | } |
| 329 | Ok(false) => continue, |
| 330 | Err(e) => return Err(e), |
| 331 | } |
| 332 | } |
| 333 | if !is_duplicate { |
| 334 | unhashable_list.push(arg.clone()); |
| 335 | new_args.push(arg.clone()); |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | new_args.shrink_to_fit(); |
| 342 | |
| 343 | // Create hashable_args frozenset if there are hashable elements |
| 344 | let hashable_args = if !hashable_list.is_empty() { |
| 345 | Some(PyFrozenSet::from_iter(vm, hashable_list.into_iter())?.into_ref(&vm.ctx)) |
no test coverage detected