tailcall-impl 2.0.0

The procedural macro implementation for the tailcall crate
Documentation
//! This crate contains the procedural macro implementation for the
//! <https://crates.io/crates/tailcall> crate.
//! It is not designed to be used directly.

#![deny(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

extern crate proc_macro;

mod helpers;
mod transforms;

use proc_macro::TokenStream;
use syn::{parse_macro_input, ImplItemMethod, ItemFn};

/// Transforms a [function definition] so that all recursive calls within the body are
/// guaranteed to use a single stack frame (via [tail recursion]).
///
/// [function definition]: https://docs.rs/syn/1.0.9/syn/struct.ItemFn.html
/// [tail recursion]: https://en.wikipedia.org/wiki/Tail_call
///
/// # Example
///
/// ```ignore
/// use tailcall::tailcall;
///
/// fn factorial(input: u64) -> u64 {
///     #[tailcall]
///     fn factorial_inner(accumulator: u64, input: u64) -> u64 {
///         if input > 0 {
///             tailcall::call! { factorial_inner(accumulator * input, input - 1) }
///         } else {
///             accumulator
///         }
///     }
///
///     factorial_inner(1, input)
/// }
/// ```
///
/// # Requirements
///
/// - Tail-call sites must be written with `tailcall::call!` and left in [tail form]:
///   `tailcall::call!` currently supports direct function paths and method syntax on `self`.
///
/// ```compile_fail
/// use tailcall::tailcall;
///   
/// #[tailcall]
/// fn factorial(input: u64) -> u64 {
///     if input > 0 {
///         input * tailcall::call! { factorial(input - 1) }
/// //      ^^^^^^^ This is not allowed.
///     } else {
///         1
///     }
/// }
/// ```
///
/// ```compile_fail
/// use tailcall::tailcall;
///
/// #[tailcall]
/// fn factorial(input: u64) -> u64 {
///     if input > 0 {
///         1 + tailcall::call! { factorial(input - 1) }
/// //          ^^^^^^^^^^^^^^^ This is not allowed.
///     } else {
///         1
///     }
/// }
/// ```
///
/// - Methods in `impl` blocks are supported, but trait methods are not:
///
/// ```compile_fail
/// use tailcall::tailcall;
///
/// trait Factorialable {
///     fn factorial(self) -> Self {
///         self.calc_factorial(1)
///     }
///
///     fn calc_factorial(self, accumulator: u64) -> u64;
/// }
///
/// impl Factorialable for u64 {
///     #[tailcall]
///     fn calc_factorial(self, accumulator: u64) -> u64 {
/// //                    ^^^^ Trait methods are not supported yet.
///         if self > 0 {
///             (self - 1).calc_factorial(self * accumulator)
///         } else {
///             accumulator
///         }
///     }
/// }
/// ```
///
/// [tail form]: https://en.wikipedia.org/wiki/Tail_call
#[proc_macro_attribute]
pub fn tailcall(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
    let tokens_clone = tokens.clone();

    let output = if let Ok(input) = syn::parse::<ImplItemMethod>(tokens) {
        if matches!(input.sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
            transforms::apply_method_tailcall_transform(input)
        } else {
            let input = parse_macro_input!(tokens_clone as ItemFn);
            transforms::apply_fn_tailcall_transform(input)
        }
    } else {
        let input = parse_macro_input!(tokens_clone as ItemFn);
        transforms::apply_fn_tailcall_transform(input)
    };

    TokenStream::from(output)
}

/// Marks an explicit trampoline-backed tail-call site inside a `#[tailcall]` function.
///
/// The macro expects either a direct function call or a method call on `self`, such as:
///
/// ```ignore
/// tailcall::call! { factorial_inner(acc * input, input - 1) }
/// tailcall::call! { self.is_odd(x - 1) }
/// ```
///
/// It expands to a call to the hidden trampoline builder generated by the `#[tailcall]`
/// attribute. The call site itself must remain in tail position.
#[proc_macro]
pub fn call(tokens: TokenStream) -> TokenStream {
    TokenStream::from(transforms::expand_call_macro(tokens.into()))
}