Path: blob/main/crates/bevy_ecs/macro_logic/src/component.rs
30636 views
use bevy_macro_utils::fq_std::{FQDefault, FQOption, FQSend, FQSync};1use proc_macro2::{Span, TokenStream};2use quote::{quote, ToTokens};3use std::collections::HashSet;4use syn::{5braced, parenthesized,6parse::Parse,7parse_quote,8punctuated::Punctuated,9spanned::Spanned,10token::{Brace, Comma, Paren},11Data, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident, LitStr, Member,12Path, Result, Token, Type, Visibility,13};1415use crate::map_entities::{map_entities, MapEntitiesAttributeKind};1617/// Whether the derive macro may contain a `component(storage = "…")` attribute.18pub enum StorageAttribute {19/// User can overwrite the storage type20Allowed,21/// User cannot overwrite the storage type22Disallowed,23}2425/// Derived `Component` trait specification, which can be used to generate a component implementation.26pub struct DeriveComponent {27/// The storage type of the component.28pub storage: Option<StorageTy>,29/// The parsed punctuated list of required components.30pub requires: Option<Punctuated<Require, Comma>>,31/// The `on_add` hook.32pub on_add: Option<HookAttributeKind>,33/// The `on_insert` hook.34pub on_insert: Option<HookAttributeKind>,35/// The `on_discard` hook.36pub on_discard: Option<HookAttributeKind>,37/// The `on_remove` hook.38pub on_remove: Option<HookAttributeKind>,39/// The `on_despawn` hook.40pub on_despawn: Option<HookAttributeKind>,41/// The relationship attribute information.42pub relationship: Option<Relationship>,43/// The relationship target attribute information.44pub relationship_target: Option<RelationshipTarget>,45/// Whether or not this component is immutable.46pub immutable: bool,47/// The clone behavior for this component.48pub clone_behavior: Option<Expr>,49/// The `map_entities` attribute information.50pub map_entities: Option<MapEntitiesAttributeKind>,51/// Additional required component registrations that are added in `Component::register_required_components`52pub additional_requires: Vec<TokenStream>,53}5455impl DeriveComponent {56/// Parse [`DeriveComponent`] from the given `ast`.57pub fn parse(ast: &DeriveInput, storage_attr: StorageAttribute) -> Result<DeriveComponent> {58let mut attrs = DeriveComponent {59storage: None,60on_add: None,61on_insert: None,62on_discard: None,63on_remove: None,64on_despawn: None,65requires: None,66relationship: None,67relationship_target: None,68immutable: false,69clone_behavior: None,70map_entities: None,71additional_requires: Vec::new(),72};7374let mut require_paths = HashSet::new();75for attr in ast.attrs.iter() {76if attr.path().is_ident(COMPONENT) {77attr.parse_nested_meta(|nested| {78if let StorageAttribute::Allowed = storage_attr79&& nested.path.is_ident(STORAGE)80{81attrs.storage = Some(match nested.value()?.parse::<LitStr>()?.value() {82s if s == TABLE => StorageTy::Table,83s if s == SPARSE_SET => StorageTy::SparseSet,84s => {85return Err(nested.error(format!(86"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",87)));88}89});90Ok(())91} else if nested.path.is_ident(ON_ADD) {92attrs.on_add = Some(HookAttributeKind::parse(nested.input, || {93parse_quote! { Self::on_add }94})?);95Ok(())96} else if nested.path.is_ident(ON_INSERT) {97attrs.on_insert = Some(HookAttributeKind::parse(nested.input, || {98parse_quote! { Self::on_insert }99})?);100Ok(())101} else if nested.path.is_ident(ON_DISCARD) {102attrs.on_discard = Some(HookAttributeKind::parse(nested.input, || {103parse_quote! { Self::on_discard }104})?);105Ok(())106} else if nested.path.is_ident(ON_REMOVE) {107attrs.on_remove = Some(HookAttributeKind::parse(nested.input, || {108parse_quote! { Self::on_remove }109})?);110Ok(())111} else if nested.path.is_ident(ON_DESPAWN) {112attrs.on_despawn = Some(HookAttributeKind::parse(nested.input, || {113parse_quote! { Self::on_despawn }114})?);115Ok(())116} else if nested.path.is_ident(IMMUTABLE) {117attrs.immutable = true;118Ok(())119} else if nested.path.is_ident(CLONE_BEHAVIOR) {120attrs.clone_behavior = Some(nested.value()?.parse()?);121Ok(())122} else if nested.path.is_ident(MAP_ENTITIES) {123attrs.map_entities =124Some(nested.input.parse::<MapEntitiesAttributeKind>()?);125Ok(())126} else {127Err(nested.error("Unsupported attribute"))128}129})?;130} else if attr.path().is_ident(REQUIRE) {131let punctuated =132attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;133for require in punctuated.iter() {134if !require_paths.insert(require.path.to_token_stream().to_string()) {135return Err(syn::Error::new(136require.path.span(),137"Duplicate required components are not allowed.",138));139}140}141if let Some(current) = &mut attrs.requires {142current.extend(punctuated);143} else {144attrs.requires = Some(punctuated);145}146} else if attr.path().is_ident(RELATIONSHIP) {147let relationship = attr.parse_args::<Relationship>()?;148attrs.relationship = Some(relationship);149} else if attr.path().is_ident(RELATIONSHIP_TARGET) {150let relationship_target = attr.parse_args::<RelationshipTarget>()?;151attrs.relationship_target = Some(relationship_target);152}153}154155if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {156return Err(syn::Error::new(157attrs.clone_behavior.span(),158"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",159));160}161162Ok(attrs)163}164165/// Generates a new `Component` trait implementation from this specification.166///167/// Note that this will add Send + Sync + 'static to the where clause168pub fn impl_component(169self,170ast: &mut DeriveInput,171bevy_ecs: &Path,172default_storage: StorageTy,173) -> Result<TokenStream> {174// We want to raise a compile time error when the generic lifetimes175// are not bound to 'static lifetime176let non_static_lifetime_error = ast177.generics178.lifetimes()179.filter(|lifetime| !lifetime.bounds.iter().any(|bound| bound.ident == "static"))180.map(|param| syn::Error::new(param.span(), "Lifetimes must be 'static"))181.reduce(|mut err_acc, err| {182err_acc.combine(err);183err_acc184});185if let Some(err) = non_static_lifetime_error {186return Err(err);187}188189let relationship = match self.derive_relationship(ast, bevy_ecs) {190Ok(value) => value,191Err(err) => Some(err.into_compile_error()),192};193let relationship_target = match self.derive_relationship_target(ast, bevy_ecs) {194Ok(value) => value,195Err(err) => Some(err.into_compile_error()),196};197198let map_entities = map_entities(199&ast.data,200bevy_ecs,201Ident::new("this", Span::call_site()),202relationship.is_some(),203relationship_target.is_some(),204self.map_entities,205)206.map(|map_entities_impl| {207quote! {208fn map_entities<M: #bevy_ecs::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {209use #bevy_ecs::entity::MapEntities;210#map_entities_impl211}212}213});214215let storage = storage_path(bevy_ecs, self.storage.unwrap_or(default_storage));216217let on_add_path = Vec::from_iter(self.on_add.map(|path| path.to_token_stream(bevy_ecs)));218let on_remove_path =219Vec::from_iter(self.on_remove.map(|path| path.to_token_stream(bevy_ecs)));220221let mut on_insert_path =222Vec::from_iter(self.on_insert.map(|path| path.to_token_stream(bevy_ecs)));223224let mut on_discard_path =225Vec::from_iter(self.on_discard.map(|path| path.to_token_stream(bevy_ecs)));226227let mut on_despawn_path =228Vec::from_iter(self.on_despawn.map(|path| path.to_token_stream(bevy_ecs)));229230if relationship.is_some() {231on_insert_path.push(quote!(<Self as #bevy_ecs::relationship::Relationship>::on_insert));232on_discard_path233.push(quote!(<Self as #bevy_ecs::relationship::Relationship>::on_discard));234}235if let Some(target) = self.relationship_target {236on_discard_path237.push(quote!(<Self as #bevy_ecs::relationship::RelationshipTarget>::on_discard));238if target.linked_spawn {239on_despawn_path.push(240quote!(<Self as #bevy_ecs::relationship::RelationshipTarget>::on_despawn),241);242}243}244245let on_add = hook_register_function_call(bevy_ecs, quote! {on_add}, &on_add_path);246let on_insert = hook_register_function_call(bevy_ecs, quote! {on_insert}, &on_insert_path);247let on_discard =248hook_register_function_call(bevy_ecs, quote! {on_discard}, &on_discard_path);249let on_remove = hook_register_function_call(bevy_ecs, quote! {on_remove}, &on_remove_path);250let on_despawn =251hook_register_function_call(bevy_ecs, quote! {on_despawn}, &on_despawn_path);252253let requires = &self.requires;254let mut register_required = Vec::with_capacity(self.requires.iter().len());255if let Some(requires) = requires {256for require in requires {257let ident = &require.path;258let constructor = match &require.func {259Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },260None => quote! { <#ident as #FQDefault>::default },261};262register_required.push(quote! {263required_components.register_required::<#ident>(#constructor);264});265}266}267let additional_requires = &self.additional_requires;268let struct_name = &ast.ident;269ast.generics270.make_where_clause()271.predicates272.push(parse_quote! { Self: #FQSend + #FQSync + 'static });273let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();274275let required_component_docs = self.requires.map(|r| {276let paths = r277.iter()278.map(|r| format!("[`{}`]", r.path.to_token_stream()))279.collect::<Vec<_>>()280.join(", ");281let 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.");282quote! {283#[doc = #doc]284}285});286287let mutable_type = (self.immutable || relationship.is_some())288.then_some(quote! { #bevy_ecs::component::Immutable })289.unwrap_or(quote! { #bevy_ecs::component::Mutable });290291let clone_behavior = if relationship_target.is_some() || relationship.is_some() {292quote!(293use #bevy_ecs::relationship::{294RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,295RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy296};297(&&&&&&&#bevy_ecs::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()298)299} else if let Some(behavior) = self.clone_behavior {300quote!(#bevy_ecs::component::ComponentCloneBehavior::#behavior)301} else {302quote!(303use #bevy_ecs::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};304(&&&#bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()305)306};307308let relationship_accessor = if (relationship.is_some() || relationship_target.is_some())309&& let Data::Struct(DataStruct {310fields,311struct_token,312..313}) = &ast.data314&& let Ok(field) = relationship_field(fields, "Relationship", struct_token.span())315{316let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);317if relationship.is_some() {318quote! {319#FQOption::Some(320// Safety: we pass valid offset of a field containing Entity (obtained via offset_off!)321unsafe {322#bevy_ecs::relationship::ComponentRelationshipAccessor::<Self>::relationship(323::core::mem::offset_of!(Self, #relationship_member)324)325}326)327}328} else {329quote! {330#FQOption::Some(#bevy_ecs::relationship::ComponentRelationshipAccessor::<Self>::relationship_target())331}332}333} else {334quote! {#FQOption::None}335};336Ok(quote! {337#required_component_docs338impl #impl_generics #bevy_ecs::component::Component for #struct_name #type_generics #where_clause {339const STORAGE_TYPE: #bevy_ecs::component::StorageType = #storage;340type Mutability = #mutable_type;341fn register_required_components(342_requiree: #bevy_ecs::component::ComponentId,343required_components: &mut #bevy_ecs::component::RequiredComponentsRegistrator,344) {345#(#register_required)*346#(#additional_requires)*347}348349#on_add350#on_insert351#on_discard352#on_remove353#on_despawn354355fn clone_behavior() -> #bevy_ecs::component::ComponentCloneBehavior {356#clone_behavior357}358359#map_entities360361fn relationship_accessor() -> #FQOption<#bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {362#relationship_accessor363}364}365366#relationship367368#relationship_target369})370}371fn derive_relationship(372&self,373ast: &DeriveInput,374bevy_ecs: &Path,375) -> Result<Option<TokenStream>> {376let Some(relationship) = &self.relationship else {377return Ok(None);378};379let Data::Struct(DataStruct {380fields,381struct_token,382..383}) = &ast.data384else {385return Err(syn::Error::new(386ast.span(),387"Relationship can only be derived for structs.",388));389};390let field = relationship_field(fields, "Relationship", struct_token.span())?;391392let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);393let members = fields394.members()395.filter(|member| member != &relationship_member);396397let struct_name = &ast.ident;398let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();399400let relationship_target = &relationship.relationship_target;401let allow_self_referential = relationship.allow_self_referential;402403let fqdefault = FQDefault.into_token_stream();404405Ok(Some(quote! {406impl #impl_generics #bevy_ecs::relationship::Relationship for #struct_name #type_generics #where_clause {407type RelationshipTarget = #relationship_target;408const ALLOW_SELF_REFERENTIAL: bool = #allow_self_referential;409410#[inline(always)]411fn get(&self) -> #bevy_ecs::entity::Entity {412self.#relationship_member413}414415#[inline]416fn from(entity: #bevy_ecs::entity::Entity) -> Self {417Self {418#(#members: #fqdefault::default(),)*419#relationship_member: entity420}421}422423#[inline]424fn set_risky(&mut self, entity: #bevy_ecs::entity::Entity) {425self.#relationship_member = entity;426}427}428}))429}430431fn derive_relationship_target(432&self,433ast: &DeriveInput,434bevy_ecs: &Path,435) -> Result<Option<TokenStream>> {436let Some(relationship_target) = &self.relationship_target else {437return Ok(None);438};439440let Data::Struct(DataStruct {441fields,442struct_token,443..444}) = &ast.data445else {446return Err(syn::Error::new(447ast.span(),448"RelationshipTarget can only be derived for structs.",449));450};451let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;452453if field.vis != Visibility::Inherited {454return 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."));455}456let collection = &field.ty;457let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);458459let members = fields460.members()461.filter(|member| member != &relationship_member);462463let relationship = &relationship_target.relationship;464let struct_name = &ast.ident;465let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();466let linked_spawn = relationship_target.linked_spawn;467let fqdefault = FQDefault.into_token_stream();468Ok(Some(quote! {469impl #impl_generics #bevy_ecs::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {470const LINKED_SPAWN: bool = #linked_spawn;471type Relationship = #relationship;472type Collection = #collection;473474#[inline]475fn collection(&self) -> &Self::Collection {476&self.#relationship_member477}478479#[inline]480fn collection_mut_risky(&mut self) -> &mut Self::Collection {481&mut self.#relationship_member482}483484#[inline]485fn from_collection_risky(collection: Self::Collection) -> Self {486Self {487#(#members: #fqdefault::default(),)*488#relationship_member: collection489}490}491}492}))493}494}495496const COMPONENT: &str = "component";497const MAP_ENTITIES: &str = "map_entities";498const STORAGE: &str = "storage";499const REQUIRE: &str = "require";500const RELATIONSHIP: &str = "relationship";501const RELATIONSHIP_TARGET: &str = "relationship_target";502503const ON_ADD: &str = "on_add";504const ON_INSERT: &str = "on_insert";505const ON_DISCARD: &str = "on_discard";506const ON_REMOVE: &str = "on_remove";507const ON_DESPAWN: &str = "on_despawn";508509const IMMUTABLE: &str = "immutable";510const CLONE_BEHAVIOR: &str = "clone_behavior";511512/// All allowed attribute value expression kinds for component hooks.513/// This doesn't simply use general expressions because of conflicting needs:514/// - we want to be able to use `Self` & generic parameters in paths515/// - call expressions producing a closure need to be wrapped in a function516/// to turn them into function pointers, which prevents access to the outer generic params517#[derive(Debug)]518pub enum HookAttributeKind {519/// expressions like function or struct names520///521/// structs will throw compile errors on the code generation so this is safe522Path(ExprPath),523/// function call like expressions524Call(ExprCall),525}526527impl HookAttributeKind {528fn parse(529input: syn::parse::ParseStream,530default_hook_path: impl FnOnce() -> ExprPath,531) -> Result<Self> {532if input.peek(Token![=]) {533input.parse::<Token![=]>()?;534input.parse::<Expr>().and_then(Self::from_expr)535} else {536Ok(Self::Path(default_hook_path()))537}538}539540fn from_expr(value: Expr) -> Result<Self> {541match value {542Expr::Path(path) => Ok(HookAttributeKind::Path(path)),543Expr::Call(call) => Ok(HookAttributeKind::Call(call)),544// throw meaningful error on all other expressions545_ => Err(syn::Error::new(546value.span(),547[548"Not supported in this position, please use one of the following:",549"- path to function",550"- call to function yielding closure",551]552.join("\n"),553)),554}555}556557fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream {558match self {559HookAttributeKind::Path(path) => path.to_token_stream(),560HookAttributeKind::Call(call) => {561quote!({562fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {563(#call)(world, ctx)564}565_internal_hook566})567}568}569}570}571572/// The derived component storage type573#[derive(Clone, Copy)]574pub enum StorageTy {575/// Table storage576Table,577/// Sparse set storage578SparseSet,579}580581/// Derived required component from the `#[require]` attribute.582pub struct Require {583path: Path,584func: Option<TokenStream>,585}586587/// Derived `#[relationship]` attribute information.588pub struct Relationship {589relationship_target: Type,590allow_self_referential: bool,591}592593/// Derived `#[relationship_target]` attribute information.594pub struct RelationshipTarget {595relationship: Type,596linked_spawn: bool,597}598599// values for `storage` attribute600const TABLE: &str = "Table";601const SPARSE_SET: &str = "SparseSet";602603impl Parse for Require {604fn parse(input: syn::parse::ParseStream) -> Result<Self> {605let mut path = input.parse::<Path>()?;606let mut last_segment_is_lower = false;607let mut is_constructor_call = false;608609// Use the case of the type name to check if it's an enum610// This doesn't match everything that can be an enum according to the rust spec611// but it matches what clippy is OK with612let is_enum = {613let mut first_chars = path614.segments615.iter()616.rev()617.filter_map(|s| s.ident.to_string().chars().next());618if let Some(last) = first_chars.next() {619if last.is_uppercase() {620if let Some(last) = first_chars.next() {621last.is_uppercase()622} else {623false624}625} else {626last_segment_is_lower = true;627false628}629} else {630false631}632};633634let func = if input.peek(Token![=]) {635// If there is an '=', then this is a "function style" require636input.parse::<Token![=]>()?;637let expr: Expr = input.parse()?;638Some(quote!(|| #expr ))639} else if input.peek(Brace) {640// This is a "value style" named-struct-like require641let content;642braced!(content in input);643let content = content.parse::<TokenStream>()?;644Some(quote!(|| #path { #content }))645} else if input.peek(Paren) {646// This is a "value style" tuple-struct-like require647let content;648parenthesized!(content in input);649let content = content.parse::<TokenStream>()?;650is_constructor_call = last_segment_is_lower;651Some(quote!(|| #path (#content)))652} else if is_enum {653// if this is an enum, then it is an inline enum component declaration654Some(quote!(|| #path))655} else {656// if this isn't any of the above, then it is a component ident, which will use Default657None658};659if is_enum || is_constructor_call {660path.segments.pop();661path.segments.pop_punct();662}663Ok(Require { path, func })664}665}666667fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream {668let storage_type = match ty {669StorageTy::Table => Ident::new("Table", Span::call_site()),670StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),671};672673quote! { #bevy_ecs_path::component::StorageType::#storage_type }674}675676fn hook_register_function_call(677bevy_ecs_path: &Path,678hook: TokenStream,679functions: &[TokenStream],680) -> TokenStream {681let hook_function = match functions {682[] => return TokenStream::new(),683[single] => single.clone(),684multiple => {685quote! {686|mut world: #bevy_ecs_path::world::DeferredWorld, context: #bevy_ecs_path::lifecycle::HookContext| {687#(#multiple(world.reborrow(), context.clone());)*688}689}690}691};692quote! {693fn #hook() -> #FQOption<#bevy_ecs_path::lifecycle::ComponentHook> {694#FQOption::Some(#hook_function)695}696}697}698699mod kw {700syn::custom_keyword!(relationship_target);701syn::custom_keyword!(relationship);702syn::custom_keyword!(linked_spawn);703syn::custom_keyword!(allow_self_referential);704}705706impl Parse for Relationship {707fn parse(input: syn::parse::ParseStream) -> Result<Self> {708let mut relationship_target: Option<Type> = None;709let mut allow_self_referential: bool = false;710711while !input.is_empty() {712let lookahead = input.lookahead1();713if lookahead.peek(kw::allow_self_referential) {714input.parse::<kw::allow_self_referential>()?;715allow_self_referential = true;716} else if lookahead.peek(kw::relationship_target) {717input.parse::<kw::relationship_target>()?;718input.parse::<Token![=]>()?;719relationship_target = Some(input.parse()?);720} else {721return Err(lookahead.error());722}723if !input.is_empty() {724input.parse::<Token![,]>()?;725}726}727Ok(Relationship {728relationship_target: relationship_target.ok_or_else(|| {729syn::Error::new(input.span(), "Missing `relationship_target = X` attribute")730})?,731allow_self_referential,732})733}734}735736impl Parse for RelationshipTarget {737fn parse(input: syn::parse::ParseStream) -> Result<Self> {738let mut relationship: Option<Type> = None;739let mut linked_spawn: bool = false;740741while !input.is_empty() {742let lookahead = input.lookahead1();743if lookahead.peek(kw::linked_spawn) {744input.parse::<kw::linked_spawn>()?;745linked_spawn = true;746} else if lookahead.peek(kw::relationship) {747input.parse::<kw::relationship>()?;748input.parse::<Token![=]>()?;749relationship = Some(input.parse()?);750} else {751return Err(lookahead.error());752}753if !input.is_empty() {754input.parse::<Token![,]>()?;755}756}757Ok(RelationshipTarget {758relationship: relationship.ok_or_else(|| {759syn::Error::new(input.span(), "Missing `relationship = X` attribute")760})?,761linked_spawn,762})763}764}765766/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,767/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.768pub(crate) fn relationship_field<'a>(769fields: &'a Fields,770derive: &'static str,771span: Span,772) -> Result<&'a Field> {773match fields {774Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),775Fields::Named(fields) => fields.named.iter().find(|field| {776field777.attrs778.iter()779.any(|attr| attr.path().is_ident(RELATIONSHIP))780}).ok_or(syn::Error::new(781span,782format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")783)),784Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),785Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {786field787.attrs788.iter()789.any(|attr| attr.path().is_ident(RELATIONSHIP))790})791.ok_or(syn::Error::new(792span,793format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),794)),795Fields::Unit => Err(syn::Error::new(796span,797format!("{derive} derive expected named or unnamed struct, found unit struct."),798)),799}800}801802803