noexcept_impl/
lib.rs

1#![cfg_attr(not(check_cfg), allow(unexpected_cfgs))]
2#![allow(
3    clippy::doc_markdown,
4    clippy::match_same_arms,
5    clippy::missing_panics_doc,
6    clippy::uninlined_format_args
7)]
8#![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))]
9
10use proc_macro::TokenStream;
11use proc_macro2::{Span, TokenStream as TokenStream2};
12use quote::quote;
13use syn::parse::{Error, Nothing, Result};
14use syn::{
15    parse_quote, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments,
16    ReturnType, Token, Type, TypeInfer, TypeParamBound,
17};
18
19#[proc_macro_attribute]
20pub fn abort_on_panic(args: TokenStream, input: TokenStream) -> TokenStream {
21    let args = TokenStream2::from(args);
22    let input = TokenStream2::from(input);
23    let expanded = match parse(args, input.clone()) {
24        Ok(function) => expand_abort_on_panic(function),
25        Err(parse_error) => {
26            let compile_error = parse_error.to_compile_error();
27            quote!(#compile_error #input)
28        }
29    };
30    TokenStream::from(expanded)
31}
32
33fn parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn> {
34    let function: ItemFn = syn::parse2(input)?;
35    let _: Nothing = syn::parse2::<Nothing>(args)?;
36    if function.sig.asyncness.is_some() {
37        return Err(Error::new(
38            Span::call_site(),
39            "abort_on_panic attribute on async fn is not supported",
40        ));
41    }
42    Ok(function)
43}
44
45// Convert `Path<impl Trait>` to `Path<_>`
46fn make_impl_trait_wild(ret: &mut Type) {
47    match ret {
48        #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
49        Type::ImplTrait(impl_trait) => {
50            *ret = Type::Infer(TypeInfer {
51                underscore_token: Token![_](impl_trait.impl_token.span),
52            });
53        }
54        Type::Array(ret) => make_impl_trait_wild(&mut ret.elem),
55        Type::Group(ret) => make_impl_trait_wild(&mut ret.elem),
56        Type::Paren(ret) => make_impl_trait_wild(&mut ret.elem),
57        Type::Path(ret) => make_impl_trait_wild_in_path(&mut ret.path),
58        Type::Ptr(ret) => make_impl_trait_wild(&mut ret.elem),
59        Type::Reference(ret) => make_impl_trait_wild(&mut ret.elem),
60        Type::Slice(ret) => make_impl_trait_wild(&mut ret.elem),
61        Type::TraitObject(ret) => {
62            for bound in &mut ret.bounds {
63                if let TypeParamBound::Trait(bound) = bound {
64                    make_impl_trait_wild_in_path(&mut bound.path);
65                }
66            }
67        }
68        Type::Tuple(ret) => ret.elems.iter_mut().for_each(make_impl_trait_wild),
69        Type::BareFn(_) | Type::Infer(_) | Type::Macro(_) | Type::Never(_) | Type::Verbatim(_) => {}
70        _ => {}
71    }
72}
73
74fn make_impl_trait_wild_in_path(path: &mut Path) {
75    for segment in &mut path.segments {
76        if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments {
77            for arg in &mut bracketed.args {
78                if let GenericArgument::Type(arg) = arg {
79                    make_impl_trait_wild(arg);
80                }
81            }
82        }
83    }
84}
85
86fn expand_abort_on_panic(mut function: ItemFn) -> TokenStream2 {
87    let mut move_self = None;
88    let mut arg_pat = Vec::new();
89    let mut arg_val = Vec::new();
90    for (i, input) in function.sig.inputs.iter_mut().enumerate() {
91        let numbered = Ident::new(&format!("__arg{}", i), Span::call_site());
92        match input {
93            FnArg::Typed(PatType { pat, .. })
94                if match pat.as_ref() {
95                    Pat::Ident(pat) => pat.ident != "self",
96                    _ => true,
97                } =>
98            {
99                arg_pat.push(quote!(#pat));
100                arg_val.push(quote!(#numbered));
101                *pat = parse_quote!(mut #numbered);
102            }
103            FnArg::Typed(_) | FnArg::Receiver(_) => {
104                move_self = Some(quote! {
105                    if false {
106                        loop {}
107                        #[allow(unreachable_code)]
108                        {
109                            let __self = self;
110                        }
111                    }
112                });
113            }
114        }
115    }
116
117    let ret = match &function.sig.output {
118        ReturnType::Default => quote!(-> ()),
119        ReturnType::Type(arrow, output) => {
120            let mut output = output.clone();
121            make_impl_trait_wild(&mut output);
122            quote!(#arrow #output)
123        }
124    };
125    let stmts = function.block.stmts;
126    function.block = Box::new(parse_quote!({
127        let __guard = ::noexcept::__private::AbortOnDrop;
128        let __result = (move || #ret {
129            #move_self
130            #(
131                let #arg_pat = #arg_val;
132            )*
133            #(#stmts)*
134        })();
135        ::core::mem::forget(__guard);
136        __result
137    }));
138
139    quote!(#function)
140}