stacksafe_macro/
lib.rs

1//! Procedural macro implementation for the `stacksafe` crate.
2//!
3//! This crate provides the `#[stacksafe]` attribute macro that transforms functions
4//! to use automatic stack growth, preventing stack overflow in deeply recursive scenarios.
5
6use proc_macro::TokenStream;
7use proc_macro_error2::abort;
8use proc_macro_error2::abort_call_site;
9use proc_macro_error2::proc_macro_error;
10use quote::ToTokens;
11use quote::quote;
12use syn::ItemFn;
13use syn::Path;
14use syn::ReturnType;
15use syn::Type;
16use syn::parse_macro_input;
17use syn::parse_quote;
18
19/// Attribute macro for automatic stack overflow prevention in recursive functions.
20///
21/// This macro transforms functions to automatically check available stack space
22/// and allocate new stack segments when needed, preventing stack overflow in
23/// deeply recursive scenarios.
24///
25/// # Parameters
26///
27/// The macro accepts an optional `crate` parameter to specify the path to the
28/// stacksafe crate:
29///
30/// ```rust
31/// use stacksafe::stacksafe;
32///
33/// #[stacksafe(crate = stacksafe)]
34/// fn my_function() {
35///     // function body
36/// }
37/// ```
38///
39/// # Examples
40///
41/// ```rust
42/// use stacksafe::stacksafe;
43///
44/// #[stacksafe]
45/// fn factorial(n: u64) -> u64 {
46///     if n <= 1 { 1 } else { n * factorial(n - 1) }
47/// }
48///
49/// #[stacksafe]
50/// fn tree_depth<T>(node: &Option<Box<TreeNode<T>>>) -> usize {
51///     match node {
52///         None => 0,
53///         Some(n) => 1 + tree_depth(&n.left).max(tree_depth(&n.right)),
54///     }
55/// }
56/// ```
57///
58/// # Limitations
59///
60/// - Cannot be applied to `async` functions
61/// - Functions with `impl Trait` return types may need type annotations
62/// - Adds small runtime overhead for stack size checking
63#[proc_macro_attribute]
64#[proc_macro_error]
65pub fn stacksafe(args: TokenStream, item: TokenStream) -> TokenStream {
66    let mut crate_path: Option<Path> = None;
67
68    let arg_parser = syn::meta::parser(|meta| {
69        if meta.path.is_ident("crate") {
70            crate_path = Some(meta.value()?.parse()?);
71            Ok(())
72        } else {
73            Err(meta.error(format!(
74                "unknown attribute parameter `{}`",
75                meta.path
76                    .get_ident()
77                    .map_or("unknown".to_string(), |i| i.to_string())
78            )))
79        }
80    });
81    parse_macro_input!(args with arg_parser);
82
83    let item_fn: ItemFn = match syn::parse(item.clone()) {
84        Ok(item) => item,
85        Err(_) => abort_call_site!("#[stacksafe] can only be applied to functions"),
86    };
87
88    if item_fn.sig.asyncness.is_some() {
89        abort!(
90            item_fn.sig.asyncness,
91            "#[stacksafe] does not support async functions"
92        );
93    }
94
95    let mut item_fn = item_fn;
96    let block = item_fn.block;
97    let ret = match &item_fn.sig.output {
98        // impl trait is not supported in closure return type, override with
99        // default, which is inferring.
100        ReturnType::Type(_, typ) if matches!(**typ, Type::ImplTrait(_)) => ReturnType::Default,
101        _ => item_fn.sig.output.clone(),
102    };
103
104    let stacksafe_crate = crate_path.unwrap_or_else(|| parse_quote!(::stacksafe));
105
106    let wrapped_block = quote! {
107        {
108            #stacksafe_crate::internal::stacker::maybe_grow(
109                #stacksafe_crate::get_minimum_stack_size(),
110                #stacksafe_crate::get_stack_allocation_size(),
111                #stacksafe_crate::internal::with_protected(move || #ret { #block })
112            )
113        }
114    };
115    item_fn.block = Box::new(syn::parse(wrapped_block.into()).unwrap());
116    item_fn.into_token_stream().into()
117}