• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 16880353614

11 Aug 2025 12:42PM UTC coverage: 26.825% (-1.0%) from 27.778%
16880353614

Pull #533

github

web-flow
Merge 9e9410bbe into 896cb945d
Pull Request #533: Feat/interface impl

8 of 179 new or added lines in 9 files covered. (4.47%)

2 existing lines in 2 files now uncovered.

1161 of 4328 relevant lines covered (26.83%)

7.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/crates/macros/src/interface.rs
1
use std::collections::{HashMap, HashSet};
2

3
use crate::class::ClassEntryAttribute;
4
use crate::constant::PhpConstAttribute;
5
use crate::function::{Args, Function};
6
use crate::helpers::{get_docs, CleanPhpAttr};
7
use darling::util::Flag;
8
use darling::FromAttributes;
9
use proc_macro2::TokenStream;
10
use quote::{format_ident, quote, ToTokens};
11
use syn::{Expr, Ident, ItemTrait, Path, TraitItem, TraitItemConst, TraitItemFn};
12

13
use crate::impl_::{FnBuilder, MethodModifier};
14
use crate::parsing::{PhpRename, RenameRule, Visibility};
15
use crate::prelude::*;
16

17
#[derive(FromAttributes, Debug, Default)]
18
#[darling(attributes(php), forward_attrs(doc), default)]
19
pub struct StructAttributes {
20
    #[darling(flatten)]
21
    rename: PhpRename,
22
    #[darling(multiple)]
23
    extends: Vec<ClassEntryAttribute>,
24
}
25

NEW
26
pub fn parser(mut input: ItemTrait) -> Result<TokenStream> {
×
NEW
27
    let interface_data: InterfaceData = input.parse()?;
×
28
    let interface_tokens = quote! { #interface_data };
29

30
    Ok(quote! {
31
        #input
32

33
        #interface_tokens
34
    })
35
}
36

37
trait Parse<'a, T> {
38
    fn parse(&'a mut self) -> Result<T>;
39
}
40

41
struct InterfaceData<'a> {
42
    ident: &'a Ident,
43
    name: String,
44
    path: Path,
45
    attrs: StructAttributes,
46
    constructor: Option<Function<'a>>,
47
    methods: Vec<FnBuilder>,
48
    constants: Vec<Constant<'a>>,
49
}
50

51
impl ToTokens for InterfaceData<'_> {
52
    #[allow(clippy::too_many_lines)]
NEW
53
    fn to_tokens(&self, tokens: &mut TokenStream) {
×
NEW
54
        let interface_name = format_ident!("PhpInterface{}", self.ident);
×
NEW
55
        let name = &self.name;
×
NEW
56
        let implements = &self.attrs.extends;
×
NEW
57
        let methods_sig = &self.methods;
×
NEW
58
        let constants = &self.constants;
×
59

NEW
60
        let _constructor = self
×
NEW
61
            .constructor
×
62
            .as_ref()
NEW
63
            .map(|func| func.constructor_meta(&self.path))
×
64
            .option_tokens();
65

NEW
66
        quote! {
×
67
            pub struct #interface_name;
68

69
            impl ::ext_php_rs::class::RegisteredClass for #interface_name {
70
                const CLASS_NAME: &'static str = #name;
71

72
                const BUILDER_MODIFIER: Option<
73
                fn(::ext_php_rs::builders::ClassBuilder) -> ::ext_php_rs::builders::ClassBuilder,
74
                > = None;
75

76
                const EXTENDS: Option<::ext_php_rs::class::ClassEntryInfo> = None;
77

78
                const FLAGS: ::ext_php_rs::flags::ClassFlags = ::ext_php_rs::flags::ClassFlags::Interface;
79

80
                const IMPLEMENTS: &'static [::ext_php_rs::class::ClassEntryInfo] = &[
81
                    #(#implements,)*
82
                ];
83

84
                fn get_metadata() -> &'static ::ext_php_rs::class::ClassMetadata<Self> {
85
                    static METADATA: ::ext_php_rs::class::ClassMetadata<#interface_name> =
86
                    ::ext_php_rs::class::ClassMetadata::new();
87

88
                    &METADATA
89
                }
