Skip to main content

tailcall_impl/
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, ImplItemMethod, ItemFn};
29
30/// Transforms a [function definition] so that all recursive calls within the body are
31/// guaranteed to use a single stack frame (via [tail recursion]).
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/// For methods, the optimized path aliases the receiver once, reuses the non-receiver arguments
41/// as mutable loop state, and rewrites each direct self tail call into "compute the next
42/// arguments, assign them, and continue".
43///
44/// # Example
45///
46/// ```ignore
47/// use tailcall::tailcall;
48///
49/// fn factorial(input: u64) -> u64 {
50///     #[tailcall]
51///     fn factorial_inner(accumulator: u64, input: u64) -> u64 {
52///         if input > 0 {
53///             tailcall::call! { factorial_inner(accumulator * input, input - 1) }
54///         } else {
55///             accumulator
56///         }
57///     }
58///
59///     factorial_inner(1, input)
60/// }
61/// ```
62///
63/// # Requirements
64///
65/// - Tail-call sites must be written with `tailcall::call!` and left in [tail form]:
66///   `tailcall::call!` currently supports direct function paths and method syntax on `self`.
67///
68/// ```compile_fail
69/// use tailcall::tailcall;
70///   
71/// #[tailcall]
72/// fn factorial(input: u64) -> u64 {
73///     if input > 0 {
74///         input * tailcall::call! { factorial(input - 1) }
75/// //      ^^^^^^^ This is not allowed.
76///     } else {
77///         1
78///     }
79/// }
80/// ```
81///
82/// ```compile_fail
83/// use tailcall::tailcall;
84///
85/// #[tailcall]
86/// fn factorial(input: u64) -> u64 {
87///     if input > 0 {
88///         1 + tailcall::call! { factorial(input - 1) }
89/// //          ^^^^^^^^^^^^^^^ This is not allowed.
90///     } else {
91///         1
92///     }
93/// }
94/// ```
95///
96/// - Function arguments must use simple identifier patterns:
97///
98/// ```compile_fail
99/// use tailcall::tailcall;
100///
101/// #[tailcall]
102/// fn sum((left, right): (u64, u64)) -> u64 {
103/// //      ^^^^^^^^^^^^^ Simple identifier arguments are required.
104///     left + right
105/// }
106/// ```
107///
108/// - Methods in `impl` blocks are supported, but trait methods are not:
109///
110/// ```compile_fail
111/// use tailcall::tailcall;
112///
113/// trait Factorialable {
114///     fn factorial(self) -> Self {
115///         self.calc_factorial(1)
116///     }
117///
118///     fn calc_factorial(self, accumulator: u64) -> u64;
119/// }
120///
121/// impl Factorialable for u64 {
122///     #[tailcall]
123///     fn calc_factorial(self, accumulator: u64) -> u64 {
124/// //                    ^^^^ Trait methods are not supported yet.
125///         if self > 0 {
126///             (self - 1).calc_factorial(self * accumulator)
127///         } else {
128///             accumulator
129///         }
130///     }
131/// }
132/// ```
133///
134/// - `async fn` and `const fn` are not supported:
135///
136/// ```compile_fail
137/// use tailcall::tailcall;
138///
139/// #[tailcall]
140/// async fn countdown(input: u64) -> u64 {
141///     if input > 0 {
142///         tailcall::call! { countdown(input - 1) }
143///     } else {
144///         0
145///     }
146/// }
147/// ```
148///
149/// ```compile_fail
150/// use tailcall::tailcall;
151///
152/// #[tailcall]
153/// const fn countdown(input: u64) -> u64 {
154///     if input > 0 {
155///         tailcall::call! { countdown(input - 1) }
156///     } else {
157///         0
158///     }
159/// }
160/// ```
161///
162/// [tail form]: https://en.wikipedia.org/wiki/Tail_call
163#[proc_macro_attribute]
164pub fn tailcall(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
165    let tokens_clone = tokens.clone();
166
167    let output = if let Ok(input) = syn::parse::<ImplItemMethod>(tokens) {
168        if matches!(input.sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
169            expand::apply_method_tailcall_transform(input)
170        } else {
171            let input = parse_macro_input!(tokens_clone as ItemFn);
172            expand::apply_fn_tailcall_transform(input)
173        }
174    } else {
175        let input = parse_macro_input!(tokens_clone as ItemFn);
176        expand::apply_fn_tailcall_transform(input)
177    };
178
179    TokenStream::from(output)
180}
181
182/// Marks an explicit trampoline-backed tail-call site inside a `#[tailcall]` function.
183///
184/// The macro expects either a direct function call or a method call on `self`, such as:
185///
186/// ```ignore
187/// tailcall::call! { factorial_inner(acc * input, input - 1) }
188/// tailcall::call! { self.is_odd(x - 1) }
189/// ```
190///
191/// It expands to a call to the hidden trampoline builder generated by the `#[tailcall]`
192/// attribute. The call site itself must remain in tail position.
193#[proc_macro]
194pub fn call(tokens: TokenStream) -> TokenStream {
195    TokenStream::from(call_syntax::expand_call_macro(tokens.into()))
196}