Skip to content

static assert that bundles do not have duplicate types #2388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 28 additions & 17 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,29 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
_ => panic!("Expected a struct with named fields."),
};

let is_bundle = named_fields
let field_data = named_fields
.iter()
.map(|field| {
field
.attrs
.iter()
.any(|a| *a.path.get_ident().as_ref().unwrap() == BUNDLE_ATTRIBUTE_NAME)
(
field.ident.as_ref().unwrap(), // name
&field.ty, // type
field // has `#[bundle]` attribute
.attrs
.iter()
.any(|a| a.path.get_ident().unwrap() == BUNDLE_ATTRIBUTE_NAME),
)
})
.collect::<Vec<bool>>();
let field = named_fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.collect::<Vec<_>>();
let field_type = named_fields
.iter()
.map(|field| &field.ty)
.collect::<Vec<_>>();

let mut field_type_infos = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for ((field_type, is_bundle), field) in
field_type.iter().zip(is_bundle.iter()).zip(field.iter())
{
let mut nested_bundle_types = Vec::new();

for (field, field_type, is_bundle) in field_data.iter() {
if *is_bundle {
nested_bundle_types.push(field_type);

field_type_infos.push(quote! {
type_info.extend(<#field_type as #ecs_path::bundle::Bundle>::type_info());
});
Expand All @@ -139,11 +137,18 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
});
}
}
let field_len = field.len();
let field_len = field_data.len();
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let struct_name = &ast.ident;

let static_assert_bundle_func = format_ident!(
"static_assert_{}_does_not_have_duplicate_types",
struct_name
);
let static_assert_trait = format_ident!("AssertDistinctComponentsFor{}", struct_name);
let field_types = field_data.iter().map(|(_, ty, _)| ty);

TokenStream::from(quote! {
/// SAFE: TypeInfo is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name#ty_generics #where_clause {
Expand All @@ -165,6 +170,12 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#(#field_get_components)*
}
}

#[allow(dead_code, non_snake_case)]
fn #static_assert_bundle_func() {
trait #static_assert_trait {}
#(impl #static_assert_trait for #field_types {})*
}
})
}

Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ use std::{any::TypeId, collections::HashMap};
/// }
/// ```
///
/// ## Note
/// Bundles do not support duplicate types.
/// Currently there is static checking for duplicate types, however, it
/// does not check nested bundles via `#[bundle]`.
///
/// ```compile_fail
/// # use bevy_ecs::bundle::Bundle;
///
/// #[derive(Bundle)]
/// struct HasDuplicateType {
/// a: usize,
/// b: usize, // `a` is already a `usize` so this won't compile.
/// }
/// ```
///
/// # Safety
/// [Bundle::type_info] must return the TypeInfo for each component type in the bundle, in the
/// _exact_ order that [Bundle::get_components] is called.
Expand Down