| 10 | |
| 11 | #[proc_macro_attribute] |
| 12 | pub fn plugin_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { |
| 13 | let input = parse_macro_input!(item as ItemFn); |
| 14 | let user_vis = &input.vis; |
| 15 | let user_name = input.sig.ident.clone(); |
| 16 | let user_inputs = input.sig.inputs.clone(); |
| 17 | let user_output = input.sig.output.clone(); |
| 18 | let user_block = input.block.clone(); |
| 19 | |
| 20 | // Wrapper claims the original name; user fn moves to `__edge_impl_<name>`. |
| 21 | let impl_name = syn::Ident::new( |
| 22 | &format!("__edge_impl_{}", user_name), |
| 23 | proc_macro2::Span::call_site(), |
| 24 | ); |
| 25 | |
| 26 | let mut bindings: Vec<(syn::Ident, syn::Type)> = Vec::new(); |
| 27 | for (i, arg) in user_inputs.iter().enumerate() { |
| 28 | match arg { |
| 29 | FnArg::Typed(pat) => { |
| 30 | let name = match &*pat.pat { |
| 31 | Pat::Ident(id) => id.ident.clone(), |
| 32 | _ => syn::Ident::new(&format!("__arg{}", i), proc_macro2::Span::call_site()), |
| 33 | }; |
| 34 | bindings.push((name, (*pat.ty).clone())); |
| 35 | } |
| 36 | FnArg::Receiver(_) => { |
| 37 | return TokenStream::from(quote! { |
| 38 | compile_error!("#[plugin_fn] does not support methods (`self` parameter)"); |
| 39 | }); |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | let return_ty: syn::Type = match &user_output { |
| 45 | ReturnType::Default => syn::parse_quote!(()), |
| 46 | ReturnType::Type(_, t) => (**t).clone(), |
| 47 | }; |
| 48 | let is_result = matches!(&return_ty, syn::Type::Path(p) if p.path.segments.last().map(|s| s.ident == "Result").unwrap_or(false)); |
| 49 | |
| 50 | // Host always appends a trailing kwargs slot. Param order: fixed positionals, optional `Args`, optional `Kwargs`. |
| 51 | let type_named = |ty: &syn::Type, n: &str| matches!(ty, Type::Path(p) if p.path.segments.last().map(|s| s.ident == n).unwrap_or(false)); |
| 52 | let last_is_kwargs = bindings.last().map(|(_, ty)| type_named(ty, "Kwargs")).unwrap_or(false); |
| 53 | let args_idx = bindings.iter().position(|(_, ty)| type_named(ty, "Args")); |
| 54 | let has_args = args_idx.is_some(); |
| 55 | // Fixed positionals precede the optional variadic `Args` and the optional trailing `Kwargs`. |
| 56 | let num_fixed = bindings.len() - (has_args as usize) - (last_is_kwargs as usize); |
| 57 | |
| 58 | if args_idx.is_some_and(|idx| idx != num_fixed) { |
| 59 | return TokenStream::from(quote! { compile_error!("#[plugin_fn] `Args` must follow the fixed params, before any `Kwargs`"); }); |
| 60 | } |
| 61 | if bindings.iter().take(bindings.len().saturating_sub(1)).any(|(_, ty)| type_named(ty, "Kwargs")) { |
| 62 | return TokenStream::from(quote! { compile_error!("#[plugin_fn] `Kwargs` must be the final parameter"); }); |
| 63 | } |
| 64 | |
| 65 | let decodes: Vec<TokenStream2> = bindings.iter().enumerate().map(|(i, (name, ty))| { |
| 66 | if type_named(ty, "Kwargs") { |
| 67 | // The kwargs handle is always the final argv slot. |
| 68 | quote! { |
| 69 | let h = unsafe { *argv.add((argc as usize) - 1) }; |