1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_quote, FnArg, Ident, PatType, ReturnType};

/// This macro is intended to support the `IonCReaderHandle` type. When any
/// function returns an `IonCError`, the error should be populated with the
/// current reader position.
///
/// Without this macro, errors could be decorated like so:
///
/// ```no_test
/// fn foo() -> IonCResult<()> {
///     fn1().map_err(|err| include_current_position(self.reader, err))?
///     // ...
///     Ok(())
/// }
/// ```
///
/// However, functions can fail in many places, so decorating each of these with
/// `map_err` is tedious and error prone:
///
/// ```no_test
/// fn foo() -> IonCResult<()> {
///   fn1()?.map_err(..);
///   fn2()?.map_err(..);
/// }
/// ```
///
/// This macro rewrites the function such that any error gets the position
/// added. It does this by moving the existing implementation into a closure and
/// then calling `map_err` on the result.
///
/// ***Note***: to make the variety of possible functions work (which include mutable
/// borrows, lifetime elision), the closure uses the `move` keyword. There are
/// some gymnastics (adapted from ***[dtolnay/no-panic][no-panic]***) to make that work out.
///
/// [no-panic]: https://github.com/dtolnay/no-panic
#[proc_macro_attribute]
pub fn position_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut function = syn::parse_macro_input!(item as syn::ItemFn);

    // this is heavily adapted for dtolnay/panic - as this code evolves to deal
    // with rustc/ion-c-sys changes refer to to the above for any changes
    // here...
    let mut move_self = None;
    let mut arg_pat = Vec::new();
    let mut arg_val = Vec::new();
    for (i, input) in function.sig.inputs.iter_mut().enumerate() {
        let numbered = Ident::new(&format!("__arg{}", i), Span::call_site());
        match input {
            FnArg::Typed(PatType { pat, .. }) => {
                arg_pat.push(quote!(#pat));
                arg_val.push(quote!(#numbered));
                *pat = parse_quote!(mut #numbered);
            }
            FnArg::Receiver(_) => {
                move_self = Some(quote! {
                    if false {
                        loop {}
                        #[allow(unreachable_code)]
                        {
                            let __self = self;
                        }
                    }
                });
            }
        }
    }

    let ret = match &function.sig.output {
        ReturnType::Default => quote!(-> ()),
        output @ ReturnType::Type(..) => quote!(#output),
    };
    let stmts = function.block.stmts;
    function.block = Box::new(parse_quote!({
        let reader = self.reader;
        let __result = (move || #ret {
            #move_self
            #(
                let #arg_pat = #arg_val;
            )*
            #(#stmts)*
        })();
        __result.map_err(|err| include_current_position(&reader, err))
    }));

    TokenStream::from(quote!(#function))
}