Skip to content

Commit 67e19e9

Browse files
committed
feat: Make proc macro for interface
1 parent e38c821 commit 67e19e9

File tree

4 files changed

+214
-91
lines changed

4 files changed

+214
-91
lines changed

crates/macros/src/function.rs

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub enum CallType<'a> {
8080
class: &'a syn::Path,
8181
receiver: MethodReceiver,
8282
},
83+
MethodInterface,
8384
}
8485

8586
/// Type of receiver on the method.
@@ -170,7 +171,7 @@ impl<'a> Function<'a> {
170171
}
171172
});
172173

173-
let result = match call_type {
174+
let result = match &call_type {
174175
CallType::Function => quote! {
175176
let parse = ex.parser()
176177
#(.arg(&mut #required_arg_names))*
@@ -183,6 +184,16 @@ impl<'a> Function<'a> {
183184

184185
#ident(#({#arg_accessors}),*)
185186
},
187+
CallType::MethodInterface => quote! {
188+
let parse = ex.parser()
189+
#(.arg(&mut #required_arg_names))*
190+
.not_required()
191+
#(.arg(&mut #not_required_arg_names))*
192+
.parse();
193+
if parse.is_err() {
194+
return;
195+
}
196+
},
186197
CallType::Method { class, receiver } => {
187198
let this = match receiver {
188199
MethodReceiver::Static => quote! {
@@ -235,34 +246,44 @@ impl<'a> Function<'a> {
235246
quote! {}
236247
};
237248

238-
Ok(quote! {
239-
::ext_php_rs::builders::FunctionBuilder::new(#name, {
240-
::ext_php_rs::zend_fastcall! {
241-
extern fn handler(
242-
ex: &mut ::ext_php_rs::zend::ExecuteData,
243-
retval: &mut ::ext_php_rs::types::Zval,
244-
) {
245-
use ::ext_php_rs::convert::IntoZval;
246-
247-
#(#arg_declarations)*
248-
let result = {
249-
#result
250-
};
251-
252-
if let Err(e) = result.set_zval(retval, false) {
253-
let e: ::ext_php_rs::exception::PhpException = e.into();
254-
e.throw().expect("Failed to throw PHP exception.");
249+
match call_type {
250+
CallType::MethodInterface => Ok(quote! {
251+
::ext_php_rs::builders::FunctionBuilder::new_abstract(#name)
252+
#(.arg(#required_args))*
253+
.not_required()
254+
#(.arg(#not_required_args))*
255+
#returns
256+
#docs
257+
}),
258+
_ => Ok(quote! {
259+
::ext_php_rs::builders::FunctionBuilder::new(#name, {
260+
::ext_php_rs::zend_fastcall! {
261+
extern fn handler(
262+
ex: &mut ::ext_php_rs::zend::ExecuteData,
263+
retval: &mut ::ext_php_rs::types::Zval,
264+
) {
265+
use ::ext_php_rs::convert::IntoZval;
266+
267+
#(#arg_declarations)*
268+
let result = {
269+
#result
270+
};
271+
272+
if let Err(e) = result.set_zval(retval, false) {
273+
let e: ::ext_php_rs::exception::PhpException = e.into();
274+
e.throw().expect("Failed to throw PHP exception.");
275+
}
255276
}
256277
}
257-
}
258-
handler
259-
})
260-
#(.arg(#required_args))*
261-
.not_required()
262-
#(.arg(#not_required_args))*
263-
#returns
264-
#docs
265-
})
278+
handler
279+
})
280+
#(.arg(#required_args))*
281+
.not_required()
282+
#(.arg(#not_required_args))*
283+
#returns
284+
#docs
285+
}),
286+
}
266287
}
267288

268289
/// Generates a struct and impl for the `PhpFunction` trait.

crates/macros/src/interface.rs

Lines changed: 152 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,184 @@
11
use std::collections::HashMap;
22

3-
use crate::{
4-
function::{Args, CallType, Function, MethodReceiver},
5-
helpers::get_docs,
6-
impl_::{Constant, FnBuilder, MethodArgs, MethodTy},
7-
prelude::*,
8-
};
9-
use darling::{ast::NestedMeta, FromMeta, ToTokens};
10-
use proc_macro2::TokenStream;
11-
use quote::quote;
12-
use syn::{Ident, ItemTrait, Lit, TraitItem, TraitItemFn};
3+
use darling::ast::NestedMeta;
4+
use darling::{FromMeta, ToTokens};
5+
use proc_macro2::{Ident, TokenStream};
6+
use quote::{format_ident, quote};
7+
use syn::parse::ParseStream;
8+
use syn::{Attribute, Expr, Fields, ItemStruct, ItemTrait, Lit, LitStr, Meta, Token};
9+
10+
use crate::function::{Args, CallType, Function, MethodReceiver};
11+
use crate::helpers::get_docs;
12+
use crate::prelude::*;
13+
14+
#[derive(Debug)]
15+
enum MethodVis {
16+
Public,
17+
Private,
18+
Protected,
19+
}
20+
21+
#[derive(Debug)]
22+
struct MethodArgs {
23+
name: String,
24+
optional: Option<Ident>,
25+
defaults: HashMap<Ident, Lit>,
26+
vis: MethodVis,
27+
}
1328

1429
#[derive(Debug, Default, FromMeta)]
1530
#[darling(default)]
16-
pub struct StructArgs {
17-
/// The name of the PHP interface. Defaults to the same name as the trait.
31+
struct InterfaceArgs {
1832
name: Option<String>,
1933
}
2034

35+
#[derive(Debug, Default)]
36+
struct InterfaceAttrs {
37+
implements: Vec<syn::Expr>,
38+
docs: Vec<String>,
39+
}
40+
41+
impl InterfaceAttrs {
42+
fn parse(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
43+
let mut unparsed = vec![];
44+
unparsed.append(attrs);
45+
for attr in unparsed {
46+
let path = attr.path();
47+
48+
if path.is_ident("implements") {
49+
let implements: syn::Expr = match attr.parse_args() {
50+
Ok(extends) => extends,
51+
Err(_) => bail!(attr => "Invalid arguments passed to implements attribute."),
52+
};
53+
self.implements.push(implements);
54+
}
55+
}
56+
self.docs = get_docs(attrs);
57+
Ok(())
58+
}
59+
}
60+
61+
impl MethodArgs {
62+
fn new(name: String) -> Self {
63+
Self {
64+
name,
65+
optional: Default::default(),
66+
defaults: Default::default(),
67+
vis: MethodVis::Public,
68+
}
69+
}
70+
71+
fn parse(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
72+
let mut unparsed = vec![];
73+
unparsed.append(attrs);
74+
for attr in unparsed {
75+
let path = &attr.path();
76+
77+
if path.is_ident("optional") {
78+
if self.optional.is_some() {
79+
bail!(attr => "Only one `#[optional]` attribute is valid per method.");
80+
}
81+
let optional = attr.parse_args().map_err(
82+
|e| err!(e.span() => "Invalid arguments passed to `#[optional]` attribute. {}", e),
83+
)?;
84+
self.optional = Some(optional);
85+
} else if path.is_ident("defaults") {
86+
let defaults = HashMap::from_meta(&attr.meta).map_err(
87+
|e| err!(e.span() => "Invalid arguments passed to `#[defaults]` attribute. {}", e),
88+
)?;
89+
self.defaults = defaults;
90+
} else if path.is_ident("public") {
91+
self.vis = MethodVis::Public;
92+
} else if path.is_ident("protected") {
93+
self.vis = MethodVis::Protected;
94+
} else if path.is_ident("private") {
95+
self.vis = MethodVis::Private;
96+
} else {
97+
attrs.push(attr);
98+
}
99+
}
100+
Ok(())
101+
}
102+
}
103+
104+
pub fn parser(args: TokenStream, mut input: ItemTrait) -> Result<TokenStream> {
105+
let meta = NestedMeta::parse_meta_list(args)?;
106+
let args = match InterfaceArgs::from_list(&meta) {
107+
Ok(args) => args,
108+
Err(e) => bail!(input => "Failed to parse impl attribute arguments: {:?}", e),
109+
};
110+
111+
let mut parsed = ParsedTrait { functions: vec![] };
112+
parsed.parse(input.items.iter_mut())?;
113+
let interface_struct_name = format_ident!("PhpInterface{}", input.ident);
114+
let functions = &parsed.functions;
115+
116+
Ok(quote::quote! {
117+
#input
118+
119+
pub(crate) struct #interface_struct_name;
120+
121+
fn get_methods() -> ::std::vec::Vec<
122+
(::ext_php_rs::builders::FunctionBuilder<'static>, ::ext_php_rs::flags::MethodFlags)
123+
> {
124+
vec![#(#functions),*]
125+
}
126+
})
127+
}
128+
21129
#[derive(Debug)]
22-
struct ParsedTrait<'a> {
23-
path: &'a syn::Path,
24-
constructor: Option<Function<'a>>,
130+
struct ParsedTrait {
25131
functions: Vec<FnBuilder>,
26-
constants: Vec<Constant<'a>>,
27132
}
28133

29-
impl<'a> ParsedTrait<'a> {
30-
fn parse(&mut self, items: impl Iterator<Item = &'a mut syn::TraitItem>) -> Result<()> {
134+
impl ParsedTrait {
135+
fn parse<'a>(&mut self, items: impl Iterator<Item = &'a mut syn::TraitItem>) -> Result<()> {
31136
for item in items {
32137
match item {
33138
syn::TraitItem::Fn(method) => {
34139
let name = method.sig.ident.to_string();
35140
let docs = get_docs(&method.attrs);
36141
let mut opts = MethodArgs::new(name);
37142
opts.parse(&mut method.attrs)?;
143+
38144
let args = Args::parse_from_fnargs(method.sig.inputs.iter(), opts.defaults)?;
39-
let mut func =
145+
let func =
40146
Function::new(&method.sig, Some(opts.name), args, opts.optional, docs)?;
41147

42-
if matches!(opts.ty, MethodTy::Constructor) {
43-
if self.constructor.replace(func).is_some() {
44-
bail!(method => "Only one constructor can be provided per class.");
45-
}
46-
} else {
47-
let call_type = CallType::Method {
48-
class: self.path,
49-
receiver: if func.args.receiver.is_some() {
50-
// `&self` or `&mut self`
51-
MethodReceiver::Class
52-
} else if func
53-
.args
54-
.typed
55-
.first()
56-
.map(|arg| arg.name == "self_")
57-
.unwrap_or_default()
58-
{
59-
// `self_: &[mut] ZendClassObject<Self>`
60-
// Need to remove arg from argument list
61-
func.args.typed.pop();
62-
MethodReceiver::ZendClassObject
63-
} else {
64-
// Static method
65-
MethodReceiver::Static
66-
},
67-
};
68-
let builder = func.function_builder(call_type)?;
69-
self.functions.push(FnBuilder {
70-
builder,
71-
vis: opts.vis,
72-
r#abstract: true,
73-
});
74-
}
148+
let builder = func.function_builder(CallType::MethodInterface)?;
149+
self.functions.push(FnBuilder {
150+
builder,
151+
vis: opts.vis,
152+
});
75153
}
76-
_ => {}
154+
_ => todo!(),
77155
}
78156
}
79157
Ok(())
80158
}
81159
}
82160

83-
pub fn parser(args: TokenStream, input: ItemTrait) -> Result<TokenStream> {
84-
let meta = NestedMeta::parse_meta_list(args)?;
161+
#[derive(Debug)]
162+
struct FnBuilder {
163+
pub builder: TokenStream,
164+
pub vis: MethodVis,
165+
}
85166

86-
let args = match StructArgs::from_list(&meta) {
87-
Ok(args) => args,
88-
Err(e) => bail!("Failed to parse struct arguments: {:?}", e),
89-
};
167+
impl quote::ToTokens for FnBuilder {
168+
fn to_tokens(&self, tokens: &mut TokenStream) {
169+
let builder = &self.builder;
170+
// TODO(cole_d): allow more flags via attributes
171+
let mut flags = vec![];
172+
flags.push(match self.vis {
173+
MethodVis::Public => quote! { ::ext_php_rs::flags::MethodFlags::Public },
174+
MethodVis::Protected => quote! { ::ext_php_rs::flags::MethodFlags::Protected },
175+
MethodVis::Private => quote! { ::ext_php_rs::flags::MethodFlags::Private },
176+
});
177+
flags.push(quote! { ::ext_php_rs::flags::MethodFlags::Abstract });
90178

91-
Ok(quote! {})
179+
quote! {
180+
(#builder, #(#flags)|*)
181+
}
182+
.to_tokens(tokens);
183+
}
92184
}

crates/macros/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod fastcall;
66
mod function;
77
mod helpers;
88
mod impl_;
9+
mod interface;
910
mod module;
1011
mod syn_ext;
1112
mod zval;
@@ -665,6 +666,15 @@ pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
665666
.into()
666667
}
667668

669+
#[proc_macro_attribute]
670+
pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
671+
let input = parse_macro_input!(input as ItemTrait);
672+
673+
interface::parser(args.into(), input)
674+
.unwrap_or_else(|e| e.to_compile_error())
675+
.into()
676+
}
677+
668678
/// # `#[php_extern]` Attribute
669679
///
670680
/// Attribute used to annotate `extern` blocks which are deemed as PHP

src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ pub mod prelude {
4747
pub use crate::php_println;
4848
pub use crate::types::ZendCallable;
4949
pub use crate::{
50-
php_class, php_const, php_extern, php_function, php_impl, php_module, wrap_constant,
51-
wrap_function, zend_fastcall, ZvalConvert,
50+
php_class, php_const, php_extern, php_function, php_impl, php_interface, php_module,
51+
wrap_constant, wrap_function, zend_fastcall, ZvalConvert,
5252
};
5353
}
5454

@@ -62,6 +62,6 @@ pub const PHP_DEBUG: bool = cfg!(php_debug);
6262
pub const PHP_ZTS: bool = cfg!(php_zts);
6363

6464
pub use ext_php_rs_derive::{
65-
php_class, php_const, php_extern, php_function, php_impl, php_module, wrap_constant,
66-
wrap_function, zend_fastcall, ZvalConvert,
65+
php_class, php_const, php_extern, php_function, php_impl, php_interface, php_module,
66+
wrap_constant, wrap_function, zend_fastcall, ZvalConvert,
6767
};

0 commit comments

Comments
 (0)