90

91
                fn method_builders() -> Vec<(
92
                    ::ext_php_rs::builders::FunctionBuilder<'static>,
93
                    ::ext_php_rs::flags::MethodFlags,
94
                )> {
95
                    vec![#(#methods_sig),*]
96
                }
97

98
                fn constructor() -> Option<::ext_php_rs::class::ConstructorMeta<Self>> {
99
                    None
100
                }
101

102
                fn constants() -> &'static [(
103
                    &'static str,
104
                    &'static dyn ext_php_rs::convert::IntoZvalDyn,
105
                    ext_php_rs::describe::DocComments,
106
                )] {
107
                    &[#(#constants),*]
108
                }
109

110
                fn get_properties<'a>() -> ::std::collections::HashMap<&'static str, ::ext_php_rs::internal::property::PropertyInfo<'a, Self>> {
111
                    panic!("Non supported for Interface");
112
                }
113
            }
114

115
            impl<'a> ::ext_php_rs::convert::FromZendObject<'a> for &'a #interface_name {
116
                #[inline]
117
                fn from_zend_object(
118
                    obj: &'a ::ext_php_rs::types::ZendObject,
119
                ) -> ::ext_php_rs::error::Result<Self> {
120
                    let obj = ::ext_php_rs::types::ZendClassObject::<#interface_name>::from_zend_obj(obj)
121
                        .ok_or(::ext_php_rs::error::Error::InvalidScope)?;
122
                    Ok(&**obj)
123
                }
124
            }
125
            impl<'a> ::ext_php_rs::convert::FromZendObjectMut<'a> for &'a mut #interface_name {
126
                #[inline]
127
                fn from_zend_object_mut(
128
                    obj: &'a mut ::ext_php_rs::types::ZendObject,
129
                ) -> ::ext_php_rs::error::Result<Self> {
130
                    let obj = ::ext_php_rs::types::ZendClassObject::<#interface_name>::from_zend_obj_mut(obj)
131
                        .ok_or(::ext_php_rs::error::Error::InvalidScope)?;
132
                    Ok(&mut **obj)
133
                }
134
            }
135
            impl<'a> ::ext_php_rs::convert::FromZval<'a> for &'a #interface_name {
136
                const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
137
                    <#interface_name as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
138
                ));
139
                #[inline]
140
                fn from_zval(zval: &'a ::ext_php_rs::types::Zval) -> ::std::option::Option<Self> {
141
                    <Self as ::ext_php_rs::convert::FromZendObject>::from_zend_object(zval.object()?).ok()
142
                }
143
            }
144
            impl<'a> ::ext_php_rs::convert::FromZvalMut<'a> for &'a mut #interface_name {
145
                const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
146
                    <#interface_name as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
147
                ));
148
                #[inline]
149
                fn from_zval_mut(zval: &'a mut ::ext_php_rs::types::Zval) -> ::std::option::Option<Self> {
150
                    <Self as ::ext_php_rs::convert::FromZendObjectMut>::from_zend_object_mut(zval.object_mut()?)
151
                        .ok()
152
                }
153
            }
154
            impl ::ext_php_rs::convert::IntoZendObject for #interface_name {
155
                #[inline]
156
                fn into_zend_object(
157
                    self,
158
                ) -> ::ext_php_rs::error::Result<::ext_php_rs::boxed::ZBox<::ext_php_rs::types::ZendObject>>
159
                {
160
                    Ok(::ext_php_rs::types::ZendClassObject::new(self).into())
161
                }
162
            }
163
            impl ::ext_php_rs::convert::IntoZval for #interface_name {
164
                const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
165
                    <#interface_name as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
166
                ));
167
                const NULLABLE: bool = false;
168
                #[inline]
