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

davidcole1340 / ext-php-rs / 14285961536

05 Apr 2025 09:28PM CUT coverage: 13.062%. Remained the same
14285961536

Pull #417

github

Xenira
docs(coverage): add coverage badge
Pull Request #417: ci(coverage): ignore release pr

520 of 3981 relevant lines covered (13.06%)

1.23 hits per line

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

0.0
/crates/macros/src/class.rs
1
use darling::ast::NestedMeta;
2
use darling::{FromMeta, ToTokens};
3
use proc_macro2::{Ident, TokenStream};
4
use quote::quote;
5
use syn::parse::ParseStream;
6
use syn::{Attribute, Expr, Fields, ItemStruct, LitStr, Meta, Token};
7

8
use crate::helpers::get_docs;
9
use crate::prelude::*;
10

11
#[derive(Debug, Default, FromMeta)]
12
#[darling(default)]
13
pub struct StructArgs {
14
    /// The name of the PHP class. Defaults to the same name as the struct.
15
    name: Option<String>,
16
    /// A modifier function which should accept one argument, a `ClassBuilder`,
17
    /// and return the same object. Allows the user to modify the class before
18
    /// it is built.
19
    modifier: Option<syn::Ident>,
20
    /// An expression of `ClassFlags` to be applied to the class.
21
    flags: Option<syn::Expr>,
22
}
23

24
/// Sub-attributes which are parsed by this macro. Must be placed underneath the
25
/// main `#[php_class]` attribute.
26
#[derive(Debug, Default)]
27
struct ClassAttrs {
28
    extends: Option<syn::Expr>,
29
    implements: Vec<syn::Expr>,
30
    docs: Vec<String>,
31
}
32

33
impl ClassAttrs {
34
    fn parse(&mut self, attrs: &mut Vec<syn::Attribute>) -> Result<()> {
×
35
        let mut unparsed = vec![];
×
36
        unparsed.append(attrs);
×
37
        for attr in unparsed {
×
38
            let path = attr.path();
×
39

40
            if path.is_ident("extends") {
×
41
                if self.extends.is_some() {
×
42
                    bail!(attr => "Only one `#[extends]` attribute is valid per struct.");
×
43
                }
44
                let extends: syn::Expr = match attr.parse_args() {
×
45
                    Ok(extends) => extends,
×
46
                    Err(_) => bail!(attr => "Invalid arguments passed to extends attribute."),
×
47
                };
48
                self.extends = Some(extends);
×
49
            } else if path.is_ident("implements") {
×
50
                let implements: syn::Expr = match attr.parse_args() {
×
51
                    Ok(extends) => extends,
×
52
                    Err(_) => bail!(attr => "Invalid arguments passed to implements attribute."),
×
53
                };
54
                self.implements.push(implements);
×
55
            } else {
56
                attrs.push(attr);
×
57
            }
58
        }
59
        self.docs = get_docs(attrs);
×
60
        Ok(())
×
61
    }
62
}
63

64
pub fn parser(args: TokenStream, mut input: ItemStruct) -> Result<TokenStream> {
×
65
    let ident = &input.ident;
×
66
    let meta = NestedMeta::parse_meta_list(args)?;
×
67
    let args = match StructArgs::from_list(&meta) {
×
68
        Ok(args) => args,
×
69
        Err(e) => bail!("Failed to parse struct arguments: {:?}", e),
×
70
    };
71

72
    let mut class_attrs = ClassAttrs::default();
×
73
    class_attrs.parse(&mut input.attrs)?;
×
74

75
    let fields = match &mut input.fields {
×
76
        Fields::Named(fields) => parse_fields(fields.named.iter_mut())?,
×
77
        _ => vec![],
×
78
    };
79

80
    let class_impl = generate_registered_class_impl(
81
        ident,
×
82
        args.name.as_deref(),
×
83
        args.modifier.as_ref(),
×
84
        class_attrs.extends.as_ref(),
×
85
        &class_attrs.implements,
×
86
        &fields,
×
87
        args.flags.as_ref(),
×
88
        &class_attrs.docs,
×
89
    );
90

91
    Ok(quote! {
×
92
        #input
×
93
        #class_impl
×
94

95
        ::ext_php_rs::class_derives!(#ident);
×
96
    })
97
}
98

99
fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<Vec<Property<'a>>> {
×
100
    #[derive(Debug, Default, FromMeta)]
101
    #[darling(default)]
102
    struct FieldAttr {
103
        rename: Option<String>,
104
    }
105

106
    let mut result = vec![];
×
107
    for field in fields {
×
108
        let mut docs = vec![];
×
109
        let mut property = None;
×
110
        let mut unparsed = vec![];
×
111
        unparsed.append(&mut field.attrs);
×
112

113
        for attr in unparsed {
×
114
            if let Some(parsed) = parse_attribute(&attr)? {
×
115
                match parsed {
×
116
                    ParsedAttribute::Property(prop) => {
×
117
                        let ident = field
×
118
                            .ident
×
119
                            .as_ref()
120
                            .ok_or_else(|| err!(attr => "Only named fields can be properties."))?;
×
121

122
                        property = Some((ident, prop));
×
123
                    }
124
                    ParsedAttribute::Comment(doc) => docs.push(doc),
×
125
                }
126
            } else {
127
                field.attrs.push(attr);
×
128
            }
129
        }
130

131
        if let Some((ident, prop)) = property {
×
132
            result.push(Property {
×
133
                ident,
×
134
                attr: prop,
×
135
                docs,
×
136
            });
137
        }
138
    }
139

140
    Ok(result)
×
141
}
142

