wowlab_engine_macros_impl/
spec_handler_delegation.rs1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{FnArg, ItemTrait, Pat, ReturnType, TraitItem, Type, visit_mut::VisitMut as _};
4
5const TYPES_CRATE_TYPES: &[&str] = &[
6 "ActorId",
7 "EnemyIdx",
8 "GroupId",
9 "PositionedActorRef",
10 "SimTime",
11 "SpatialTransform",
12 "SpellIdx",
13 "WaveId",
14];
15
16fn qualify_path_root(path: &mut syn::Path) {
17 if path.leading_colon.is_some() || path.segments.len() != 1 {
18 return;
19 }
20
21 let ident = path.segments[0].ident.to_string();
22
23 if TYPES_CRATE_TYPES.contains(&ident.as_str()) {
24 let original = path.segments[0].clone();
25
26 *path = syn::parse_quote!(::wowlab_types::sim::#original);
27 } else if !matches!(
28 ident.as_str(),
29 "bool"
30 | "dyn"
31 | "f32"
32 | "f64"
33 | "Fn"
34 | "FnMut"
35 | "FnOnce"
36 | "i8"
37 | "i16"
38 | "i32"
39 | "i64"
40 | "i128"
41 | "isize"
42 | "Option"
43 | "Result"
44 | "Self"
45 | "String"
46 | "u8"
47 | "u16"
48 | "u32"
49 | "u64"
50 | "u128"
51 | "usize"
52 | "Vec"
53 ) {
54 let original = path.segments[0].clone();
55
56 *path = syn::parse_quote!(::wowlab_engine_ports::#original);
57 }
58}
59
60struct TypeQualifier;
61
62impl syn::visit_mut::VisitMut for TypeQualifier {
63 fn visit_path_mut(&mut self, i: &mut syn::Path) {
64 syn::visit_mut::visit_path_mut(self, i);
65 qualify_path_root(i);
66 }
67}
68
69fn qualify_type(ty: &mut Type) {
70 TypeQualifier.visit_type_mut(ty);
71}
72
73fn delegated_method(item: &syn::TraitItemFn) -> syn::Result<TokenStream> {
74 let mut signature = item.sig.clone();
75 let mut arguments = Vec::with_capacity(signature.inputs.len());
76
77 for input in &mut signature.inputs {
78 match input {
79 FnArg::Receiver(_) => {}
80 FnArg::Typed(argument) => {
81 qualify_type(&mut argument.ty);
82
83 let Pat::Ident(pattern) = argument.pat.as_ref() else {
84 return Err(syn::Error::new_spanned(
85 &argument.pat,
86 "SpecHandler delegation requires identifier argument patterns",
87 ));
88 };
89
90 arguments.push(quote!(#pattern));
91 }
92 }
93 }
94
95 if let ReturnType::Type(_, output) = &mut signature.output {
96 qualify_type(output);
97 }
98
99 let method = &signature.ident;
100
101 Ok(quote! {
102 #signature {
103 self.$inner.#method(#(#arguments),*)
104 }
105 })
106}
107
108pub(crate) fn expand_spec_handler_delegation(
109 _attribute: TokenStream,
110 mut item: ItemTrait,
111) -> syn::Result<TokenStream> {
112 let mut helpers = Vec::with_capacity(item.items.len());
113 let mut helper_calls = Vec::with_capacity(item.items.len());
114
115 for trait_item in &item.items {
116 let TraitItem::Fn(method) = trait_item else {
117 continue;
118 };
119
120 let name = &method.sig.ident;
121 let helper = format_ident!("__wowlab_delegate_spec_handler_{name}");
122 let delegated = delegated_method(method)?;
123
124 helpers.push(quote! {
125 #[doc(hidden)]
126 #[macro_export]
127 macro_rules! #helper {
128 ($inner:ident; #name $(, $rest:ident)*) => {};
129 ($inner:ident; $other:ident $(, $rest:ident)*) => {
130 $crate::#helper!($inner; $($rest),*);
131 };
132 ($inner:ident;) => {
133 #delegated
134 };
135 }
136 });
137 helper_calls.push(quote! {
138 $crate::#helper!($inner; $($skip),*);
139 });
140 }
141
142 item.attrs
143 .retain(|attr| !attr.path().is_ident("spec_handler_delegation"));
144
145 Ok(quote! {
146 #item
147
148 #(#helpers)*
149
150 #[doc(hidden)]
152 #[macro_export]
153 macro_rules! delegate_spec_handler {
154 ($inner:ident; skip [$($skip:ident),* $(,)?]) => {
155 #(#helper_calls)*
156 };
157 }
158 })
159}
160
161#[cfg(test)]
162mod tests {
163 use googletest::prelude::*;
164 use syn::parse_quote;
165
166 use super::*;
167
168 #[gtest]
169 fn trait_methods_drive_forwarder_signatures() -> Result<()> {
170 let item: ItemTrait = parse_quote! {
171 pub trait SpecHandler {
172 fn on_event(&mut self, event: Event, ctx: &mut SimContext);
173 fn total_damage(&self) -> f64;
174 }
175 };
176
177 let output = expand_spec_handler_delegation(TokenStream::new(), item)?.to_string();
178
179 verify_that!(
180 output,
181 contains_substring("fn on_event (& mut self , event : :: wowlab_engine_ports :: Event")
182 )?;
183 verify_that!(output, contains_substring("self . $ inner . on_event"))?;
184
185 verify_that!(
186 output,
187 contains_substring("macro_rules ! delegate_spec_handler")
188 )
189 }
190}