169
                fn set_zval(
170
                    self,
171
                    zv: &mut ::ext_php_rs::types::Zval,
172
                    persistent: bool,
173
                ) -> ::ext_php_rs::error::Result<()> {
174
                    use ::ext_php_rs::convert::IntoZendObject;
175
                    self.into_zend_object()?.set_zval(zv, persistent)
176
                }
177
            }
178
        }.to_tokens(tokens);
179
    }
180
}
181

182
impl<'a> InterfaceData<'a> {
NEW
183
    fn new(
×
184
        ident: &'a Ident,
185
        name: String,
186
        path: Path,
187
        attrs: StructAttributes,
188
        constructor: Option<Function<'a>>,
189
        methods: Vec<FnBuilder>,
190
        constants: Vec<Constant<'a>>,
191
    ) -> Self {
192
        Self {
193
            ident,
194
            name,
195
            path,
196
            attrs,
197
            constructor,
198
            methods,
199
            constants,
200
        }
201
    }
202
}
203

204
impl<'a> Parse<'a, InterfaceData<'a>> for ItemTrait {
NEW
205
    fn parse(&'a mut self) -> Result<InterfaceData<'a>> {
×
NEW
206
        let attrs = StructAttributes::from_attributes(&self.attrs)?;
×
NEW
207
        let ident = &self.ident;
×
NEW
208
        let name = attrs.rename.rename(ident.to_string(), RenameRule::Pascal);
×
NEW
209
        self.attrs.clean_php();
×
NEW
210
        let interface_name = format_ident!("PhpInterface{ident}");
×
NEW
211
        let ts = quote! { #interface_name };
×
NEW
212
        let path: Path = syn::parse2(ts)?;
×
NEW
213
        let mut data = InterfaceData::new(ident, name, path, attrs, None, Vec::new(), Vec::new());
×
214

NEW
215
        for item in &mut self.items {
×
NEW
216
            match item {
×
NEW
217
                TraitItem::Fn(f) => {
×
NEW
218
                    match f.parse()? {
×
NEW
219
                        MethodKind::Method(builder) => data.methods.push(builder),
×
NEW
220
                        MethodKind::Constructor(builder) => {
×
NEW
221
                            if data.constructor.replace(builder).is_some() {
×
NEW
222
                                bail!("Only one constructor can be provided per class.");
×
223
                            }
224
                        }
225
                    };
226
                }
NEW
227
                TraitItem::Const(c) => data.constants.push(c.parse()?),
×
NEW
228
                _ => {}
×
229
            }
230
        }
231

NEW
232
        Ok(data)
×
233
    }
234
}
235

236
#[derive(FromAttributes, Default, Debug)]
237
#[darling(default, attributes(php), forward_attrs(doc))]
238
pub struct PhpFunctionInterfaceAttribute {
239
    #[darling(flatten)]
240
    rename: PhpRename,
241
    defaults: HashMap<Ident, Expr>,
242
    optional: Option<Ident>,
243
    vis: Option<Visibility>,
244
    attrs: Vec<syn::Attribute>,
245
    getter: Flag,
246
    setter: Flag,
247
    constructor: Flag,
248
}
249

250
enum MethodKind<'a> {
251
    Method(FnBuilder),
252
    Constructor(Function<'a>),
253
}
254

255
impl<'a> Parse<'a, MethodKind<'a>> for TraitItemFn {
NEW
256
    fn parse(&'a mut self) -> Result<MethodKind<'a>> {
×
NEW
257
        if self.default.is_some() {
×
NEW
258
            bail!(self => "Interface could not have default impl");
×
259
        }
260

NEW
261
        let php_attr = PhpFunctionInterfaceAttribute::from_attributes(&self.attrs)?;
×
NEW
262
        self.attrs.clean_php();
×
263

NEW
264
        let mut args = Args::parse_from_fnargs(self.sig.inputs.iter(), php_attr.defaults)?;
×
265

NEW
266
        let docs = get_docs(&php_attr.attrs)?;
×
267

NEW
268
        let mut modifiers: HashSet<MethodModifier> = HashSet::new();
×
NEW
269
        modifiers.insert(MethodModifier::Abstract);
×
270

NEW
271
        if args.typed.first().is_some_and(|arg| arg.name == "self_") {
×
NEW
272
            args.typed.pop();
×
NEW
273
        } else if args.receiver.is_none() {
×
NEW
274
            modifiers.insert(MethodModifier::Static);
×
275
        }
276

277
        let f = Function::new(
NEW
278
            &self.sig,
×
NEW
279
            php_attr
×
NEW
280
                .rename
×
NEW
281
                .rename(self.sig.ident.to_string(), RenameRule::Camel),
×
NEW
282
            args,
×
NEW
283
            php_attr.optional,
×
NEW
284
            docs,
×
285
        );
286

NEW
287
        if php_attr.constructor.is_present() {
×
NEW
288
            Ok(MethodKind::Constructor(f))
×
289
        } else {
290
            let builder = FnBuilder {
NEW
291
                builder: f.abstract_function_builder(),
×
NEW
292
                vis: php_attr.vis.unwrap_or(Visibility::Public),
×
293
                modifiers,
294
            };
295

NEW
296
            Ok(MethodKind::Method(builder))
×
297
        }
298
    }
299
}
300

301
impl<'a> Parse<'a, Vec<MethodKind<'a>>> for ItemTrait {
NEW
302
    fn parse(&'a mut self) -> Result<Vec<MethodKind<'a>>> {
×
NEW
303
        Ok(self
×
NEW
304
            .items
×
NEW
305
            .iter_mut()
×
NEW
306
            .filter_map(|item| match item {
×
NEW
307
                TraitItem::Fn(f) => Some(f),
×
NEW
308
                _ => None,
×
309
            })
NEW
310
            .flat_map(Parse::parse)
×
NEW
311
            .collect())
×
312
    }
313
}
314

315
#[derive(Debug)]
316
struct Constant<'a> {
317
    name: String,
318
    expr: &'a Expr,
319
    docs: Vec<String>,
320
}
321

