use proc_macro::TokenStream; use proc_macro2::Ident; use syn::__private::{quote, ToTokens}; use syn::spanned::Spanned; use syn::{parse_macro_input, FnArg, ItemFn, Pat}; pub fn builder_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut stream = proc_macro2::TokenStream::new(); let item_fn = parse_macro_input!(item as syn::ItemFn); stream.extend(item_fn.to_token_stream()); stream.extend( build_function(&item_fn).unwrap_or_else(|e| e.to_compile_error().to_token_stream()), ); stream.into() } fn build_function(item_fn: &ItemFn) -> Result { let mod_name = Ident::new( &format!("_{}", uuid::Uuid::new_v4().as_simple()), item_fn.span(), ); let fn_name = &item_fn.sig.ident; let fn_name_decorated = Ident::new(&format!("{fn_name}_bfn"), fn_name.span()); let args = &item_fn.sig.inputs.iter().collect::>(); let arg_names = args .iter() .map(|arg| match arg { FnArg::Receiver(_) => Ok(Ident::new("self", arg.span())), FnArg::Typed(ty) => match ty.pat.as_ref() { Pat::Ident(ident) => Ok(Ident::new(&ident.ident.to_string(), ident.span())), _ => Err(syn::Error::new( ty.span(), "unsupported argument formulation", )), }, }) .collect::, syn::Error>>()?; let attributes = item_fn.attrs.iter().collect::>(); if attributes.is_empty() { return Err(syn::Error::new( item_fn.span(), err("`#[builder_fn]` be attached to a function prior to the `#[pg_extern]` definition"), )); } let code = quote::quote! { mod #mod_name { use pgrx::{default, pg_extern, AnyElement, AnyNumeric, PostgresEnum, Range}; use crate::schema::AnyEnum; #(#attributes)* pub fn #fn_name_decorated(field: crate::api::FieldName, #(#args),*) -> crate::query::SearchQueryInput { crate::query::pdb_query::to_search_query_input(field, super::super::#fn_name(#(#arg_names),*)) } } }; Ok(code) } fn err(msg: &str) -> String { format!("`#[builder_fn]` requires that {msg}") }