php_tokio_derive/
lib.rs

1use anyhow::{bail, Result};
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4use quote::TokenStreamExt;
5use quote::{quote, ToTokens};
6use syn::parse_quote;
7use syn::FnArg;
8use syn::GenericArgument;
9use syn::ImplItemFn;
10use syn::Pat;
11use syn::PathArguments;
12use syn::Type;
13use syn::{parse_macro_input, ItemImpl};
14
15#[proc_macro_attribute]
16pub fn php_async_impl(
17    _: proc_macro::TokenStream,
18    input: proc_macro::TokenStream,
19) -> proc_macro::TokenStream {
20    match parser(parse_macro_input!(input as ItemImpl)) {
21        Ok(parsed) => parsed,
22        Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(),
23    }
24    .into()
25}
26
27fn parser(input: ItemImpl) -> Result<TokenStream> {
28    let ItemImpl { self_ty, items, .. } = input;
29
30    if input.trait_.is_some() {
31        bail!("This macro cannot be used on trait implementations.");
32    }
33
34    let tokens = items
35        .into_iter()
36        .map(|item| {
37            Ok(match item {
38                syn::ImplItem::Fn(method) => handle_method(method)?,
39                item => item.to_token_stream(),
40            })
41        })
42        .collect::<Result<Vec<_>>>()?;
43
44    let output = quote! {
45        #[::ext_php_rs::php_impl]
46        impl #self_ty {
47            #(#tokens)*
48        }
49    };
50
51    Ok(output)
52}
53
54fn handle_method(input: ImplItemFn) -> Result<TokenStream> {
55    let mut receiver = false;
56    let mut receiver_mutable = false;
57    let mut hack_tokens = quote! {};
58    for arg in input.sig.inputs.iter() {
59        match arg {
60            FnArg::Receiver(r) => {
61                receiver = true;
62                receiver_mutable = r.mutability.is_some();
63            }
64            FnArg::Typed(ty) => {
65                let mut this = false;
66                for attr in ty.attrs.iter() {
67                    if attr.path().to_token_stream().to_string() == "this" {
68                        this = true;
69                    }
70                }
71
72                if !this {
73                    let param = match &*ty.pat {
74                        Pat::Ident(pat) => &pat.ident,
75                        _ => bail!("Invalid parameter type."),
76                    };
77
78                    let mut ty_inner = &*ty.ty;
79                    let mut is_option = false;
80
81                    if let Type::Path(t) = ty_inner {
82                        if t.path.segments[0].ident.to_string() == "Option" {
83                            if let PathArguments::AngleBracketed(t) = &t.path.segments[0].arguments
84                            {
85                                if let GenericArgument::Type(t) = &t.args[0] {
86                                    ty_inner = t;
87                                    is_option = true;
88                                }
89                            }
90                        }
91                    }
92                    let mut is_str = false;
93                    if let Type::Reference(t) = ty_inner {
94                        if t.mutability.is_none() {
95                            if let Type::Path(t) = &*t.elem {
96                                is_str = t.path.is_ident("str");
97                            }
98                        }
99                        hack_tokens.append_all(if is_str {
100                            if is_option {
101                                quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); }
102                            } else {
103                                quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; }
104                            }
105                        } else {
106                            if is_option {
107                                quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::php_tokio::borrow_unchecked::borrow_unchecked(__temp) })); }
108                            } else {
109                                quote! { let #param = unsafe { ::php_tokio::borrow_unchecked::borrow_unchecked(#param) }; }
110                            }
111                        });
112                    }
113                }
114            }
115        }
116    }
117
118    let mut input = input.clone();
119    if input.sig.asyncness.is_some() {
120        input.sig.asyncness = None;
121        let stmts = input.block;
122        let this = if receiver {
123            if receiver_mutable {
124                quote! { let this = unsafe { std::mem::transmute::<&mut Self, &'static mut Self>(self) }; }
125            } else {
126                quote! { let this = unsafe { std::mem::transmute::<&Self, &'static Self>(self) }; }
127            }
128        } else {
129            quote! {}
130        };
131        input.block = parse_quote! {{
132            #this
133            #hack_tokens
134
135            ::php_tokio::EventLoop::suspend_on(async move #stmts)
136        }};
137    }
138
139    let result = quote! {
140        #input
141    };
142    Ok(result)
143}