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