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

  1. Change the struct to use named fields: `struct Foo { option: u32 }` instead of `struct Foo(u32)`.
  2. Remove the derive from tuple/unit structs it was not designed for and hand-implement the generated trait.
  3. 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

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


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/29d95021da788e21. Report an issue: GitHub.