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}