process_fun_macro/
lib.rs

1//! # process-fun-macro
2//!
3//! Procedural macros for the process-fun library. This crate provides the implementation
4//! of the `#[process]` attribute macro.
5//!
6//! This crate is not meant to be used directly - instead, use the `process-fun` crate
7//! which re-exports these macros in a more convenient way.
8
9use 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/// Attribute macro that creates an additional version of a function that executes in a separate process.
15///
16/// When applied to a function named `foo`, this macro:
17/// 1. Keeps the original function unchanged, allowing normal in-process calls
18/// 2. Creates a new function named `foo_process` that returns a ProcessWrapper
19///
20/// # Requirements
21///
22/// The function must:
23/// * Have arguments and return type that implement `Serialize` and `Deserialize`
24///
25#[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    // Check for duplicate process attributes
31    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    // Check for mutable arguments
53    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            // Create pipes for result and start time communication
121            #[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            // Fork the process
127            #[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                    // Parent process - close write ends immediately
132                    std::mem::drop(write_pipe);
133
134                    // Create ProcessWrapper with child pid and receiver
135                    Ok(process_fun::ProcessWrapper::new(child, read_pipe))
136                }
137                process_fun::sys::ForkResult::Child => {
138                    // Child process - close read ends immediately
139                    std::mem::drop(read_pipe);
140
141                    #[cfg(feature = "debug")]
142                    eprintln!("[process-fun-debug] Child process started");
143
144                    // Get and send start time by stating the child process
145                    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                    // Execute the function with the original arguments
156                    let result = #call;
157
158                    #[cfg(feature = "debug")]
159                    eprintln!("[process-fun-debug] Child process result: {:?}", &result);
160
161                    // Serialize and write result
162                    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                    // Exit child process
169                    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}