{"record":{"id":"04d5a9ea847df49f","repo":"pydantic/monty","slug":"bad-arg-bad-arg-named-cannot-be-combined-with-style-def","errorCode":null,"errorMessage":"`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","messagePattern":"`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","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"crates/monty-macros/src/from_args.rs","lineNumber":217,"sourceCode":"            vectorcall,\n            kwarg_error_name,\n            bad_arg,\n            kwargs_not_supported_yet,\n        };\n        signature.validate()?;\n        Ok(signature)\n    }\n\n    /// Style/modifier/field compatibility checks — the single place invalid\n    /// combinations are rejected. Grouped per style, then the orthogonal\n    /// 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            }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-macros/src/from_args.rs#L199-L235","documentation":"A `#[derive(FromArgs)]` struct declared `style = def` together with a `bad_arg` or `bad_arg_named` attribute. This compile-time check enforces CPython semantics: `def`-style binding never type-checks or raises bad-argument errors while binding (too-few/invalid args are caught later at coercion), so a bad-argument override has nothing to attach to. The fix is to declare the affected fields as raw `Value` and do the type check in the function body.","triggerScenarios":"Compiling a struct annotated `#[derive(FromArgs)] #[from_args(style = \"def\", bad_arg = \"...\")]` (or `bad_arg_named = \"...\"`). Exactly this combination, checked in `Signature::validate` at crates/monty-macros/src/from_args.rs:216-220.","commonSituations":"Porting a handler from the default `clinic` style to `style = def` to get CPython def-binding error behavior while leaving the old `bad_arg` override in place; copy-pasting a struct that used `bad_arg` under a C-family style; misunderstanding that `def` styles defer errors to coercion rather than binding.","solutions":["Remove the `bad_arg`/`bad_arg_named` attribute from the struct when using `style = def`.","If you need a custom bad-argument message, switch the style back to the default `clinic` (or a C family) where `bad_arg` is allowed.","Keep `style = def`, declare the field as `Value` instead of the concrete type, and perform the type check/coercion manually in the function body, raising the desired error there."],"exampleFix":"// before\n#[derive(FromArgs)]\n#[from_args(style = \"def\", bad_arg = \"my_func\")]\nstruct Args { count: i64 }\n\n// after\n#[derive(FromArgs)]\n#[from_args(style = \"def\")]\nstruct Args { count: Value }\n\n// in the body: coerce and raise your own error\nlet count = args.count.get_int()?;","handlingStrategy":"validation","validationCode":"// Struct-level attribute check before compiling:\n// ensure any struct with style = \"def\" has no bad_arg / bad_arg_named in its #[from_args(...)] list.\nfn uses_def_style_and_bad_arg(attrs: &[&str]) -> bool {\n    let def = attrs.contains(&\"style = \\\"def\\\"\");\n    let bad = attrs.iter().any(|a| a.starts_with(\"bad_arg\"));\n    def && bad\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember the rule: def-style binding defers type errors to coercion — any custom bad-argument handling lives in the function body, not the macro attributes.","Read crates/monty-macros/README.md's style table before mixing attributes.","When porting a struct between styles, audit its full #[from_args(...)] attribute list."],"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"}