fut_ret/
lib.rs

1//! A rust library to parse the return type generated by [async_trait](https://crates.io/crates/async-trait) in procedural macro.
2
3use proc_macro2::TokenStream;
4use quote::quote;
5use syn::{
6    GenericArgument, PathArguments, ReturnType, TraitBound, Type, TypeParamBound, TypePath,
7    TypeTraitObject,
8};
9
10const PIN: &str = "Pin";
11const BOX: &str = "Box";
12const FUTURE: &str = "Future";
13const RESULT: &str = "Result";
14
15/// The return type information of a function.
16pub struct PinBoxFutRet {
17    is_pin_box_fut: bool,
18    is_fut_ret_result: bool,
19    ret_ty: TokenStream,
20}
21
22impl Default for PinBoxFutRet {
23    fn default() -> Self {
24        PinBoxFutRet {
25            is_pin_box_fut: false,
26            is_fut_ret_result: false,
27            ret_ty: quote! {},
28        }
29    }
30}
31
32impl PinBoxFutRet {
33    /// Parse a function return type in proc-macro.
34    pub fn parse(ret_ty: &ReturnType) -> PinBoxFutRet {
35        let expect_ty = match ret_ty {
36            ReturnType::Type(_, ty) => ty,
37            _ => return PinBoxFutRet::default(),
38        };
39
40        let expect_pin = match *(expect_ty.clone()) {
41            Type::Path(TypePath { qself: _, path }) => {
42                let last_seg = path.segments.last().cloned();
43                match last_seg.map(|ls| (ls.ident.clone(), ls)) {
44                    Some((ls_ident, ls)) if ls_ident == PIN => ls,
45                    _ => return PinBoxFutRet::default(),
46                }
47            }
48            _ => return PinBoxFutRet::default(),
49        };
50
51        let expect_box = match &expect_pin.arguments {
52            PathArguments::AngleBracketed(wrapper) => match wrapper.args.last() {
53                Some(GenericArgument::Type(Type::Path(TypePath { qself: _, path }))) => {
54                    match path.segments.last().map(|ls| (ls.ident.clone(), ls)) {
55                        Some((ls_ident, ls)) if ls_ident == BOX => ls,
56                        _ => return PinBoxFutRet::default(),
57                    }
58                }
59                _ => return PinBoxFutRet::default(),
60            },
61            _ => return PinBoxFutRet::default(),
62        };
63
64        match &expect_box.arguments {
65            PathArguments::AngleBracketed(wrapper) => match wrapper.args.last() {
66                Some(GenericArgument::Type(Type::TraitObject(TypeTraitObject {
67                    dyn_token: _,
68                    bounds,
69                }))) => {
70                    let mut fut_ret = PinBoxFutRet::default();
71
72                    for bound in bounds.iter() {
73                        if let TypeParamBound::Trait(TraitBound { path, .. }) = bound {
74                            if let Some(arg) = path.segments.last() {
75                                if arg.ident == FUTURE {
76                                    fut_ret.is_pin_box_fut = true;
77                                    fut_ret.is_fut_ret_result =
78                                        is_fut_ret_result(&arg.arguments, &mut fut_ret);
79                                    break;
80                                }
81                            }
82                        }
83                    }
84                    fut_ret
85                }
86                _ => PinBoxFutRet::default(),
87            },
88            _ => PinBoxFutRet::default(),
89        }
90    }
91
92    /// Whether the function return a
93    /// [`Pin<Box<dyn Future<Output = RetTy>>>`](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.pin).
94    pub fn is_ret_pin_box_fut(&self) -> bool {
95        self.is_pin_box_fut
96    }
97
98    /// Whether the output type of
99    /// [`Future`](https://docs.rs/futures/0.3.21/futures/future/trait.Future.html)
100    /// is a [`Result`](https://doc.rust-lang.org/std/result/index.html).
101    pub fn is_fut_ret_result(&self) -> bool {
102        self.is_fut_ret_result
103    }
104
105    /// The token stream of the return type.
106    pub fn return_type(&self) -> TokenStream {
107        self.ret_ty.clone()
108    }
109}
110
111fn is_fut_ret_result(input: &PathArguments, fut_ret: &mut PinBoxFutRet) -> bool {
112    match input {
113        PathArguments::AngleBracketed(angle_arg) => {
114            match angle_arg.args.first().expect("future output") {
115                GenericArgument::Binding(binding) => match &binding.ty {
116                    Type::Path(path) => {
117                        fut_ret.ret_ty = quote! { #path };
118                        path.path
119                            .segments
120                            .last()
121                            .unwrap()
122                            .ident
123                            .to_string()
124                            .contains(RESULT)
125                    }
126                    _ => false,
127                },
128                _ => false,
129            }
130        }
131        _ => false,
132    }
133}