1mod bevy;
17
18use proc_macro::TokenStream;
19use proc_macro2::Span;
20use proc_macro_crate::{crate_name, FoundCrate};
21use quote::{format_ident, quote, ToTokens};
22use std::collections::HashMap;
23use std::mem;
24use syn::{
25 braced,
26 parse::{Parse, ParseStream},
27 parse_macro_input,
28 punctuated::Punctuated,
29 spanned::Spanned,
30 Attribute, Block, Data, DeriveInput, Fields, FnArg, Ident, ImplItem, ImplItemFn, ItemFn,
31 ItemImpl, Lit, LitStr, Meta, Pat, PatIdent, PatType, Path, Result, Signature, Stmt, Token,
32 Type, Visibility,
33};
34
35fn get_crate_name() -> proc_macro2::TokenStream {
38 match crate_name("issun") {
39 Ok(FoundCrate::Itself) => quote!(crate),
40 Ok(FoundCrate::Name(name)) => {
41 let ident = format_ident!("{}", name);
42 quote!(::#ident)
43 }
44 Err(_) => quote!(::issun),
45 }
46}
47
48#[proc_macro_derive(Scene, attributes(scene))]
65pub fn derive_scene(input: TokenStream) -> TokenStream {
66 let input = parse_macro_input!(input as DeriveInput);
67
68 let scene_name = &input.ident;
69
70 let scene_attrs = parse_scene_attributes(&input.attrs);
72
73 let crate_name = get_crate_name();
74
75 let scene_impl = quote! {
77 #[::async_trait::async_trait]
78 impl #crate_name::scene::Scene for #scene_name {
79 async fn on_enter(
80 &mut self,
81 _services: &#crate_name::context::ServiceContext,
82 _systems: &mut #crate_name::context::SystemContext,
83 _resources: &mut #crate_name::context::ResourceContext,
84 ) {
85 }
87
88 async fn on_update(
89 &mut self,
90 _services: &#crate_name::context::ServiceContext,
91 _systems: &mut #crate_name::context::SystemContext,
92 _resources: &mut #crate_name::context::ResourceContext,
93 ) -> #crate_name::scene::SceneTransition<Self> {
94 #crate_name::scene::SceneTransition::Stay
96 }
97
98 async fn on_exit(
99 &mut self,
100 _services: &#crate_name::context::ServiceContext,
101 _systems: &mut #crate_name::context::SystemContext,
102 _resources: &mut #crate_name::context::ResourceContext,
103 ) {
104 }
106
107 async fn on_suspend(
108 &mut self,
109 _services: &#crate_name::context::ServiceContext,
110 _systems: &mut #crate_name::context::SystemContext,
111 _resources: &mut #crate_name::context::ResourceContext,
112 ) {
113 }
114
115 async fn on_resume(
116 &mut self,
117 _services: &#crate_name::context::ServiceContext,
118 _systems: &mut #crate_name::context::SystemContext,
119 _resources: &mut #crate_name::context::ResourceContext,
120 ) {
121 }
122 }
123 };
124
125 let game_state_gen =
127 if let (Some(context), Some(initial)) = (&scene_attrs.context, &scene_attrs.initial) {
128 let state_name = scene_attrs
129 .name
130 .as_ref()
131 .map(|n| format_ident!("{}", n))
132 .unwrap_or_else(|| format_ident!("GameState"));
133
134 let context_ident = format_ident!("{}", context);
135 let initial_expr: proc_macro2::TokenStream = initial.parse().unwrap();
136
137 quote! {
138 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
140 pub struct #state_name {
141 pub scene: #scene_name,
142 pub ctx: #context_ident,
143 pub should_quit: bool,
144 }
145
146 impl #state_name {
147 pub fn new() -> Self {
148 Self {
149 scene: #scene_name::#initial_expr,
150 ctx: #context_ident::new(),
151 should_quit: false,
152 }
153 }
154 }
155
156 impl Default for #state_name {
157 fn default() -> Self {
158 Self::new()
159 }
160 }
161 }
162 } else {
163 quote! {}
164 };
165
166 let handler_gen = if let Some(params) = &scene_attrs.handler_params {
168 let handler_name = scene_attrs
169 .handler
170 .as_ref()
171 .map(|h| format_ident!("{}", h))
172 .unwrap_or_else(|| format_ident!("handle_input"));
173
174 let params_tokens: proc_macro2::TokenStream = params.parse().unwrap();
175
176 let param_names = extract_param_names(params);
178
179 let _return_type = scene_attrs
181 .handler_return
182 .as_ref()
183 .map(|r| r.parse::<proc_macro2::TokenStream>().unwrap())
184 .unwrap_or_else(|| {
185 quote! { (#scene_name, ::issun::scene::SceneTransition<#scene_name>) }
186 });
187
188 let variants = if let Data::Enum(data_enum) = &input.data {
190 &data_enum.variants
191 } else {
192 panic!("Scene derive macro only works on enums");
193 };
194
195 let match_arms = variants.iter().map(|variant| {
197 let variant_name = &variant.ident;
198 quote! {
199 #scene_name::#variant_name(data) => data.#handler_name(
200 services,
201 systems,
202 resources,
203 #(#param_names),*
204 ).await
205 }
206 });
207
208 quote! {
209 pub async fn handle_scene_input(
213 scene: &mut #scene_name,
214 services: &#crate_name::context::ServiceContext,
215 systems: &mut #crate_name::context::SystemContext,
216 resources: &mut #crate_name::context::ResourceContext,
217 #params_tokens,
218 ) -> ::issun::scene::SceneTransition<#scene_name> {
219 match scene {
220 #(#match_arms),*
221 }
222 }
223 }
224 } else {
225 quote! {}
226 };
227
228 let expanded = quote! {
229 #scene_impl
230 #game_state_gen
231 #handler_gen
232 };
233
234 TokenStream::from(expanded)
235}
236
237struct SceneAttributes {
239 context: Option<String>,
240 initial: Option<String>,
241 name: Option<String>,
242 handler: Option<String>,
243 handler_params: Option<String>,
244 handler_return: Option<String>,
245}
246
247fn parse_scene_attributes(attrs: &[syn::Attribute]) -> SceneAttributes {
248 let mut context = None;
249 let mut initial = None;
250 let mut name = None;
251 let mut handler = None;
252 let mut handler_params = None;
253 let mut handler_return = None;
254
255 for attr in attrs {
256 if attr.path().is_ident("scene") {
257 let _ = attr.parse_nested_meta(|meta| {
258 if meta.path.is_ident("context") {
259 if let Ok(value) = meta.value() {
260 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
261 context = Some(s.value());
262 }
263 }
264 } else if meta.path.is_ident("initial") {
265 if let Ok(value) = meta.value() {
266 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
267 initial = Some(s.value());
268 }
269 }
270 } else if meta.path.is_ident("name") {
271 if let Ok(value) = meta.value() {
272 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
273 name = Some(s.value());
274 }
275 }
276 } else if meta.path.is_ident("handler") {
277 if let Ok(value) = meta.value() {
278 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
279 handler = Some(s.value());
280 }
281 }
282 } else if meta.path.is_ident("handler_params") {
283 if let Ok(value) = meta.value() {
284 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
285 handler_params = Some(s.value());
286 }
287 }
288 } else if meta.path.is_ident("handler_return") {
289 if let Ok(value) = meta.value() {
290 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
291 handler_return = Some(s.value());
292 }
293 }
294 }
295 Ok(())
296 });
297 }
298 }
299
300 SceneAttributes {
301 context,
302 initial,
303 name,
304 handler,
305 handler_params,
306 handler_return,
307 }
308}
309
310fn extract_param_names(params_str: &str) -> Vec<proc_macro2::Ident> {
313 params_str
314 .split(',')
315 .filter_map(|param| {
316 let param = param.trim();
317 param.split(':').next().map(|name| {
319 let name = name.trim();
320 format_ident!("{}", name)
321 })
322 })
323 .collect()
324}
325
326#[proc_macro_derive(Entity, attributes(entity))]
338pub fn derive_entity(input: TokenStream) -> TokenStream {
339 let input = parse_macro_input!(input as DeriveInput);
340
341 let name = &input.ident;
342
343 let id_field = match &input.data {
345 Data::Struct(data) => {
346 match &data.fields {
347 Fields::Named(fields) => {
348 fields
350 .named
351 .iter()
352 .find(|field| {
353 field
354 .attrs
355 .iter()
356 .any(|attr| attr.path().is_ident("entity"))
357 })
358 .map(|field| field.ident.as_ref().unwrap())
359 }
360 _ => None,
361 }
362 }
363 _ => None,
364 };
365
366 let id_impl = if let Some(field_name) = id_field {
367 quote! {
368 fn id(&self) -> &str {
369 &self.#field_name
370 }
371 }
372 } else {
373 quote! {
375 fn id(&self) -> &str {
376 ""
377 }
378 }
379 };
380
381 let expanded = quote! {
382 #[async_trait::async_trait]
383 impl Entity for #name {
384 #id_impl
385
386 async fn update(&mut self, _ctx: &mut Context) {
387 }
389 }
390 };
391
392 TokenStream::from(expanded)
393}
394
395#[proc_macro_derive(Service, attributes(service))]
418pub fn derive_service(input: TokenStream) -> TokenStream {
419 let input = parse_macro_input!(input as DeriveInput);
420
421 let struct_name = &input.ident;
422
423 let service_name = parse_service_name(&input.attrs);
425 let service_name_lit = syn::LitStr::new(&service_name, proc_macro2::Span::call_site());
426
427 let crate_name = get_crate_name();
428
429 let expanded = quote! {
430 impl #struct_name {
431 pub const NAME: &'static str = #service_name_lit;
432 }
433
434 #[::async_trait::async_trait]
435 impl #crate_name::service::Service for #struct_name {
436 fn name(&self) -> &'static str {
437 #service_name
438 }
439
440 fn clone_box(&self) -> Box<dyn #crate_name::service::Service> {
441 Box::new(self.clone())
442 }
443
444 fn as_any(&self) -> &dyn ::std::any::Any {
445 self
446 }
447
448 fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
449 self
450 }
451 }
452 };
453
454 TokenStream::from(expanded)
455}
456
457fn parse_service_name(attrs: &[syn::Attribute]) -> String {
459 for attr in attrs {
460 if attr.path().is_ident("service") {
461 let mut name = None;
462 let _ = attr.parse_nested_meta(|meta| {
463 if meta.path.is_ident("name") {
464 if let Ok(value) = meta.value() {
465 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
466 name = Some(s.value());
467 }
468 }
469 }
470 Ok(())
471 });
472 if let Some(n) = name {
473 return n;
474 }
475 }
476 }
477
478 "unknown_service".to_string()
480}
481
482#[proc_macro_derive(System, attributes(system))]
506pub fn derive_system(input: TokenStream) -> TokenStream {
507 let input = parse_macro_input!(input as DeriveInput);
508
509 let struct_name = &input.ident;
510
511 let system_name = parse_system_name(&input.attrs);
513 let system_name_lit = syn::LitStr::new(&system_name, proc_macro2::Span::call_site());
514
515 let crate_name = get_crate_name();
516
517 let expanded = quote! {
518 impl #struct_name {
519 pub const NAME: &'static str = #system_name_lit;
520 }
521
522 #[::async_trait::async_trait]
523 impl #crate_name::system::System for #struct_name {
524 fn name(&self) -> &'static str {
525 #system_name
526 }
527
528 fn as_any(&self) -> &dyn ::std::any::Any {
529 self
530 }
531
532 fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
533 self
534 }
535 }
536 };
537
538 TokenStream::from(expanded)
539}
540
541fn parse_system_name(attrs: &[syn::Attribute]) -> String {
543 for attr in attrs {
544 if attr.path().is_ident("system") {
545 let mut name = None;
546 let _ = attr.parse_nested_meta(|meta| {
547 if meta.path.is_ident("name") {
548 if let Ok(value) = meta.value() {
549 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
550 name = Some(s.value());
551 }
552 }
553 }
554 Ok(())
555 });
556 if let Some(n) = name {
557 return n;
558 }
559 }
560 }
561
562 "unknown_system".to_string()
564}
565
566#[proc_macro_derive(Asset)]
577pub fn derive_asset(input: TokenStream) -> TokenStream {
578 let input = parse_macro_input!(input as DeriveInput);
579
580 let name = &input.ident;
581
582 let expanded = quote! {
583 impl Asset for #name {
584 }
586 };
587
588 TokenStream::from(expanded)
589}
590
591#[proc_macro_derive(Resource)]
612pub fn derive_resource(input: TokenStream) -> TokenStream {
613 let input = parse_macro_input!(input as DeriveInput);
614
615 let name = &input.ident;
616 let crate_name = get_crate_name();
617
618 let expanded = quote! {
619 impl #crate_name::resources::Resource for #name {
620 }
622 };
623
624 TokenStream::from(expanded)
625}
626
627#[proc_macro_derive(Plugin, attributes(plugin, resource, state, system, service))]
639pub fn derive_plugin(input: TokenStream) -> TokenStream {
640 let input = parse_macro_input!(input as DeriveInput);
641 let name = &input.ident;
642 let crate_name = get_crate_name();
643
644 let mut plugin_name = None;
646 let mut services = Vec::new();
647 let mut systems = Vec::new();
648 let mut states = Vec::new();
649 let mut resources = Vec::new();
650
651 for attr in &input.attrs {
652 if !attr.path().is_ident("plugin") {
653 continue;
654 }
655
656 let result: Result<()> = attr.parse_nested_meta(|meta| {
657 if meta.path.is_ident("name") {
658 let value = meta.value()?;
659 let lit: LitStr = value.parse()?;
660 plugin_name = Some(lit.value());
661 Ok(())
662 } else if meta.path.is_ident("service") {
663 let value = meta.value()?;
664 let ty: Type = value.parse()?;
665 services.push(ty);
666 Ok(())
667 } else if meta.path.is_ident("system") {
668 let value = meta.value()?;
669 let ty: Type = value.parse()?;
670 systems.push(ty);
671 Ok(())
672 } else if meta.path.is_ident("state") {
673 let value = meta.value()?;
674 let ty: Type = value.parse()?;
675 states.push(ty);
676 Ok(())
677 } else if meta.path.is_ident("resource") {
678 let value = meta.value()?;
679 let ty: Type = value.parse()?;
680 resources.push(ty);
681 Ok(())
682 } else {
683 Err(meta.error("expected `name`, `service`, `system`, `state`, or `resource`"))
684 }
685 });
686
687 if let Err(err) = result {
688 return err.to_compile_error().into();
689 }
690 }
691
692 let plugin_name = plugin_name.unwrap_or_else(|| {
693 let name_str = name.to_string();
695 name_str
696 .trim_end_matches("Plugin")
697 .chars()
698 .enumerate()
699 .flat_map(|(i, c)| {
700 if i > 0 && c.is_uppercase() {
701 vec!['_', c.to_ascii_lowercase()]
702 } else {
703 vec![c.to_ascii_lowercase()]
704 }
705 })
706 .collect::<String>()
707 });
708
709 let attr_service_registrations = services.iter().map(|ty| {
711 quote! {
712 builder.register_service(Box::new(#ty::default()));
713 }
714 });
715
716 let attr_system_registrations = systems.iter().map(|ty| {
717 quote! {
718 builder.register_system(Box::new(#ty::default()));
719 }
720 });
721
722 let attr_state_registrations = states.iter().map(|ty| {
723 quote! {
724 builder.register_runtime_state(#ty::default());
725 }
726 });
727
728 let attr_resource_registrations = resources.iter().map(|ty| {
729 quote! {
730 builder.register_resource(#ty::default());
731 }
732 });
733
734 let mut field_registrations = Vec::new();
736
737 if let Data::Struct(data) = &input.data {
738 for field in &data.fields {
739 let field_name = &field.ident;
740
741 if field_name.is_none() {
744 continue;
745 }
746 let field_access = quote! { self.#field_name };
747
748 for attr in &field.attrs {
749 if attr.path().is_ident("plugin") {
751 let result: Result<()> = attr.parse_nested_meta(|meta| {
753 if meta.path.is_ident("resource") {
754 field_registrations.push(quote! {
755 builder.register_resource(#field_access.clone());
756 });
757 } else if meta.path.is_ident("state") || meta.path.is_ident("runtime_state") {
758 field_registrations.push(quote! {
759 builder.register_runtime_state(#field_access.clone());
760 });
761 } else if meta.path.is_ident("system") {
762 field_registrations.push(quote! {
763 builder.register_system(Box::new(#field_access.clone()));
764 });
765 } else if meta.path.is_ident("service") {
766 field_registrations.push(quote! {
767 builder.register_service(Box::new(#field_access.clone()));
768 });
769 } else if meta.path.is_ident("skip") {
770 } else {
772 return Err(meta.error("expected `resource`, `state`, `runtime_state`, `system`, `service`, or `skip`"));
773 }
774 Ok(())
775 });
776
777 if let Err(err) = result {
778 return err.to_compile_error().into();
779 }
780 } else if attr.path().is_ident("resource") {
781 field_registrations.push(quote! {
783 builder.register_resource(#field_access.clone());
784 });
785 } else if attr.path().is_ident("state") || attr.path().is_ident("runtime_state") {
786 field_registrations.push(quote! {
788 builder.register_runtime_state(#field_access.clone());
789 });
790 } else if attr.path().is_ident("system") {
791 field_registrations.push(quote! {
793 builder.register_system(Box::new(#field_access.clone()));
794 });
795 } else if attr.path().is_ident("service") {
796 field_registrations.push(quote! {
798 builder.register_service(Box::new(#field_access.clone()));
799 });
800 }
801 }
802 }
803 }
804
805 let expanded = quote! {
806 #[::async_trait::async_trait]
807 impl #crate_name::plugin::Plugin for #name {
808 fn name(&self) -> &'static str {
809 #plugin_name
810 }
811
812 fn build(&self, builder: &mut dyn #crate_name::plugin::PluginBuilder) {
813 use #crate_name::plugin::PluginBuilderExt;
814
815 #(#attr_service_registrations)*
817 #(#attr_system_registrations)*
818 #(#attr_state_registrations)*
819 #(#attr_resource_registrations)*
820
821 #(#field_registrations)*
823 }
824 }
825 };
826
827 TokenStream::from(expanded)
828}
829
830#[proc_macro_attribute]
832pub fn event_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
833 let args = parse_macro_input!(attr as EventHandlerArgs);
834 let mut item_impl = parse_macro_input!(item as ItemImpl);
835
836 if item_impl.trait_.is_some() {
837 return syn::Error::new(
838 item_impl.impl_token.span(),
839 "#[event_handler] can only be used on inherent impl blocks",
840 )
841 .to_compile_error()
842 .into();
843 }
844
845 let crate_name = get_crate_name();
846 let mut context = match EventHandlerContext::new(args) {
847 Ok(ctx) => ctx,
848 Err(err) => return err.to_compile_error().into(),
849 };
850
851 for impl_item in &mut item_impl.items {
852 if let ImplItem::Fn(method) = impl_item {
853 let mut subscribe_attr = None;
854 method.attrs.retain(|attr| {
855 if attr.path().is_ident("subscribe") {
856 subscribe_attr = Some(attr.clone());
857 false
858 } else {
859 true
860 }
861 });
862
863 if let Some(attr) = subscribe_attr {
864 if let Err(err) = context.register_handler(method, attr) {
865 return err.to_compile_error().into();
866 }
867 }
868 }
869 }
870
871 if context.handlers.is_empty() {
872 return syn::Error::new(
873 item_impl.impl_token.span(),
874 "#[event_handler] requires at least one #[subscribe] method",
875 )
876 .to_compile_error()
877 .into();
878 }
879
880 match context.generate_process_fn(&crate_name) {
881 Ok(process_fn) => {
882 item_impl.items.push(ImplItem::Fn(process_fn));
883 TokenStream::from(quote! { #item_impl })
884 }
885 Err(err) => err.to_compile_error().into(),
886 }
887}
888
889#[derive(Default)]
890struct EventHandlerArgs {
891 default_state: Option<DefaultState>,
892}
893
894struct DefaultState {
895 ty: Type,
896 repr: String,
897}
898
899impl Parse for EventHandlerArgs {
900 fn parse(input: ParseStream) -> Result<Self> {
901 let mut args = EventHandlerArgs::default();
902
903 while !input.is_empty() {
904 let ident: Ident = input.parse()?;
905 input.parse::<Token![=]>()?;
906
907 match ident.to_string().as_str() {
908 "state" | "default_state" => {
909 let ty = parse_type_value(input)?;
910 let repr = type_to_key(&ty);
911 args.default_state = Some(DefaultState { ty, repr });
912 }
913 "system" => {
914 let _ = parse_type_value(input)?;
916 }
917 other => {
918 return Err(syn::Error::new(
919 ident.span(),
920 format!("Unknown event_handler attribute key `{}`", other),
921 ));
922 }
923 }
924
925 if input.peek(Token![,]) {
926 input.parse::<Token![,]>()?;
927 }
928 }
929
930 Ok(args)
931 }
932}
933
934struct EventHandlerContext {
935 default_state: Option<DefaultState>,
936 events: Vec<EventCollection>,
937 event_lookup: HashMap<String, usize>,
938 handlers: Vec<Handler>,
939 uses_services: bool,
940}
941
942impl EventHandlerContext {
943 fn new(args: EventHandlerArgs) -> Result<Self> {
944 Ok(Self {
945 default_state: args.default_state,
946 events: Vec::new(),
947 event_lookup: HashMap::new(),
948 handlers: Vec::new(),
949 uses_services: false,
950 })
951 }
952
953 fn register_handler(
954 &mut self,
955 method: &mut ImplItemFn,
956 subscribe_attr: Attribute,
957 ) -> Result<()> {
958 if method.sig.asyncness.is_none() {
959 return Err(syn::Error::new(
960 method.sig.fn_token.span(),
961 "#[subscribe] handlers must be async",
962 ));
963 }
964
965 if method.sig.inputs.is_empty() {
966 return Err(syn::Error::new(
967 method.sig.fn_token.span(),
968 "Event handler must accept &mut self",
969 ));
970 }
971
972 let mut inputs_iter = method.sig.inputs.iter_mut();
973
974 match inputs_iter.next() {
975 Some(FnArg::Receiver(receiver)) => {
976 if receiver.mutability.is_none() {
977 return Err(syn::Error::new(
978 receiver.self_token.span(),
979 "event handlers must take `&mut self`",
980 ));
981 }
982 }
983 _ => {
984 return Err(syn::Error::new(
985 method.sig.fn_token.span(),
986 "event handlers must start with `&mut self`",
987 ));
988 }
989 }
990
991 let subscribe = parse_subscribe_attr(subscribe_attr)?;
992 let event_index = self.register_event(&subscribe.event_type);
993
994 let event_input = inputs_iter.next().ok_or_else(|| {
995 syn::Error::new(
996 method.sig.fn_token.span(),
997 "event handler must accept event parameter",
998 )
999 })?;
1000
1001 let event_ty = match event_input {
1002 FnArg::Typed(pat_type) => match pat_type.ty.as_ref() {
1003 Type::Reference(reference) => {
1004 if reference.mutability.is_some() {
1005 return Err(syn::Error::new(
1006 reference.and_token.span(),
1007 "event parameter must be `&EventType`",
1008 ));
1009 }
1010 reference.elem.as_ref().clone()
1011 }
1012 _ => {
1013 return Err(syn::Error::new(
1014 pat_type.ty.span(),
1015 "event parameter must be a reference",
1016 ))
1017 }
1018 },
1019 _ => {
1020 return Err(syn::Error::new(
1021 method.sig.fn_token.span(),
1022 "event parameter must be named",
1023 ))
1024 }
1025 };
1026
1027 let requested_event = subscribe.event_type.to_token_stream().to_string();
1028 let actual_event = event_ty.to_token_stream().to_string();
1029 if requested_event != actual_event {
1030 return Err(syn::Error::new(
1031 method.sig.ident.span(),
1032 "event parameter type must match #[subscribe(...)]",
1033 ));
1034 }
1035
1036 let mut args = Vec::new();
1037 for input in inputs_iter {
1038 let pat_type = match input {
1039 FnArg::Typed(pat_type) => pat_type,
1040 FnArg::Receiver(_) => {
1041 return Err(syn::Error::new(
1042 input.span(),
1043 "unexpected self parameter in handler",
1044 ))
1045 }
1046 };
1047
1048 let arg = parse_handler_arg(pat_type, self.default_state.as_ref())?;
1049 if matches!(arg.kind, HandlerArgKind::Service { .. }) {
1050 self.uses_services = true;
1051 }
1052 args.push(arg);
1053 }
1054
1055 self.handlers.push(Handler {
1056 method_ident: method.sig.ident.clone(),
1057 event_index,
1058 filter: subscribe.filter,
1059 args,
1060 });
1061
1062 Ok(())
1063 }
1064
1065 fn register_event(&mut self, ty: &Type) -> usize {
1066 let key = type_to_key(ty);
1067 if let Some(index) = self.event_lookup.get(&key) {
1068 *index
1069 } else {
1070 let ident = format_ident!("__events_{}", sanitize_ident(&key));
1071 let index = self.events.len();
1072 self.events.push(EventCollection {
1073 ty: ty.clone(),
1074 ident,
1075 });
1076 self.event_lookup.insert(key, index);
1077 index
1078 }
1079 }
1080
1081 fn generate_process_fn(&self, crate_name: &proc_macro2::TokenStream) -> Result<ImplItemFn> {
1082 let event_bus_ty = quote! { #crate_name::event::EventBus };
1083 let resource_ctx_ty = quote! { #crate_name::context::ResourceContext };
1084 let service_ctx_ty = quote! { #crate_name::context::ServiceContext };
1085
1086 let collects = self.events.iter().map(|event| {
1087 let ident = &event.ident;
1088 let ty = &event.ty;
1089 quote! {
1090 let #ident: ::std::vec::Vec<#ty> =
1091 event_bus.reader::<#ty>().iter().cloned().collect();
1092 }
1093 });
1094
1095 let empty_check = if self.events.is_empty() {
1096 quote! {}
1097 } else {
1098 let empties = self.events.iter().map(|event| {
1099 let ident = &event.ident;
1100 quote! { #ident.is_empty() }
1101 });
1102 quote! {
1103 if true #(&& #empties)* {
1104 return;
1105 }
1106 }
1107 };
1108
1109 let handler_blocks = self
1110 .handlers
1111 .iter()
1112 .map(|handler| handler.expand(self.events.as_slice()));
1113
1114 let service_usage = if self.uses_services {
1115 quote! {}
1116 } else {
1117 quote! { let _ = services; }
1118 };
1119
1120 let body = quote! {
1121 let mut event_bus = match resources.get_mut::<#event_bus_ty>().await {
1122 Some(bus) => bus,
1123 None => return,
1124 };
1125
1126 #(#collects)*
1127
1128 #empty_check
1129
1130 drop(event_bus);
1131
1132 #service_usage
1133 #(#handler_blocks)*
1134 };
1135
1136 let process_fn: ImplItemFn = syn::parse_quote! {
1137 pub async fn process_events(
1138 &mut self,
1139 services: &#service_ctx_ty,
1140 resources: &mut #resource_ctx_ty,
1141 ) {
1142 #body
1143 }
1144 };
1145
1146 Ok(process_fn)
1147 }
1148}
1149
1150struct EventCollection {
1151 ty: Type,
1152 ident: Ident,
1153}
1154
1155struct Handler {
1156 method_ident: Ident,
1157 event_index: usize,
1158 filter: Option<Ident>,
1159 args: Vec<HandlerArg>,
1160}
1161
1162impl Handler {
1163 fn expand(&self, events: &[EventCollection]) -> proc_macro2::TokenStream {
1164 let event_ident = &events[self.event_index].ident;
1165 let method_ident = &self.method_ident;
1166 let filter_check = if let Some(filter) = &self.filter {
1167 quote! {
1168 if !self.#filter(event) {
1169 continue;
1170 }
1171 }
1172 } else {
1173 quote! {}
1174 };
1175
1176 let arg_exprs: Vec<_> = self
1177 .args
1178 .iter()
1179 .map(|arg| arg.argument_expression())
1180 .collect();
1181
1182 let mut block = quote! {
1183 for event in #event_ident.iter() {
1184 #filter_check
1185 self.#method_ident(event #(, #arg_exprs)*).await;
1186 }
1187 };
1188
1189 for arg in self.args.iter().rev() {
1190 block = arg.wrap_block(block);
1191 }
1192
1193 quote! {
1194 if !#event_ident.is_empty() {
1195 #block
1196 }
1197 }
1198 }
1199}
1200
1201struct HandlerArg {
1202 ident: Ident,
1203 kind: HandlerArgKind,
1204}
1205
1206impl HandlerArg {
1207 fn argument_expression(&self) -> proc_macro2::TokenStream {
1208 let ident = &self.ident;
1209 match &self.kind {
1210 HandlerArgKind::State { mutable, .. } => {
1211 if *mutable {
1212 quote! { &mut *#ident }
1213 } else {
1214 quote! { &*#ident }
1215 }
1216 }
1217 HandlerArgKind::Service { .. } => quote! { #ident },
1218 }
1219 }
1220
1221 fn wrap_block(&self, block: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1222 let ident = &self.ident;
1223 match &self.kind {
1224 HandlerArgKind::State { ty, mutable } => {
1225 if *mutable {
1226 quote! {
1227 if let Some(mut #ident) = resources.get_mut::<#ty>().await {
1228 #block
1229 }
1230 }
1231 } else {
1232 quote! {
1233 if let Some(#ident) = resources.get::<#ty>().await {
1234 #block
1235 }
1236 }
1237 }
1238 }
1239 HandlerArgKind::Service { ty, service_name } => {
1240 let name = service_name.as_str();
1241 quote! {
1242 if let Some(#ident) = services.get_as::<#ty>(#name) {
1243 #block
1244 }
1245 }
1246 }
1247 }
1248 }
1249}
1250
1251enum HandlerArgKind {
1252 State { ty: Type, mutable: bool },
1253 Service { ty: Type, service_name: String },
1254}
1255
1256struct SubscribeAttr {
1257 event_type: Type,
1258 filter: Option<Ident>,
1259}
1260
1261fn parse_subscribe_attr(attr: Attribute) -> Result<SubscribeAttr> {
1262 attr.parse_args_with(|input: ParseStream| {
1263 let event_type: Type = input.parse()?;
1264 let mut filter = None;
1265
1266 while input.peek(Token![,]) {
1267 input.parse::<Token![,]>()?;
1268 let key: Ident = input.parse()?;
1269 if key == "filter" {
1270 input.parse::<Token![=]>()?;
1271 let lit: LitStr = input.parse()?;
1272 let ident = Ident::new(&lit.value(), lit.span());
1273 filter = Some(ident);
1274 } else {
1275 return Err(syn::Error::new(key.span(), "unknown #[subscribe] option"));
1276 }
1277 }
1278
1279 Ok(SubscribeAttr { event_type, filter })
1280 })
1281}
1282
1283fn parse_handler_arg(
1284 pat_type: &mut PatType,
1285 default_state: Option<&DefaultState>,
1286) -> Result<HandlerArg> {
1287 let ident = extract_ident(&pat_type.pat)?;
1288 let mut kind = None;
1289 let attrs = mem::take(&mut pat_type.attrs);
1290
1291 for attr in attrs {
1292 if attr.path().is_ident("state") {
1293 if !matches!(attr.meta, Meta::Path(_)) {
1294 return Err(syn::Error::new(
1295 attr.span(),
1296 "#[state] does not take arguments",
1297 ));
1298 }
1299 kind = Some(create_state_arg(&pat_type.ty, attr.span())?);
1300 } else if attr.path().is_ident("service") {
1301 let service_name = parse_service_attr(&attr)?;
1302 kind = Some(create_service_arg(&pat_type.ty, service_name, attr.span())?);
1303 } else {
1304 pat_type.attrs.push(attr);
1305 }
1306 }
1307
1308 if let Some(kind) = kind {
1309 return Ok(HandlerArg { ident, kind });
1310 }
1311
1312 if let Some(default_state) = default_state {
1313 if state_matches(&pat_type.ty, default_state)? {
1314 return Ok(HandlerArg {
1315 ident,
1316 kind: HandlerArgKind::State {
1317 ty: default_state.ty.clone(),
1318 mutable: true,
1319 },
1320 });
1321 }
1322 }
1323
1324 Err(syn::Error::new(
1325 pat_type.ty.span(),
1326 "additional parameters must be marked with #[state] or #[service]",
1327 ))
1328}
1329
1330fn create_state_arg(ty: &Type, span: Span) -> Result<HandlerArgKind> {
1331 if let Type::Reference(reference) = ty {
1332 if reference.mutability.is_none() {
1333 return Err(syn::Error::new(span, "state parameters must be `&mut T`"));
1334 }
1335 Ok(HandlerArgKind::State {
1336 ty: reference.elem.as_ref().clone(),
1337 mutable: true,
1338 })
1339 } else {
1340 Err(syn::Error::new(span, "state parameters must be references"))
1341 }
1342}
1343
1344fn create_service_arg(ty: &Type, service_name: String, span: Span) -> Result<HandlerArgKind> {
1345 if let Type::Reference(reference) = ty {
1346 if reference.mutability.is_some() {
1347 return Err(syn::Error::new(
1348 span,
1349 "services must be borrowed immutably as `&T`",
1350 ));
1351 }
1352 Ok(HandlerArgKind::Service {
1353 ty: reference.elem.as_ref().clone(),
1354 service_name,
1355 })
1356 } else {
1357 Err(syn::Error::new(
1358 span,
1359 "service parameters must be references",
1360 ))
1361 }
1362}
1363
1364fn state_matches(param_ty: &Type, default: &DefaultState) -> Result<bool> {
1365 if let Type::Reference(reference) = param_ty {
1366 if reference.mutability.is_none() {
1367 return Err(syn::Error::new(
1368 reference.and_token.span(),
1369 "default state parameter must be `&mut` reference",
1370 ));
1371 }
1372 let repr = type_to_key(reference.elem.as_ref());
1373 Ok(repr == default.repr)
1374 } else {
1375 Err(syn::Error::new(
1376 param_ty.span(),
1377 "default state parameter must be a reference",
1378 ))
1379 }
1380}
1381
1382fn extract_ident(pat: &Pat) -> Result<Ident> {
1383 if let Pat::Ident(PatIdent { ident, .. }) = pat {
1384 Ok(ident.clone())
1385 } else {
1386 Err(syn::Error::new(
1387 pat.span(),
1388 "parameters must be simple identifiers",
1389 ))
1390 }
1391}
1392
1393fn parse_service_attr(attr: &Attribute) -> Result<String> {
1394 if matches!(attr.meta, Meta::Path(_)) {
1395 return Err(syn::Error::new(
1396 attr.span(),
1397 "#[service] requires a `name = \"...\"` argument",
1398 ));
1399 }
1400
1401 attr.parse_args_with(|input: ParseStream| {
1402 if input.peek(LitStr) {
1403 Ok(input.parse::<LitStr>()?.value())
1404 } else {
1405 let ident: Ident = input.parse()?;
1406 if ident == "name" {
1407 input.parse::<Token![=]>()?;
1408 Ok(input.parse::<LitStr>()?.value())
1409 } else {
1410 Err(syn::Error::new(
1411 ident.span(),
1412 "expected `name = \"...\"` for #[service]",
1413 ))
1414 }
1415 }
1416 })
1417}
1418
1419fn parse_type_value(input: ParseStream) -> Result<Type> {
1420 if input.peek(LitStr) {
1421 let lit: LitStr = input.parse()?;
1422 lit.parse()
1423 } else {
1424 input.parse()
1425 }
1426}
1427
1428fn type_to_key(ty: &Type) -> String {
1429 ty.to_token_stream().to_string().replace(' ', "")
1430}
1431
1432fn sanitize_ident(raw: &str) -> String {
1433 raw.chars()
1434 .map(|ch| match ch {
1435 '<' | '>' | ':' | ',' | '&' | '*' | '(' | ')' | '[' | ']' | '{' | '}' => '_',
1436 other if other.is_whitespace() => '_',
1437 other => other,
1438 })
1439 .collect()
1440}
1441
1442#[proc_macro]
1447pub fn event(input: TokenStream) -> TokenStream {
1448 let input = parse_macro_input!(input as EventMacroInput);
1449 let crate_name = get_crate_name();
1450
1451 let expanded = input
1452 .events
1453 .into_iter()
1454 .map(|event| event.expand(&crate_name));
1455
1456 TokenStream::from(quote! {
1457 #(#expanded)*
1458 })
1459}
1460
1461struct EventMacroInput {
1462 events: Vec<EventDefinition>,
1463}
1464
1465impl Parse for EventMacroInput {
1466 fn parse(input: ParseStream) -> Result<Self> {
1467 let mut events = Vec::new();
1468 while !input.is_empty() {
1469 events.push(input.parse()?);
1470
1471 if input.peek(Token![,]) {
1472 input.parse::<Token![,]>()?;
1473 }
1474 }
1475 Ok(Self { events })
1476 }
1477}
1478
1479struct EventDefinition {
1480 attrs: Vec<Attribute>,
1481 additional_derives: Vec<Path>,
1482 visibility: Visibility,
1483 name: Ident,
1484 fields: EventFields,
1485}
1486
1487impl EventDefinition {
1488 fn expand(self, crate_name: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1489 let mut derives = vec![
1490 syn::parse_quote!(Debug),
1491 syn::parse_quote!(Clone),
1492 syn::parse_quote!(::serde::Serialize),
1493 syn::parse_quote!(::serde::Deserialize),
1494 ];
1495
1496 for path in self.additional_derives {
1497 push_unique_path(&mut derives, path);
1498 }
1499
1500 let derive_attr = quote! {
1501 #[derive(#(#derives),*)]
1502 };
1503
1504 let body = match self.fields {
1505 EventFields::Unit => quote!(;),
1506 EventFields::Struct(fields) => {
1507 let rendered_fields = fields.into_iter().map(|field| {
1508 let attrs = field.attrs;
1509 let vis = field.visibility;
1510 let name = field.name;
1511 let ty = field.ty;
1512 quote! {
1513 #(#attrs)*
1514 #vis #name: #ty
1515 }
1516 });
1517
1518 quote! {
1519 {
1520 #(#rendered_fields,)*
1521 }
1522 }
1523 }
1524 };
1525
1526 let attrs = self.attrs;
1527 let vis = self.visibility;
1528 let name = self.name;
1529
1530 quote! {
1531 #(#attrs)*
1532 #derive_attr
1533 #vis struct #name #body
1534
1535 impl #crate_name::event::Event for #name {}
1536 }
1537 }
1538}
1539
1540impl Parse for EventDefinition {
1541 fn parse(input: ParseStream) -> Result<Self> {
1542 let outer_attrs = input.call(Attribute::parse_outer)?;
1543 let mut attrs = Vec::new();
1544 let mut derives = Vec::new();
1545
1546 for attr in outer_attrs {
1547 if attr.path().is_ident("derive") {
1548 let parsed: Punctuated<Path, Token![,]> =
1549 attr.parse_args_with(Punctuated::parse_terminated)?;
1550 derives.extend(parsed.into_iter());
1551 } else {
1552 attrs.push(attr);
1553 }
1554 }
1555
1556 let visibility = if input.peek(Token![pub]) {
1557 input.parse()?
1558 } else {
1559 Visibility::Inherited
1560 };
1561
1562 if input.peek(Token![struct]) {
1563 input.parse::<Token![struct]>()?;
1564 }
1565
1566 let name: Ident = input.parse()?;
1567
1568 let fields = if input.peek(Token![;]) {
1569 input.parse::<Token![;]>()?;
1570 EventFields::Unit
1571 } else {
1572 let content;
1573 braced!(content in input);
1574
1575 let mut parsed_fields = Vec::new();
1576 while !content.is_empty() {
1577 let attrs = content.call(Attribute::parse_outer)?;
1578 if content.is_empty() {
1579 return Err(content.error("expected field definition after attributes"));
1580 }
1581
1582 let visibility = if content.peek(Token![pub]) {
1583 content.parse()?
1584 } else {
1585 Visibility::Inherited
1586 };
1587 let name: Ident = content.parse()?;
1588 content.parse::<Token![:]>()?;
1589 let ty: Type = content.parse()?;
1590
1591 if content.peek(Token![,]) {
1592 content.parse::<Token![,]>()?;
1593 } else if content.peek(Token![;]) {
1594 content.parse::<Token![;]>()?;
1595 } else if !content.is_empty() {
1596 return Err(content.error("expected `,` or `;` after field"));
1597 }
1598
1599 parsed_fields.push(EventField {
1600 attrs,
1601 visibility,
1602 name,
1603 ty,
1604 });
1605 }
1606
1607 EventFields::Struct(parsed_fields)
1608 };
1609
1610 if input.peek(Token![;]) {
1611 input.parse::<Token![;]>()?;
1612 }
1613
1614 Ok(Self {
1615 attrs,
1616 additional_derives: derives,
1617 visibility,
1618 name,
1619 fields,
1620 })
1621 }
1622}
1623
1624enum EventFields {
1625 Unit,
1626 Struct(Vec<EventField>),
1627}
1628
1629struct EventField {
1630 attrs: Vec<Attribute>,
1631 visibility: Visibility,
1632 name: Ident,
1633 ty: Type,
1634}
1635
1636fn push_unique_path(paths: &mut Vec<Path>, new_path: Path) {
1637 let repr = path_to_string(&new_path);
1638 if paths
1639 .iter()
1640 .all(|existing| path_to_string(existing) != repr)
1641 {
1642 paths.push(new_path);
1643 }
1644}
1645
1646fn path_to_string(path: &Path) -> String {
1647 path.to_token_stream().to_string()
1648}
1649
1650#[proc_macro_derive(IssunEntity, attributes(primary, components))]
1679pub fn derive_issun_entity(input: TokenStream) -> TokenStream {
1680 bevy::derive_issun_entity_impl(input)
1681}
1682
1683#[proc_macro_derive(IssunQuery, attributes(query))]
1692pub fn derive_issun_query(input: TokenStream) -> TokenStream {
1693 bevy::derive_issun_query_impl(input)
1694}
1695
1696#[proc_macro]
1703pub fn log(input: TokenStream) -> TokenStream {
1704 bevy::log_impl(input)
1705}
1706
1707#[proc_macro_derive(IssunBevyPlugin, attributes(plugin, config, resource, skip))]
1769pub fn derive_issun_bevy_plugin(input: TokenStream) -> TokenStream {
1770 bevy::derive_issun_bevy_plugin_impl(input)
1771}
1772
1773#[proc_macro_attribute]
1775pub fn auto_pump(attr: TokenStream, item: TokenStream) -> TokenStream {
1776 let args = parse_macro_input!(attr as AutoPumpArgs);
1777
1778 let item_clone = item.clone();
1779 if let Ok(mut function) = syn::parse::<ItemFn>(item_clone) {
1780 match apply_auto_pump(&function.sig, &mut function.block, &args) {
1781 Ok(()) => return TokenStream::from(quote! { #function }),
1782 Err(err) => return err.to_compile_error().into(),
1783 }
1784 }
1785
1786 let mut method = parse_macro_input!(item as ImplItemFn);
1787 match apply_auto_pump(&method.sig, &mut method.block, &args) {
1788 Ok(()) => TokenStream::from(quote! { #method }),
1789 Err(err) => err.to_compile_error().into(),
1790 }
1791}
1792
1793#[derive(Clone)]
1794struct AutoPumpArgs {
1795 before: bool,
1796 after: bool,
1797 pump_fn: Option<Path>,
1798 sides_specified: bool,
1799}
1800
1801impl Default for AutoPumpArgs {
1802 fn default() -> Self {
1803 Self {
1804 before: true,
1805 after: true,
1806 pump_fn: None,
1807 sides_specified: false,
1808 }
1809 }
1810}
1811
1812impl Parse for AutoPumpArgs {
1813 fn parse(input: ParseStream) -> Result<Self> {
1814 if input.is_empty() {
1815 return Ok(Self::default());
1816 }
1817
1818 let mut args = AutoPumpArgs {
1819 before: false,
1820 after: false,
1821 pump_fn: None,
1822 sides_specified: false,
1823 };
1824
1825 while !input.is_empty() {
1826 let ident: Ident = input.parse()?;
1827 match ident.to_string().as_str() {
1828 "before" => {
1829 args.before = true;
1830 args.sides_specified = true;
1831 }
1832 "after" => {
1833 args.after = true;
1834 args.sides_specified = true;
1835 }
1836 "pump_fn" => {
1837 input.parse::<Token![=]>()?;
1838 let path = if input.peek(LitStr) {
1839 input.parse::<LitStr>()?.parse::<Path>()?
1840 } else {
1841 input.parse::<Path>()?
1842 };
1843 args.pump_fn = Some(path);
1844 }
1845 other => {
1846 return Err(syn::Error::new(
1847 ident.span(),
1848 format!("Unknown auto_pump option `{}`", other),
1849 ));
1850 }
1851 }
1852
1853 if input.peek(Token![,]) {
1854 input.parse::<Token![,]>()?;
1855 }
1856 }
1857
1858 if !args.sides_specified {
1859 args.before = true;
1860 args.after = true;
1861 }
1862
1863 Ok(args)
1864 }
1865}
1866
1867struct PumpParams {
1868 services: Ident,
1869 systems: Ident,
1870 resources: Ident,
1871}
1872
1873fn apply_auto_pump(signature: &Signature, block: &mut Block, args: &AutoPumpArgs) -> Result<()> {
1874 if !args.before && !args.after {
1875 return Ok(());
1876 }
1877
1878 let params = extract_pump_params(signature)?;
1879 let pump_path = args.pump_fn.clone().unwrap_or_else(|| {
1880 syn::parse_str::<Path>("crate::plugins::pump_event_systems").expect("valid path")
1881 });
1882
1883 let mut original = mem::take(&mut block.stmts);
1884 let mut stmts = Vec::new();
1885
1886 if args.before {
1888 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1889 }
1890
1891 let has_trailing_expr = original
1894 .last()
1895 .is_some_and(|stmt| matches!(stmt, Stmt::Expr(_, None)));
1896
1897 if args.after && has_trailing_expr {
1898 if original.len() > 1 {
1900 stmts.extend(original.drain(..original.len() - 1));
1901 }
1902 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1904 stmts.extend(original);
1906 } else {
1907 stmts.extend(original);
1909 if args.after {
1910 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1911 }
1912 }
1913
1914 block.stmts = stmts;
1915 Ok(())
1916}
1917
1918fn build_pump_stmt(path: &Path, params: &PumpParams) -> Stmt {
1919 let services = ¶ms.services;
1920 let systems = ¶ms.systems;
1921 let resources = ¶ms.resources;
1922
1923 let tokens = quote! {
1925 #path(#services, #systems, #resources).await;
1926 };
1927 syn::parse2(tokens).expect("failed to parse pump statement")
1928}
1929
1930fn extract_pump_params(signature: &Signature) -> Result<PumpParams> {
1931 let mut services = None;
1932 let mut systems = None;
1933 let mut resources = None;
1934
1935 for input in signature.inputs.iter() {
1936 if let FnArg::Typed(pat_type) = input {
1937 if let Ok(ident) = extract_ident(&pat_type.pat) {
1938 if matches_context(&pat_type.ty, "ServiceContext") {
1939 services = Some(ident);
1940 } else if matches_context(&pat_type.ty, "SystemContext") {
1941 systems = Some(ident);
1942 } else if matches_context(&pat_type.ty, "ResourceContext") {
1943 resources = Some(ident);
1944 }
1945 }
1946 }
1947 }
1948
1949 match (services, systems, resources) {
1950 (Some(sv), Some(sys), Some(res)) => Ok(PumpParams {
1951 services: sv,
1952 systems: sys,
1953 resources: res,
1954 }),
1955 _ => Err(syn::Error::new(
1956 signature.fn_token.span(),
1957 "#[auto_pump] requires parameters for ServiceContext, SystemContext, and ResourceContext",
1958 )),
1959 }
1960}
1961
1962fn matches_context(ty: &Type, expected: &str) -> bool {
1963 match ty {
1964 Type::Reference(reference) => match reference.elem.as_ref() {
1965 Type::Path(path) => path
1966 .path
1967 .segments
1968 .last()
1969 .map(|segment| segment.ident == expected)
1970 .unwrap_or(false),
1971 _ => false,
1972 },
1973 Type::Path(path) => path
1974 .path
1975 .segments
1976 .last()
1977 .map(|segment| segment.ident == expected)
1978 .unwrap_or(false),
1979 _ => false,
1980 }
1981}