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/// - Methods in `impl` blocks are supported, but trait methods are not:
87///
88/// ```compile_fail
89/// use tailcall::tailcall;
90///
91/// trait Factorialable {
92/// fn factorial(self) -> Self {
93/// self.calc_factorial(1)
94/// }
95///
96/// fn calc_factorial(self, accumulator: u64) -> u64;
97/// }
98///
99/// impl Factorialable for u64 {
100/// #[tailcall]
101/// fn calc_factorial(self, accumulator: u64) -> u64 {
102/// // ^^^^ Trait methods are not supported yet.
103/// if self > 0 {
104/// (self - 1).calc_factorial(self * accumulator)
105/// } else {
106/// accumulator
107/// }
108/// }
109/// }
110/// ```
111///
112/// [tail form]: https://en.wikipedia.org/wiki/Tail_call
113#[proc_macro_attribute]
114pub fn tailcall(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
115 let tokens_clone = tokens.clone();
116
117 let output = if let Ok(input) = syn::parse::<ImplItemMethod>(tokens) {
118 if matches!(input.sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
119 expand::apply_method_tailcall_transform(input)
120 } else {
121 let input = parse_macro_input!(tokens_clone as ItemFn);
122 expand::apply_fn_tailcall_transform(input)
123 }
124 } else {
125 let input = parse_macro_input!(tokens_clone as ItemFn);
126 expand::apply_fn_tailcall_transform(input)
127 };
128
129 TokenStream::from(output)
130}
131
132/// Marks an explicit trampoline-backed tail-call site inside a `#[tailcall]` function.
133///
134/// The macro expects either a direct function call or a method call on `self`, such as:
135///
136/// ```ignore
137/// tailcall::call! { factorial_inner(acc * input, input - 1) }
138/// tailcall::call! { self.is_odd(x - 1) }
139/// ```
140///
141/// It expands to a call to the hidden trampoline builder generated by the `#[tailcall]`
142/// attribute. The call site itself must remain in tail position.
143#[proc_macro]
144pub fn call(tokens: TokenStream) -> TokenStream {
145 TokenStream::from(call_syntax::expand_call_macro(tokens.into()))
146}