pydantic/monty · error · syn::Error
`style = unpack` models a positional-only `PyArg_UnpackTuple
Error message
`style = unpack` models a positional-only `PyArg_UnpackTuple` signature — every positional field must be `pos_only`
What it means
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.
Source
Thrown at crates/monty-macros/src/from_args.rs:228
/// modifiers, so each rule reads as one line of the compatibility table.
fn validate(&self) -> syn::Result<()> {
let err = |msg: &str| Err(syn::Error::new(self.struct_ident.span(), msg));
match self.style {
Style::Def => {
if self.bad_arg.is_some() {
return err("`bad_arg`/`bad_arg_named` cannot be combined with `style = def` \
— CPython `def` binding never type-checks while binding; declare \
fields as raw `Value` and coerce in the function body");
}
if self.varargs_idx.is_some() {
return err("`style = def` cannot be combined with `varargs` — a `*args` \
signature can never raise too-many-positional, so the style has no effect");
}
}
Style::Unpack => {
if self.fields.iter().any(|f| matches!(f.kind, FieldKind::PosOrKeyword)) {
return err("`style = unpack` models a positional-only `PyArg_UnpackTuple` \
signature — every positional field must be `pos_only`");
}
if self.varargs_idx.is_some() || self.varkwargs_idx.is_some() {
return err("`style = unpack` cannot be combined with `varargs` or `varkwargs` \
— it models a fixed positional min..max range");
}
}
Style::Clinic | Style::C | Style::CNamed => {}
}
if self.at_most_total {
if matches!(self.style, Style::Def | Style::Unpack) {
return err(
"`at_most_total` cannot be combined with `style = def` or `style = unpack` \
— the total pre-count models `PyArg_ParseTupleAndKeywords`-family C parsers",
);
}
if self.varargs_idx.is_some() || self.varkwargs_idx.is_some() {View on GitHub (pinned to adc986b362)
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.
Example fix
// before
#[derive(FromArgs)]
#[from_args(style = "unpack")]
struct DivModArgs { a: Value, b: Value }
// after
#[derive(FromArgs)]
#[from_args(style = "unpack")]
struct DivModArgs {
#[from_args(pos_only)]
a: Value,
#[from_args(pos_only)]
b: Value,
} Defensive patterns
Strategy: validation
Validate before calling
// For style = "unpack", assert every field is pos_only before compiling:
// unpack models PyArg_UnpackTuple (positional-only).
fn unpack_fields_all_pos_only(field_kinds: &[&str]) -> bool {
field_kinds.iter().all(|k| *k == "pos_only")
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- `bad_arg`/`bad_arg_named` cannot be combined with `style = d
- `style = def` cannot be combined with `varargs` — a `*args`
- `style = unpack` cannot be combined with `varargs` or `varkw
- `at_most_total` cannot be combined with `style = def` or `st
- `at_most_total` cannot be combined with `varargs` or `varkwa
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/7a6f43f8980a1aae.
Report an issue: GitHub.