Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/macros/src/component.rs
6600 views
1
use proc_macro::TokenStream;
2
use proc_macro2::{Span, TokenStream as TokenStream2};
3
use quote::{format_ident, quote, ToTokens};
4
use std::collections::HashSet;
5
use syn::{
6
braced, parenthesized,
7
parse::Parse,
8
parse_macro_input, parse_quote,
9
punctuated::Punctuated,
10
spanned::Spanned,
11
token::{Brace, Comma, Paren},
12
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident,
13
LitStr, Member, Path, Result, Token, Type, Visibility,
14
};
15
16
pub const EVENT: &str = "entity_event";
17
pub const AUTO_PROPAGATE: &str = "auto_propagate";
18
pub const TRAVERSAL: &str = "traversal";
19
20
pub fn derive_event(input: TokenStream) -> TokenStream {
21
let mut ast = parse_macro_input!(input as DeriveInput);
22
let bevy_ecs_path: Path = crate::bevy_ecs_path();
23
24
ast.generics
25
.make_where_clause()
26
.predicates
27
.push(parse_quote! { Self: Send + Sync + 'static });
28
29
let struct_name = &ast.ident;
30
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
31
32
TokenStream::from(quote! {
33
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}
34
})
35
}
36
37
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
38
let mut ast = parse_macro_input!(input as DeriveInput);
39
let mut auto_propagate = false;
40
let mut traversal: Type = parse_quote!(());
41
let bevy_ecs_path: Path = crate::bevy_ecs_path();
42
43
let mut processed_attrs = Vec::new();
44
45
ast.generics
46
.make_where_clause()
47
.predicates
48
.push(parse_quote! { Self: Send + Sync + 'static });
49
50
for attr in ast.attrs.iter().filter(|attr| attr.path().is_ident(EVENT)) {
51
if let Err(e) = attr.parse_nested_meta(|meta| match meta.path.get_ident() {
52
Some(ident) if processed_attrs.iter().any(|i| ident == i) => {
53
Err(meta.error(format!("duplicate attribute: {ident}")))
54
}
55
Some(ident) if ident == AUTO_PROPAGATE => {
56
auto_propagate = true;
57
processed_attrs.push(AUTO_PROPAGATE);
58
Ok(())
59
}
60
Some(ident) if ident == TRAVERSAL => {
61
traversal = meta.value()?.parse()?;
62
processed_attrs.push(TRAVERSAL);
63
Ok(())
64
}
65
Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))),
66
None => Err(meta.error("expected identifier")),
67
}) {
68
return e.to_compile_error().into();
69
}
70
}
71
72
let struct_name = &ast.ident;
73
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
74
75
TokenStream::from(quote! {
76
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}
77
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
78
type Traversal = #traversal;
79
const AUTO_PROPAGATE: bool = #auto_propagate;
80
}
81
})
82
}
83
84
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
85
let mut ast = parse_macro_input!(input as DeriveInput);
86
let bevy_ecs_path: Path = crate::bevy_ecs_path();
87
88
ast.generics
89
.make_where_clause()
90
.predicates
91
.push(parse_quote! { Self: Send + Sync + 'static });
92
93
let struct_name = &ast.ident;
94
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
95
96
TokenStream::from(quote! {
97
impl #impl_generics #bevy_ecs_path::event::BufferedEvent for #struct_name #type_generics #where_clause {}
98
})
99
}
100
101
pub fn derive_resource(input: TokenStream) -> TokenStream {
102
let mut ast = parse_macro_input!(input as DeriveInput);
103
let bevy_ecs_path: Path = crate::bevy_ecs_path();
104
105
ast.generics
106
.make_where_clause()
107
.predicates
108
.push(parse_quote! { Self: Send + Sync + 'static });
109
110
let struct_name = &ast.ident;
111
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
112
113
TokenStream::from(quote! {
114
impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {
115
}
116
})
117
}
118
119
/// Component derive syntax is documented on both the macro and the trait.
120
pub fn derive_component(input: TokenStream) -> TokenStream {
121
let mut ast = parse_macro_input!(input as DeriveInput);
122
let bevy_ecs_path: Path = crate::bevy_ecs_path();
123
124
let attrs = match parse_component_attr(&ast) {
125
Ok(attrs) => attrs,
126
Err(e) => return e.into_compile_error().into(),
127
};
128
129
let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {
130
Ok(value) => value,
131
Err(err) => err.into_compile_error().into(),
132
};
133
let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {
134
Ok(value) => value,
135
Err(err) => err.into_compile_error().into(),
136
};
137
138
let map_entities = map_entities(
139
&ast.data,
140
&bevy_ecs_path,
141
Ident::new("this", Span::call_site()),
142
relationship.is_some(),
143
relationship_target.is_some(),
144
attrs.map_entities
145
).map(|map_entities_impl| quote! {
146
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
147
use #bevy_ecs_path::entity::MapEntities;
148
#map_entities_impl
149
}
150
});
151
152
let storage = storage_path(&bevy_ecs_path, attrs.storage);
153
154
let on_add_path = attrs
155
.on_add
156
.map(|path| path.to_token_stream(&bevy_ecs_path));
157
let on_remove_path = attrs
158
.on_remove
159
.map(|path| path.to_token_stream(&bevy_ecs_path));
160
161
let on_insert_path = if relationship.is_some() {
162
if attrs.on_insert.is_some() {
163
return syn::Error::new(
164
ast.span(),
165
"Custom on_insert hooks are not supported as relationships already define an on_insert hook",
166
)
167
.into_compile_error()
168
.into();
169
}
170
171
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))
172
} else {
173
attrs
174
.on_insert
175
.map(|path| path.to_token_stream(&bevy_ecs_path))
176
};
177
178
let on_replace_path = if relationship.is_some() {
179
if attrs.on_replace.is_some() {
180
return syn::Error::new(
181
ast.span(),
182
"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",
183
)
184
.into_compile_error()
185
.into();
186
}
187
188
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))
189
} else if attrs.relationship_target.is_some() {
190
if attrs.on_replace.is_some() {
191
return syn::Error::new(
192
ast.span(),
193
"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",
194
)
195
.into_compile_error()
196
.into();
197
}
198
199
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))
200
} else {
201
attrs
202
.on_replace
203
.map(|path| path.to_token_stream(&bevy_ecs_path))
204
};
205
206
let on_despawn_path = if attrs
207
.relationship_target
208
.is_some_and(|target| target.linked_spawn)
209
{
210
if attrs.on_despawn.is_some() {
211
return syn::Error::new(
212
ast.span(),
213
"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",
214
)
215
.into_compile_error()
216
.into();
217
}
218
219
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))
220
} else {
221
attrs
222
.on_despawn
223
.map(|path| path.to_token_stream(&bevy_ecs_path))
224
};
225
226
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
227
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
228
let on_replace =
229
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
230
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
231
let on_despawn =
232
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
233
234
ast.generics
235
.make_where_clause()
236
.predicates
237
.push(parse_quote! { Self: Send + Sync + 'static });
238
239
let requires = &attrs.requires;
240
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
241
if let Some(requires) = requires {
242
for require in requires {
243
let ident = &require.path;
244
let constructor = match &require.func {
245
Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },
246
None => quote! { <#ident as Default>::default },
247
};
248
register_required.push(quote! {
249
required_components.register_required::<#ident>(#constructor);
250
});
251
}
252
}
253
let struct_name = &ast.ident;
254
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
255
256
let required_component_docs = attrs.requires.map(|r| {
257
let paths = r
258
.iter()
259
.map(|r| format!("[`{}`]", r.path.to_token_stream()))
260
.collect::<Vec<_>>()
261
.join(", ");
262
let 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.");
263
quote! {
264
#[doc = #doc]
265
}
266
});
267
268
let mutable_type = (attrs.immutable || relationship.is_some())
269
.then_some(quote! { #bevy_ecs_path::component::Immutable })
270
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
271
272
let clone_behavior = if relationship_target.is_some() || relationship.is_some() {
273
quote!(
274
use #bevy_ecs_path::relationship::{
275
RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,
276
RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy
277
};
278
(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
279
)
280
} else if let Some(behavior) = attrs.clone_behavior {
281
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
282
} else {
283
quote!(
284
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
285
(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
286
)
287
};
288
289
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
290
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
291
TokenStream::from(quote! {
292
#required_component_docs
293
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
294
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
295
type Mutability = #mutable_type;
296
fn register_required_components(
297
_requiree: #bevy_ecs_path::component::ComponentId,
298
required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,
299
) {
300
#(#register_required)*
301
}
302
303
#on_add
304
#on_insert
305
#on_replace
306
#on_remove
307
#on_despawn
308
309
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
310
#clone_behavior
311
}
312
313
#map_entities
314
}
315
316
#relationship
317
318
#relationship_target
319
})
320
}
321
322
const ENTITIES: &str = "entities";
323
324
pub(crate) fn map_entities(
325
data: &Data,
326
bevy_ecs_path: &Path,
327
self_ident: Ident,
328
is_relationship: bool,
329
is_relationship_target: bool,
330
map_entities_attr: Option<MapEntitiesAttributeKind>,
331
) -> Option<TokenStream2> {
332
if let Some(map_entities_override) = map_entities_attr {
333
let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs_path);
334
return Some(quote!(
335
#map_entities_tokens(#self_ident, mapper)
336
));
337
}
338
339
match data {
340
Data::Struct(DataStruct { fields, .. }) => {
341
let mut map = Vec::with_capacity(fields.len());
342
343
let relationship = if is_relationship || is_relationship_target {
344
relationship_field(fields, "MapEntities", fields.span()).ok()
345
} else {
346
None
347
};
348
fields
349
.iter()
350
.enumerate()
351
.filter(|(_, field)| {
352
field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))
353
|| relationship.is_some_and(|relationship| relationship == *field)
354
})
355
.for_each(|(index, field)| {
356
let field_member = field
357
.ident
358
.clone()
359
.map_or(Member::from(index), Member::Named);
360
361
map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
362
});
363
if map.is_empty() {
364
return None;
365
};
366
Some(quote!(
367
#(#map)*
368
))
369
}
370
Data::Enum(DataEnum { variants, .. }) => {
371
let mut map = Vec::with_capacity(variants.len());
372
373
for variant in variants.iter() {
374
let field_members = variant
375
.fields
376
.iter()
377
.enumerate()
378
.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))
379
.map(|(index, field)| {
380
field
381
.ident
382
.clone()
383
.map_or(Member::from(index), Member::Named)
384
})
385
.collect::<Vec<_>>();
386
387
let ident = &variant.ident;
388
let field_idents = field_members
389
.iter()
390
.map(|member| format_ident!("__self_{}", member))
391
.collect::<Vec<_>>();
392
393
map.push(
394
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
395
#(#field_idents.map_entities(mapper);)*
396
}),
397
);
398
}
399
400
if map.is_empty() {
401
return None;
402
};
403
404
Some(quote!(
405
match #self_ident {
406
#(#map,)*
407
_ => {}
408
}
409
))
410
}
411
Data::Union(_) => None,
412
}
413
}
414
415
pub const COMPONENT: &str = "component";
416
pub const STORAGE: &str = "storage";
417
pub const REQUIRE: &str = "require";
418
pub const RELATIONSHIP: &str = "relationship";
419
pub const RELATIONSHIP_TARGET: &str = "relationship_target";
420
421
pub const ON_ADD: &str = "on_add";
422
pub const ON_INSERT: &str = "on_insert";
423
pub const ON_REPLACE: &str = "on_replace";
424
pub const ON_REMOVE: &str = "on_remove";
425
pub const ON_DESPAWN: &str = "on_despawn";
426
pub const MAP_ENTITIES: &str = "map_entities";
427
428
pub const IMMUTABLE: &str = "immutable";
429
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
430
431
/// All allowed attribute value expression kinds for component hooks.
432
/// This doesn't simply use general expressions because of conflicting needs:
433
/// - we want to be able to use `Self` & generic parameters in paths
434
/// - call expressions producing a closure need to be wrapped in a function
435
/// to turn them into function pointers, which prevents access to the outer generic params
436
#[derive(Debug)]
437
enum HookAttributeKind {
438
/// expressions like function or struct names
439
///
440
/// structs will throw compile errors on the code generation so this is safe
441
Path(ExprPath),
442
/// function call like expressions
443
Call(ExprCall),
444
}
445
446
impl HookAttributeKind {
447
fn from_expr(value: Expr) -> Result<Self> {
448
match value {
449
Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
450
Expr::Call(call) => Ok(HookAttributeKind::Call(call)),
451
// throw meaningful error on all other expressions
452
_ => Err(syn::Error::new(
453
value.span(),
454
[
455
"Not supported in this position, please use one of the following:",
456
"- path to function",
457
"- call to function yielding closure",
458
]
459
.join("\n"),
460
)),
461
}
462
}
463
464
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {
465
match self {
466
HookAttributeKind::Path(path) => path.to_token_stream(),
467
HookAttributeKind::Call(call) => {
468
quote!({
469
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
470
(#call)(world, ctx)
471
}
472
_internal_hook
473
})
474
}
475
}
476
}
477
}
478
479
impl Parse for HookAttributeKind {
480
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
481
input.parse::<Expr>().and_then(Self::from_expr)
482
}
483
}
484
485
#[derive(Debug)]
486
pub(super) enum MapEntitiesAttributeKind {
487
/// expressions like function or struct names
488
///
489
/// structs will throw compile errors on the code generation so this is safe
490
Path(ExprPath),
491
/// When no value is specified
492
Default,
493
}
494
495
impl MapEntitiesAttributeKind {
496
fn from_expr(value: Expr) -> Result<Self> {
497
match value {
498
Expr::Path(path) => Ok(Self::Path(path)),
499
// throw meaningful error on all other expressions
500
_ => Err(syn::Error::new(
501
value.span(),
502
[
503
"Not supported in this position, please use one of the following:",
504
"- path to function",
505
"- nothing to default to MapEntities implementation",
506
]
507
.join("\n"),
508
)),
509
}
510
}
511
512
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {
513
match self {
514
MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),
515
MapEntitiesAttributeKind::Default => {
516
quote!(
517
<Self as #bevy_ecs_path::entity::MapEntities>::map_entities
518
)
519
}
520
}
521
}
522
}
523
524
impl Parse for MapEntitiesAttributeKind {
525
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
526
if input.peek(Token![=]) {
527
input.parse::<Token![=]>()?;
528
input.parse::<Expr>().and_then(Self::from_expr)
529
} else {
530
Ok(Self::Default)
531
}
532
}
533
}
534
535
struct Attrs {
536
storage: StorageTy,
537
requires: Option<Punctuated<Require, Comma>>,
538
on_add: Option<HookAttributeKind>,
539
on_insert: Option<HookAttributeKind>,
540
on_replace: Option<HookAttributeKind>,
541
on_remove: Option<HookAttributeKind>,
542
on_despawn: Option<HookAttributeKind>,
543
relationship: Option<Relationship>,
544
relationship_target: Option<RelationshipTarget>,
545
immutable: bool,
546
clone_behavior: Option<Expr>,
547
map_entities: Option<MapEntitiesAttributeKind>,
548
}
549
550
#[derive(Clone, Copy)]
551
enum StorageTy {
552
Table,
553
SparseSet,
554
}
555
556
struct Require {
557
path: Path,
558
func: Option<TokenStream2>,
559
}
560
561
struct Relationship {
562
relationship_target: Type,
563
}
564
565
struct RelationshipTarget {
566
relationship: Type,
567
linked_spawn: bool,
568
}
569
570
// values for `storage` attribute
571
const TABLE: &str = "Table";
572
const SPARSE_SET: &str = "SparseSet";
573
574
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
575
let mut attrs = Attrs {
576
storage: StorageTy::Table,
577
on_add: None,
578
on_insert: None,
579
on_replace: None,
580
on_remove: None,
581
on_despawn: None,
582
requires: None,
583
relationship: None,
584
relationship_target: None,
585
immutable: false,
586
clone_behavior: None,
587
map_entities: None,
588
};
589
590
let mut require_paths = HashSet::new();
591
for attr in ast.attrs.iter() {
592
if attr.path().is_ident(COMPONENT) {
593
attr.parse_nested_meta(|nested| {
594
if nested.path.is_ident(STORAGE) {
595
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
596
s if s == TABLE => StorageTy::Table,
597
s if s == SPARSE_SET => StorageTy::SparseSet,
598
s => {
599
return Err(nested.error(format!(
600
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
601
)));
602
}
603
};
604
Ok(())
605
} else if nested.path.is_ident(ON_ADD) {
606
attrs.on_add = Some(nested.value()?.parse::<HookAttributeKind>()?);
607
Ok(())
608
} else if nested.path.is_ident(ON_INSERT) {
609
attrs.on_insert = Some(nested.value()?.parse::<HookAttributeKind>()?);
610
Ok(())
611
} else if nested.path.is_ident(ON_REPLACE) {
612
attrs.on_replace = Some(nested.value()?.parse::<HookAttributeKind>()?);
613
Ok(())
614
} else if nested.path.is_ident(ON_REMOVE) {
615
attrs.on_remove = Some(nested.value()?.parse::<HookAttributeKind>()?);
616
Ok(())
617
} else if nested.path.is_ident(ON_DESPAWN) {
618
attrs.on_despawn = Some(nested.value()?.parse::<HookAttributeKind>()?);
619
Ok(())
620
} else if nested.path.is_ident(IMMUTABLE) {
621
attrs.immutable = true;
622
Ok(())
623
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
624
attrs.clone_behavior = Some(nested.value()?.parse()?);
625
Ok(())
626
} else if nested.path.is_ident(MAP_ENTITIES) {
627
attrs.map_entities = Some(nested.input.parse::<MapEntitiesAttributeKind>()?);
628
Ok(())
629
} else {
630
Err(nested.error("Unsupported attribute"))
631
}
632
})?;
633
} else if attr.path().is_ident(REQUIRE) {
634
let punctuated =
635
attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;
636
for require in punctuated.iter() {
637
if !require_paths.insert(require.path.to_token_stream().to_string()) {
638
return Err(syn::Error::new(
639
require.path.span(),
640
"Duplicate required components are not allowed.",
641
));
642
}
643
}
644
if let Some(current) = &mut attrs.requires {
645
current.extend(punctuated);
646
} else {
647
attrs.requires = Some(punctuated);
648
}
649
} else if attr.path().is_ident(RELATIONSHIP) {
650
let relationship = attr.parse_args::<Relationship>()?;
651
attrs.relationship = Some(relationship);
652
} else if attr.path().is_ident(RELATIONSHIP_TARGET) {
653
let relationship_target = attr.parse_args::<RelationshipTarget>()?;
654
attrs.relationship_target = Some(relationship_target);
655
}
656
}
657
658
if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {
659
return Err(syn::Error::new(
660
attrs.clone_behavior.span(),
661
"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",
662
));
663
}
664
665
Ok(attrs)
666
}
667
668
impl Parse for Require {
669
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
670
let mut path = input.parse::<Path>()?;
671
let mut last_segment_is_lower = false;
672
let mut is_constructor_call = false;
673
674
// Use the case of the type name to check if it's an enum
675
// This doesn't match everything that can be an enum according to the rust spec
676
// but it matches what clippy is OK with
677
let is_enum = {
678
let mut first_chars = path
679
.segments
680
.iter()
681
.rev()
682
.filter_map(|s| s.ident.to_string().chars().next());
683
if let Some(last) = first_chars.next() {
684
if last.is_uppercase() {
685
if let Some(last) = first_chars.next() {
686
last.is_uppercase()
687
} else {
688
false
689
}
690
} else {
691
last_segment_is_lower = true;
692
false
693
}
694
} else {
695
false
696
}
697
};
698
699
let func = if input.peek(Token![=]) {
700
// If there is an '=', then this is a "function style" require
701
input.parse::<Token![=]>()?;
702
let expr: Expr = input.parse()?;
703
Some(quote!(|| #expr ))
704
} else if input.peek(Brace) {
705
// This is a "value style" named-struct-like require
706
let content;
707
braced!(content in input);
708
let content = content.parse::<TokenStream2>()?;
709
Some(quote!(|| #path { #content }))
710
} else if input.peek(Paren) {
711
// This is a "value style" tuple-struct-like require
712
let content;
713
parenthesized!(content in input);
714
let content = content.parse::<TokenStream2>()?;
715
is_constructor_call = last_segment_is_lower;
716
Some(quote!(|| #path (#content)))
717
} else if is_enum {
718
// if this is an enum, then it is an inline enum component declaration
719
Some(quote!(|| #path))
720
} else {
721
// if this isn't any of the above, then it is a component ident, which will use Default
722
None
723
};
724
if is_enum || is_constructor_call {
725
path.segments.pop();
726
path.segments.pop_punct();
727
}
728
Ok(Require { path, func })
729
}
730
}
731
732
fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
733
let storage_type = match ty {
734
StorageTy::Table => Ident::new("Table", Span::call_site()),
735
StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),
736
};
737
738
quote! { #bevy_ecs_path::component::StorageType::#storage_type }
739
}
740
741
fn hook_register_function_call(
742
bevy_ecs_path: &Path,
743
hook: TokenStream2,
744
function: Option<TokenStream2>,
745
) -> Option<TokenStream2> {
746
function.map(|meta| {
747
quote! {
748
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
749
::core::option::Option::Some(#meta)
750
}
751
}
752
})
753
}
754
755
mod kw {
756
syn::custom_keyword!(relationship_target);
757
syn::custom_keyword!(relationship);
758
syn::custom_keyword!(linked_spawn);
759
}
760
761
impl Parse for Relationship {
762
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
763
input.parse::<kw::relationship_target>()?;
764
input.parse::<Token![=]>()?;
765
Ok(Relationship {
766
relationship_target: input.parse::<Type>()?,
767
})
768
}
769
}
770
771
impl Parse for RelationshipTarget {
772
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
773
let mut relationship: Option<Type> = None;
774
let mut linked_spawn: bool = false;
775
776
while !input.is_empty() {
777
let lookahead = input.lookahead1();
778
if lookahead.peek(kw::linked_spawn) {
779
input.parse::<kw::linked_spawn>()?;
780
linked_spawn = true;
781
} else if lookahead.peek(kw::relationship) {
782
input.parse::<kw::relationship>()?;
783
input.parse::<Token![=]>()?;
784
relationship = Some(input.parse()?);
785
} else {
786
return Err(lookahead.error());
787
}
788
if !input.is_empty() {
789
input.parse::<Token![,]>()?;
790
}
791
}
792
Ok(RelationshipTarget {
793
relationship: relationship.ok_or_else(|| {
794
syn::Error::new(input.span(), "Missing `relationship = X` attribute")
795
})?,
796
linked_spawn,
797
})
798
}
799
}
800
801
fn derive_relationship(
802
ast: &DeriveInput,
803
attrs: &Attrs,
804
bevy_ecs_path: &Path,
805
) -> Result<Option<TokenStream2>> {
806
let Some(relationship) = &attrs.relationship else {
807
return Ok(None);
808
};
809
let Data::Struct(DataStruct {
810
fields,
811
struct_token,
812
..
813
}) = &ast.data
814
else {
815
return Err(syn::Error::new(
816
ast.span(),
817
"Relationship can only be derived for structs.",
818
));
819
};
820
let field = relationship_field(fields, "Relationship", struct_token.span())?;
821
822
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
823
let members = fields
824
.members()
825
.filter(|member| member != &relationship_member);
826
827
let struct_name = &ast.ident;
828
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
829
830
let relationship_target = &relationship.relationship_target;
831
832
Ok(Some(quote! {
833
impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {
834
type RelationshipTarget = #relationship_target;
835
836
#[inline(always)]
837
fn get(&self) -> #bevy_ecs_path::entity::Entity {
838
self.#relationship_member
839
}
840
841
#[inline]
842
fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {
843
Self {
844
#(#members: core::default::Default::default(),)*
845
#relationship_member: entity
846
}
847
}
848
849
#[inline]
850
fn set_risky(&mut self, entity: Entity) {
851
self.#relationship_member = entity;
852
}
853
}
854
}))
855
}
856
857
fn derive_relationship_target(
858
ast: &DeriveInput,
859
attrs: &Attrs,
860
bevy_ecs_path: &Path,
861
) -> Result<Option<TokenStream2>> {
862
let Some(relationship_target) = &attrs.relationship_target else {
863
return Ok(None);
864
};
865
866
let Data::Struct(DataStruct {
867
fields,
868
struct_token,
869
..
870
}) = &ast.data
871
else {
872
return Err(syn::Error::new(
873
ast.span(),
874
"RelationshipTarget can only be derived for structs.",
875
));
876
};
877
let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;
878
879
if field.vis != Visibility::Inherited {
880
return 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."));
881
}
882
let collection = &field.ty;
883
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
884
885
let members = fields
886
.members()
887
.filter(|member| member != &relationship_member);
888
889
let relationship = &relationship_target.relationship;
890
let struct_name = &ast.ident;
891
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
892
let linked_spawn = relationship_target.linked_spawn;
893
Ok(Some(quote! {
894
impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {
895
const LINKED_SPAWN: bool = #linked_spawn;
896
type Relationship = #relationship;
897
type Collection = #collection;
898
899
#[inline]
900
fn collection(&self) -> &Self::Collection {
901
&self.#relationship_member
902
}
903
904
#[inline]
905
fn collection_mut_risky(&mut self) -> &mut Self::Collection {
906
&mut self.#relationship_member
907
}
908
909
#[inline]
910
fn from_collection_risky(collection: Self::Collection) -> Self {
911
Self {
912
#(#members: core::default::Default::default(),)*
913
#relationship_member: collection
914
}
915
}
916
}
917
}))
918
}
919
920
/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,
921
/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.
922
fn relationship_field<'a>(
923
fields: &'a Fields,
924
derive: &'static str,
925
span: Span,
926
) -> Result<&'a Field> {
927
match fields {
928
Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),
929
Fields::Named(fields) => fields.named.iter().find(|field| {
930
field
931
.attrs
932
.iter()
933
.any(|attr| attr.path().is_ident(RELATIONSHIP))
934
}).ok_or(syn::Error::new(
935
span,
936
format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")
937
)),
938
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),
939
Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {
940
field
941
.attrs
942
.iter()
943
.any(|attr| attr.path().is_ident(RELATIONSHIP))
944
})
945
.ok_or(syn::Error::new(
946
span,
947
format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),
948
)),
949
Fields::Unit => Err(syn::Error::new(
950
span,
951
format!("{derive} derive expected named or unnamed struct, found unit struct."),
952
)),
953
}
954
}
955
956