Skip to content

Commit

Permalink
DeriveWorld for enums (#17496)
Browse files Browse the repository at this point in the history
# Objective

Fixes #17457 

## Solution

#[derive(FromWorld)] now works with enums by specifying which variant
should be used.

## Showcase

```rust
#[Derive(FromWorld)]
enum Game {
    #[from_world]
    Playing, 
    Stopped
}
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com>
  • Loading branch information
3 people authored Jan 23, 2025
1 parent fd2afee commit da57dfb
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 16 deletions.
60 changes: 46 additions & 14 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,20 +612,41 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {
states::derive_substates(input)
}

#[proc_macro_derive(FromWorld)]
#[proc_macro_derive(FromWorld, attributes(from_world))]
pub fn derive_from_world(input: TokenStream) -> TokenStream {
let bevy_ecs_path = bevy_ecs_path();
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = ast.ident;
let name = ast.ident;
let (impl_generics, ty_generics, where_clauses) = ast.generics.split_for_impl();

let Data::Struct(DataStruct { fields, .. }) = &ast.data else {
return syn::Error::new(
Span::call_site(),
"#[derive(FromWorld)]` only supports structs",
)
.into_compile_error()
.into();
let (fields, variant_ident) = match &ast.data {
Data::Struct(data) => (&data.fields, None),
Data::Enum(data) => {
match data.variants.iter().find(|variant| {
variant
.attrs
.iter()
.any(|attr| attr.path().is_ident("from_world"))
}) {
Some(variant) => (&variant.fields, Some(&variant.ident)),
None => {
return syn::Error::new(
Span::call_site(),
"No #[from_world] attribute was found on any of this enum's variants.",
)
.into_compile_error()
.into();
}
}
}
Data::Union(_) => {
return syn::Error::new(
Span::call_site(),
"#[derive(FromWorld)]` does not support unions",
)
.into_compile_error()
.into();
}
};

let field_init_expr = quote!(#bevy_ecs_path::world::FromWorld::from_world(world));
Expand All @@ -645,12 +666,23 @@ pub fn derive_from_world(input: TokenStream) -> TokenStream {
syn::Fields::Unit => Punctuated::new(),
};

let field_initializers: TokenStream2 = if !field_initializers.is_empty() {
quote!({ #field_initializers })
} else {
quote!(#field_initializers)
};

let field_initializers = match variant_ident {
Some(variant_ident) => quote!( Self::#variant_ident #field_initializers),
None => quote!( Self #field_initializers),
};

TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::world::FromWorld for #struct_name #ty_generics #where_clauses {
fn from_world(world: &mut #bevy_ecs_path::world::World) -> Self {
#[allow(clippy::init_numbered_fields)]
Self { #field_initializers }
impl #impl_generics #bevy_ecs_path::world::FromWorld for #name #ty_generics #where_clauses {
fn from_world(world: &mut #bevy_ecs_path::world::World) -> Self {
#[allow(clippy::init_numbered_fields)]
#field_initializers
}
}
}
})
}
15 changes: 13 additions & 2 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3716,9 +3716,13 @@ unsafe impl Sync for World {}
///
/// This can be helpful for complex initialization or context-aware defaults.
///
/// [`FromWorld`] is automatically implemented for any type implementing [`Default`],
/// and may also be derived for any struct whose fields all implement `FromWorld`:
/// [`FromWorld`] is automatically implemented for any type implementing [`Default`]
/// and may also be derived for:
/// - any struct whose fields all implement `FromWorld`
/// - any enum where one variant has the attribute `#[from_world]`
///
/// ```rs
///
/// #[derive(Default)]
/// struct A;
///
Expand All @@ -3735,6 +3739,13 @@ unsafe impl Sync for World {}
///
/// #[derive(FromWorld)]
/// struct D(A, B, C);
///
/// #[derive(FromWorld)]
/// enum E {
/// #[from_world]
/// F,
/// G
/// }
/// ```
pub trait FromWorld {
/// Creates `Self` using data from the given [`World`].
Expand Down

0 comments on commit da57dfb

Please sign in to comment.