diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 27383ff71ee55..7955fd4ba31f8 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -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::>(); - let field = named_fields - .iter() - .map(|field| field.ident.as_ref().unwrap()) - .collect::>(); - let field_type = named_fields - .iter() - .map(|field| &field.ty) .collect::>(); 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()); }); @@ -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 { @@ -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 {})* + } }) } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 00f3bac9e527e..2748df0be47e8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -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.