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}