{"record":{"id":"7a6f43f8980a1aae","repo":"pydantic/monty","slug":"style-unpack-models-a-positional-only-pyarg-unpacktuple","errorCode":null,"errorMessage":"`style = unpack` models a positional-only `PyArg_UnpackTuple` signature — every positional field must be `pos_only`","messagePattern":"`style = unpack` models a positional-only `PyArg_UnpackTuple` signature — every positional field must be `pos_only`","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"crates/monty-macros/src/from_args.rs","lineNumber":228,"sourceCode":"    /// modifiers, so each rule reads as one line of the compatibility table.\n    fn validate(&self) -> syn::Result<()> {\n        let err = |msg: &str| Err(syn::Error::new(self.struct_ident.span(), msg));\n\n        match self.style {\n            Style::Def => {\n                if self.bad_arg.is_some() {\n                    return err(\"`bad_arg`/`bad_arg_named` cannot be combined with `style = def` \\\n                         — CPython `def` binding never type-checks while binding; declare \\\n                         fields as raw `Value` and coerce in the function body\");\n                }\n                if self.varargs_idx.is_some() {\n                    return err(\"`style = def` cannot be combined with `varargs` — a `*args` \\\n                         signature can never raise too-many-positional, so the style has no effect\");\n                }\n            }\n            Style::Unpack => {\n                if self.fields.iter().any(|f| matches!(f.kind, FieldKind::PosOrKeyword)) {\n                    return err(\"`style = unpack` models a positional-only `PyArg_UnpackTuple` \\\n                         signature — every positional field must be `pos_only`\");\n                }\n                if self.varargs_idx.is_some() || self.varkwargs_idx.is_some() {\n                    return err(\"`style = unpack` cannot be combined with `varargs` or `varkwargs` \\\n                         — it models a fixed positional min..max range\");\n                }\n            }\n            Style::Clinic | Style::C | Style::CNamed => {}\n        }\n\n        if self.at_most_total {\n            if matches!(self.style, Style::Def | Style::Unpack) {\n                return err(\n                    \"`at_most_total` cannot be combined with `style = def` or `style = unpack` \\\n                     — the total pre-count models `PyArg_ParseTupleAndKeywords`-family C parsers\",\n                );\n            }\n            if self.varargs_idx.is_some() || self.varkwargs_idx.is_some() {","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-macros/src/from_args.rs#L210-L246","documentation":"A `#[derive(FromArgs)]` struct with `style = unpack` contains a positional field that is `PosOrKeyword` (the default kind) rather than `pos_only`. `style = unpack` models CPython's `PyArg_UnpackTuple`, which is strictly positional-only — there is no keyword channel, so every field must be declared `pos_only` or the generated parser would be wrong.","triggerScenarios":"Compiling `#[derive(FromArgs)] #[from_args(style = \"unpack\")] struct Args { a: i64, b: i64 }` where neither field carries `#[from_args(pos_only)]`. Checked in `Signature::validate` (crates/monty-macros/src/from_args.rs:227-230).","commonSituations":"Converting a `divmod`-style C function to `unpack` and forgetting that field kinds default to position-or-keyword; writing a new unpack-style struct without reading the attribute surface in crates/monty-macros/README.md.","solutions":["Annotate every positional field with `#[from_args(pos_only)]` on the unpack-style struct.","If the function actually accepts keywords, drop `style = \"unpack\"` and use the default `clinic` style, which supports keyword fields."],"exampleFix":"// before\n#[derive(FromArgs)]\n#[from_args(style = \"unpack\")]\nstruct DivModArgs { a: Value, b: Value }\n\n// after\n#[derive(FromArgs)]\n#[from_args(style = \"unpack\")]\nstruct DivModArgs {\n    #[from_args(pos_only)]\n    a: Value,\n    #[from_args(pos_only)]\n    b: Value,\n}","handlingStrategy":"validation","validationCode":"// For style = \"unpack\", assert every field is pos_only before compiling:\n// unpack models PyArg_UnpackTuple (positional-only).\nfn unpack_fields_all_pos_only(field_kinds: &[&str]) -> bool {\n    field_kinds.iter().all(|k| *k == \"pos_only\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default field kind is pos-or-keyword; explicitly mark #[from_args(pos_only)] when using unpack.","Prefer the default clinic style unless the target CPython function truly uses PyArg_UnpackTuple."],"tags":["rust","proc-macro","fromargs","compile-time"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}