143
#[derive(Debug)]
144
pub struct Property<'a> {
145
    pub ident: &'a syn::Ident,
146
    pub attr: PropertyAttr,
147
    pub docs: Vec<String>,
148
}
149

150
impl Property<'_> {
151
    pub fn name(&self) -> String {
×
152
        self.attr
×
153
            .rename
×
154
            .to_owned()
155
            .unwrap_or_else(|| self.ident.to_string())
×
156
    }
157
}
158

159
#[derive(Debug, Default)]
160
pub struct PropertyAttr {
161
    pub rename: Option<String>,
162
    pub flags: Option<Expr>,
163
}
164

165
impl syn::parse::Parse for PropertyAttr {
166
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
×
167
        let mut this = Self::default();
×
168
        while !input.is_empty() {
×
169
            let field = input.parse::<Ident>()?.to_string();
×
170
            input.parse::<Token![=]>()?;
×
171

172
            match field.as_str() {
×
173
                "rename" => {
×
174
                    this.rename.replace(input.parse::<LitStr>()?.value());
×
175
                }
176
                "flags" => {
×
177
                    this.flags.replace(input.parse::<Expr>()?);
×
178
                }
179
                _ => return Err(input.error("invalid attribute field")),
×
180
            }
181

182
            let _ = input.parse::<Token![,]>();
×
183
        }
184

185
        Ok(this)
×
186
    }
187
}
188

189
#[derive(Debug)]
190
pub enum ParsedAttribute {
191
    Property(PropertyAttr),
192
    Comment(String),
193
}
194

195
pub fn parse_attribute(attr: &Attribute) -> Result<Option<ParsedAttribute>> {
×
196
    let name = attr.path().to_token_stream().to_string();
×
197

198
    Ok(match name.as_ref() {
×
199
        "doc" => {
×
200
            struct DocComment(pub String);
×
201

202
            impl syn::parse::Parse for DocComment {
×
203
                fn parse(input: ParseStream) -> syn::Result<Self> {
×
204
                    input.parse::<Token![=]>()?;
×
205
                    let comment: LitStr = input.parse()?;
×
206
                    Ok(Self(comment.value()))
×
207
                }
208
            }
209

210
            let comment: DocComment = syn::parse2(attr.to_token_stream())
×
211
                .map_err(|e| err!(attr => "Failed to parse doc comment {}", e))?;
×
212
            Some(ParsedAttribute::Comment(comment.0))
×
213
        }
214
        "prop" | "property" => {
×
215
            let attr = match attr.meta {
×
216
                Meta::Path(_) => PropertyAttr::default(),
×
217
                Meta::List(_) => attr
×
218
                    .parse_args()
×
219
                    .map_err(|e| err!(attr => "Unable to parse `#[{}]` attribute: {}", name, e))?,
×
220
                _ => {
221
                    bail!(attr => "Invalid attribute format for `#[{}]`", name);
×
222
                }
223
            };
224

225
            Some(ParsedAttribute::Property(attr))
×
226
        }
227
        _ => None,
×
228
    })
229
}
230

