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}