rust-try-catch-macros 0.1.0

try catch proc macros
Documentation
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};
use syn::{parse_macro_input, parse_quote, ExprClosure, ItemFn};
use syn::spanned::Spanned;
use crate::parse::FnOrClosure;

mod parse;


#[proc_macro_attribute]
pub fn throw_guard(attr: TokenStream1, item: TokenStream1) -> TokenStream1 {
    if !attr.is_empty() {
        let message = format!("Unexpected attributes \"{attr}\"");
        return syn::Error::new(TokenStream::from(attr).span(), message)
            .into_compile_error()
            .into()
    }
    
    let tokens = match parse_macro_input!(item as FnOrClosure) {
        FnOrClosure::Function(func) => wrap_func(func),
        FnOrClosure::Closure(closure) => wrap_closure(closure),
    };
    
    tokens.into()
}


#[proc_macro]
pub fn closure_throw_guard(item: TokenStream1) -> TokenStream1 {
    wrap_closure(parse_macro_input!(item as ExprClosure)).into()
}

fn throw_guard_body(async_ness: Option<&syn::Token![async]>, fn_body: TokenStream) -> TokenStream {
    let span = fn_body.span();
    match &async_ness {
        Some(_) => quote_spanned! { span=>
            {
                let mut fut = ::core::pin::pin!(async { #fn_body });
                ::core::future::poll_fn(move |cx| {
                    ::rust_try_catch::__throw_driver(|| {
                        ::core::future::Future::poll(::core::pin::Pin::as_mut(&mut fut), cx)
                    })
                }).await
            }
        },
        None => quote_spanned! { span=> ::rust_try_catch::__throw_driver(|| #fn_body) },
    }
}

fn wrap_closure(closure: ExprClosure) -> TokenStream {
    if let Some(token) = closure.constness {
        return syn::Error::new(token.span, "can't drive try catch logic in const")
            .into_compile_error()
    }
    let body_tokens = throw_guard_body(closure.asyncness.as_ref(), closure.body.into_token_stream());
    
    let closure = ExprClosure {
        body: parse_quote! { #body_tokens },
        ..closure
    };

    closure.into_token_stream()
}

fn wrap_func(input: ItemFn) -> TokenStream {
    if let Some(token) = input.sig.constness {
        return syn::Error::new(token.span, "can't drive try catch logic in const")
            .into_compile_error()
    }
    
    if let Some(variadic) = input.sig.variadic { 
        return syn::Error::new(variadic.span(), "using variadic arguments would cause UB!!!")
            .into_compile_error()
    }
    
    let outer_fn_body = throw_guard_body(input.sig.asyncness.as_ref(), input.block.into_token_stream());
    
    let new_fn = ItemFn {
        attrs: input.attrs,
        vis: input.vis,
        sig: input.sig,
        block: parse_quote!({ #outer_fn_body }),
    };
    
    new_fn.into_token_stream()
}