Better macro errors for get_struct_fields (#17639)

# Objective

- Currently, the error span for `get_struct_field` when encountering an
enum or union points to the macro invocation, rather than the `enum` or
`union` token. It also doesn't mention which macro reported the error.

## Solution

- Report the correct error span
- Add parameter for passing in the name of the macro invocation

## Testing

Bevy compiles fine with this change

## Migration Guide

```rs
// before
let fields = get_struct_fields(&ast.data);

// after
let fields = get_struct_fields(&ast.data, "derive(Bundle)");
```

---------

Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
This commit is contained in:
Emerson Coskey 2025-05-26 09:57:03 -07:00 committed by GitHub
parent f33074116b
commit fb2d79ad60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 18 deletions

View File

@ -34,7 +34,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path(); let ecs_path = bevy_ecs_path();
let named_fields = match get_struct_fields(&ast.data) { let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
Ok(fields) => fields, Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(), Err(e) => return e.into_compile_error().into(),
}; };
@ -191,12 +191,14 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
pub fn derive_map_entities(input: TokenStream) -> TokenStream { pub fn derive_map_entities(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path(); let ecs_path = bevy_ecs_path();
let map_entities_impl = map_entities( let map_entities_impl = map_entities(
&ast.data, &ast.data,
Ident::new("self", Span::call_site()), Ident::new("self", Span::call_site()),
false, false,
false, false,
); );
let struct_name = &ast.ident; let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! { TokenStream::from(quote! {

View File

@ -1,24 +1,29 @@
use proc_macro::Span; use syn::{
use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Error, Field, Fields}; punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataEnum, DataUnion, Error,
Field, Fields,
};
/// Get the fields of a data structure if that structure is a struct with named fields; /// Get the fields of a data structure if that structure is a struct with named fields;
/// otherwise, return a compile error that points to the site of the macro invocation. /// otherwise, return a compile error that points to the site of the macro invocation.
pub fn get_struct_fields(data: &Data) -> syn::Result<&Punctuated<Field, Comma>> { ///
/// `meta` should be the name of the macro calling this function.
pub fn get_struct_fields<'a>(
data: &'a Data,
meta: &str,
) -> syn::Result<&'a Punctuated<Field, Comma>> {
match data { match data {
Data::Struct(DataStruct { Data::Struct(data_struct) => match &data_struct.fields {
fields: Fields::Named(fields), Fields::Named(fields_named) => Ok(&fields_named.named),
.. Fields::Unnamed(fields_unnamed) => Ok(&fields_unnamed.unnamed),
}) => Ok(&fields.named), Fields::Unit => Ok(const { &Punctuated::new() }),
Data::Struct(DataStruct { },
fields: Fields::Unnamed(fields), Data::Enum(DataEnum { enum_token, .. }) => Err(Error::new(
.. enum_token.span(),
}) => Ok(&fields.unnamed), format!("#[{meta}] only supports structs, not enums"),
_ => Err(Error::new( )),
// This deliberately points to the call site rather than the structure Data::Union(DataUnion { union_token, .. }) => Err(Error::new(
// body; marking the entire body as the source of the error makes it union_token.span(),
// impossible to figure out which `derive` has a problem. format!("#[{meta}] only supports structs, not unions"),
Span::call_site().into(),
"Only structs are supported",
)), )),
} }
} }