method_chaining 0.1.1

A Rust procedural macro that automatically makes functions and structs chainable.
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, visit_mut::VisitMut, Expr, ItemFn, ReturnType, Stmt};

struct ReturnVisitor {
    errors: Vec<syn::Error>,
}

impl ReturnVisitor {
    fn new() -> Self {
        Self { errors: Vec::new() }
    }
}

impl VisitMut for ReturnVisitor {
    fn visit_expr_mut(&mut self, expr: &mut Expr) {
        match expr {
            Expr::Return(return_expr) => {
                if return_expr.expr.is_some() {
                    self.errors.push(syn::Error::new_spanned(
                        return_expr.expr.clone(),
                        "chainable functions should not have explicit return values",
                    ));
                }
                return_expr.expr = Some(Box::new(syn::parse_quote!(self)));
            }
            _ => {
                syn::visit_mut::visit_expr_mut(self, expr);
            }
        }
    }
}

pub fn make_function_chainable(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut input_fn = parse_macro_input!(item as ItemFn);

    // Check if the function has any arguments
    if input_fn.sig.inputs.is_empty() {
        let error = syn::Error::new_spanned(
            &input_fn.sig.ident,
            "chainable functions must have at least one argument",
        )
        .to_compile_error();
        return quote! {
            #error
            #input_fn
        }
        .into();
    }

    // Check if the first argument is `&mut self` or `mut self`
    let (is_ref, is_mut_self) = input_fn
        .sig
        .inputs
        .first()
        .map(|arg| match arg {
            syn::FnArg::Receiver(recv) => (recv.reference.is_some(), recv.mutability.is_some()),
            _ => (false, false),
        })
        .unwrap();

    if !is_mut_self {
        let error = syn::Error::new_spanned(
            &input_fn.sig.inputs[0],
            "expected '&mut self' or 'mut self'",
        )
        .to_compile_error();
        return quote! {
            #error
            #input_fn
        }
        .into();
    }

    // Check if the function has an explicit return type
    match &input_fn.sig.output {
        ReturnType::Type(_, _) => {
            let error = syn::Error::new_spanned(
                &input_fn.sig.output,
                "chainable functions should not have explicit return types",
            )
            .to_compile_error();
            return quote! {
                #error
                #input_fn
            }
            .into();
        }
        ReturnType::Default => {} // No explicit return type, proceed
    }

    // Change the return type to `Self` or `&mut Self`
    input_fn.sig.output = if is_ref {
        syn::parse_quote!(-> &mut Self)
    } else {
        syn::parse_quote!(-> Self)
    };

    // Visit the function body to ensure it does not contain any explicit return statements and change any return expressions to return `self`
    let mut visitor = ReturnVisitor::new();
    visitor.visit_block_mut(&mut input_fn.block);
    if !visitor.errors.is_empty() {
        let error = visitor.errors[0].to_compile_error();
        return quote! {
            #error
            #input_fn
        }
        .into();
    }

    // Ensure the last statement is a return statement returning `self`
    if let Some(Stmt::Expr(Expr::Return(_), _)) = input_fn.block.stmts.last() {
    } else {
        let return_stmt: Stmt = syn::parse_quote!(return self;);
        input_fn.block.stmts.push(return_stmt);
    }

    let result = quote! {
        #input_fn
    };

    result.into()
}