{"record":{"id":"6a8c5f8021a2fe2d","repo":"PyO3/pyo3","slug":"should-always-have-positional-defaults-position","errorCode":null,"errorMessage":"should always have positional defaults <= positional parameters","messagePattern":"should always have positional defaults <= positional parameters","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pyo3-macros-backend/src/pyfunction/signature.rs","lineNumber":297,"sourceCode":"    pub varargs: Option<String>,\n    // Tuples of keyword name and optional default value\n    pub keyword_only_parameters: Vec<(String, Option<Expr>)>,\n    pub kwargs: Option<String>,\n}\n\nimpl PythonSignature {\n    pub fn has_no_args(&self) -> bool {\n        self.positional_parameters.is_empty()\n            && self.keyword_only_parameters.is_empty()\n            && self.varargs.is_none()\n            && self.kwargs.is_none()\n    }\n\n    pub fn required_positional_parameters(&self) -> usize {\n        self.positional_parameters\n            .len()\n            .checked_sub(self.default_positional_parameters.len())\n            .expect(\"should always have positional defaults <= positional parameters\")\n    }\n\n    /// Makes every positional parameter positional-only, exactly as a trailing `/` in a\n    /// signature does. Deliberately leaves keyword-only parameters alone.\n    pub fn make_all_parameters_positional_only(&mut self) {\n        self.positional_only_parameters = self.positional_parameters.len();\n    }\n}\n\n#[derive(Clone)]\npub struct FunctionSignature<'a> {\n    pub arguments: Vec<FnArg<'a>>,\n    pub python_signature: PythonSignature,\n    pub attribute: Option<SignatureAttribute>,\n}\n\npub enum ParseState {\n    /// Accepting positional parameters, which might be positional only","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/pyo3-macros-backend/src/pyfunction/signature.rs#L279-L315","documentation":"In pyo3's function signature builder, required_positional_parameters computes positional_parameters.len() - default_positional_parameters.len() and expects it not to underflow. The invariant is that every default must correspond to a declared positional parameter, so defaults can never outnumber parameters. Violating it means the SignatureBuilder was mutated inconsistently (e.g. a default registered without a parameter).","triggerScenarios":"Internal state corruption while parsing a #[pyfunction] signature: e.g. a `= default` annotation or signature attribute registering a positional default with no matching positional parameter, then querying required_positional_parameters.","commonSituations":"Custom forks or third-party macro plugins that mutate SignatureBuilder directly; upstream parsing keeps defaults and parameters in lockstep.","solutions":["Only push a default after (or together with) pushing the corresponding positional parameter.","Audit any code that modifies positional_parameters / default_positional_parameters so lengths satisfy defaults <= parameters.","Report to pyo3 if a plain #[pyfunction] with defaults triggers it."],"exampleFix":"// before\ndefaults.push(default_ty); // without params.push(...)\n// after\nparams.push(param);\ndefaults.push(default_ty);","handlingStrategy":"validation","validationCode":"// Maintain the invariant when mutating SignatureBuilder\ndebug_assert!(defaults.len() <= params.len(), \"defaults exceed positional parameters\");","typeGuard":"fn signature_consistent(positional: usize, defaults: usize) -> bool {\n    defaults <= positional\n}","tryCatchPattern":null,"preventionTips":["Push a positional default only alongside its parameter.","Never mutate positional_parameters/default_positional_parameters from outside the builder's methods.","Add round-trip tests for #[pyfunction] signatures with defaults, *, /, and keyword-only args."],"tags":["pyo3","proc-macro","signature","internal-invariant"],"backgroundTag":"internal-invariant-panic","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}