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}