Skip to content

Commit a8cf75c

Browse files
committed
feat: Add methods, const registration for interface
1 parent 90bf52a commit a8cf75c

File tree

8 files changed

+271
-85
lines changed

8 files changed

+271
-85
lines changed

crates/macros/src/class.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use darling::util::Flag;
22
use darling::{FromAttributes, FromMeta, ToTokens};
33
use proc_macro2::TokenStream;
4-
use quote::quote;
4+
use quote::{quote, TokenStreamExt};
55
use syn::{Attribute, Expr, Fields, ItemStruct};
66

77
use crate::helpers::get_docs;
@@ -28,8 +28,17 @@ pub struct StructAttributes {
2828

2929
#[derive(FromMeta, Debug)]
3030
pub struct ClassEntryAttribute {
31-
ce: syn::Expr,
32-
stub: String,
31+
pub ce: syn::Expr,
32+
pub stub: String,
33+
}
34+
35+
impl ToTokens for ClassEntryAttribute {
36+
fn to_tokens(&self, tokens: &mut TokenStream) {
37+
let ce = &self.ce;
38+
let stub = &self.stub;
39+
let token = quote! { (#ce, #stub) };
40+
tokens.append_all(token);
41+
}
3342
}
3443

3544
pub fn parser(mut input: ItemStruct) -> Result<TokenStream> {
@@ -157,10 +166,8 @@ fn generate_registered_class_impl(
157166
};
158167

159168
let extends = if let Some(extends) = extends {
160-
let ce = &extends.ce;
161-
let stub = &extends.stub;
162169
quote! {
163-
Some((#ce, #stub))
170+
Some(#extends)
164171
}
165172
} else {
166173
quote! { None }

crates/macros/src/function.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,41 @@ impl<'a> Function<'a> {
131131
format_ident!("_internal_{}", &self.ident)
132132
}
133133

134+
pub fn abstract_function_builder(&self) -> TokenStream {
135+
let name = &self.name;
136+
let (required, not_required) = self.args.split_args(self.optional.as_ref());
137+
138+
// `entry` impl
139+
let required_args = required
140+
.iter()
141+
.map(TypedArg::arg_builder)
142+
.collect::<Vec<_>>();
143+
let not_required_args = not_required
144+
.iter()
145+
.map(TypedArg::arg_builder)
146+
.collect::<Vec<_>>();
147+
148+
let returns = self.build_returns();
149+
let docs = if self.docs.is_empty() {
150+
quote! {}
151+
} else {
152+
let docs = &self.docs;
153+
quote! {
154+
.docs(&[#(#docs),*])
155+
}
156+
};
157+
158+
quote! {
159+
::ext_php_rs::builders::FunctionBuilder::new_abstract(#name)
160+
#(.arg(#required_args))*
161+
.not_required()
162+
#(.arg(#not_required_args))*
163+
#returns
164+
#docs
165+
}
166+
167+
}
168+
134169
/// Generates the function builder for the function.
135170
pub fn function_builder(&self, call_type: CallType) -> TokenStream {
136171
let name = &self.name;

crates/macros/src/impl_.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ struct ParsedImpl<'a> {
125125
}
126126

127127
#[derive(Debug, Eq, Hash, PartialEq)]
128-
enum MethodModifier {
128+
pub enum MethodModifier {
129129
Abstract,
130130
Static,
131131
}
@@ -141,7 +141,7 @@ impl quote::ToTokens for MethodModifier {
141141
}
142142

143143
#[derive(Debug)]
144-
struct FnBuilder {
144+
pub struct FnBuilder {
145145
/// Tokens which represent the `FunctionBuilder` for this function.
146146
pub builder: TokenStream,
147147
/// The visibility of this method.
@@ -151,13 +151,19 @@ struct FnBuilder {
151151
}
152152

153153
#[derive(Debug)]
154-
struct Constant<'a> {
154+
pub struct Constant<'a> {
155155
/// Name of the constant in PHP land.
156-
name: String,
156+
pub name: String,
157157
/// Identifier of the constant in Rust land.
158-
ident: &'a syn::Ident,
158+
pub ident: &'a syn::Ident,
159159
/// Documentation for the constant.
160-
docs: Vec<String>,
160+
pub docs: Vec<String>,
161+
}
162+
163+
impl<'a> Constant<'a> {
164+
pub fn new(name: String, ident: &'a syn::Ident, docs: Vec<String>) -> Self {
165+
Self {name, ident, docs}
166+
}
161167
}
162168

163169
impl<'a> ParsedImpl<'a> {

crates/macros/src/interface.rs

Lines changed: 161 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,94 @@
1+
use std::collections::{HashMap, HashSet};
2+
3+
use darling::util::Flag;
14
use darling::{FromAttributes};
25
use proc_macro2::TokenStream;
36
use quote::{format_ident, quote};
4-
use syn::{ItemTrait, TraitItem, TraitItemFn};
5-
use crate::helpers::CleanPhpAttr;
7+
use syn::{Expr, Ident, ItemTrait, Path, TraitItem, TraitItemConst, TraitItemFn};
8+
use crate::class::ClassEntryAttribute;
9+
use crate::constant::PhpConstAttribute;
10+
use crate::function::{Args, Function};
11+
use crate::helpers::{get_docs, CleanPhpAttr};
612

7-
use crate::parsing::{PhpRename, RenameRule};
13+
use crate::impl_::{Constant, FnBuilder, MethodModifier};
14+
use crate::parsing::{PhpRename, RenameRule, Visibility};
815
use crate::prelude::*;
916

1017
#[derive(FromAttributes, Debug, Default)]
1118
#[darling(attributes(php), forward_attrs(doc), default)]
1219
pub struct StructAttributes {
1320
#[darling(flatten)]
1421
rename: PhpRename,
22+
#[darling(multiple)]
23+
extends: Vec<ClassEntryAttribute>,
1524
}
1625

1726
pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
1827
let attr = StructAttributes::from_attributes(&input.attrs)?;
1928
let ident = &input.ident;
2029

2130
let interface_name = format_ident!("PhpInterface{ident}");
31+
let ts = quote! { #interface_name };
32+
let path: Path = syn::parse2(ts)?;
33+
2234
let name = attr.rename.rename(ident.to_string(), RenameRule::Pascal);
2335
input.attrs.clean_php();
2436

25-
let mut interface_methods: Vec<TraitItemFn> = Vec::new();
26-
for i in input.items.clone().into_iter() {
27-
match i {
28-
TraitItem::Fn(f) => {
29-
if f.default.is_some() {
30-
bail!("Interface could not have default impl");
37+
let methods: Vec<FnBuilder> = input.items.iter_mut()
38+
.flat_map(
39+
|item: &mut TraitItem| {
40+
match item {
41+
TraitItem::Fn(f) => Some(f),
42+
_ => None,
3143
}
32-
interface_methods.push(f);
44+
})
45+
.flat_map(|f| f.parse())
46+
.collect();
47+
48+
let constants: Vec<_> = input.items.iter_mut()
49+
.flat_map(|item: &mut TraitItem| {
50+
match item {
51+
TraitItem::Const(c) => Some(c),
52+
_ => None,
3353
}
34-
_ => {}
54+
})
55+
.flat_map(|c| c.parse())
56+
.map(|c| {
57+
let name = &c.name;
58+
let ident = c.ident;
59+
let docs = &c.docs;
60+
quote! {
61+
(#name, &#path::#ident, &[#(#docs),*])
62+
}
63+
})
64+
.collect();
65+
66+
let impl_const: Vec<&TraitItemConst> = input.items.iter().flat_map(|item| {
67+
match item {
68+
TraitItem::Const(c) => Some(c),
69+
_ => None,
3570
}
36-
};
71+
})
72+
.map(|c| {
73+
if c.default.is_none() {
74+
bail!("Interface const canot be empty");
75+
}
76+
Ok(c)
77+
})
78+
.flat_map(|c| c)
79+
.collect();
80+
81+
let implements = attr.extends;
3782

3883
Ok(quote! {
3984
#input
4085

4186
pub struct #interface_name;
4287

88+
impl #interface_name {
89+
#(pub #impl_const)*
90+
}
91+
4392
impl ::ext_php_rs::class::RegisteredClass for #interface_name {
4493
const CLASS_NAME: &'static str = #name;
4594

@@ -51,7 +100,9 @@ pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
51100

52101
const FLAGS: ::ext_php_rs::flags::ClassFlags = ::ext_php_rs::flags::ClassFlags::Interface;
53102

54-
const IMPLEMENTS: &'static [::ext_php_rs::class::ClassEntryInfo] = &[];
103+
const IMPLEMENTS: &'static [::ext_php_rs::class::ClassEntryInfo] = &[
104+
#(#implements,)*
105+
];
55106

56107
fn get_metadata() -> &'static ::ext_php_rs::class::ClassMetadata<Self> {
57108
static METADATA: ::ext_php_rs::class::ClassMetadata<#interface_name> =
@@ -64,7 +115,7 @@ pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
64115
::ext_php_rs::builders::FunctionBuilder<'static>,
65116
::ext_php_rs::flags::MethodFlags,
66117
)> {
67-
vec![ ]
118+
vec![#(#methods),*]
68119
}
69120

70121
fn constructor() -> Option<::ext_php_rs::class::ConstructorMeta<Self>> {
@@ -76,14 +127,37 @@ pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
76127
&'static dyn ext_php_rs::convert::IntoZvalDyn,
77128
ext_php_rs::describe::DocComments,
78129
)] {
79-
&[]
130+
use ::ext_php_rs::internal::class::PhpClassImpl;
131+
::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_constants()
80132
}
81133

82134
fn get_properties<'a>() -> std::collections::HashMap<&'static str, ::ext_php_rs::internal::property::PropertyInfo<'a, Self>> {
83135
HashMap::new()
84136
}
85137

86138
}
139+
impl ::ext_php_rs::internal::class::PhpClassImpl<#path>
140+
for ::ext_php_rs::internal::class::PhpClassImplCollector<#path>
141+
{
142+
fn get_methods(self) -> ::std::vec::Vec<
143+
(::ext_php_rs::builders::FunctionBuilder<'static>, ::ext_php_rs::flags::MethodFlags)
144+
> {
145+
vec![]
146+
}
147+
148+
fn get_method_props<'a>(self) -> ::std::collections::HashMap<&'static str, ::ext_php_rs::props::Property<'a, #path>> {
149+
todo!()
150+
}
151+
152+
fn get_constructor(self) -> ::std::option::Option<::ext_php_rs::class::ConstructorMeta<#path>> {
153+
None
154+
}
155+
156+
fn get_constants(self) -> &'static [(&'static str, &'static dyn ::ext_php_rs::convert::IntoZvalDyn, &'static [&'static str])] {
157+
&[#(#constants),*]
158+
}
159+
}
160+
87161

88162
impl<'a> ::ext_php_rs::convert::FromZendObject<'a> for &'a #interface_name {
89163
#[inline]
@@ -150,3 +224,75 @@ pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
150224
}
151225
})
152226
}
227+
228+
#[derive(FromAttributes, Default, Debug)]
229+
#[darling(default, attributes(php), forward_attrs(doc))]
230+
pub struct PhpFunctionInterfaceAttribute {
231+
#[darling(flatten)]
232+
rename: PhpRename,
233+
defaults: HashMap<Ident, Expr>,
234+
optional: Option<Ident>,
235+
vis: Option<Visibility>,
236+
attrs: Vec<syn::Attribute>,
237+
getter: Flag,
238+
setter: Flag,
239+
constructor: Flag,
240+
}
241+
242+
trait Parse<'a, T> {
243+
fn parse(&'a mut self) -> Result<T>;
244+
}
245+
246+
impl<'a> Parse<'a, Constant<'a>> for TraitItemConst {
247+
fn parse(&'a mut self) -> Result<Constant<'a>> {
248+
let attr = PhpConstAttribute::from_attributes(&self.attrs)?;
249+
let name = self.ident.to_string();
250+
let docs = get_docs(&attr.attrs)?;
251+
self.attrs.clean_php();
252+
253+
Ok(Constant::new(name, &self.ident, docs))
254+
}
255+
}
256+
257+
impl<'a> Parse<'a, FnBuilder> for TraitItemFn {
258+
fn parse(&'a mut self) -> Result<FnBuilder> {
259+
let php_attr = PhpFunctionInterfaceAttribute::from_attributes(
260+
&self.attrs
261+
)?;
262+
if self.default.is_some() {
263+
bail!("Interface could not have default impl");
264+
}
265+
266+
let mut args = Args::parse_from_fnargs(
267+
self.sig.inputs.iter(),
268+
php_attr.defaults
269+
)?;
270+
let docs = get_docs(&php_attr.attrs)?;
271+
272+
self.attrs.clean_php();
273+
274+
let mut modifiers: HashSet<MethodModifier> = HashSet::new();
275+
modifiers.insert(MethodModifier::Abstract);
276+
if args.typed.first().is_some_and(|arg| arg.name == "self_") {
277+
args.typed.pop();
278+
} else if args.receiver.is_none() {
279+
modifiers.insert(MethodModifier::Static);
280+
};
281+
282+
let f = Function::new(
283+
&self.sig,
284+
php_attr
285+
.rename
286+
.rename(self.sig.ident.to_string(), RenameRule::Camel),
287+
args,
288+
php_attr.optional,
289+
docs,
290+
);
291+
292+
Ok(FnBuilder {
293+
builder: f.abstract_function_builder(),
294+
vis: php_attr.vis.unwrap_or(Visibility::Public),
295+
modifiers,
296+
})
297+
}
298+
}

0 commit comments

Comments
 (0)