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