astral-sh/ruff · error

Expected to handle named fields

Error message

Expected to handle named fields

What it means

`handle_option_group` in the `ConfigurationOptions` derive walks the fields of a configuration group struct and reads each field's name via `field.ident`. Only named-field structs are supported, so a tuple or unit field makes `ident` `None` and the `.expect` panics at macro expansion time. It is invoked from `derive_impl` for each group-level field.

Source

Thrown at crates/ruff_macros/src/config.rs:112

                    #documentation
                }
            })
        }
        _ => Err(syn::Error::new(
            ident.span(),
            "Can only derive ConfigurationOptions from structs with named fields.",
        )),
    }
}

/// For a field with type `Option<Foobar>` where `Foobar` itself is a struct
/// deriving `ConfigurationOptions`, create code that calls retrieves options
/// from that group: `Foobar::get_available_options()`
fn handle_option_group(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,
                arguments:
                    PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
            }) if type_ident == "Option" => {
                let path = &args[0];
                let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());

                Ok(quote_spanned!(
                    ident.span() => (visit.record_set(#kebab_name, ruff_options_metadata::OptionSet::of::<#path>()))
                ))
            }

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Give the group struct named fields: `struct GlobalOptions { version: Version }`.
  2. Drop the `ConfigurationOptions` derive from the offending type and implement `get_available_options` manually.
  3. Patch `handle_option_group` in crates/ruff_macros/src/config.rs to return a spanned `syn::Error` for non-named fields rather than panicking.

Example fix

// before
#[derive(ConfigurationOptions)]
struct CacheOptions(PathBuf);

// after
#[derive(ConfigurationOptions)]
struct CacheOptions {
    dir: PathBuf,
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the group struct is named-field before deriving:
// struct Group { version: Version }   // OK
// struct Group(Version);              // panics the macro
// Macro-side guard:
let Some(ident) = field.ident.as_ref() else {
    return Err(syn::Error::new_spanned(field, "option groups require named fields"));
};

Type guard

fn all_fields_named(data: &syn::Data) -> bool {
    match data {
        syn::Data::Struct(s) => s.fields.iter().all(|f| f.ident.is_some()),
        _ => false,
    }
}

Prevention

When it happens

Trigger: Deriving `ConfigurationOptions` on a struct containing tuple fields (e.g. `struct GlobalOptions(Version)`) or unit fields, causing `field.ident.as_ref()` to be `None` during expansion.

Common situations: Adding a new group type to ty/ruff configuration as a new-type wrapper, or refactoring a named-field struct into tuple syntax while keeping the derive.

Related errors


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