322
impl ToTokens for Constant<'_> {
NEW
323
    fn to_tokens(&self, tokens: &mut TokenStream) {
×
NEW
324
        let name = &self.name;
×
NEW
325
        let expr = &self.expr;
×
NEW
326
        let docs = &self.docs;
×
NEW
327
        quote! {
×
328
            (#name, &#expr, &[#(#docs),*])
329
        }
NEW
330
        .to_tokens(tokens);
×
331
    }
332
}
333

334
impl<'a> Constant<'a> {
NEW
335
    fn new(name: String, expr: &'a Expr, docs: Vec<String>) -> Self {
×
336
        Self { name, expr, docs }
337
    }
338
}
339

340
impl<'a> Parse<'a, Constant<'a>> for TraitItemConst {
NEW
341
    fn parse(&'a mut self) -> Result<Constant<'a>> {
×
NEW
342
        if self.default.is_none() {
×
NEW
343
            bail!(self => "Interface const could not be empty");
×
344
        }
345

NEW
346
        let attr = PhpConstAttribute::from_attributes(&self.attrs)?;
×
NEW
347
        let name = self.ident.to_string();
×
NEW
348
        let docs = get_docs(&attr.attrs)?;
×
NEW
349
        self.attrs.clean_php();
×
350

NEW
351
        let (_, expr) = self.default.as_ref().unwrap();
×
NEW
352
        Ok(Constant::new(name, expr, docs))
×
353
    }
354
}
355

356
impl<'a> Parse<'a, Vec<Constant<'a>>> for ItemTrait {
NEW
357
    fn parse(&'a mut self) -> Result<Vec<Constant<'a>>> {
×
NEW
358
        Ok(self
×
NEW
359
            .items
×
NEW
360
            .iter_mut()
×
NEW
361
            .filter_map(|item| match item {
×
NEW
362
                TraitItem::Const(c) => Some(c),
×
NEW
363
                _ => None,
×
364
            })
NEW
365
            .flat_map(Parse::parse)
×
NEW
366
            .collect())
×
367
    }
368
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc