{"record":{"id":"0d8347b59606cf22","repo":"astral-sh/ruff","slug":"paramspec-sub-call-should-only-contain-a-single-ca","errorCode":null,"errorMessage":"ParamSpec sub-call should only contain a single CallableBinding","messagePattern":"ParamSpec sub-call should only contain a single CallableBinding","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_semantic/src/types/call/bind.rs","lineNumber":6794,"sourceCode":"            }\n            self.parameter_ty_builders[parameter_index] = Some(builder);\n        } else {\n            self.parameter_tys[parameter_index] = Some(argument_type);\n        }\n    }\n\n    fn is_gradual_variadic_parameter(parameters: &Parameters<'db>, parameter_index: usize) -> bool {\n        let parameter = &parameters[parameter_index];\n\n        matches!(parameters.kind(), ParametersKind::Gradual)\n            && matches!(parameter.annotated_type(), Type::Dynamic(_))\n            && (parameter.is_variadic() || parameter.is_keyword_variadic())\n    }\n\n    // TODO: Remove this workaround once call binding can infer a `TypeVarTuple` from `*args` and\n    // callable argument checking preserves correlations across overloads.\n    fn should_defer_typevartuple_callable_check(\n        &self,\n        declared_type: Type<'db>,\n        expected_type: Type<'db>,\n        argument_type: Type<'db>,\n    ) -> bool {\n        let db = self.db;\n        let Some(declared_callables) = declared_type.try_upcast_to_callable(db, self.env) else {\n            return false;\n        };\n        let parameters_contain_typevartuple = declared_callables.iter().any(|callable| {\n            callable.signatures(db).iter().any(|signature| {\n                signature.parameters().iter().any(|parameter| {\n                    any_over_type(db, self.env, parameter.annotated_type(), false, |ty| {\n                        matches!(\n                            ty,\n                            Type::TypeVar(typevar) if typevar.is_typevartuple(db)\n                        )\n                    })\n                })","sourceCodeStart":6776,"sourceCodeEnd":6812,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ty_python_semantic/src/types/call/bind.rs#L6776-L6812","documentation":"For ParamSpec forwarding (e.g., `def f(cb: Callable[P, R]) -> R: return cb(...)`), inference performs the sub-call of the forwarded callable and, per the SAFETY comment, assumes the resulting Bindings contain exactly one CallableBinding because the sub-call was constructed from a single callable. The expect on single_element() fires when that assumption breaks and the sub-call yields multiple binding elements.","triggerScenarios":"A ParamSpec sub-call where the rebound callable expands to a union during binding - e.g., the callback is or becomes a union of callables/overloads, so infer_call returns merged multi-element Bindings instead of one.","commonSituations":"Higher-order functions whose callback argument is a union of overloaded callables; churn in union-call binding merging recently touched this area, so nightly regressions are plausible.","solutions":["Reduce to the higher-order function (ParamSpec plus a union/overloaded callback) and file a ty issue","Retry on the latest ty build, since ParamSpec binding logic is under active development","As a contributor: either iterate all elements (extend_errors per element) or construct the sub-call from a single callable so it cannot union"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check_file(&db, file)));\nmatch result {\n    Ok(diags) => diags,\n    Err(payload) => { log::warn!(\"ty panicked on ParamSpec sub-call in {}: {:?}\", file, payload); vec![] }\n}","preventionTips":["Report ParamSpec-forwarding repros with union/overloaded callbacks - they pinpoint the multi-element path","Retry on the latest ty build before reporting; this area changes frequently","Contributors: construct sub-calls from single callables or iterate all binding elements rather than assuming single_element()"],"tags":["rust","ty","call-inference","paramspec","union","panic"],"backgroundTag":"single-element-expect-mismatch","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}