Skip to main content

tailcall_proc_macro/
lib.rs

1//! This crate contains the procedural macro implementation for the
2//! <https://crates.io/crates/tailcall> crate.
3//! It is not designed to be used directly.
4
5#![deny(
6    missing_docs,
7    missing_debug_implementations,
8    missing_copy_implementations,
9    trivial_casts,
10    trivial_numeric_casts,
11    unsafe_code,
12    unstable_features,
13    unused_import_braces,
14    unused_qualifications
15)]
16
17extern crate proc_macro;
18
19mod analyze;
20mod call_syntax;
21mod expand;
22mod loop_lower;
23mod naming;
24mod rewrite;
25mod signature;
26
27use proc_macro::TokenStream;
28use syn::{parse_macro_input, ImplItemFn, ItemFn};
29
30/// Transforms a [function definition] so that explicit tail-call sites can execute without
31/// growing the call stack.
32///
33/// [function definition]: https://docs.rs/syn/1.0.9/syn/struct.ItemFn.html
34/// [tail recursion]: https://en.wikipedia.org/wiki/Tail_call
35///
36/// For simple free functions and inherent methods that only tail-call themselves directly, the
37/// macro lowers the item into an inline `loop`. More complex cases keep the hidden
38/// thunk-builder shape and run through `tailcall::runtime::Thunk`.
39///
40/// In other words, the macro preserves stack safety, but it does not always use the same runtime
41/// strategy. Some transformed functions compile down to a loop, while others bounce through the
42/// thunk runtime.
43///
44/// For methods, the optimized path aliases the receiver once, reuses the non-receiver arguments
45/// as mutable loop state, and rewrites each direct self tail call into "compute the next
46/// arguments, assign them, and continue".
47///
48/// # Example
49///
50/// ```ignore
51/// use tailcall::tailcall;
52///
53/// fn factorial(input: u64) -> u64 {
54///     #[tailcall]
55///     fn factorial_inner(accumulator: u64, input: u64) -> u64 {
56///         if input > 0 {
57///             tailcall::call! { factorial_inner(accumulator * input, input - 1) }
58///         } else {
59///             accumulator
60///         }
61///     }
62///
63///     factorial_inner(1, input)
64/// }
65/// ```
66///
67/// # Requirements
68///
69/// - Tail-call sites must be written with `tailcall::call!` and left in [tail form]:
70///   `tailcall::call!` currently supports direct function paths and method syntax on `self`.
71///
72/// ```compile_fail
73/// use tailcall::tailcall;
74///   
75/// #[tailcall]
76/// fn factorial(input: u64) -> u64 {
77///     if input > 0 {
78///         input * tailcall::call! { factorial(input - 1) }
79/// //      ^^^^^^^ This is not allowed.
80///     } else {
81///         1
82///     }
83/// }
84/// ```
85///
86/// ```compile_fail
87/// use tailcall::tailcall;
88///
89/// #[tailcall]
90/// fn factorial(input: u64) -> u64 {
91///     if input > 0 {
92///         1 + tailcall::call! { factorial(input - 1) }
93/// //          ^^^^^^^^^^^^^^^ This is not allowed.
94///     } else {
95///         1
96///     }
97/// }
98/// ```
99///
100/// - Function arguments must use simple identifier patterns:
101///
102/// ```compile_fail
103/// use tailcall::tailcall;
104///
105/// #[tailcall]
106/// fn sum((left, right): (u64, u64)) -> u64 {
107/// //      ^^^^^^^^^^^^^ Simple identifier arguments are required.
108///     left + right
109/// }
110/// ```
111///
112/// - Methods in `impl` blocks are supported, but trait methods are not:
113///
114/// ```compile_fail
115/// use tailcall::tailcall;
116///
117/// trait Factorialable {
118///     fn factorial(self) -> Self {
119///         self.calc_factorial(1)
120///     }
121///
122///     fn calc_factorial(self, accumulator: u64) -> u64;
123/// }
124///
125/// impl Factorialable for u64 {
126///     #[tailcall]
127///     fn calc_factorial(self, accumulator: u64) -> u64 {
128/// //                    ^^^^ Trait methods are not supported yet.
129///         if self > 0 {
130///             (self - 1).calc_factorial(self * accumulator)
131///         } else {
132///             accumulator
133///         }
134///     }
135/// }
136/// ```
137///
138/// - `async fn` and `const fn` are not supported:
139///
140/// ```compile_fail
141/// use tailcall::tailcall;
142///
143/// #[tailcall]
144/// async fn countdown(input: u64) -> u64 {
145///     if input > 0 {
146///         tailcall::call! { countdown(input - 1) }
147///     } else {
148///         0
149///     }
150/// }
151/// ```
152///
153/// ```compile_fail
154/// use tailcall::tailcall;
155///
156/// #[tailcall]
157/// const fn countdown(input: u64) -> u64 {
158///     if input > 0 {
159///         tailcall::call! { countdown(input - 1) }
160///     } else {
161///         0
162///     }
163/// }
164/// ```
165///
166/// [tail form]: https://en.wikipedia.org/wiki/Tail_call
167#[proc_macro_attribute]
168pub fn tailcall(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
169    let tokens_clone = tokens.clone();
170
171    let output = match syn::parse::<ImplItemFn>(tokens) {
172        Ok(input) => {
173            if matches!(input.sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
174                expand::apply_method_tailcall_transform(input)
175            } else {
176                let input = parse_macro_input!(tokens_clone as ItemFn);
177                expand::apply_fn_tailcall_transform(input)
178            }
179        }
180        _ => {
181            let input = parse_macro_input!(tokens_clone as ItemFn);
182            expand::apply_fn_tailcall_transform(input)
183        }
184    };
185
186    TokenStream::from(output)
187}
188
189/// Marks an explicit stack-safe tail-call site inside a `#[tailcall]` function.
190///
191/// The macro expects either a direct function call or a method call on `self`, such as:
192///
193/// ```ignore
194/// tailcall::call! { factorial_inner(acc * input, input - 1) }
195/// tailcall::call! { self.is_odd(x - 1) }
196/// ```
197///
198/// It expands to the hidden helper generated by the `#[tailcall]` attribute. Depending on the
199/// surrounding function, that helper may execute through the thunk runtime or be optimized away
200/// into direct loop lowering. The call site itself must remain in tail position.
201#[proc_macro]
202pub fn call(tokens: TokenStream) -> TokenStream {
203    TokenStream::from(call_syntax::expand_call_macro(tokens.into()))
204}