1use proc_macro::TokenStream;
10use proc_macro_error::{proc_macro_error, Diagnostic, Level};
11use quote::{format_ident, quote};
12use syn::{parse_macro_input, spanned::Spanned, ItemFn, PatType, Type};
13
14#[proc_macro_error]
26#[proc_macro_attribute]
27pub fn process(_attr: TokenStream, item: TokenStream) -> TokenStream {
28 let input_fn = parse_macro_input!(item as ItemFn);
29
30 let process_attrs: Vec<_> = input_fn
32 .attrs
33 .iter()
34 .filter(|attr| attr.path().is_ident("process"))
35 .collect();
36
37 if process_attrs.len() > 1 {
38 panic!("#[process] can only be used once per function");
39 }
40
41 let fn_name = &input_fn.sig.ident;
42 let process_fn_name = format_ident!("{}_process", fn_name);
43 let fn_args = &input_fn.sig.inputs;
44 let generics = &input_fn.sig.generics;
45 let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
46
47 let fn_output = match &input_fn.sig.output {
48 syn::ReturnType::Default => quote!(()),
49 syn::ReturnType::Type(_, ty) => quote!(#ty),
50 };
51
52 for arg in fn_args.iter() {
54 if let syn::FnArg::Typed(PatType { ty, .. }) = arg {
55 if let Type::Reference(type_ref) = &**ty {
56 if type_ref.mutability.is_some() {
57 Diagnostic::spanned(
58 ty.span().unwrap().into(),
59 Level::Warning,
60 "Mutable variables changes will not be reflected in the parent process."
61 .to_string(),
62 )
63 .emit();
64 }
65 }
66 }
67 }
68
69 let mut self_stream = false;
70 let arg_names: Vec<_> = fn_args
71 .iter()
72 .filter_map(|arg| match arg {
73 syn::FnArg::Typed(pat_type) => {
74 if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
75 let id = pat_ident.ident.clone();
76 Some(quote!(#id))
77 } else {
78 panic!("Unsupported argument pattern")
79 }
80 }
81 syn::FnArg::Receiver(_) => {
82 self_stream = true;
83 None
84 }
85 })
86 .collect();
87
88 let arg_types: Vec<_> = fn_args
89 .iter()
90 .map(|arg| match arg {
91 syn::FnArg::Typed(pat_type) => pat_type.ty.clone(),
92 syn::FnArg::Receiver(receiver) => {
93 if let Some((_and_token, lifetime)) = &receiver.reference {
94 if receiver.mutability.is_some() {
95 syn::parse_quote!(&#lifetime mut Self)
96 } else {
97 syn::parse_quote!(&#lifetime Self)
98 }
99 } else {
100 syn::parse_quote!(Self)
101 }
102 }
103 })
104 .collect();
105
106 let args_types_tuple = quote! { (#(#arg_types),*) };
107 let fn_name_str = fn_name.to_string();
108
109 let call = if self_stream {
110 quote!(self.#fn_name(#(#arg_names),*))
111 } else {
112 quote!(#fn_name(#(#arg_names),*))
113 };
114
115 let expanded = quote! {
116 #input_fn
117
118 #[allow(non_snake_case)]
119 pub fn #process_fn_name #ty_generics(#fn_args) -> Result<process_fun::ProcessWrapper<#fn_output>, process_fun::ProcessFunError> #where_clause {
120 #[cfg(feature = "debug")]
122 eprintln!("[process-fun-debug] Creating pipes for process function: {}", #fn_name_str);
123
124 let (mut read_pipe, mut write_pipe) = process_fun::create_pipes()?;
125
126 #[cfg(feature = "debug")]
128 eprintln!("[process-fun-debug] Forking process for function: {}", #fn_name_str);
129 match process_fun::fork_process()? {
130 process_fun::sys::ForkResult::Parent { child } => {
131 std::mem::drop(write_pipe);
133
134 Ok(process_fun::ProcessWrapper::new(child, read_pipe))
136 }
137 process_fun::sys::ForkResult::Child => {
138 std::mem::drop(read_pipe);
140
141 #[cfg(feature = "debug")]
142 eprintln!("[process-fun-debug] Child process started");
143
144 let pid = process_fun::sys::getpid();
146 let start_time = process_fun::stat_pid_start(pid)?;
147 process_fun::write_time(&mut write_pipe, start_time)?;
148
149 #[cfg(feature = "debug")]
150 {
151 eprintln!("[process-fun-debug] Processing function: {}", &#fn_name_str);
152 eprintln!("[process-fun-debug] Arguments tuple type: {}", stringify!(#args_types_tuple));
153 }
154
155 let result = #call;
157
158 #[cfg(feature = "debug")]
159 eprintln!("[process-fun-debug] Child process result: {:?}", &result);
160
161 let result_bytes = process_fun::ser::to_vec(&result)?;
163 process_fun::write_to_pipe(write_pipe, &result_bytes)?;
164
165 #[cfg(feature = "debug")]
166 eprintln!("[process-fun-debug] Child process completed");
167
168 std::process::exit(0);
170 }
171 }
172 }
173 };
174
175 #[cfg(feature = "debug")]
176 {
177 dbg!(expanded.to_string());
178 }
179
180 TokenStream::from(expanded)
181}