astral-sh/ruff · error
Expected to handle named fields
Error message
Expected to handle named fields
What it means
This panic comes from `handle_field` in the `combine_options` proc-macro, which only knows how to generate code for structs with named fields. When the derive is applied to a struct with tuple or unit fields, `field.ident` is `None` and the `.expect` panics during macro expansion instead of emitting a proper compile error.
Source
Thrown at crates/ruff_macros/src/combine_options.rs:43
#output
),*
}
}
}
})
}
_ => Err(syn::Error::new(
ident.span(),
"Can only derive CombineOptions from structs with named fields.",
)),
}
}
fn handle_field(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
let ident = field
.ident
.as_ref()
.expect("Expected to handle named fields");
match &field.ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => match segments.first() {
Some(PathSegment {
ident: type_ident, ..
}) if type_ident == "Option" => Ok(quote_spanned!(
ident.span() => #ident: self.#ident.or(other.#ident)
)),
_ => Err(syn::Error::new(
ident.span(),
"Expected `Option<_>` or `Vec<_>` as type.",
)),
},
_ => Err(syn::Error::new(ident.span(), "Expected type.")),
}View on GitHub (pinned to 26f38c119c)
Solutions
- Change the struct to use named fields: `struct Foo { option: u32 }` instead of `struct Foo(u32)`.
- Remove the derive from tuple/unit structs it was not designed for and hand-implement the generated trait.
- Extend `handle_field` in crates/ruff_macros/src/combine_options.rs to return a spanned `syn::Error` for `field.ident == None` instead of panicking.
Example fix
// before
#[derive(CombineOptions)]
struct LintOptions(u32);
// after
#[derive(CombineOptions)]
struct LintOptions {
select: u32,
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the target struct has named fields before applying the derive:
// struct MyOptions { select: u32 } // OK
// struct MyOptions(u32); // panics the macro
// Prefer fixing the macro itself:
let Some(ident) = field.ident.as_ref() else {
return Err(syn::Error::new_spanned(field, "expected named fields"));
}; Type guard
fn has_named_fields(input: &syn::DeriveInput) -> bool {
matches!(&input.data, syn::Data::Struct(d)
if d.fields.iter().all(|f| f.ident.is_some()))
} Prevention
- Only apply the derive to structs written with `field: Type` syntax.
- Review struct declarations after refactors between named and tuple syntax.
- Prefer `syn::Error::new_spanned` over `.expect` inside proc-macros so failures become compile errors.
- Add a trybuild UI test covering tuple-struct input for the derive.
When it happens
Trigger: Applying the derive handled by `combine_options.rs` to a struct declared with positional fields such as `struct Foo(u32);`, or to a tuple/unit enum variant, instead of a named-field struct like `struct Foo { option: u32 }`.
Common situations: A developer adds the derive to a new-type wrapper or tuple struct while refactoring option types in ruff, or generates the struct via a macro that emits positional fields.
Related errors
- Expected to handle named fields
- Missing #[prefix = "..."] attribute
- expected a doc comment
- expected doc attribute to be in the form of #[doc = "..."]
- expected doc comment to be in the form of `/// [name](https:
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/29d95021da788e21.
Report an issue: GitHub.