issun_macros/
lib.rs

1//! Proc macros for ISSUN game engine
2//!
3//! This crate provides derive macros for:
4//! - `#[derive(Scene)]` - Auto-implement Scene trait
5//! - `#[derive(Entity)]` - Auto-generate entity methods
6//! - `#[derive(Service)]` - Auto-implement Service trait
7//! - `#[derive(System)]` - Auto-implement System trait
8//! - `#[derive(Asset)]` - Auto-generate asset loading
9
10use proc_macro::TokenStream;
11use proc_macro2::Span;
12use proc_macro_crate::{crate_name, FoundCrate};
13use quote::{format_ident, quote, ToTokens};
14use std::collections::HashMap;
15use std::mem;
16use syn::{
17    braced,
18    parse::{Parse, ParseStream},
19    parse_macro_input,
20    punctuated::Punctuated,
21    spanned::Spanned,
22    Attribute, Block, Data, DeriveInput, Fields, FnArg, Ident, ImplItem, ImplItemFn, ItemFn,
23    ItemImpl, Lit, LitStr, Meta, Pat, PatIdent, PatType, Path, Result, Signature, Stmt, Token,
24    Type, Visibility,
25};
26
27/// Helper function to get the issun crate identifier
28/// Returns `crate` if called from within issun crate, otherwise `::issun`
29fn get_crate_name() -> proc_macro2::TokenStream {
30    match crate_name("issun") {
31        Ok(FoundCrate::Itself) => quote!(crate),
32        Ok(FoundCrate::Name(name)) => {
33            let ident = format_ident!("{}", name);
34            quote!(::#ident)
35        }
36        Err(_) => quote!(::issun),
37    }
38}
39
40/// Derive macro for Scene trait
41///
42/// # Example
43/// ```ignore
44/// #[derive(Scene)]
45/// #[scene(context = "GameContext", initial = "Title(TitleSceneData::new())")]
46/// enum MyGameScene {
47///     Title(TitleSceneData),
48///     Combat(CombatData),
49/// }
50/// ```
51///
52/// This will auto-generate:
53/// - Scene trait implementation
54/// - GameState struct (or custom name via `name` attribute)
55/// - GameState::new() with initial scene and context
56#[proc_macro_derive(Scene, attributes(scene))]
57pub fn derive_scene(input: TokenStream) -> TokenStream {
58    let input = parse_macro_input!(input as DeriveInput);
59
60    let scene_name = &input.ident;
61
62    // Parse #[scene(...)] attributes
63    let scene_attrs = parse_scene_attributes(&input.attrs);
64
65    let crate_name = get_crate_name();
66
67    // Generate Scene trait implementation
68    let scene_impl = quote! {
69        #[::async_trait::async_trait]
70        impl #crate_name::scene::Scene for #scene_name {
71            async fn on_enter(
72                &mut self,
73                _services: &#crate_name::context::ServiceContext,
74                _systems: &mut #crate_name::context::SystemContext,
75                _resources: &mut #crate_name::context::ResourceContext,
76            ) {
77                // Default implementation: do nothing
78            }
79
80            async fn on_update(
81                &mut self,
82                _services: &#crate_name::context::ServiceContext,
83                _systems: &mut #crate_name::context::SystemContext,
84                _resources: &mut #crate_name::context::ResourceContext,
85            ) -> #crate_name::scene::SceneTransition<Self> {
86                // Default implementation: stay in current scene
87                #crate_name::scene::SceneTransition::Stay
88            }
89
90            async fn on_exit(
91                &mut self,
92                _services: &#crate_name::context::ServiceContext,
93                _systems: &mut #crate_name::context::SystemContext,
94                _resources: &mut #crate_name::context::ResourceContext,
95            ) {
96                // Default implementation: do nothing
97            }
98
99            async fn on_suspend(
100                &mut self,
101                _services: &#crate_name::context::ServiceContext,
102                _systems: &mut #crate_name::context::SystemContext,
103                _resources: &mut #crate_name::context::ResourceContext,
104            ) {
105            }
106
107            async fn on_resume(
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    };
116
117    // Generate GameState struct if context and initial are specified
118    let game_state_gen =
119        if let (Some(context), Some(initial)) = (&scene_attrs.context, &scene_attrs.initial) {
120            let state_name = scene_attrs
121                .name
122                .as_ref()
123                .map(|n| format_ident!("{}", n))
124                .unwrap_or_else(|| format_ident!("GameState"));
125
126            let context_ident = format_ident!("{}", context);
127            let initial_expr: proc_macro2::TokenStream = initial.parse().unwrap();
128
129            quote! {
130                /// Auto-generated game state combining scene and context
131                #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
132                pub struct #state_name {
133                    pub scene: #scene_name,
134                    pub ctx: #context_ident,
135                    pub should_quit: bool,
136                }
137
138                impl #state_name {
139                    pub fn new() -> Self {
140                        Self {
141                            scene: #scene_name::#initial_expr,
142                            ctx: #context_ident::new(),
143                            should_quit: false,
144                        }
145                    }
146                }
147
148                impl Default for #state_name {
149                    fn default() -> Self {
150                        Self::new()
151                    }
152                }
153            }
154        } else {
155            quote! {}
156        };
157
158    // Generate handler dispatcher if handler_params is specified
159    let handler_gen = if let Some(params) = &scene_attrs.handler_params {
160        let handler_name = scene_attrs
161            .handler
162            .as_ref()
163            .map(|h| format_ident!("{}", h))
164            .unwrap_or_else(|| format_ident!("handle_input"));
165
166        let params_tokens: proc_macro2::TokenStream = params.parse().unwrap();
167
168        // Extract parameter names from "ctx: &mut GameContext, input: InputEvent" -> ["ctx", "input"]
169        let param_names = extract_param_names(params);
170
171        // Default return type based on scene name (kept for potential future use)
172        let _return_type = scene_attrs
173            .handler_return
174            .as_ref()
175            .map(|r| r.parse::<proc_macro2::TokenStream>().unwrap())
176            .unwrap_or_else(|| {
177                quote! { (#scene_name, ::issun::scene::SceneTransition<#scene_name>) }
178            });
179
180        // Extract variants from enum
181        let variants = if let Data::Enum(data_enum) = &input.data {
182            &data_enum.variants
183        } else {
184            panic!("Scene derive macro only works on enums");
185        };
186
187        // Generate match arms for each variant
188        let match_arms = variants.iter().map(|variant| {
189            let variant_name = &variant.ident;
190            quote! {
191                #scene_name::#variant_name(data) => data.#handler_name(
192                    services,
193                    systems,
194                    resources,
195                    #(#param_names),*
196                ).await
197            }
198        });
199
200        quote! {
201            /// Auto-generated scene input handler dispatcher
202            ///
203            /// Takes a mutable reference to the scene and returns a transition.
204            pub async fn handle_scene_input(
205                scene: &mut #scene_name,
206                services: &#crate_name::context::ServiceContext,
207                systems: &mut #crate_name::context::SystemContext,
208                resources: &mut #crate_name::context::ResourceContext,
209                #params_tokens,
210            ) -> ::issun::scene::SceneTransition<#scene_name> {
211                match scene {
212                    #(#match_arms),*
213                }
214            }
215        }
216    } else {
217        quote! {}
218    };
219
220    let expanded = quote! {
221        #scene_impl
222        #game_state_gen
223        #handler_gen
224    };
225
226    TokenStream::from(expanded)
227}
228
229/// Parse #[scene(...)] attributes
230struct SceneAttributes {
231    context: Option<String>,
232    initial: Option<String>,
233    name: Option<String>,
234    handler: Option<String>,
235    handler_params: Option<String>,
236    handler_return: Option<String>,
237}
238
239fn parse_scene_attributes(attrs: &[syn::Attribute]) -> SceneAttributes {
240    let mut context = None;
241    let mut initial = None;
242    let mut name = None;
243    let mut handler = None;
244    let mut handler_params = None;
245    let mut handler_return = None;
246
247    for attr in attrs {
248        if attr.path().is_ident("scene") {
249            let _ = attr.parse_nested_meta(|meta| {
250                if meta.path.is_ident("context") {
251                    if let Ok(value) = meta.value() {
252                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
253                            context = Some(s.value());
254                        }
255                    }
256                } else if meta.path.is_ident("initial") {
257                    if let Ok(value) = meta.value() {
258                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
259                            initial = Some(s.value());
260                        }
261                    }
262                } else if meta.path.is_ident("name") {
263                    if let Ok(value) = meta.value() {
264                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
265                            name = Some(s.value());
266                        }
267                    }
268                } else if meta.path.is_ident("handler") {
269                    if let Ok(value) = meta.value() {
270                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
271                            handler = Some(s.value());
272                        }
273                    }
274                } else if meta.path.is_ident("handler_params") {
275                    if let Ok(value) = meta.value() {
276                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
277                            handler_params = Some(s.value());
278                        }
279                    }
280                } else if meta.path.is_ident("handler_return") {
281                    if let Ok(value) = meta.value() {
282                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
283                            handler_return = Some(s.value());
284                        }
285                    }
286                }
287                Ok(())
288            });
289        }
290    }
291
292    SceneAttributes {
293        context,
294        initial,
295        name,
296        handler,
297        handler_params,
298        handler_return,
299    }
300}
301
302/// Extract parameter names from function parameters string
303/// "ctx: &mut GameContext, input: InputEvent" -> vec![ident("ctx"), ident("input")]
304fn extract_param_names(params_str: &str) -> Vec<proc_macro2::Ident> {
305    params_str
306        .split(',')
307        .filter_map(|param| {
308            let param = param.trim();
309            // Extract the identifier before the colon
310            param.split(':').next().map(|name| {
311                let name = name.trim();
312                format_ident!("{}", name)
313            })
314        })
315        .collect()
316}
317
318/// Derive macro for Entity trait
319///
320/// # Example
321/// ```ignore
322/// #[derive(Entity)]
323/// pub struct Player {
324///     #[entity(id)]
325///     pub name: String,
326///     pub hp: i32,
327/// }
328/// ```
329#[proc_macro_derive(Entity, attributes(entity))]
330pub fn derive_entity(input: TokenStream) -> TokenStream {
331    let input = parse_macro_input!(input as DeriveInput);
332
333    let name = &input.ident;
334
335    // Extract fields to find #[entity(id)]
336    let id_field = match &input.data {
337        Data::Struct(data) => {
338            match &data.fields {
339                Fields::Named(fields) => {
340                    // Find field with #[entity(id)] attribute
341                    fields
342                        .named
343                        .iter()
344                        .find(|field| {
345                            field
346                                .attrs
347                                .iter()
348                                .any(|attr| attr.path().is_ident("entity"))
349                        })
350                        .map(|field| field.ident.as_ref().unwrap())
351                }
352                _ => None,
353            }
354        }
355        _ => None,
356    };
357
358    let id_impl = if let Some(field_name) = id_field {
359        quote! {
360            fn id(&self) -> &str {
361                &self.#field_name
362            }
363        }
364    } else {
365        // No #[entity(id)] found - use default implementation
366        quote! {
367            fn id(&self) -> &str {
368                ""
369            }
370        }
371    };
372
373    let expanded = quote! {
374        #[async_trait::async_trait]
375        impl Entity for #name {
376            #id_impl
377
378            async fn update(&mut self, _ctx: &mut Context) {
379                // Default implementation: do nothing
380            }
381        }
382    };
383
384    TokenStream::from(expanded)
385}
386
387/// Derive macro for Service trait
388///
389/// Auto-generates the boilerplate Service trait implementation.
390/// Requires #[service(name = "service_name")] attribute.
391///
392/// # Example
393/// ```ignore
394/// use crate::service::Service; // or use issun::service::Service;
395///
396/// #[derive(Service)]
397/// #[service(name = "combat_service")]
398/// pub struct CombatService {
399///     min_damage: i32,
400/// }
401/// ```
402///
403/// This generates:
404/// - name() method returning the specified service name
405/// - as_any() and as_any_mut() for downcasting
406/// - async_trait wrapper
407///
408/// Note: You must have `Service` trait in scope via `use` statement.
409#[proc_macro_derive(Service, attributes(service))]
410pub fn derive_service(input: TokenStream) -> TokenStream {
411    let input = parse_macro_input!(input as DeriveInput);
412
413    let struct_name = &input.ident;
414
415    // Parse #[service(name = "...")] attribute
416    let service_name = parse_service_name(&input.attrs);
417    let service_name_lit = syn::LitStr::new(&service_name, proc_macro2::Span::call_site());
418
419    let crate_name = get_crate_name();
420
421    let expanded = quote! {
422        impl #struct_name {
423            pub const NAME: &'static str = #service_name_lit;
424        }
425
426        #[::async_trait::async_trait]
427        impl #crate_name::service::Service for #struct_name {
428            fn name(&self) -> &'static str {
429                #service_name
430            }
431
432            fn clone_box(&self) -> Box<dyn #crate_name::service::Service> {
433                Box::new(self.clone())
434            }
435
436            fn as_any(&self) -> &dyn ::std::any::Any {
437                self
438            }
439
440            fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
441                self
442            }
443        }
444    };
445
446    TokenStream::from(expanded)
447}
448
449/// Parse #[service(name = "service_name")] attribute
450fn parse_service_name(attrs: &[syn::Attribute]) -> String {
451    for attr in attrs {
452        if attr.path().is_ident("service") {
453            let mut name = None;
454            let _ = attr.parse_nested_meta(|meta| {
455                if meta.path.is_ident("name") {
456                    if let Ok(value) = meta.value() {
457                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
458                            name = Some(s.value());
459                        }
460                    }
461                }
462                Ok(())
463            });
464            if let Some(n) = name {
465                return n;
466            }
467        }
468    }
469
470    // Default: use lowercase struct name
471    "unknown_service".to_string()
472}
473
474/// Derive macro for System trait
475///
476/// Auto-generates the boilerplate System trait implementation.
477/// Requires #[system(name = "system_name")] attribute.
478///
479/// # Example
480/// ```ignore
481/// use crate::system::System; // or use issun::system::System;
482///
483/// #[derive(System)]
484/// #[system(name = "combat_engine")]
485/// pub struct CombatSystem {
486///     turn_count: u32,
487///     log: Vec<String>,
488/// }
489/// ```
490///
491/// This generates:
492/// - name() method returning the specified system name
493/// - as_any() and as_any_mut() for downcasting
494/// - async_trait wrapper
495///
496/// Note: You must have `System` trait in scope via `use` statement.
497#[proc_macro_derive(System, attributes(system))]
498pub fn derive_system(input: TokenStream) -> TokenStream {
499    let input = parse_macro_input!(input as DeriveInput);
500
501    let struct_name = &input.ident;
502
503    // Parse #[system(name = "...")] attribute
504    let system_name = parse_system_name(&input.attrs);
505    let system_name_lit = syn::LitStr::new(&system_name, proc_macro2::Span::call_site());
506
507    let crate_name = get_crate_name();
508
509    let expanded = quote! {
510        impl #struct_name {
511            pub const NAME: &'static str = #system_name_lit;
512        }
513
514        #[::async_trait::async_trait]
515        impl #crate_name::system::System for #struct_name {
516            fn name(&self) -> &'static str {
517                #system_name
518            }
519
520            fn as_any(&self) -> &dyn ::std::any::Any {
521                self
522            }
523
524            fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
525                self
526            }
527        }
528    };
529
530    TokenStream::from(expanded)
531}
532
533/// Parse #[system(name = "system_name")] attribute
534fn parse_system_name(attrs: &[syn::Attribute]) -> String {
535    for attr in attrs {
536        if attr.path().is_ident("system") {
537            let mut name = None;
538            let _ = attr.parse_nested_meta(|meta| {
539                if meta.path.is_ident("name") {
540                    if let Ok(value) = meta.value() {
541                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
542                            name = Some(s.value());
543                        }
544                    }
545                }
546                Ok(())
547            });
548            if let Some(n) = name {
549                return n;
550            }
551        }
552    }
553
554    // Default: use lowercase struct name
555    "unknown_system".to_string()
556}
557
558/// Derive macro for Asset trait
559///
560/// # Example
561/// ```ignore
562/// #[derive(Asset)]
563/// pub struct EnemyAsset {
564///     pub name: &'static str,
565///     pub hp: i32,
566/// }
567/// ```
568#[proc_macro_derive(Asset)]
569pub fn derive_asset(input: TokenStream) -> TokenStream {
570    let input = parse_macro_input!(input as DeriveInput);
571
572    let name = &input.ident;
573
574    let expanded = quote! {
575        impl Asset for #name {
576            // Uses default implementation from trait
577        }
578    };
579
580    TokenStream::from(expanded)
581}
582
583/// Derive macro for Resource trait
584///
585/// Automatically implements the `Resource` marker trait for types that
586/// can be stored in the global resource registry.
587///
588/// # Example
589/// ```ignore
590/// use issun::prelude::*;
591///
592/// #[derive(Resource)]
593/// pub struct GameConfig {
594///     pub fps: u32,
595///     pub difficulty: f32,
596/// }
597///
598/// #[derive(Resource)]
599/// pub struct EnemyDatabase {
600///     pub enemies: Vec<EnemyAsset>,
601/// }
602/// ```
603#[proc_macro_derive(Resource)]
604pub fn derive_resource(input: TokenStream) -> TokenStream {
605    let input = parse_macro_input!(input as DeriveInput);
606
607    let name = &input.ident;
608    let crate_name = get_crate_name();
609
610    let expanded = quote! {
611        impl #crate_name::resources::Resource for #name {
612            // Uses default implementation from trait
613        }
614    };
615
616    TokenStream::from(expanded)
617}
618
619/// Derive macro for Plugin trait
620///
621/// # Example
622/// ```ignore
623/// #[derive(Default, Plugin)]
624/// #[plugin(name = "my_plugin")]
625/// #[plugin(service = MyService)]
626/// #[plugin(system = MySystem)]
627/// #[plugin(state = MyState)]
628/// pub struct MyPlugin;
629/// ```
630#[proc_macro_derive(Plugin, attributes(plugin, resource, state, system, service))]
631pub fn derive_plugin(input: TokenStream) -> TokenStream {
632    let input = parse_macro_input!(input as DeriveInput);
633    let name = &input.ident;
634    let crate_name = get_crate_name();
635
636    // Parse plugin attributes (struct level)
637    let mut plugin_name = None;
638    let mut services = Vec::new();
639    let mut systems = Vec::new();
640    let mut states = Vec::new();
641    let mut resources = Vec::new();
642
643    for attr in &input.attrs {
644        if !attr.path().is_ident("plugin") {
645            continue;
646        }
647
648        let result: Result<()> = attr.parse_nested_meta(|meta| {
649            if meta.path.is_ident("name") {
650                let value = meta.value()?;
651                let lit: LitStr = value.parse()?;
652                plugin_name = Some(lit.value());
653                Ok(())
654            } else if meta.path.is_ident("service") {
655                let value = meta.value()?;
656                let ty: Type = value.parse()?;
657                services.push(ty);
658                Ok(())
659            } else if meta.path.is_ident("system") {
660                let value = meta.value()?;
661                let ty: Type = value.parse()?;
662                systems.push(ty);
663                Ok(())
664            } else if meta.path.is_ident("state") {
665                let value = meta.value()?;
666                let ty: Type = value.parse()?;
667                states.push(ty);
668                Ok(())
669            } else if meta.path.is_ident("resource") {
670                let value = meta.value()?;
671                let ty: Type = value.parse()?;
672                resources.push(ty);
673                Ok(())
674            } else {
675                Err(meta.error("expected `name`, `service`, `system`, `state`, or `resource`"))
676            }
677        });
678
679        if let Err(err) = result {
680            return err.to_compile_error().into();
681        }
682    }
683
684    let plugin_name = plugin_name.unwrap_or_else(|| {
685        // Default: convert MyPlugin -> "my_plugin"
686        let name_str = name.to_string();
687        name_str
688            .trim_end_matches("Plugin")
689            .chars()
690            .enumerate()
691            .flat_map(|(i, c)| {
692                if i > 0 && c.is_uppercase() {
693                    vec!['_', c.to_ascii_lowercase()]
694                } else {
695                    vec![c.to_ascii_lowercase()]
696                }
697            })
698            .collect::<String>()
699    });
700
701    // Generate registrations from attributes (Type::default())
702    let attr_service_registrations = services.iter().map(|ty| {
703        quote! {
704            builder.register_service(Box::new(#ty::default()));
705        }
706    });
707
708    let attr_system_registrations = systems.iter().map(|ty| {
709        quote! {
710            builder.register_system(Box::new(#ty::default()));
711        }
712    });
713
714    let attr_state_registrations = states.iter().map(|ty| {
715        quote! {
716            builder.register_runtime_state(#ty::default());
717        }
718    });
719
720    let attr_resource_registrations = resources.iter().map(|ty| {
721        quote! {
722            builder.register_resource(#ty::default());
723        }
724    });
725
726    // Parse field attributes (instance registration)
727    let mut field_registrations = Vec::new();
728
729    if let Data::Struct(data) = &input.data {
730        for field in &data.fields {
731            let field_name = &field.ident;
732
733            // Skip fields without identifiers (tuple structs) for now, or handle them if needed.
734            // The requirement implies named fields for clarity, but let's support named fields primarily.
735            if field_name.is_none() {
736                continue;
737            }
738            let field_access = quote! { self.#field_name };
739
740            for attr in &field.attrs {
741                // Handle both #[plugin(...)] and #[...] attribute formats
742                if attr.path().is_ident("plugin") {
743                    // Handle #[plugin(...)] format using parse_nested_meta
744                    let result: Result<()> = attr.parse_nested_meta(|meta| {
745                        if meta.path.is_ident("resource") {
746                            field_registrations.push(quote! {
747                                builder.register_resource(#field_access.clone());
748                            });
749                        } else if meta.path.is_ident("state") || meta.path.is_ident("runtime_state") {
750                            field_registrations.push(quote! {
751                                builder.register_runtime_state(#field_access.clone());
752                            });
753                        } else if meta.path.is_ident("system") {
754                            field_registrations.push(quote! {
755                                builder.register_system(Box::new(#field_access.clone()));
756                            });
757                        } else if meta.path.is_ident("service") {
758                            field_registrations.push(quote! {
759                                builder.register_service(Box::new(#field_access.clone()));
760                            });
761                        } else if meta.path.is_ident("skip") {
762                            // Explicitly skip this field - no registration
763                        } else {
764                            return Err(meta.error("expected `resource`, `state`, `runtime_state`, `system`, `service`, or `skip`"));
765                        }
766                        Ok(())
767                    });
768
769                    if let Err(err) = result {
770                        return err.to_compile_error().into();
771                    }
772                } else if attr.path().is_ident("resource") {
773                    // Handle legacy #[resource] format
774                    field_registrations.push(quote! {
775                        builder.register_resource(#field_access.clone());
776                    });
777                } else if attr.path().is_ident("state") || attr.path().is_ident("runtime_state") {
778                    // Handle legacy #[state] or #[runtime_state] format
779                    field_registrations.push(quote! {
780                        builder.register_runtime_state(#field_access.clone());
781                    });
782                } else if attr.path().is_ident("system") {
783                    // Handle legacy #[system] format
784                    field_registrations.push(quote! {
785                        builder.register_system(Box::new(#field_access.clone()));
786                    });
787                } else if attr.path().is_ident("service") {
788                    // Handle legacy #[service] format
789                    field_registrations.push(quote! {
790                        builder.register_service(Box::new(#field_access.clone()));
791                    });
792                }
793            }
794        }
795    }
796
797    let expanded = quote! {
798        #[::async_trait::async_trait]
799        impl #crate_name::plugin::Plugin for #name {
800            fn name(&self) -> &'static str {
801                #plugin_name
802            }
803
804            fn build(&self, builder: &mut dyn #crate_name::plugin::PluginBuilder) {
805                use #crate_name::plugin::PluginBuilderExt;
806
807                // Attribute-based registrations (Types)
808                #(#attr_service_registrations)*
809                #(#attr_system_registrations)*
810                #(#attr_state_registrations)*
811                #(#attr_resource_registrations)*
812
813                // Field-based registrations (Instances)
814                #(#field_registrations)*
815            }
816        }
817    };
818
819    TokenStream::from(expanded)
820}
821
822/// Attribute macro that generates `process_events` for systems reacting to events.
823#[proc_macro_attribute]
824pub fn event_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
825    let args = parse_macro_input!(attr as EventHandlerArgs);
826    let mut item_impl = parse_macro_input!(item as ItemImpl);
827
828    if item_impl.trait_.is_some() {
829        return syn::Error::new(
830            item_impl.impl_token.span(),
831            "#[event_handler] can only be used on inherent impl blocks",
832        )
833        .to_compile_error()
834        .into();
835    }
836
837    let crate_name = get_crate_name();
838    let mut context = match EventHandlerContext::new(args) {
839        Ok(ctx) => ctx,
840        Err(err) => return err.to_compile_error().into(),
841    };
842
843    for impl_item in &mut item_impl.items {
844        if let ImplItem::Fn(method) = impl_item {
845            let mut subscribe_attr = None;
846            method.attrs.retain(|attr| {
847                if attr.path().is_ident("subscribe") {
848                    subscribe_attr = Some(attr.clone());
849                    false
850                } else {
851                    true
852                }
853            });
854
855            if let Some(attr) = subscribe_attr {
856                if let Err(err) = context.register_handler(method, attr) {
857                    return err.to_compile_error().into();
858                }
859            }
860        }
861    }
862
863    if context.handlers.is_empty() {
864        return syn::Error::new(
865            item_impl.impl_token.span(),
866            "#[event_handler] requires at least one #[subscribe] method",
867        )
868        .to_compile_error()
869        .into();
870    }
871
872    match context.generate_process_fn(&crate_name) {
873        Ok(process_fn) => {
874            item_impl.items.push(ImplItem::Fn(process_fn));
875            TokenStream::from(quote! { #item_impl })
876        }
877        Err(err) => err.to_compile_error().into(),
878    }
879}
880
881#[derive(Default)]
882struct EventHandlerArgs {
883    default_state: Option<DefaultState>,
884}
885
886struct DefaultState {
887    ty: Type,
888    repr: String,
889}
890
891impl Parse for EventHandlerArgs {
892    fn parse(input: ParseStream) -> Result<Self> {
893        let mut args = EventHandlerArgs::default();
894
895        while !input.is_empty() {
896            let ident: Ident = input.parse()?;
897            input.parse::<Token![=]>()?;
898
899            match ident.to_string().as_str() {
900                "state" | "default_state" => {
901                    let ty = parse_type_value(input)?;
902                    let repr = type_to_key(&ty);
903                    args.default_state = Some(DefaultState { ty, repr });
904                }
905                "system" => {
906                    // Consume the value for validation but ignore it for now.
907                    let _ = parse_type_value(input)?;
908                }
909                other => {
910                    return Err(syn::Error::new(
911                        ident.span(),
912                        format!("Unknown event_handler attribute key `{}`", other),
913                    ));
914                }
915            }
916
917            if input.peek(Token![,]) {
918                input.parse::<Token![,]>()?;
919            }
920        }
921
922        Ok(args)
923    }
924}
925
926struct EventHandlerContext {
927    default_state: Option<DefaultState>,
928    events: Vec<EventCollection>,
929    event_lookup: HashMap<String, usize>,
930    handlers: Vec<Handler>,
931    uses_services: bool,
932}
933
934impl EventHandlerContext {
935    fn new(args: EventHandlerArgs) -> Result<Self> {
936        Ok(Self {
937            default_state: args.default_state,
938            events: Vec::new(),
939            event_lookup: HashMap::new(),
940            handlers: Vec::new(),
941            uses_services: false,
942        })
943    }
944
945    fn register_handler(
946        &mut self,
947        method: &mut ImplItemFn,
948        subscribe_attr: Attribute,
949    ) -> Result<()> {
950        if method.sig.asyncness.is_none() {
951            return Err(syn::Error::new(
952                method.sig.fn_token.span(),
953                "#[subscribe] handlers must be async",
954            ));
955        }
956
957        if method.sig.inputs.is_empty() {
958            return Err(syn::Error::new(
959                method.sig.fn_token.span(),
960                "Event handler must accept &mut self",
961            ));
962        }
963
964        let mut inputs_iter = method.sig.inputs.iter_mut();
965
966        match inputs_iter.next() {
967            Some(FnArg::Receiver(receiver)) => {
968                if receiver.mutability.is_none() {
969                    return Err(syn::Error::new(
970                        receiver.self_token.span(),
971                        "event handlers must take `&mut self`",
972                    ));
973                }
974            }
975            _ => {
976                return Err(syn::Error::new(
977                    method.sig.fn_token.span(),
978                    "event handlers must start with `&mut self`",
979                ));
980            }
981        }
982
983        let subscribe = parse_subscribe_attr(subscribe_attr)?;
984        let event_index = self.register_event(&subscribe.event_type);
985
986        let event_input = inputs_iter.next().ok_or_else(|| {
987            syn::Error::new(
988                method.sig.fn_token.span(),
989                "event handler must accept event parameter",
990            )
991        })?;
992
993        let event_ty = match event_input {
994            FnArg::Typed(pat_type) => match pat_type.ty.as_ref() {
995                Type::Reference(reference) => {
996                    if reference.mutability.is_some() {
997                        return Err(syn::Error::new(
998                            reference.and_token.span(),
999                            "event parameter must be `&EventType`",
1000                        ));
1001                    }
1002                    reference.elem.as_ref().clone()
1003                }
1004                _ => {
1005                    return Err(syn::Error::new(
1006                        pat_type.ty.span(),
1007                        "event parameter must be a reference",
1008                    ))
1009                }
1010            },
1011            _ => {
1012                return Err(syn::Error::new(
1013                    method.sig.fn_token.span(),
1014                    "event parameter must be named",
1015                ))
1016            }
1017        };
1018
1019        let requested_event = subscribe.event_type.to_token_stream().to_string();
1020        let actual_event = event_ty.to_token_stream().to_string();
1021        if requested_event != actual_event {
1022            return Err(syn::Error::new(
1023                method.sig.ident.span(),
1024                "event parameter type must match #[subscribe(...)]",
1025            ));
1026        }
1027
1028        let mut args = Vec::new();
1029        for input in inputs_iter {
1030            let pat_type = match input {
1031                FnArg::Typed(pat_type) => pat_type,
1032                FnArg::Receiver(_) => {
1033                    return Err(syn::Error::new(
1034                        input.span(),
1035                        "unexpected self parameter in handler",
1036                    ))
1037                }
1038            };
1039
1040            let arg = parse_handler_arg(pat_type, self.default_state.as_ref())?;
1041            if matches!(arg.kind, HandlerArgKind::Service { .. }) {
1042                self.uses_services = true;
1043            }
1044            args.push(arg);
1045        }
1046
1047        self.handlers.push(Handler {
1048            method_ident: method.sig.ident.clone(),
1049            event_index,
1050            filter: subscribe.filter,
1051            args,
1052        });
1053
1054        Ok(())
1055    }
1056
1057    fn register_event(&mut self, ty: &Type) -> usize {
1058        let key = type_to_key(ty);
1059        if let Some(index) = self.event_lookup.get(&key) {
1060            *index
1061        } else {
1062            let ident = format_ident!("__events_{}", sanitize_ident(&key));
1063            let index = self.events.len();
1064            self.events.push(EventCollection {
1065                ty: ty.clone(),
1066                ident,
1067            });
1068            self.event_lookup.insert(key, index);
1069            index
1070        }
1071    }
1072
1073    fn generate_process_fn(&self, crate_name: &proc_macro2::TokenStream) -> Result<ImplItemFn> {
1074        let event_bus_ty = quote! { #crate_name::event::EventBus };
1075        let resource_ctx_ty = quote! { #crate_name::context::ResourceContext };
1076        let service_ctx_ty = quote! { #crate_name::context::ServiceContext };
1077
1078        let collects = self.events.iter().map(|event| {
1079            let ident = &event.ident;
1080            let ty = &event.ty;
1081            quote! {
1082                let #ident: ::std::vec::Vec<#ty> =
1083                    event_bus.reader::<#ty>().iter().cloned().collect();
1084            }
1085        });
1086
1087        let empty_check = if self.events.is_empty() {
1088            quote! {}
1089        } else {
1090            let empties = self.events.iter().map(|event| {
1091                let ident = &event.ident;
1092                quote! { #ident.is_empty() }
1093            });
1094            quote! {
1095                if true #(&& #empties)* {
1096                    return;
1097                }
1098            }
1099        };
1100
1101        let handler_blocks = self
1102            .handlers
1103            .iter()
1104            .map(|handler| handler.expand(self.events.as_slice()));
1105
1106        let service_usage = if self.uses_services {
1107            quote! {}
1108        } else {
1109            quote! { let _ = services; }
1110        };
1111
1112        let body = quote! {
1113            let mut event_bus = match resources.get_mut::<#event_bus_ty>().await {
1114                Some(bus) => bus,
1115                None => return,
1116            };
1117
1118            #(#collects)*
1119
1120            #empty_check
1121
1122            drop(event_bus);
1123
1124            #service_usage
1125            #(#handler_blocks)*
1126        };
1127
1128        let process_fn: ImplItemFn = syn::parse_quote! {
1129            pub async fn process_events(
1130                &mut self,
1131                services: &#service_ctx_ty,
1132                resources: &mut #resource_ctx_ty,
1133            ) {
1134                #body
1135            }
1136        };
1137
1138        Ok(process_fn)
1139    }
1140}
1141
1142struct EventCollection {
1143    ty: Type,
1144    ident: Ident,
1145}
1146
1147struct Handler {
1148    method_ident: Ident,
1149    event_index: usize,
1150    filter: Option<Ident>,
1151    args: Vec<HandlerArg>,
1152}
1153
1154impl Handler {
1155    fn expand(&self, events: &[EventCollection]) -> proc_macro2::TokenStream {
1156        let event_ident = &events[self.event_index].ident;
1157        let method_ident = &self.method_ident;
1158        let filter_check = if let Some(filter) = &self.filter {
1159            quote! {
1160                if !self.#filter(event) {
1161                    continue;
1162                }
1163            }
1164        } else {
1165            quote! {}
1166        };
1167
1168        let arg_exprs: Vec<_> = self
1169            .args
1170            .iter()
1171            .map(|arg| arg.argument_expression())
1172            .collect();
1173
1174        let mut block = quote! {
1175            for event in #event_ident.iter() {
1176                #filter_check
1177                self.#method_ident(event #(, #arg_exprs)*).await;
1178            }
1179        };
1180
1181        for arg in self.args.iter().rev() {
1182            block = arg.wrap_block(block);
1183        }
1184
1185        quote! {
1186            if !#event_ident.is_empty() {
1187                #block
1188            }
1189        }
1190    }
1191}
1192
1193struct HandlerArg {
1194    ident: Ident,
1195    kind: HandlerArgKind,
1196}
1197
1198impl HandlerArg {
1199    fn argument_expression(&self) -> proc_macro2::TokenStream {
1200        let ident = &self.ident;
1201        match &self.kind {
1202            HandlerArgKind::State { mutable, .. } => {
1203                if *mutable {
1204                    quote! { &mut *#ident }
1205                } else {
1206                    quote! { &*#ident }
1207                }
1208            }
1209            HandlerArgKind::Service { .. } => quote! { #ident },
1210        }
1211    }
1212
1213    fn wrap_block(&self, block: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1214        let ident = &self.ident;
1215        match &self.kind {
1216            HandlerArgKind::State { ty, mutable } => {
1217                if *mutable {
1218                    quote! {
1219                        if let Some(mut #ident) = resources.get_mut::<#ty>().await {
1220                            #block
1221                        }
1222                    }
1223                } else {
1224                    quote! {
1225                        if let Some(#ident) = resources.get::<#ty>().await {
1226                            #block
1227                        }
1228                    }
1229                }
1230            }
1231            HandlerArgKind::Service { ty, service_name } => {
1232                let name = service_name.as_str();
1233                quote! {
1234                    if let Some(#ident) = services.get_as::<#ty>(#name) {
1235                        #block
1236                    }
1237                }
1238            }
1239        }
1240    }
1241}
1242
1243enum HandlerArgKind {
1244    State { ty: Type, mutable: bool },
1245    Service { ty: Type, service_name: String },
1246}
1247
1248struct SubscribeAttr {
1249    event_type: Type,
1250    filter: Option<Ident>,
1251}
1252
1253fn parse_subscribe_attr(attr: Attribute) -> Result<SubscribeAttr> {
1254    attr.parse_args_with(|input: ParseStream| {
1255        let event_type: Type = input.parse()?;
1256        let mut filter = None;
1257
1258        while input.peek(Token![,]) {
1259            input.parse::<Token![,]>()?;
1260            let key: Ident = input.parse()?;
1261            if key == "filter" {
1262                input.parse::<Token![=]>()?;
1263                let lit: LitStr = input.parse()?;
1264                let ident = Ident::new(&lit.value(), lit.span());
1265                filter = Some(ident);
1266            } else {
1267                return Err(syn::Error::new(key.span(), "unknown #[subscribe] option"));
1268            }
1269        }
1270
1271        Ok(SubscribeAttr { event_type, filter })
1272    })
1273}
1274
1275fn parse_handler_arg(
1276    pat_type: &mut PatType,
1277    default_state: Option<&DefaultState>,
1278) -> Result<HandlerArg> {
1279    let ident = extract_ident(&pat_type.pat)?;
1280    let mut kind = None;
1281    let attrs = mem::take(&mut pat_type.attrs);
1282
1283    for attr in attrs {
1284        if attr.path().is_ident("state") {
1285            if !matches!(attr.meta, Meta::Path(_)) {
1286                return Err(syn::Error::new(
1287                    attr.span(),
1288                    "#[state] does not take arguments",
1289                ));
1290            }
1291            kind = Some(create_state_arg(&pat_type.ty, attr.span())?);
1292        } else if attr.path().is_ident("service") {
1293            let service_name = parse_service_attr(&attr)?;
1294            kind = Some(create_service_arg(&pat_type.ty, service_name, attr.span())?);
1295        } else {
1296            pat_type.attrs.push(attr);
1297        }
1298    }
1299
1300    if let Some(kind) = kind {
1301        return Ok(HandlerArg { ident, kind });
1302    }
1303
1304    if let Some(default_state) = default_state {
1305        if state_matches(&pat_type.ty, default_state)? {
1306            return Ok(HandlerArg {
1307                ident,
1308                kind: HandlerArgKind::State {
1309                    ty: default_state.ty.clone(),
1310                    mutable: true,
1311                },
1312            });
1313        }
1314    }
1315
1316    Err(syn::Error::new(
1317        pat_type.ty.span(),
1318        "additional parameters must be marked with #[state] or #[service]",
1319    ))
1320}
1321
1322fn create_state_arg(ty: &Type, span: Span) -> Result<HandlerArgKind> {
1323    if let Type::Reference(reference) = ty {
1324        if reference.mutability.is_none() {
1325            return Err(syn::Error::new(span, "state parameters must be `&mut T`"));
1326        }
1327        Ok(HandlerArgKind::State {
1328            ty: reference.elem.as_ref().clone(),
1329            mutable: true,
1330        })
1331    } else {
1332        Err(syn::Error::new(span, "state parameters must be references"))
1333    }
1334}
1335
1336fn create_service_arg(ty: &Type, service_name: String, span: Span) -> Result<HandlerArgKind> {
1337    if let Type::Reference(reference) = ty {
1338        if reference.mutability.is_some() {
1339            return Err(syn::Error::new(
1340                span,
1341                "services must be borrowed immutably as `&T`",
1342            ));
1343        }
1344        Ok(HandlerArgKind::Service {
1345            ty: reference.elem.as_ref().clone(),
1346            service_name,
1347        })
1348    } else {
1349        Err(syn::Error::new(
1350            span,
1351            "service parameters must be references",
1352        ))
1353    }
1354}
1355
1356fn state_matches(param_ty: &Type, default: &DefaultState) -> Result<bool> {
1357    if let Type::Reference(reference) = param_ty {
1358        if reference.mutability.is_none() {
1359            return Err(syn::Error::new(
1360                reference.and_token.span(),
1361                "default state parameter must be `&mut` reference",
1362            ));
1363        }
1364        let repr = type_to_key(reference.elem.as_ref());
1365        Ok(repr == default.repr)
1366    } else {
1367        Err(syn::Error::new(
1368            param_ty.span(),
1369            "default state parameter must be a reference",
1370        ))
1371    }
1372}
1373
1374fn extract_ident(pat: &Pat) -> Result<Ident> {
1375    if let Pat::Ident(PatIdent { ident, .. }) = pat {
1376        Ok(ident.clone())
1377    } else {
1378        Err(syn::Error::new(
1379            pat.span(),
1380            "parameters must be simple identifiers",
1381        ))
1382    }
1383}
1384
1385fn parse_service_attr(attr: &Attribute) -> Result<String> {
1386    if matches!(attr.meta, Meta::Path(_)) {
1387        return Err(syn::Error::new(
1388            attr.span(),
1389            "#[service] requires a `name = \"...\"` argument",
1390        ));
1391    }
1392
1393    attr.parse_args_with(|input: ParseStream| {
1394        if input.peek(LitStr) {
1395            Ok(input.parse::<LitStr>()?.value())
1396        } else {
1397            let ident: Ident = input.parse()?;
1398            if ident == "name" {
1399                input.parse::<Token![=]>()?;
1400                Ok(input.parse::<LitStr>()?.value())
1401            } else {
1402                Err(syn::Error::new(
1403                    ident.span(),
1404                    "expected `name = \"...\"` for #[service]",
1405                ))
1406            }
1407        }
1408    })
1409}
1410
1411fn parse_type_value(input: ParseStream) -> Result<Type> {
1412    if input.peek(LitStr) {
1413        let lit: LitStr = input.parse()?;
1414        lit.parse()
1415    } else {
1416        input.parse()
1417    }
1418}
1419
1420fn type_to_key(ty: &Type) -> String {
1421    ty.to_token_stream().to_string().replace(' ', "")
1422}
1423
1424fn sanitize_ident(raw: &str) -> String {
1425    raw.chars()
1426        .map(|ch| match ch {
1427            '<' | '>' | ':' | ',' | '&' | '*' | '(' | ')' | '[' | ']' | '{' | '}' => '_',
1428            other if other.is_whitespace() => '_',
1429            other => other,
1430        })
1431        .collect()
1432}
1433
1434/// Function-like macro that declares ISSUN events with common derives.
1435///
1436/// Generates a struct definition for each event along with the required derives
1437/// and an implementation of [`issun::event::Event`].
1438#[proc_macro]
1439pub fn event(input: TokenStream) -> TokenStream {
1440    let input = parse_macro_input!(input as EventMacroInput);
1441    let crate_name = get_crate_name();
1442
1443    let expanded = input
1444        .events
1445        .into_iter()
1446        .map(|event| event.expand(&crate_name));
1447
1448    TokenStream::from(quote! {
1449        #(#expanded)*
1450    })
1451}
1452
1453struct EventMacroInput {
1454    events: Vec<EventDefinition>,
1455}
1456
1457impl Parse for EventMacroInput {
1458    fn parse(input: ParseStream) -> Result<Self> {
1459        let mut events = Vec::new();
1460        while !input.is_empty() {
1461            events.push(input.parse()?);
1462
1463            if input.peek(Token![,]) {
1464                input.parse::<Token![,]>()?;
1465            }
1466        }
1467        Ok(Self { events })
1468    }
1469}
1470
1471struct EventDefinition {
1472    attrs: Vec<Attribute>,
1473    additional_derives: Vec<Path>,
1474    visibility: Visibility,
1475    name: Ident,
1476    fields: EventFields,
1477}
1478
1479impl EventDefinition {
1480    fn expand(self, crate_name: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1481        let mut derives = vec![
1482            syn::parse_quote!(Debug),
1483            syn::parse_quote!(Clone),
1484            syn::parse_quote!(::serde::Serialize),
1485            syn::parse_quote!(::serde::Deserialize),
1486        ];
1487
1488        for path in self.additional_derives {
1489            push_unique_path(&mut derives, path);
1490        }
1491
1492        let derive_attr = quote! {
1493            #[derive(#(#derives),*)]
1494        };
1495
1496        let body = match self.fields {
1497            EventFields::Unit => quote!(;),
1498            EventFields::Struct(fields) => {
1499                let rendered_fields = fields.into_iter().map(|field| {
1500                    let attrs = field.attrs;
1501                    let vis = field.visibility;
1502                    let name = field.name;
1503                    let ty = field.ty;
1504                    quote! {
1505                        #(#attrs)*
1506                        #vis #name: #ty
1507                    }
1508                });
1509
1510                quote! {
1511                    {
1512                        #(#rendered_fields,)*
1513                    }
1514                }
1515            }
1516        };
1517
1518        let attrs = self.attrs;
1519        let vis = self.visibility;
1520        let name = self.name;
1521
1522        quote! {
1523            #(#attrs)*
1524            #derive_attr
1525            #vis struct #name #body
1526
1527            impl #crate_name::event::Event for #name {}
1528        }
1529    }
1530}
1531
1532impl Parse for EventDefinition {
1533    fn parse(input: ParseStream) -> Result<Self> {
1534        let outer_attrs = input.call(Attribute::parse_outer)?;
1535        let mut attrs = Vec::new();
1536        let mut derives = Vec::new();
1537
1538        for attr in outer_attrs {
1539            if attr.path().is_ident("derive") {
1540                let parsed: Punctuated<Path, Token![,]> =
1541                    attr.parse_args_with(Punctuated::parse_terminated)?;
1542                derives.extend(parsed.into_iter());
1543            } else {
1544                attrs.push(attr);
1545            }
1546        }
1547
1548        let visibility = if input.peek(Token![pub]) {
1549            input.parse()?
1550        } else {
1551            Visibility::Inherited
1552        };
1553
1554        if input.peek(Token![struct]) {
1555            input.parse::<Token![struct]>()?;
1556        }
1557
1558        let name: Ident = input.parse()?;
1559
1560        let fields = if input.peek(Token![;]) {
1561            input.parse::<Token![;]>()?;
1562            EventFields::Unit
1563        } else {
1564            let content;
1565            braced!(content in input);
1566
1567            let mut parsed_fields = Vec::new();
1568            while !content.is_empty() {
1569                let attrs = content.call(Attribute::parse_outer)?;
1570                if content.is_empty() {
1571                    return Err(content.error("expected field definition after attributes"));
1572                }
1573
1574                let visibility = if content.peek(Token![pub]) {
1575                    content.parse()?
1576                } else {
1577                    Visibility::Inherited
1578                };
1579                let name: Ident = content.parse()?;
1580                content.parse::<Token![:]>()?;
1581                let ty: Type = content.parse()?;
1582
1583                if content.peek(Token![,]) {
1584                    content.parse::<Token![,]>()?;
1585                } else if content.peek(Token![;]) {
1586                    content.parse::<Token![;]>()?;
1587                } else if !content.is_empty() {
1588                    return Err(content.error("expected `,` or `;` after field"));
1589                }
1590
1591                parsed_fields.push(EventField {
1592                    attrs,
1593                    visibility,
1594                    name,
1595                    ty,
1596                });
1597            }
1598
1599            EventFields::Struct(parsed_fields)
1600        };
1601
1602        if input.peek(Token![;]) {
1603            input.parse::<Token![;]>()?;
1604        }
1605
1606        Ok(Self {
1607            attrs,
1608            additional_derives: derives,
1609            visibility,
1610            name,
1611            fields,
1612        })
1613    }
1614}
1615
1616enum EventFields {
1617    Unit,
1618    Struct(Vec<EventField>),
1619}
1620
1621struct EventField {
1622    attrs: Vec<Attribute>,
1623    visibility: Visibility,
1624    name: Ident,
1625    ty: Type,
1626}
1627
1628fn push_unique_path(paths: &mut Vec<Path>, new_path: Path) {
1629    let repr = path_to_string(&new_path);
1630    if paths
1631        .iter()
1632        .all(|existing| path_to_string(existing) != repr)
1633    {
1634        paths.push(new_path);
1635    }
1636}
1637
1638fn path_to_string(path: &Path) -> String {
1639    path.to_token_stream().to_string()
1640}
1641
1642/// Attribute macro that injects `pump_event_systems` calls before/after input handlers.
1643#[proc_macro_attribute]
1644pub fn auto_pump(attr: TokenStream, item: TokenStream) -> TokenStream {
1645    let args = parse_macro_input!(attr as AutoPumpArgs);
1646
1647    let item_clone = item.clone();
1648    if let Ok(mut function) = syn::parse::<ItemFn>(item_clone) {
1649        match apply_auto_pump(&function.sig, &mut function.block, &args) {
1650            Ok(()) => return TokenStream::from(quote! { #function }),
1651            Err(err) => return err.to_compile_error().into(),
1652        }
1653    }
1654
1655    let mut method = parse_macro_input!(item as ImplItemFn);
1656    match apply_auto_pump(&method.sig, &mut method.block, &args) {
1657        Ok(()) => TokenStream::from(quote! { #method }),
1658        Err(err) => err.to_compile_error().into(),
1659    }
1660}
1661
1662#[derive(Clone)]
1663struct AutoPumpArgs {
1664    before: bool,
1665    after: bool,
1666    pump_fn: Option<Path>,
1667    sides_specified: bool,
1668}
1669
1670impl Default for AutoPumpArgs {
1671    fn default() -> Self {
1672        Self {
1673            before: true,
1674            after: true,
1675            pump_fn: None,
1676            sides_specified: false,
1677        }
1678    }
1679}
1680
1681impl Parse for AutoPumpArgs {
1682    fn parse(input: ParseStream) -> Result<Self> {
1683        if input.is_empty() {
1684            return Ok(Self::default());
1685        }
1686
1687        let mut args = AutoPumpArgs {
1688            before: false,
1689            after: false,
1690            pump_fn: None,
1691            sides_specified: false,
1692        };
1693
1694        while !input.is_empty() {
1695            let ident: Ident = input.parse()?;
1696            match ident.to_string().as_str() {
1697                "before" => {
1698                    args.before = true;
1699                    args.sides_specified = true;
1700                }
1701                "after" => {
1702                    args.after = true;
1703                    args.sides_specified = true;
1704                }
1705                "pump_fn" => {
1706                    input.parse::<Token![=]>()?;
1707                    let path = if input.peek(LitStr) {
1708                        input.parse::<LitStr>()?.parse::<Path>()?
1709                    } else {
1710                        input.parse::<Path>()?
1711                    };
1712                    args.pump_fn = Some(path);
1713                }
1714                other => {
1715                    return Err(syn::Error::new(
1716                        ident.span(),
1717                        format!("Unknown auto_pump option `{}`", other),
1718                    ));
1719                }
1720            }
1721
1722            if input.peek(Token![,]) {
1723                input.parse::<Token![,]>()?;
1724            }
1725        }
1726
1727        if !args.sides_specified {
1728            args.before = true;
1729            args.after = true;
1730        }
1731
1732        Ok(args)
1733    }
1734}
1735
1736struct PumpParams {
1737    services: Ident,
1738    systems: Ident,
1739    resources: Ident,
1740}
1741
1742fn apply_auto_pump(signature: &Signature, block: &mut Block, args: &AutoPumpArgs) -> Result<()> {
1743    if !args.before && !args.after {
1744        return Ok(());
1745    }
1746
1747    let params = extract_pump_params(signature)?;
1748    let pump_path = args.pump_fn.clone().unwrap_or_else(|| {
1749        syn::parse_str::<Path>("crate::plugins::pump_event_systems").expect("valid path")
1750    });
1751
1752    let mut original = mem::take(&mut block.stmts);
1753    let mut stmts = Vec::new();
1754
1755    // Add 'before' pump call
1756    if args.before {
1757        stmts.push(build_pump_stmt(&pump_path, &params));
1758    }
1759
1760    // Check if the last statement is a trailing expression (no semicolon)
1761    // If so, we need to insert 'after' pump BEFORE it to preserve return value
1762    let has_trailing_expr = original
1763        .last()
1764        .is_some_and(|stmt| matches!(stmt, Stmt::Expr(_, None)));
1765
1766    if args.after && has_trailing_expr {
1767        // Insert all but last statement
1768        if original.len() > 1 {
1769            stmts.extend(original.drain(..original.len() - 1));
1770        }
1771        // Insert 'after' pump
1772        stmts.push(build_pump_stmt(&pump_path, &params));
1773        // Insert trailing expression last
1774        stmts.extend(original);
1775    } else {
1776        // No trailing expression, just append everything
1777        stmts.extend(original);
1778        if args.after {
1779            stmts.push(build_pump_stmt(&pump_path, &params));
1780        }
1781    }
1782
1783    block.stmts = stmts;
1784    Ok(())
1785}
1786
1787fn build_pump_stmt(path: &Path, params: &PumpParams) -> Stmt {
1788    let services = &params.services;
1789    let systems = &params.systems;
1790    let resources = &params.resources;
1791
1792    // Use quote! to generate tokens, then parse into Stmt
1793    let tokens = quote! {
1794        #path(#services, #systems, #resources).await;
1795    };
1796    syn::parse2(tokens).expect("failed to parse pump statement")
1797}
1798
1799fn extract_pump_params(signature: &Signature) -> Result<PumpParams> {
1800    let mut services = None;
1801    let mut systems = None;
1802    let mut resources = None;
1803
1804    for input in signature.inputs.iter() {
1805        if let FnArg::Typed(pat_type) = input {
1806            if let Ok(ident) = extract_ident(&pat_type.pat) {
1807                if matches_context(&pat_type.ty, "ServiceContext") {
1808                    services = Some(ident);
1809                } else if matches_context(&pat_type.ty, "SystemContext") {
1810                    systems = Some(ident);
1811                } else if matches_context(&pat_type.ty, "ResourceContext") {
1812                    resources = Some(ident);
1813                }
1814            }
1815        }
1816    }
1817
1818    match (services, systems, resources) {
1819        (Some(sv), Some(sys), Some(res)) => Ok(PumpParams {
1820            services: sv,
1821            systems: sys,
1822            resources: res,
1823        }),
1824        _ => Err(syn::Error::new(
1825            signature.fn_token.span(),
1826            "#[auto_pump] requires parameters for ServiceContext, SystemContext, and ResourceContext",
1827        )),
1828    }
1829}
1830
1831fn matches_context(ty: &Type, expected: &str) -> bool {
1832    match ty {
1833        Type::Reference(reference) => match reference.elem.as_ref() {
1834            Type::Path(path) => path
1835                .path
1836                .segments
1837                .last()
1838                .map(|segment| segment.ident == expected)
1839                .unwrap_or(false),
1840            _ => false,
1841        },
1842        Type::Path(path) => path
1843            .path
1844            .segments
1845            .last()
1846            .map(|segment| segment.ident == expected)
1847            .unwrap_or(false),
1848        _ => false,
1849    }
1850}