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