Path: blob/main/crates/bevy_ecs/macros/src/component.rs
6600 views
use proc_macro::TokenStream;1use proc_macro2::{Span, TokenStream as TokenStream2};2use quote::{format_ident, quote, ToTokens};3use std::collections::HashSet;4use syn::{5braced, parenthesized,6parse::Parse,7parse_macro_input, parse_quote,8punctuated::Punctuated,9spanned::Spanned,10token::{Brace, Comma, Paren},11Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident,12LitStr, Member, Path, Result, Token, Type, Visibility,13};1415pub const EVENT: &str = "entity_event";16pub const AUTO_PROPAGATE: &str = "auto_propagate";17pub const TRAVERSAL: &str = "traversal";1819pub fn derive_event(input: TokenStream) -> TokenStream {20let mut ast = parse_macro_input!(input as DeriveInput);21let bevy_ecs_path: Path = crate::bevy_ecs_path();2223ast.generics24.make_where_clause()25.predicates26.push(parse_quote! { Self: Send + Sync + 'static });2728let struct_name = &ast.ident;29let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();3031TokenStream::from(quote! {32impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}33})34}3536pub fn derive_entity_event(input: TokenStream) -> TokenStream {37let mut ast = parse_macro_input!(input as DeriveInput);38let mut auto_propagate = false;39let mut traversal: Type = parse_quote!(());40let bevy_ecs_path: Path = crate::bevy_ecs_path();4142let mut processed_attrs = Vec::new();4344ast.generics45.make_where_clause()46.predicates47.push(parse_quote! { Self: Send + Sync + 'static });4849for attr in ast.attrs.iter().filter(|attr| attr.path().is_ident(EVENT)) {50if let Err(e) = attr.parse_nested_meta(|meta| match meta.path.get_ident() {51Some(ident) if processed_attrs.iter().any(|i| ident == i) => {52Err(meta.error(format!("duplicate attribute: {ident}")))53}54Some(ident) if ident == AUTO_PROPAGATE => {55auto_propagate = true;56processed_attrs.push(AUTO_PROPAGATE);57Ok(())58}59Some(ident) if ident == TRAVERSAL => {60traversal = meta.value()?.parse()?;61processed_attrs.push(TRAVERSAL);62Ok(())63}64Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))),65None => Err(meta.error("expected identifier")),66}) {67return e.to_compile_error().into();68}69}7071let struct_name = &ast.ident;72let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();7374TokenStream::from(quote! {75impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}76impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {77type Traversal = #traversal;78const AUTO_PROPAGATE: bool = #auto_propagate;79}80})81}8283pub fn derive_buffered_event(input: TokenStream) -> TokenStream {84let mut ast = parse_macro_input!(input as DeriveInput);85let bevy_ecs_path: Path = crate::bevy_ecs_path();8687ast.generics88.make_where_clause()89.predicates90.push(parse_quote! { Self: Send + Sync + 'static });9192let struct_name = &ast.ident;93let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();9495TokenStream::from(quote! {96impl #impl_generics #bevy_ecs_path::event::BufferedEvent for #struct_name #type_generics #where_clause {}97})98}99100pub fn derive_resource(input: TokenStream) -> TokenStream {101let mut ast = parse_macro_input!(input as DeriveInput);102let bevy_ecs_path: Path = crate::bevy_ecs_path();103104ast.generics105.make_where_clause()106.predicates107.push(parse_quote! { Self: Send + Sync + 'static });108109let struct_name = &ast.ident;110let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();111112TokenStream::from(quote! {113impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {114}115})116}117118/// Component derive syntax is documented on both the macro and the trait.119pub fn derive_component(input: TokenStream) -> TokenStream {120let mut ast = parse_macro_input!(input as DeriveInput);121let bevy_ecs_path: Path = crate::bevy_ecs_path();122123let attrs = match parse_component_attr(&ast) {124Ok(attrs) => attrs,125Err(e) => return e.into_compile_error().into(),126};127128let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {129Ok(value) => value,130Err(err) => err.into_compile_error().into(),131};132let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {133Ok(value) => value,134Err(err) => err.into_compile_error().into(),135};136137let map_entities = map_entities(138&ast.data,139&bevy_ecs_path,140Ident::new("this", Span::call_site()),141relationship.is_some(),142relationship_target.is_some(),143attrs.map_entities144).map(|map_entities_impl| quote! {145fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {146use #bevy_ecs_path::entity::MapEntities;147#map_entities_impl148}149});150151let storage = storage_path(&bevy_ecs_path, attrs.storage);152153let on_add_path = attrs154.on_add155.map(|path| path.to_token_stream(&bevy_ecs_path));156let on_remove_path = attrs157.on_remove158.map(|path| path.to_token_stream(&bevy_ecs_path));159160let on_insert_path = if relationship.is_some() {161if attrs.on_insert.is_some() {162return syn::Error::new(163ast.span(),164"Custom on_insert hooks are not supported as relationships already define an on_insert hook",165)166.into_compile_error()167.into();168}169170Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))171} else {172attrs173.on_insert174.map(|path| path.to_token_stream(&bevy_ecs_path))175};176177let on_replace_path = if relationship.is_some() {178if attrs.on_replace.is_some() {179return syn::Error::new(180ast.span(),181"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",182)183.into_compile_error()184.into();185}186187Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))188} else if attrs.relationship_target.is_some() {189if attrs.on_replace.is_some() {190return syn::Error::new(191ast.span(),192"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",193)194.into_compile_error()195.into();196}197198Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))199} else {200attrs201.on_replace202.map(|path| path.to_token_stream(&bevy_ecs_path))203};204205let on_despawn_path = if attrs206.relationship_target207.is_some_and(|target| target.linked_spawn)208{209if attrs.on_despawn.is_some() {210return syn::Error::new(211ast.span(),212"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",213)214.into_compile_error()215.into();216}217218Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))219} else {220attrs221.on_despawn222.map(|path| path.to_token_stream(&bevy_ecs_path))223};224225let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);226let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);227let on_replace =228hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);229let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);230let on_despawn =231hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);232233ast.generics234.make_where_clause()235.predicates236.push(parse_quote! { Self: Send + Sync + 'static });237238let requires = &attrs.requires;239let mut register_required = Vec::with_capacity(attrs.requires.iter().len());240if let Some(requires) = requires {241for require in requires {242let ident = &require.path;243let constructor = match &require.func {244Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },245None => quote! { <#ident as Default>::default },246};247register_required.push(quote! {248required_components.register_required::<#ident>(#constructor);249});250}251}252let struct_name = &ast.ident;253let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();254255let required_component_docs = attrs.requires.map(|r| {256let paths = r257.iter()258.map(|r| format!("[`{}`]", r.path.to_token_stream()))259.collect::<Vec<_>>()260.join(", ");261let doc = format!("**Required Components**: {paths}. \n\n A component's Required Components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.");262quote! {263#[doc = #doc]264}265});266267let mutable_type = (attrs.immutable || relationship.is_some())268.then_some(quote! { #bevy_ecs_path::component::Immutable })269.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });270271let clone_behavior = if relationship_target.is_some() || relationship.is_some() {272quote!(273use #bevy_ecs_path::relationship::{274RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,275RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy276};277(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()278)279} else if let Some(behavior) = attrs.clone_behavior {280quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)281} else {282quote!(283use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};284(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()285)286};287288// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top289// level components are initialized first, giving them precedence over recursively defined constructors for the same component type290TokenStream::from(quote! {291#required_component_docs292impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {293const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;294type Mutability = #mutable_type;295fn register_required_components(296_requiree: #bevy_ecs_path::component::ComponentId,297required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,298) {299#(#register_required)*300}301302#on_add303#on_insert304#on_replace305#on_remove306#on_despawn307308fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {309#clone_behavior310}311312#map_entities313}314315#relationship316317#relationship_target318})319}320321const ENTITIES: &str = "entities";322323pub(crate) fn map_entities(324data: &Data,325bevy_ecs_path: &Path,326self_ident: Ident,327is_relationship: bool,328is_relationship_target: bool,329map_entities_attr: Option<MapEntitiesAttributeKind>,330) -> Option<TokenStream2> {331if let Some(map_entities_override) = map_entities_attr {332let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs_path);333return Some(quote!(334#map_entities_tokens(#self_ident, mapper)335));336}337338match data {339Data::Struct(DataStruct { fields, .. }) => {340let mut map = Vec::with_capacity(fields.len());341342let relationship = if is_relationship || is_relationship_target {343relationship_field(fields, "MapEntities", fields.span()).ok()344} else {345None346};347fields348.iter()349.enumerate()350.filter(|(_, field)| {351field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))352|| relationship.is_some_and(|relationship| relationship == *field)353})354.for_each(|(index, field)| {355let field_member = field356.ident357.clone()358.map_or(Member::from(index), Member::Named);359360map.push(quote!(#self_ident.#field_member.map_entities(mapper);));361});362if map.is_empty() {363return None;364};365Some(quote!(366#(#map)*367))368}369Data::Enum(DataEnum { variants, .. }) => {370let mut map = Vec::with_capacity(variants.len());371372for variant in variants.iter() {373let field_members = variant374.fields375.iter()376.enumerate()377.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))378.map(|(index, field)| {379field380.ident381.clone()382.map_or(Member::from(index), Member::Named)383})384.collect::<Vec<_>>();385386let ident = &variant.ident;387let field_idents = field_members388.iter()389.map(|member| format_ident!("__self_{}", member))390.collect::<Vec<_>>();391392map.push(393quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {394#(#field_idents.map_entities(mapper);)*395}),396);397}398399if map.is_empty() {400return None;401};402403Some(quote!(404match #self_ident {405#(#map,)*406_ => {}407}408))409}410Data::Union(_) => None,411}412}413414pub const COMPONENT: &str = "component";415pub const STORAGE: &str = "storage";416pub const REQUIRE: &str = "require";417pub const RELATIONSHIP: &str = "relationship";418pub const RELATIONSHIP_TARGET: &str = "relationship_target";419420pub const ON_ADD: &str = "on_add";421pub const ON_INSERT: &str = "on_insert";422pub const ON_REPLACE: &str = "on_replace";423pub const ON_REMOVE: &str = "on_remove";424pub const ON_DESPAWN: &str = "on_despawn";425pub const MAP_ENTITIES: &str = "map_entities";426427pub const IMMUTABLE: &str = "immutable";428pub const CLONE_BEHAVIOR: &str = "clone_behavior";429430/// All allowed attribute value expression kinds for component hooks.431/// This doesn't simply use general expressions because of conflicting needs:432/// - we want to be able to use `Self` & generic parameters in paths433/// - call expressions producing a closure need to be wrapped in a function434/// to turn them into function pointers, which prevents access to the outer generic params435#[derive(Debug)]436enum HookAttributeKind {437/// expressions like function or struct names438///439/// structs will throw compile errors on the code generation so this is safe440Path(ExprPath),441/// function call like expressions442Call(ExprCall),443}444445impl HookAttributeKind {446fn from_expr(value: Expr) -> Result<Self> {447match value {448Expr::Path(path) => Ok(HookAttributeKind::Path(path)),449Expr::Call(call) => Ok(HookAttributeKind::Call(call)),450// throw meaningful error on all other expressions451_ => Err(syn::Error::new(452value.span(),453[454"Not supported in this position, please use one of the following:",455"- path to function",456"- call to function yielding closure",457]458.join("\n"),459)),460}461}462463fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {464match self {465HookAttributeKind::Path(path) => path.to_token_stream(),466HookAttributeKind::Call(call) => {467quote!({468fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {469(#call)(world, ctx)470}471_internal_hook472})473}474}475}476}477478impl Parse for HookAttributeKind {479fn parse(input: syn::parse::ParseStream) -> Result<Self> {480input.parse::<Expr>().and_then(Self::from_expr)481}482}483484#[derive(Debug)]485pub(super) enum MapEntitiesAttributeKind {486/// expressions like function or struct names487///488/// structs will throw compile errors on the code generation so this is safe489Path(ExprPath),490/// When no value is specified491Default,492}493494impl MapEntitiesAttributeKind {495fn from_expr(value: Expr) -> Result<Self> {496match value {497Expr::Path(path) => Ok(Self::Path(path)),498// throw meaningful error on all other expressions499_ => Err(syn::Error::new(500value.span(),501[502"Not supported in this position, please use one of the following:",503"- path to function",504"- nothing to default to MapEntities implementation",505]506.join("\n"),507)),508}509}510511fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {512match self {513MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),514MapEntitiesAttributeKind::Default => {515quote!(516<Self as #bevy_ecs_path::entity::MapEntities>::map_entities517)518}519}520}521}522523impl Parse for MapEntitiesAttributeKind {524fn parse(input: syn::parse::ParseStream) -> Result<Self> {525if input.peek(Token![=]) {526input.parse::<Token![=]>()?;527input.parse::<Expr>().and_then(Self::from_expr)528} else {529Ok(Self::Default)530}531}532}533534struct Attrs {535storage: StorageTy,536requires: Option<Punctuated<Require, Comma>>,537on_add: Option<HookAttributeKind>,538on_insert: Option<HookAttributeKind>,539on_replace: Option<HookAttributeKind>,540on_remove: Option<HookAttributeKind>,541on_despawn: Option<HookAttributeKind>,542relationship: Option<Relationship>,543relationship_target: Option<RelationshipTarget>,544immutable: bool,545clone_behavior: Option<Expr>,546map_entities: Option<MapEntitiesAttributeKind>,547}548549#[derive(Clone, Copy)]550enum StorageTy {551Table,552SparseSet,553}554555struct Require {556path: Path,557func: Option<TokenStream2>,558}559560struct Relationship {561relationship_target: Type,562}563564struct RelationshipTarget {565relationship: Type,566linked_spawn: bool,567}568569// values for `storage` attribute570const TABLE: &str = "Table";571const SPARSE_SET: &str = "SparseSet";572573fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {574let mut attrs = Attrs {575storage: StorageTy::Table,576on_add: None,577on_insert: None,578on_replace: None,579on_remove: None,580on_despawn: None,581requires: None,582relationship: None,583relationship_target: None,584immutable: false,585clone_behavior: None,586map_entities: None,587};588589let mut require_paths = HashSet::new();590for attr in ast.attrs.iter() {591if attr.path().is_ident(COMPONENT) {592attr.parse_nested_meta(|nested| {593if nested.path.is_ident(STORAGE) {594attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {595s if s == TABLE => StorageTy::Table,596s if s == SPARSE_SET => StorageTy::SparseSet,597s => {598return Err(nested.error(format!(599"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",600)));601}602};603Ok(())604} else if nested.path.is_ident(ON_ADD) {605attrs.on_add = Some(nested.value()?.parse::<HookAttributeKind>()?);606Ok(())607} else if nested.path.is_ident(ON_INSERT) {608attrs.on_insert = Some(nested.value()?.parse::<HookAttributeKind>()?);609Ok(())610} else if nested.path.is_ident(ON_REPLACE) {611attrs.on_replace = Some(nested.value()?.parse::<HookAttributeKind>()?);612Ok(())613} else if nested.path.is_ident(ON_REMOVE) {614attrs.on_remove = Some(nested.value()?.parse::<HookAttributeKind>()?);615Ok(())616} else if nested.path.is_ident(ON_DESPAWN) {617attrs.on_despawn = Some(nested.value()?.parse::<HookAttributeKind>()?);618Ok(())619} else if nested.path.is_ident(IMMUTABLE) {620attrs.immutable = true;621Ok(())622} else if nested.path.is_ident(CLONE_BEHAVIOR) {623attrs.clone_behavior = Some(nested.value()?.parse()?);624Ok(())625} else if nested.path.is_ident(MAP_ENTITIES) {626attrs.map_entities = Some(nested.input.parse::<MapEntitiesAttributeKind>()?);627Ok(())628} else {629Err(nested.error("Unsupported attribute"))630}631})?;632} else if attr.path().is_ident(REQUIRE) {633let punctuated =634attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;635for require in punctuated.iter() {636if !require_paths.insert(require.path.to_token_stream().to_string()) {637return Err(syn::Error::new(638require.path.span(),639"Duplicate required components are not allowed.",640));641}642}643if let Some(current) = &mut attrs.requires {644current.extend(punctuated);645} else {646attrs.requires = Some(punctuated);647}648} else if attr.path().is_ident(RELATIONSHIP) {649let relationship = attr.parse_args::<Relationship>()?;650attrs.relationship = Some(relationship);651} else if attr.path().is_ident(RELATIONSHIP_TARGET) {652let relationship_target = attr.parse_args::<RelationshipTarget>()?;653attrs.relationship_target = Some(relationship_target);654}655}656657if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {658return Err(syn::Error::new(659attrs.clone_behavior.span(),660"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",661));662}663664Ok(attrs)665}666667impl Parse for Require {668fn parse(input: syn::parse::ParseStream) -> Result<Self> {669let mut path = input.parse::<Path>()?;670let mut last_segment_is_lower = false;671let mut is_constructor_call = false;672673// Use the case of the type name to check if it's an enum674// This doesn't match everything that can be an enum according to the rust spec675// but it matches what clippy is OK with676let is_enum = {677let mut first_chars = path678.segments679.iter()680.rev()681.filter_map(|s| s.ident.to_string().chars().next());682if let Some(last) = first_chars.next() {683if last.is_uppercase() {684if let Some(last) = first_chars.next() {685last.is_uppercase()686} else {687false688}689} else {690last_segment_is_lower = true;691false692}693} else {694false695}696};697698let func = if input.peek(Token![=]) {699// If there is an '=', then this is a "function style" require700input.parse::<Token![=]>()?;701let expr: Expr = input.parse()?;702Some(quote!(|| #expr ))703} else if input.peek(Brace) {704// This is a "value style" named-struct-like require705let content;706braced!(content in input);707let content = content.parse::<TokenStream2>()?;708Some(quote!(|| #path { #content }))709} else if input.peek(Paren) {710// This is a "value style" tuple-struct-like require711let content;712parenthesized!(content in input);713let content = content.parse::<TokenStream2>()?;714is_constructor_call = last_segment_is_lower;715Some(quote!(|| #path (#content)))716} else if is_enum {717// if this is an enum, then it is an inline enum component declaration718Some(quote!(|| #path))719} else {720// if this isn't any of the above, then it is a component ident, which will use Default721None722};723if is_enum || is_constructor_call {724path.segments.pop();725path.segments.pop_punct();726}727Ok(Require { path, func })728}729}730731fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {732let storage_type = match ty {733StorageTy::Table => Ident::new("Table", Span::call_site()),734StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),735};736737quote! { #bevy_ecs_path::component::StorageType::#storage_type }738}739740fn hook_register_function_call(741bevy_ecs_path: &Path,742hook: TokenStream2,743function: Option<TokenStream2>,744) -> Option<TokenStream2> {745function.map(|meta| {746quote! {747fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {748::core::option::Option::Some(#meta)749}750}751})752}753754mod kw {755syn::custom_keyword!(relationship_target);756syn::custom_keyword!(relationship);757syn::custom_keyword!(linked_spawn);758}759760impl Parse for Relationship {761fn parse(input: syn::parse::ParseStream) -> Result<Self> {762input.parse::<kw::relationship_target>()?;763input.parse::<Token![=]>()?;764Ok(Relationship {765relationship_target: input.parse::<Type>()?,766})767}768}769770impl Parse for RelationshipTarget {771fn parse(input: syn::parse::ParseStream) -> Result<Self> {772let mut relationship: Option<Type> = None;773let mut linked_spawn: bool = false;774775while !input.is_empty() {776let lookahead = input.lookahead1();777if lookahead.peek(kw::linked_spawn) {778input.parse::<kw::linked_spawn>()?;779linked_spawn = true;780} else if lookahead.peek(kw::relationship) {781input.parse::<kw::relationship>()?;782input.parse::<Token![=]>()?;783relationship = Some(input.parse()?);784} else {785return Err(lookahead.error());786}787if !input.is_empty() {788input.parse::<Token![,]>()?;789}790}791Ok(RelationshipTarget {792relationship: relationship.ok_or_else(|| {793syn::Error::new(input.span(), "Missing `relationship = X` attribute")794})?,795linked_spawn,796})797}798}799800fn derive_relationship(801ast: &DeriveInput,802attrs: &Attrs,803bevy_ecs_path: &Path,804) -> Result<Option<TokenStream2>> {805let Some(relationship) = &attrs.relationship else {806return Ok(None);807};808let Data::Struct(DataStruct {809fields,810struct_token,811..812}) = &ast.data813else {814return Err(syn::Error::new(815ast.span(),816"Relationship can only be derived for structs.",817));818};819let field = relationship_field(fields, "Relationship", struct_token.span())?;820821let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);822let members = fields823.members()824.filter(|member| member != &relationship_member);825826let struct_name = &ast.ident;827let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();828829let relationship_target = &relationship.relationship_target;830831Ok(Some(quote! {832impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {833type RelationshipTarget = #relationship_target;834835#[inline(always)]836fn get(&self) -> #bevy_ecs_path::entity::Entity {837self.#relationship_member838}839840#[inline]841fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {842Self {843#(#members: core::default::Default::default(),)*844#relationship_member: entity845}846}847848#[inline]849fn set_risky(&mut self, entity: Entity) {850self.#relationship_member = entity;851}852}853}))854}855856fn derive_relationship_target(857ast: &DeriveInput,858attrs: &Attrs,859bevy_ecs_path: &Path,860) -> Result<Option<TokenStream2>> {861let Some(relationship_target) = &attrs.relationship_target else {862return Ok(None);863};864865let Data::Struct(DataStruct {866fields,867struct_token,868..869}) = &ast.data870else {871return Err(syn::Error::new(872ast.span(),873"RelationshipTarget can only be derived for structs.",874));875};876let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;877878if field.vis != Visibility::Inherited {879return Err(syn::Error::new(field.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships."));880}881let collection = &field.ty;882let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);883884let members = fields885.members()886.filter(|member| member != &relationship_member);887888let relationship = &relationship_target.relationship;889let struct_name = &ast.ident;890let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();891let linked_spawn = relationship_target.linked_spawn;892Ok(Some(quote! {893impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {894const LINKED_SPAWN: bool = #linked_spawn;895type Relationship = #relationship;896type Collection = #collection;897898#[inline]899fn collection(&self) -> &Self::Collection {900&self.#relationship_member901}902903#[inline]904fn collection_mut_risky(&mut self) -> &mut Self::Collection {905&mut self.#relationship_member906}907908#[inline]909fn from_collection_risky(collection: Self::Collection) -> Self {910Self {911#(#members: core::default::Default::default(),)*912#relationship_member: collection913}914}915}916}))917}918919/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,920/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.921fn relationship_field<'a>(922fields: &'a Fields,923derive: &'static str,924span: Span,925) -> Result<&'a Field> {926match fields {927Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),928Fields::Named(fields) => fields.named.iter().find(|field| {929field930.attrs931.iter()932.any(|attr| attr.path().is_ident(RELATIONSHIP))933}).ok_or(syn::Error::new(934span,935format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")936)),937Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),938Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {939field940.attrs941.iter()942.any(|attr| attr.path().is_ident(RELATIONSHIP))943})944.ok_or(syn::Error::new(945span,946format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),947)),948Fields::Unit => Err(syn::Error::new(949span,950format!("{derive} derive expected named or unnamed struct, found unit struct."),951)),952}953}954955956