Path: blob/main/crates/bevy_ecs/macros/src/component.rs
9368 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 fn derive_resource(input: TokenStream) -> TokenStream {16let mut ast = parse_macro_input!(input as DeriveInput);17let bevy_ecs_path: Path = crate::bevy_ecs_path();1819// We want to raise a compile time error when the generic lifetimes20// are not bound to 'static lifetime21let non_static_lifetime_error = ast22.generics23.lifetimes()24.filter(|lifetime| !lifetime.bounds.iter().any(|bound| bound.ident == "static"))25.map(|param| syn::Error::new(param.span(), "Lifetimes must be 'static"))26.reduce(|mut err_acc, err| {27err_acc.combine(err);28err_acc29});30if let Some(err) = non_static_lifetime_error {31return err.into_compile_error().into();32}3334// Implement the Component trait.35let map_entities = map_entities(36&ast.data,37&bevy_ecs_path,38Ident::new("this", Span::call_site()),39false,40false,41None42).map(|map_entities_impl| quote! {43fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {44use #bevy_ecs_path::entity::MapEntities;45#map_entities_impl46}47});4849let storage = storage_path(&bevy_ecs_path, StorageTy::Table);5051let on_add_path = None;52let on_remove_path = None;53let on_insert_path = None;54let on_replace_path = None;55let on_despawn_path = None;5657let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);58let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);59let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);60let on_replace =61hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);62let on_despawn =63hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);6465ast.generics66.make_where_clause()67.predicates68.push(parse_quote! { Self: Send + Sync + 'static });6970let struct_name = &ast.ident;71let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();7273let mut register_required = Vec::with_capacity(1);74// We add the component_id existence check here to avoid recursive init during required components initialization.75register_required.push(quote! {76let resource_component_id = if let Some(id) = required_components.components_registrator().component_id::<#struct_name #type_generics>() {77id78} else {79required_components.components_registrator().register_component::<#struct_name #type_generics>()80};81required_components.register_required::<#bevy_ecs_path::resource::IsResource>(move || #bevy_ecs_path::resource::IsResource::new(resource_component_id));82});8384// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top85// level components are initialized first, giving them precedence over recursively defined constructors for the same component type86let component_derive_token_stream = TokenStream::from(quote! {87impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {88const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;89type Mutability = #bevy_ecs_path::component::Mutable;90fn register_required_components(91_requiree: #bevy_ecs_path::component::ComponentId,92required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,93) {94#(#register_required)*95}9697#on_add98#on_insert99#on_replace100#on_remove101#on_despawn102103fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {104#bevy_ecs_path::component::ComponentCloneBehavior::Default105}106107#map_entities108109fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor<Self>> {110None111}112}113});114115// Implement the Resource trait.116let resource_impl_token_stream = TokenStream::from(quote! {117impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {118}119});120121resource_impl_token_stream122.into_iter()123.chain(component_derive_token_stream)124.collect()125}126127/// Component derive syntax is documented on both the macro and the trait.128pub fn derive_component(input: TokenStream) -> TokenStream {129let mut ast = parse_macro_input!(input as DeriveInput);130let bevy_ecs_path: Path = crate::bevy_ecs_path();131132let attrs = match parse_component_attr(&ast) {133Ok(attrs) => attrs,134Err(e) => return e.into_compile_error().into(),135};136137let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {138Ok(value) => value,139Err(err) => err.into_compile_error().into(),140};141let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {142Ok(value) => value,143Err(err) => err.into_compile_error().into(),144};145146let map_entities = map_entities(147&ast.data,148&bevy_ecs_path,149Ident::new("this", Span::call_site()),150relationship.is_some(),151relationship_target.is_some(),152attrs.map_entities153).map(|map_entities_impl| quote! {154fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {155use #bevy_ecs_path::entity::MapEntities;156#map_entities_impl157}158});159160let storage = storage_path(&bevy_ecs_path, attrs.storage);161162let on_add_path = attrs163.on_add164.map(|path| path.to_token_stream(&bevy_ecs_path));165let on_remove_path = attrs166.on_remove167.map(|path| path.to_token_stream(&bevy_ecs_path));168169let on_insert_path = if relationship.is_some() {170if attrs.on_insert.is_some() {171return syn::Error::new(172ast.span(),173"Custom on_insert hooks are not supported as relationships already define an on_insert hook",174)175.into_compile_error()176.into();177}178179Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))180} else {181attrs182.on_insert183.map(|path| path.to_token_stream(&bevy_ecs_path))184};185186let on_replace_path = if relationship.is_some() {187if attrs.on_replace.is_some() {188return syn::Error::new(189ast.span(),190"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",191)192.into_compile_error()193.into();194}195196Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))197} else if attrs.relationship_target.is_some() {198if attrs.on_replace.is_some() {199return syn::Error::new(200ast.span(),201"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",202)203.into_compile_error()204.into();205}206207Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))208} else {209attrs210.on_replace211.map(|path| path.to_token_stream(&bevy_ecs_path))212};213214let on_despawn_path = if attrs215.relationship_target216.is_some_and(|target| target.linked_spawn)217{218if attrs.on_despawn.is_some() {219return syn::Error::new(220ast.span(),221"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",222)223.into_compile_error()224.into();225}226227Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))228} else {229attrs230.on_despawn231.map(|path| path.to_token_stream(&bevy_ecs_path))232};233234let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);235let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);236let on_replace =237hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);238let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);239let on_despawn =240hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);241242ast.generics243.make_where_clause()244.predicates245.push(parse_quote! { Self: Send + Sync + 'static });246247let requires = &attrs.requires;248let mut register_required = Vec::with_capacity(attrs.requires.iter().len());249if let Some(requires) = requires {250for require in requires {251let ident = &require.path;252let constructor = match &require.func {253Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },254None => quote! { <#ident as Default>::default },255};256register_required.push(quote! {257required_components.register_required::<#ident>(#constructor);258});259}260}261let struct_name = &ast.ident;262let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();263264let required_component_docs = attrs.requires.map(|r| {265let paths = r266.iter()267.map(|r| format!("[`{}`]", r.path.to_token_stream()))268.collect::<Vec<_>>()269.join(", ");270let 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.");271quote! {272#[doc = #doc]273}274});275276let mutable_type = (attrs.immutable || relationship.is_some())277.then_some(quote! { #bevy_ecs_path::component::Immutable })278.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });279280let clone_behavior = if relationship_target.is_some() || relationship.is_some() {281quote!(282use #bevy_ecs_path::relationship::{283RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,284RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy285};286(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()287)288} else if let Some(behavior) = attrs.clone_behavior {289quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)290} else {291quote!(292use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};293(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()294)295};296297let relationship_accessor = if (relationship.is_some() || relationship_target.is_some())298&& let Data::Struct(DataStruct {299fields,300struct_token,301..302}) = &ast.data303&& let Ok(field) = relationship_field(fields, "Relationship", struct_token.span())304{305let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);306if relationship.is_some() {307quote! {308Some(309// Safety: we pass valid offset of a field containing Entity (obtained via offset_off!)310unsafe {311#bevy_ecs_path::relationship::ComponentRelationshipAccessor::<Self>::relationship(312::core::mem::offset_of!(Self, #relationship_member)313)314}315)316}317} else {318quote! {319Some(#bevy_ecs_path::relationship::ComponentRelationshipAccessor::<Self>::relationship_target())320}321}322} else {323quote! {None}324};325326// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top327// level components are initialized first, giving them precedence over recursively defined constructors for the same component type328TokenStream::from(quote! {329#required_component_docs330impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {331const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;332type Mutability = #mutable_type;333fn register_required_components(334_requiree: #bevy_ecs_path::component::ComponentId,335required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,336) {337#(#register_required)*338}339340#on_add341#on_insert342#on_replace343#on_remove344#on_despawn345346fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {347#clone_behavior348}349350#map_entities351352fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor<Self>> {353#relationship_accessor354}355}356357#relationship358359#relationship_target360})361}362363const ENTITIES: &str = "entities";364365pub(crate) fn map_entities(366data: &Data,367bevy_ecs_path: &Path,368self_ident: Ident,369is_relationship: bool,370is_relationship_target: bool,371map_entities_attr: Option<MapEntitiesAttributeKind>,372) -> Option<TokenStream2> {373if let Some(map_entities_override) = map_entities_attr {374let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs_path);375return Some(quote!(376#map_entities_tokens(#self_ident, mapper)377));378}379380match data {381Data::Struct(DataStruct { fields, .. }) => {382let mut map = Vec::with_capacity(fields.len());383384let relationship = if is_relationship || is_relationship_target {385relationship_field(fields, "MapEntities", fields.span()).ok()386} else {387None388};389fields390.iter()391.enumerate()392.filter(|(_, field)| {393field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))394|| relationship.is_some_and(|relationship| relationship == *field)395})396.for_each(|(index, field)| {397let field_member = field398.ident399.clone()400.map_or(Member::from(index), Member::Named);401402map.push(quote!(#self_ident.#field_member.map_entities(mapper);));403});404if map.is_empty() {405return None;406};407Some(quote!(408#(#map)*409))410}411Data::Enum(DataEnum { variants, .. }) => {412let mut map = Vec::with_capacity(variants.len());413414for variant in variants.iter() {415let field_members = variant416.fields417.iter()418.enumerate()419.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))420.map(|(index, field)| {421field422.ident423.clone()424.map_or(Member::from(index), Member::Named)425})426.collect::<Vec<_>>();427428let ident = &variant.ident;429let field_idents = field_members430.iter()431.map(|member| format_ident!("__self{}", member))432.collect::<Vec<_>>();433434map.push(435quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {436#(#field_idents.map_entities(mapper);)*437}),438);439}440441if map.is_empty() {442return None;443};444445Some(quote!(446match #self_ident {447#(#map,)*448_ => {}449}450))451}452Data::Union(_) => None,453}454}455456pub const COMPONENT: &str = "component";457pub const STORAGE: &str = "storage";458pub const REQUIRE: &str = "require";459pub const RELATIONSHIP: &str = "relationship";460pub const RELATIONSHIP_TARGET: &str = "relationship_target";461462pub const ON_ADD: &str = "on_add";463pub const ON_INSERT: &str = "on_insert";464pub const ON_REPLACE: &str = "on_replace";465pub const ON_REMOVE: &str = "on_remove";466pub const ON_DESPAWN: &str = "on_despawn";467pub const MAP_ENTITIES: &str = "map_entities";468469pub const IMMUTABLE: &str = "immutable";470pub const CLONE_BEHAVIOR: &str = "clone_behavior";471472/// All allowed attribute value expression kinds for component hooks.473/// This doesn't simply use general expressions because of conflicting needs:474/// - we want to be able to use `Self` & generic parameters in paths475/// - call expressions producing a closure need to be wrapped in a function476/// to turn them into function pointers, which prevents access to the outer generic params477#[derive(Debug)]478enum HookAttributeKind {479/// expressions like function or struct names480///481/// structs will throw compile errors on the code generation so this is safe482Path(ExprPath),483/// function call like expressions484Call(ExprCall),485}486487impl HookAttributeKind {488fn parse(489input: syn::parse::ParseStream,490default_hook_path: impl FnOnce() -> ExprPath,491) -> Result<Self> {492if input.peek(Token![=]) {493input.parse::<Token![=]>()?;494input.parse::<Expr>().and_then(Self::from_expr)495} else {496Ok(Self::Path(default_hook_path()))497}498}499500fn from_expr(value: Expr) -> Result<Self> {501match value {502Expr::Path(path) => Ok(HookAttributeKind::Path(path)),503Expr::Call(call) => Ok(HookAttributeKind::Call(call)),504// throw meaningful error on all other expressions505_ => Err(syn::Error::new(506value.span(),507[508"Not supported in this position, please use one of the following:",509"- path to function",510"- call to function yielding closure",511]512.join("\n"),513)),514}515}516517fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {518match self {519HookAttributeKind::Path(path) => path.to_token_stream(),520HookAttributeKind::Call(call) => {521quote!({522fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {523(#call)(world, ctx)524}525_internal_hook526})527}528}529}530}531532#[derive(Debug)]533pub(super) enum MapEntitiesAttributeKind {534/// expressions like function or struct names535///536/// structs will throw compile errors on the code generation so this is safe537Path(ExprPath),538/// When no value is specified539Default,540}541542impl MapEntitiesAttributeKind {543fn from_expr(value: Expr) -> Result<Self> {544match value {545Expr::Path(path) => Ok(Self::Path(path)),546// throw meaningful error on all other expressions547_ => Err(syn::Error::new(548value.span(),549[550"Not supported in this position, please use one of the following:",551"- path to function",552"- nothing to default to MapEntities implementation",553]554.join("\n"),555)),556}557}558559fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {560match self {561MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),562MapEntitiesAttributeKind::Default => {563quote!(564<Self as #bevy_ecs_path::entity::MapEntities>::map_entities565)566}567}568}569}570571impl Parse for MapEntitiesAttributeKind {572fn parse(input: syn::parse::ParseStream) -> Result<Self> {573if input.peek(Token![=]) {574input.parse::<Token![=]>()?;575input.parse::<Expr>().and_then(Self::from_expr)576} else {577Ok(Self::Default)578}579}580}581582struct Attrs {583storage: StorageTy,584requires: Option<Punctuated<Require, Comma>>,585on_add: Option<HookAttributeKind>,586on_insert: Option<HookAttributeKind>,587on_replace: Option<HookAttributeKind>,588on_remove: Option<HookAttributeKind>,589on_despawn: Option<HookAttributeKind>,590relationship: Option<Relationship>,591relationship_target: Option<RelationshipTarget>,592immutable: bool,593clone_behavior: Option<Expr>,594map_entities: Option<MapEntitiesAttributeKind>,595}596597#[derive(Clone, Copy)]598enum StorageTy {599Table,600SparseSet,601}602603struct Require {604path: Path,605func: Option<TokenStream2>,606}607608struct Relationship {609relationship_target: Type,610allow_self_referential: bool,611}612613struct RelationshipTarget {614relationship: Type,615linked_spawn: bool,616}617618// values for `storage` attribute619const TABLE: &str = "Table";620const SPARSE_SET: &str = "SparseSet";621622fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {623let mut attrs = Attrs {624storage: StorageTy::Table,625on_add: None,626on_insert: None,627on_replace: None,628on_remove: None,629on_despawn: None,630requires: None,631relationship: None,632relationship_target: None,633immutable: false,634clone_behavior: None,635map_entities: None,636};637638let mut require_paths = HashSet::new();639for attr in ast.attrs.iter() {640if attr.path().is_ident(COMPONENT) {641attr.parse_nested_meta(|nested| {642if nested.path.is_ident(STORAGE) {643attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {644s if s == TABLE => StorageTy::Table,645s if s == SPARSE_SET => StorageTy::SparseSet,646s => {647return Err(nested.error(format!(648"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",649)));650}651};652Ok(())653} else if nested.path.is_ident(ON_ADD) {654attrs.on_add = Some(HookAttributeKind::parse(nested.input, || {655parse_quote! { Self::on_add }656})?);657Ok(())658} else if nested.path.is_ident(ON_INSERT) {659attrs.on_insert = Some(HookAttributeKind::parse(nested.input, || {660parse_quote! { Self::on_insert }661})?);662Ok(())663} else if nested.path.is_ident(ON_REPLACE) {664attrs.on_replace = Some(HookAttributeKind::parse(nested.input, || {665parse_quote! { Self::on_replace }666})?);667Ok(())668} else if nested.path.is_ident(ON_REMOVE) {669attrs.on_remove = Some(HookAttributeKind::parse(nested.input, || {670parse_quote! { Self::on_remove }671})?);672Ok(())673} else if nested.path.is_ident(ON_DESPAWN) {674attrs.on_despawn = Some(HookAttributeKind::parse(nested.input, || {675parse_quote! { Self::on_despawn }676})?);677Ok(())678} else if nested.path.is_ident(IMMUTABLE) {679attrs.immutable = true;680Ok(())681} else if nested.path.is_ident(CLONE_BEHAVIOR) {682attrs.clone_behavior = Some(nested.value()?.parse()?);683Ok(())684} else if nested.path.is_ident(MAP_ENTITIES) {685attrs.map_entities = Some(nested.input.parse::<MapEntitiesAttributeKind>()?);686Ok(())687} else {688Err(nested.error("Unsupported attribute"))689}690})?;691} else if attr.path().is_ident(REQUIRE) {692let punctuated =693attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;694for require in punctuated.iter() {695if !require_paths.insert(require.path.to_token_stream().to_string()) {696return Err(syn::Error::new(697require.path.span(),698"Duplicate required components are not allowed.",699));700}701}702if let Some(current) = &mut attrs.requires {703current.extend(punctuated);704} else {705attrs.requires = Some(punctuated);706}707} else if attr.path().is_ident(RELATIONSHIP) {708let relationship = attr.parse_args::<Relationship>()?;709attrs.relationship = Some(relationship);710} else if attr.path().is_ident(RELATIONSHIP_TARGET) {711let relationship_target = attr.parse_args::<RelationshipTarget>()?;712attrs.relationship_target = Some(relationship_target);713}714}715716if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {717return Err(syn::Error::new(718attrs.clone_behavior.span(),719"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",720));721}722723Ok(attrs)724}725726impl Parse for Require {727fn parse(input: syn::parse::ParseStream) -> Result<Self> {728let mut path = input.parse::<Path>()?;729let mut last_segment_is_lower = false;730let mut is_constructor_call = false;731732// Use the case of the type name to check if it's an enum733// This doesn't match everything that can be an enum according to the rust spec734// but it matches what clippy is OK with735let is_enum = {736let mut first_chars = path737.segments738.iter()739.rev()740.filter_map(|s| s.ident.to_string().chars().next());741if let Some(last) = first_chars.next() {742if last.is_uppercase() {743if let Some(last) = first_chars.next() {744last.is_uppercase()745} else {746false747}748} else {749last_segment_is_lower = true;750false751}752} else {753false754}755};756757let func = if input.peek(Token![=]) {758// If there is an '=', then this is a "function style" require759input.parse::<Token![=]>()?;760let expr: Expr = input.parse()?;761Some(quote!(|| #expr ))762} else if input.peek(Brace) {763// This is a "value style" named-struct-like require764let content;765braced!(content in input);766let content = content.parse::<TokenStream2>()?;767Some(quote!(|| #path { #content }))768} else if input.peek(Paren) {769// This is a "value style" tuple-struct-like require770let content;771parenthesized!(content in input);772let content = content.parse::<TokenStream2>()?;773is_constructor_call = last_segment_is_lower;774Some(quote!(|| #path (#content)))775} else if is_enum {776// if this is an enum, then it is an inline enum component declaration777Some(quote!(|| #path))778} else {779// if this isn't any of the above, then it is a component ident, which will use Default780None781};782if is_enum || is_constructor_call {783path.segments.pop();784path.segments.pop_punct();785}786Ok(Require { path, func })787}788}789790fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {791let storage_type = match ty {792StorageTy::Table => Ident::new("Table", Span::call_site()),793StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),794};795796quote! { #bevy_ecs_path::component::StorageType::#storage_type }797}798799fn hook_register_function_call(800bevy_ecs_path: &Path,801hook: TokenStream2,802function: Option<TokenStream2>,803) -> Option<TokenStream2> {804function.map(|meta| {805quote! {806fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {807::core::option::Option::Some(#meta)808}809}810})811}812813mod kw {814syn::custom_keyword!(relationship_target);815syn::custom_keyword!(relationship);816syn::custom_keyword!(linked_spawn);817syn::custom_keyword!(allow_self_referential);818}819820impl Parse for Relationship {821fn parse(input: syn::parse::ParseStream) -> Result<Self> {822let mut relationship_target: Option<Type> = None;823let mut allow_self_referential: bool = false;824825while !input.is_empty() {826let lookahead = input.lookahead1();827if lookahead.peek(kw::allow_self_referential) {828input.parse::<kw::allow_self_referential>()?;829allow_self_referential = true;830} else if lookahead.peek(kw::relationship_target) {831input.parse::<kw::relationship_target>()?;832input.parse::<Token![=]>()?;833relationship_target = Some(input.parse()?);834} else {835return Err(lookahead.error());836}837if !input.is_empty() {838input.parse::<Token![,]>()?;839}840}841Ok(Relationship {842relationship_target: relationship_target.ok_or_else(|| {843syn::Error::new(input.span(), "Missing `relationship_target = X` attribute")844})?,845allow_self_referential,846})847}848}849850impl Parse for RelationshipTarget {851fn parse(input: syn::parse::ParseStream) -> Result<Self> {852let mut relationship: Option<Type> = None;853let mut linked_spawn: bool = false;854855while !input.is_empty() {856let lookahead = input.lookahead1();857if lookahead.peek(kw::linked_spawn) {858input.parse::<kw::linked_spawn>()?;859linked_spawn = true;860} else if lookahead.peek(kw::relationship) {861input.parse::<kw::relationship>()?;862input.parse::<Token![=]>()?;863relationship = Some(input.parse()?);864} else {865return Err(lookahead.error());866}867if !input.is_empty() {868input.parse::<Token![,]>()?;869}870}871Ok(RelationshipTarget {872relationship: relationship.ok_or_else(|| {873syn::Error::new(input.span(), "Missing `relationship = X` attribute")874})?,875linked_spawn,876})877}878}879880fn derive_relationship(881ast: &DeriveInput,882attrs: &Attrs,883bevy_ecs_path: &Path,884) -> Result<Option<TokenStream2>> {885let Some(relationship) = &attrs.relationship else {886return Ok(None);887};888let Data::Struct(DataStruct {889fields,890struct_token,891..892}) = &ast.data893else {894return Err(syn::Error::new(895ast.span(),896"Relationship can only be derived for structs.",897));898};899let field = relationship_field(fields, "Relationship", struct_token.span())?;900901let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);902let members = fields903.members()904.filter(|member| member != &relationship_member);905906let struct_name = &ast.ident;907let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();908909let relationship_target = &relationship.relationship_target;910let allow_self_referential = relationship.allow_self_referential;911912Ok(Some(quote! {913impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {914type RelationshipTarget = #relationship_target;915const ALLOW_SELF_REFERENTIAL: bool = #allow_self_referential;916917#[inline(always)]918fn get(&self) -> #bevy_ecs_path::entity::Entity {919self.#relationship_member920}921922#[inline]923fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {924Self {925#(#members: ::core::default::Default::default(),)*926#relationship_member: entity927}928}929930#[inline]931fn set_risky(&mut self, entity: #bevy_ecs_path::entity::Entity) {932self.#relationship_member = entity;933}934}935}))936}937938fn derive_relationship_target(939ast: &DeriveInput,940attrs: &Attrs,941bevy_ecs_path: &Path,942) -> Result<Option<TokenStream2>> {943let Some(relationship_target) = &attrs.relationship_target else {944return Ok(None);945};946947let Data::Struct(DataStruct {948fields,949struct_token,950..951}) = &ast.data952else {953return Err(syn::Error::new(954ast.span(),955"RelationshipTarget can only be derived for structs.",956));957};958let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;959960if field.vis != Visibility::Inherited {961return 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."));962}963let collection = &field.ty;964let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);965966let members = fields967.members()968.filter(|member| member != &relationship_member);969970let relationship = &relationship_target.relationship;971let struct_name = &ast.ident;972let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();973let linked_spawn = relationship_target.linked_spawn;974Ok(Some(quote! {975impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {976const LINKED_SPAWN: bool = #linked_spawn;977type Relationship = #relationship;978type Collection = #collection;979980#[inline]981fn collection(&self) -> &Self::Collection {982&self.#relationship_member983}984985#[inline]986fn collection_mut_risky(&mut self) -> &mut Self::Collection {987&mut self.#relationship_member988}989990#[inline]991fn from_collection_risky(collection: Self::Collection) -> Self {992Self {993#(#members: ::core::default::Default::default(),)*994#relationship_member: collection995}996}997}998}))999}10001001/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,1002/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.1003fn relationship_field<'a>(1004fields: &'a Fields,1005derive: &'static str,1006span: Span,1007) -> Result<&'a Field> {1008match fields {1009Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),1010Fields::Named(fields) => fields.named.iter().find(|field| {1011field1012.attrs1013.iter()1014.any(|attr| attr.path().is_ident(RELATIONSHIP))1015}).ok_or(syn::Error::new(1016span,1017format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")1018)),1019Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),1020Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {1021field1022.attrs1023.iter()1024.any(|attr| attr.path().is_ident(RELATIONSHIP))1025})1026.ok_or(syn::Error::new(1027span,1028format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),1029)),1030Fields::Unit => Err(syn::Error::new(1031span,1032format!("{derive} derive expected named or unnamed struct, found unit struct."),1033)),1034}1035}103610371038