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}