Path: blob/main/crates/bevy_ecs/macros/src/component.rs
6849 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();1819ast.generics20.make_where_clause()21.predicates22.push(parse_quote! { Self: Send + Sync + 'static });2324let struct_name = &ast.ident;25let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();2627TokenStream::from(quote! {28impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {29}30})31}3233/// Component derive syntax is documented on both the macro and the trait.34pub fn derive_component(input: TokenStream) -> TokenStream {35let mut ast = parse_macro_input!(input as DeriveInput);36let bevy_ecs_path: Path = crate::bevy_ecs_path();3738let attrs = match parse_component_attr(&ast) {39Ok(attrs) => attrs,40Err(e) => return e.into_compile_error().into(),41};4243let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {44Ok(value) => value,45Err(err) => err.into_compile_error().into(),46};47let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {48Ok(value) => value,49Err(err) => err.into_compile_error().into(),50};5152let map_entities = map_entities(53&ast.data,54&bevy_ecs_path,55Ident::new("this", Span::call_site()),56relationship.is_some(),57relationship_target.is_some(),58attrs.map_entities59).map(|map_entities_impl| quote! {60fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {61use #bevy_ecs_path::entity::MapEntities;62#map_entities_impl63}64});6566let storage = storage_path(&bevy_ecs_path, attrs.storage);6768let on_add_path = attrs69.on_add70.map(|path| path.to_token_stream(&bevy_ecs_path));71let on_remove_path = attrs72.on_remove73.map(|path| path.to_token_stream(&bevy_ecs_path));7475let on_insert_path = if relationship.is_some() {76if attrs.on_insert.is_some() {77return syn::Error::new(78ast.span(),79"Custom on_insert hooks are not supported as relationships already define an on_insert hook",80)81.into_compile_error()82.into();83}8485Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))86} else {87attrs88.on_insert89.map(|path| path.to_token_stream(&bevy_ecs_path))90};9192let on_replace_path = if relationship.is_some() {93if attrs.on_replace.is_some() {94return syn::Error::new(95ast.span(),96"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",97)98.into_compile_error()99.into();100}101102Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))103} else if attrs.relationship_target.is_some() {104if attrs.on_replace.is_some() {105return syn::Error::new(106ast.span(),107"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",108)109.into_compile_error()110.into();111}112113Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))114} else {115attrs116.on_replace117.map(|path| path.to_token_stream(&bevy_ecs_path))118};119120let on_despawn_path = if attrs121.relationship_target122.is_some_and(|target| target.linked_spawn)123{124if attrs.on_despawn.is_some() {125return syn::Error::new(126ast.span(),127"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",128)129.into_compile_error()130.into();131}132133Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))134} else {135attrs136.on_despawn137.map(|path| path.to_token_stream(&bevy_ecs_path))138};139140let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);141let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);142let on_replace =143hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);144let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);145let on_despawn =146hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);147148ast.generics149.make_where_clause()150.predicates151.push(parse_quote! { Self: Send + Sync + 'static });152153let requires = &attrs.requires;154let mut register_required = Vec::with_capacity(attrs.requires.iter().len());155if let Some(requires) = requires {156for require in requires {157let ident = &require.path;158let constructor = match &require.func {159Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },160None => quote! { <#ident as Default>::default },161};162register_required.push(quote! {163required_components.register_required::<#ident>(#constructor);164});165}166}167let struct_name = &ast.ident;168let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();169170let required_component_docs = attrs.requires.map(|r| {171let paths = r172.iter()173.map(|r| format!("[`{}`]", r.path.to_token_stream()))174.collect::<Vec<_>>()175.join(", ");176let 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.");177quote! {178#[doc = #doc]179}180});181182let mutable_type = (attrs.immutable || relationship.is_some())183.then_some(quote! { #bevy_ecs_path::component::Immutable })184.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });185186let clone_behavior = if relationship_target.is_some() || relationship.is_some() {187quote!(188use #bevy_ecs_path::relationship::{189RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,190RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy191};192(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()193)194} else if let Some(behavior) = attrs.clone_behavior {195quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)196} else {197quote!(198use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};199(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()200)201};202203// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top204// level components are initialized first, giving them precedence over recursively defined constructors for the same component type205TokenStream::from(quote! {206#required_component_docs207impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {208const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;209type Mutability = #mutable_type;210fn register_required_components(211_requiree: #bevy_ecs_path::component::ComponentId,212required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,213) {214#(#register_required)*215}216217#on_add218#on_insert219#on_replace220#on_remove221#on_despawn222223fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {224#clone_behavior225}226227#map_entities228}229230#relationship231232#relationship_target233})234}235236const ENTITIES: &str = "entities";237238pub(crate) fn map_entities(239data: &Data,240bevy_ecs_path: &Path,241self_ident: Ident,242is_relationship: bool,243is_relationship_target: bool,244map_entities_attr: Option<MapEntitiesAttributeKind>,245) -> Option<TokenStream2> {246if let Some(map_entities_override) = map_entities_attr {247let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs_path);248return Some(quote!(249#map_entities_tokens(#self_ident, mapper)250));251}252253match data {254Data::Struct(DataStruct { fields, .. }) => {255let mut map = Vec::with_capacity(fields.len());256257let relationship = if is_relationship || is_relationship_target {258relationship_field(fields, "MapEntities", fields.span()).ok()259} else {260None261};262fields263.iter()264.enumerate()265.filter(|(_, field)| {266field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))267|| relationship.is_some_and(|relationship| relationship == *field)268})269.for_each(|(index, field)| {270let field_member = field271.ident272.clone()273.map_or(Member::from(index), Member::Named);274275map.push(quote!(#self_ident.#field_member.map_entities(mapper);));276});277if map.is_empty() {278return None;279};280Some(quote!(281#(#map)*282))283}284Data::Enum(DataEnum { variants, .. }) => {285let mut map = Vec::with_capacity(variants.len());286287for variant in variants.iter() {288let field_members = variant289.fields290.iter()291.enumerate()292.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))293.map(|(index, field)| {294field295.ident296.clone()297.map_or(Member::from(index), Member::Named)298})299.collect::<Vec<_>>();300301let ident = &variant.ident;302let field_idents = field_members303.iter()304.map(|member| format_ident!("__self_{}", member))305.collect::<Vec<_>>();306307map.push(308quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {309#(#field_idents.map_entities(mapper);)*310}),311);312}313314if map.is_empty() {315return None;316};317318Some(quote!(319match #self_ident {320#(#map,)*321_ => {}322}323))324}325Data::Union(_) => None,326}327}328329pub const COMPONENT: &str = "component";330pub const STORAGE: &str = "storage";331pub const REQUIRE: &str = "require";332pub const RELATIONSHIP: &str = "relationship";333pub const RELATIONSHIP_TARGET: &str = "relationship_target";334335pub const ON_ADD: &str = "on_add";336pub const ON_INSERT: &str = "on_insert";337pub const ON_REPLACE: &str = "on_replace";338pub const ON_REMOVE: &str = "on_remove";339pub const ON_DESPAWN: &str = "on_despawn";340pub const MAP_ENTITIES: &str = "map_entities";341342pub const IMMUTABLE: &str = "immutable";343pub const CLONE_BEHAVIOR: &str = "clone_behavior";344345/// All allowed attribute value expression kinds for component hooks.346/// This doesn't simply use general expressions because of conflicting needs:347/// - we want to be able to use `Self` & generic parameters in paths348/// - call expressions producing a closure need to be wrapped in a function349/// to turn them into function pointers, which prevents access to the outer generic params350#[derive(Debug)]351enum HookAttributeKind {352/// expressions like function or struct names353///354/// structs will throw compile errors on the code generation so this is safe355Path(ExprPath),356/// function call like expressions357Call(ExprCall),358}359360impl HookAttributeKind {361fn from_expr(value: Expr) -> Result<Self> {362match value {363Expr::Path(path) => Ok(HookAttributeKind::Path(path)),364Expr::Call(call) => Ok(HookAttributeKind::Call(call)),365// throw meaningful error on all other expressions366_ => Err(syn::Error::new(367value.span(),368[369"Not supported in this position, please use one of the following:",370"- path to function",371"- call to function yielding closure",372]373.join("\n"),374)),375}376}377378fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {379match self {380HookAttributeKind::Path(path) => path.to_token_stream(),381HookAttributeKind::Call(call) => {382quote!({383fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {384(#call)(world, ctx)385}386_internal_hook387})388}389}390}391}392393impl Parse for HookAttributeKind {394fn parse(input: syn::parse::ParseStream) -> Result<Self> {395input.parse::<Expr>().and_then(Self::from_expr)396}397}398399#[derive(Debug)]400pub(super) enum MapEntitiesAttributeKind {401/// expressions like function or struct names402///403/// structs will throw compile errors on the code generation so this is safe404Path(ExprPath),405/// When no value is specified406Default,407}408409impl MapEntitiesAttributeKind {410fn from_expr(value: Expr) -> Result<Self> {411match value {412Expr::Path(path) => Ok(Self::Path(path)),413// throw meaningful error on all other expressions414_ => Err(syn::Error::new(415value.span(),416[417"Not supported in this position, please use one of the following:",418"- path to function",419"- nothing to default to MapEntities implementation",420]421.join("\n"),422)),423}424}425426fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {427match self {428MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),429MapEntitiesAttributeKind::Default => {430quote!(431<Self as #bevy_ecs_path::entity::MapEntities>::map_entities432)433}434}435}436}437438impl Parse for MapEntitiesAttributeKind {439fn parse(input: syn::parse::ParseStream) -> Result<Self> {440if input.peek(Token![=]) {441input.parse::<Token![=]>()?;442input.parse::<Expr>().and_then(Self::from_expr)443} else {444Ok(Self::Default)445}446}447}448449struct Attrs {450storage: StorageTy,451requires: Option<Punctuated<Require, Comma>>,452on_add: Option<HookAttributeKind>,453on_insert: Option<HookAttributeKind>,454on_replace: Option<HookAttributeKind>,455on_remove: Option<HookAttributeKind>,456on_despawn: Option<HookAttributeKind>,457relationship: Option<Relationship>,458relationship_target: Option<RelationshipTarget>,459immutable: bool,460clone_behavior: Option<Expr>,461map_entities: Option<MapEntitiesAttributeKind>,462}463464#[derive(Clone, Copy)]465enum StorageTy {466Table,467SparseSet,468}469470struct Require {471path: Path,472func: Option<TokenStream2>,473}474475struct Relationship {476relationship_target: Type,477}478479struct RelationshipTarget {480relationship: Type,481linked_spawn: bool,482}483484// values for `storage` attribute485const TABLE: &str = "Table";486const SPARSE_SET: &str = "SparseSet";487488fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {489let mut attrs = Attrs {490storage: StorageTy::Table,491on_add: None,492on_insert: None,493on_replace: None,494on_remove: None,495on_despawn: None,496requires: None,497relationship: None,498relationship_target: None,499immutable: false,500clone_behavior: None,501map_entities: None,502};503504let mut require_paths = HashSet::new();505for attr in ast.attrs.iter() {506if attr.path().is_ident(COMPONENT) {507attr.parse_nested_meta(|nested| {508if nested.path.is_ident(STORAGE) {509attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {510s if s == TABLE => StorageTy::Table,511s if s == SPARSE_SET => StorageTy::SparseSet,512s => {513return Err(nested.error(format!(514"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",515)));516}517};518Ok(())519} else if nested.path.is_ident(ON_ADD) {520attrs.on_add = Some(nested.value()?.parse::<HookAttributeKind>()?);521Ok(())522} else if nested.path.is_ident(ON_INSERT) {523attrs.on_insert = Some(nested.value()?.parse::<HookAttributeKind>()?);524Ok(())525} else if nested.path.is_ident(ON_REPLACE) {526attrs.on_replace = Some(nested.value()?.parse::<HookAttributeKind>()?);527Ok(())528} else if nested.path.is_ident(ON_REMOVE) {529attrs.on_remove = Some(nested.value()?.parse::<HookAttributeKind>()?);530Ok(())531} else if nested.path.is_ident(ON_DESPAWN) {532attrs.on_despawn = Some(nested.value()?.parse::<HookAttributeKind>()?);533Ok(())534} else if nested.path.is_ident(IMMUTABLE) {535attrs.immutable = true;536Ok(())537} else if nested.path.is_ident(CLONE_BEHAVIOR) {538attrs.clone_behavior = Some(nested.value()?.parse()?);539Ok(())540} else if nested.path.is_ident(MAP_ENTITIES) {541attrs.map_entities = Some(nested.input.parse::<MapEntitiesAttributeKind>()?);542Ok(())543} else {544Err(nested.error("Unsupported attribute"))545}546})?;547} else if attr.path().is_ident(REQUIRE) {548let punctuated =549attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;550for require in punctuated.iter() {551if !require_paths.insert(require.path.to_token_stream().to_string()) {552return Err(syn::Error::new(553require.path.span(),554"Duplicate required components are not allowed.",555));556}557}558if let Some(current) = &mut attrs.requires {559current.extend(punctuated);560} else {561attrs.requires = Some(punctuated);562}563} else if attr.path().is_ident(RELATIONSHIP) {564let relationship = attr.parse_args::<Relationship>()?;565attrs.relationship = Some(relationship);566} else if attr.path().is_ident(RELATIONSHIP_TARGET) {567let relationship_target = attr.parse_args::<RelationshipTarget>()?;568attrs.relationship_target = Some(relationship_target);569}570}571572if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {573return Err(syn::Error::new(574attrs.clone_behavior.span(),575"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",576));577}578579Ok(attrs)580}581582impl Parse for Require {583fn parse(input: syn::parse::ParseStream) -> Result<Self> {584let mut path = input.parse::<Path>()?;585let mut last_segment_is_lower = false;586let mut is_constructor_call = false;587588// Use the case of the type name to check if it's an enum589// This doesn't match everything that can be an enum according to the rust spec590// but it matches what clippy is OK with591let is_enum = {592let mut first_chars = path593.segments594.iter()595.rev()596.filter_map(|s| s.ident.to_string().chars().next());597if let Some(last) = first_chars.next() {598if last.is_uppercase() {599if let Some(last) = first_chars.next() {600last.is_uppercase()601} else {602false603}604} else {605last_segment_is_lower = true;606false607}608} else {609false610}611};612613let func = if input.peek(Token![=]) {614// If there is an '=', then this is a "function style" require615input.parse::<Token![=]>()?;616let expr: Expr = input.parse()?;617Some(quote!(|| #expr ))618} else if input.peek(Brace) {619// This is a "value style" named-struct-like require620let content;621braced!(content in input);622let content = content.parse::<TokenStream2>()?;623Some(quote!(|| #path { #content }))624} else if input.peek(Paren) {625// This is a "value style" tuple-struct-like require626let content;627parenthesized!(content in input);628let content = content.parse::<TokenStream2>()?;629is_constructor_call = last_segment_is_lower;630Some(quote!(|| #path (#content)))631} else if is_enum {632// if this is an enum, then it is an inline enum component declaration633Some(quote!(|| #path))634} else {635// if this isn't any of the above, then it is a component ident, which will use Default636None637};638if is_enum || is_constructor_call {639path.segments.pop();640path.segments.pop_punct();641}642Ok(Require { path, func })643}644}645646fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {647let storage_type = match ty {648StorageTy::Table => Ident::new("Table", Span::call_site()),649StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),650};651652quote! { #bevy_ecs_path::component::StorageType::#storage_type }653}654655fn hook_register_function_call(656bevy_ecs_path: &Path,657hook: TokenStream2,658function: Option<TokenStream2>,659) -> Option<TokenStream2> {660function.map(|meta| {661quote! {662fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {663::core::option::Option::Some(#meta)664}665}666})667}668669mod kw {670syn::custom_keyword!(relationship_target);671syn::custom_keyword!(relationship);672syn::custom_keyword!(linked_spawn);673}674675impl Parse for Relationship {676fn parse(input: syn::parse::ParseStream) -> Result<Self> {677input.parse::<kw::relationship_target>()?;678input.parse::<Token![=]>()?;679Ok(Relationship {680relationship_target: input.parse::<Type>()?,681})682}683}684685impl Parse for RelationshipTarget {686fn parse(input: syn::parse::ParseStream) -> Result<Self> {687let mut relationship: Option<Type> = None;688let mut linked_spawn: bool = false;689690while !input.is_empty() {691let lookahead = input.lookahead1();692if lookahead.peek(kw::linked_spawn) {693input.parse::<kw::linked_spawn>()?;694linked_spawn = true;695} else if lookahead.peek(kw::relationship) {696input.parse::<kw::relationship>()?;697input.parse::<Token![=]>()?;698relationship = Some(input.parse()?);699} else {700return Err(lookahead.error());701}702if !input.is_empty() {703input.parse::<Token![,]>()?;704}705}706Ok(RelationshipTarget {707relationship: relationship.ok_or_else(|| {708syn::Error::new(input.span(), "Missing `relationship = X` attribute")709})?,710linked_spawn,711})712}713}714715fn derive_relationship(716ast: &DeriveInput,717attrs: &Attrs,718bevy_ecs_path: &Path,719) -> Result<Option<TokenStream2>> {720let Some(relationship) = &attrs.relationship else {721return Ok(None);722};723let Data::Struct(DataStruct {724fields,725struct_token,726..727}) = &ast.data728else {729return Err(syn::Error::new(730ast.span(),731"Relationship can only be derived for structs.",732));733};734let field = relationship_field(fields, "Relationship", struct_token.span())?;735736let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);737let members = fields738.members()739.filter(|member| member != &relationship_member);740741let struct_name = &ast.ident;742let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();743744let relationship_target = &relationship.relationship_target;745746Ok(Some(quote! {747impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {748type RelationshipTarget = #relationship_target;749750#[inline(always)]751fn get(&self) -> #bevy_ecs_path::entity::Entity {752self.#relationship_member753}754755#[inline]756fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {757Self {758#(#members: core::default::Default::default(),)*759#relationship_member: entity760}761}762763#[inline]764fn set_risky(&mut self, entity: Entity) {765self.#relationship_member = entity;766}767}768}))769}770771fn derive_relationship_target(772ast: &DeriveInput,773attrs: &Attrs,774bevy_ecs_path: &Path,775) -> Result<Option<TokenStream2>> {776let Some(relationship_target) = &attrs.relationship_target else {777return Ok(None);778};779780let Data::Struct(DataStruct {781fields,782struct_token,783..784}) = &ast.data785else {786return Err(syn::Error::new(787ast.span(),788"RelationshipTarget can only be derived for structs.",789));790};791let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;792793if field.vis != Visibility::Inherited {794return 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."));795}796let collection = &field.ty;797let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);798799let members = fields800.members()801.filter(|member| member != &relationship_member);802803let relationship = &relationship_target.relationship;804let struct_name = &ast.ident;805let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();806let linked_spawn = relationship_target.linked_spawn;807Ok(Some(quote! {808impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {809const LINKED_SPAWN: bool = #linked_spawn;810type Relationship = #relationship;811type Collection = #collection;812813#[inline]814fn collection(&self) -> &Self::Collection {815&self.#relationship_member816}817818#[inline]819fn collection_mut_risky(&mut self) -> &mut Self::Collection {820&mut self.#relationship_member821}822823#[inline]824fn from_collection_risky(collection: Self::Collection) -> Self {825Self {826#(#members: core::default::Default::default(),)*827#relationship_member: collection828}829}830}831}))832}833834/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,835/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.836fn relationship_field<'a>(837fields: &'a Fields,838derive: &'static str,839span: Span,840) -> Result<&'a Field> {841match fields {842Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),843Fields::Named(fields) => fields.named.iter().find(|field| {844field845.attrs846.iter()847.any(|attr| attr.path().is_ident(RELATIONSHIP))848}).ok_or(syn::Error::new(849span,850format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")851)),852Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),853Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {854field855.attrs856.iter()857.any(|attr| attr.path().is_ident(RELATIONSHIP))858})859.ok_or(syn::Error::new(860span,861format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),862)),863Fields::Unit => Err(syn::Error::new(864span,865format!("{derive} derive expected named or unnamed struct, found unit struct."),866)),867}868}869870871