231
/// Generates an implementation of `RegisteredClass` for struct `ident`.
232
#[allow(clippy::too_many_arguments)]
233
fn generate_registered_class_impl(
×
234
    ident: &syn::Ident,
235
    class_name: Option<&str>,
236
    modifier: Option<&syn::Ident>,
237
    extends: Option<&syn::Expr>,
238
    implements: &[syn::Expr],
239
    fields: &[Property],
240
    flags: Option<&syn::Expr>,
241
    docs: &[String],
242
) -> TokenStream {
243
    let ident_str = ident.to_string();
×
244
    let class_name = match class_name {
×
245
        Some(class_name) => class_name,
×
246
        None => &ident_str,
×
247
    };
248
    let modifier = modifier.option_tokens();
×
249
    let extends = extends.option_tokens();
×
250

251
    let fields = fields.iter().map(|prop| {
×
252
        let name = prop.name();
×
253
        let ident = prop.ident;
×
254
        let flags = prop
×
255
            .attr
×
256
            .flags
×
257
            .as_ref()
×
258
            .map(|flags| flags.to_token_stream())
×
259
            .unwrap_or(quote! { ::ext_php_rs::flags::PropertyFlags::Public });
×
260
        let docs = &prop.docs;
×
261

262
        quote! {
×
263
            (#name, ::ext_php_rs::internal::property::PropertyInfo {
×
264
                prop: ::ext_php_rs::props::Property::field(|this: &mut Self| &mut this.#ident),
×
265
                flags: #flags,
×
266
                docs: &[#(#docs,)*]
×
267
            })
268
        }
269
    });
270

271
    let flags = match flags {
×
272
        Some(flags) => flags.to_token_stream(),
×
273
        None => quote! { ::ext_php_rs::flags::ClassFlags::empty() }.to_token_stream(),
×
274
    };
275

276
    let docs = quote! {
×
277
        #(#docs)*
×
278
    };
279

280
    quote! {
×
281
        impl ::ext_php_rs::class::RegisteredClass for #ident {
×
282
            const CLASS_NAME: &'static str = #class_name;
×
283
            const BUILDER_MODIFIER: ::std::option::Option<
×
284
                fn(::ext_php_rs::builders::ClassBuilder) -> ::ext_php_rs::builders::ClassBuilder
×
285
            > = #modifier;
×
286
            const EXTENDS: ::std::option::Option<
×
287
                fn() -> &'static ::ext_php_rs::zend::ClassEntry
×
288
            > = #extends;
×
289
            const IMPLEMENTS: &'static [fn() -> &'static ::ext_php_rs::zend::ClassEntry] = &[
×
290
                #(#implements,)*
×
291
            ];
292
            const FLAGS: ::ext_php_rs::flags::ClassFlags = #flags;
×
293
            const DOC_COMMENTS: &'static [&'static str] = &[
×
294
                #docs
×
295
            ];
296

297
            #[inline]
×
298
            fn get_metadata() -> &'static ::ext_php_rs::class::ClassMetadata<Self> {
×
299
                static METADATA: ::ext_php_rs::class::ClassMetadata<#ident> =
×
300
                    ::ext_php_rs::class::ClassMetadata::new();
×
301
                &METADATA
×
302
            }
303

304
            fn get_properties<'a>() -> ::std::collections::HashMap<
×
305
                &'static str, ::ext_php_rs::internal::property::PropertyInfo<'a, Self>
×
306
            > {
×
307
                use ::std::iter::FromIterator;
×
308
                ::std::collections::HashMap::from_iter([
×
309
                    #(#fields,)*
×
310
                ])
311
            }
312

313
            #[inline]
×
314
            fn method_builders() -> ::std::vec::Vec<
×
315
                (::ext_php_rs::builders::FunctionBuilder<'static>, ::ext_php_rs::flags::MethodFlags)
×
316
            > {
×
317
                use ::ext_php_rs::internal::class::PhpClassImpl;
×
318
                ::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_methods()
×
319
            }
320

321
            #[inline]
×
322
            fn constructor() -> ::std::option::Option<::ext_php_rs::class::ConstructorMeta<Self>> {
×
323
                use ::ext_php_rs::internal::class::PhpClassImpl;
×
324
                ::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_constructor()
×
325
            }
326

327
            #[inline]
×
328
            fn constants() -> &'static [(&'static str, &'static dyn ::ext_php_rs::convert::IntoZvalDyn, &'static [&'static str])] {
×
329
                use ::ext_php_rs::internal::class::PhpClassImpl;
×
330
                ::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_constants()
×
331
            }
332
        }
333
    }
334
}
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