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

davidcole1340 / ext-php-rs / 15331766277

29 May 2025 07:22PM UTC coverage: 20.798% (-0.1%) from 20.927%
15331766277

Pull #436

github

Xenira
chore(macro)!: change rename defaults to match psr

BREAKING CHANGE: Methods and Properties are renamed to camelCase by default. Classes to PascalCase and constants to UPPER_CASE.

Refs: #189
Pull Request #436: chore(macro)!: change rename defaults to match psr

8 of 29 new or added lines in 5 files covered. (27.59%)

2 existing lines in 2 files now uncovered.

818 of 3933 relevant lines covered (20.8%)

2.05 hits per line

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

0.0
/crates/macros/src/impl_.rs
1
use darling::util::Flag;
2
use darling::FromAttributes;
3
use proc_macro2::TokenStream;
4
use quote::quote;
5
use std::collections::{HashMap, HashSet};
6
use syn::{Expr, Ident, ItemImpl};
7

8
use crate::constant::PhpConstAttribute;
9
use crate::function::{Args, CallType, Function, MethodReceiver};
10
use crate::helpers::get_docs;
11
use crate::parsing::{PhpRename, RenameRule, Visibility};
12
use crate::prelude::*;
13

14
/// Method types.
15
#[derive(Debug)]
16
enum MethodTy {
17
    /// Regular PHP method.
18
    Normal,
19
    /// Constructor method.
20
    Constructor,
21
    /// Property getter method.
22
    Getter,
23
    /// Property setter method.
24
    Setter,
25
    /// Abstract method.
26
    Abstract,
27
}
28

29
#[derive(FromAttributes, Debug, Default)]
30
#[darling(attributes(php), default)]
31
pub struct PhpImpl {
32
    /// Rename methods to match the given rule.
33
    rename_methods: Option<RenameRule>,
34
    /// Rename constants to match the given rule.
35
    rename_constants: Option<RenameRule>,
36
}
37

38
pub fn parser(mut input: ItemImpl) -> Result<TokenStream> {
×
39
    let args = PhpImpl::from_attributes(&input.attrs)?;
×
40
    input.attrs.retain(|attr| !attr.path().is_ident("php"));
×
41
    let path = match &*input.self_ty {
×
42
        syn::Type::Path(ty) => &ty.path,
×
43
        _ => {
44
            bail!(input.self_ty => "The `#[php_impl]` attribute is only valid for struct implementations.")
×
45
        }
46
    };
47

48
    let mut parsed = ParsedImpl::new(
49
        path,
×
50
        args.rename_methods.unwrap_or(RenameRule::Camel),
×
51
        args.rename_constants
×
52
            .unwrap_or(RenameRule::ScreamingSnakeCase),
×
53
    );
54
    parsed.parse(input.items.iter_mut())?;
×
55

56
    let php_class_impl = parsed.generate_php_class_impl();
×
57
    Ok(quote::quote! {
×
58
        #input
×
59
        #php_class_impl
×
60
    })
61
}
62

63
/// Arguments applied to methods.
64
#[derive(Debug)]
65
struct MethodArgs {
66
    /// Method name. Only applies to PHP (not the Rust method name).
67
    name: String,
68
    /// The first optional argument of the function signature.
69
    optional: Option<Ident>,
70
    /// Default values for optional arguments.
71
    defaults: HashMap<Ident, Expr>,
72
    /// Visibility of the method (public, protected, private).
73
    vis: Visibility,
74
    /// Method type.
75
    ty: MethodTy,
76
}
77

78
#[derive(FromAttributes, Default, Debug)]
79
#[darling(default, attributes(php), forward_attrs(doc))]
80
pub struct PhpFunctionImplAttribute {
81
    #[darling(flatten)]
82
    rename: PhpRename,
83
    defaults: HashMap<Ident, Expr>,
84
    optional: Option<Ident>,
85
    vis: Option<Visibility>,
86
    attrs: Vec<syn::Attribute>,
87
    getter: Flag,
88
    setter: Flag,
89
    constructor: Flag,
90
    abstract_method: Flag,
91
}
92

93
impl MethodArgs {
94
    fn new(name: String, attr: PhpFunctionImplAttribute) -> Self {
×
95
        let ty = if name == "__construct" || attr.constructor.is_present() {
×
96
            MethodTy::Constructor
×
97
        } else if attr.getter.is_present() {
×
98
            MethodTy::Getter
×
99
        } else if attr.setter.is_present() {
×
100
            MethodTy::Setter
×
101
        } else if attr.abstract_method.is_present() {
×
102
            MethodTy::Abstract
×
103
        } else {
104
            MethodTy::Normal
×
105
        };
106

107
        Self {
108
            name,
109
            optional: attr.optional,
×
110
            defaults: attr.defaults,
×
111
            vis: attr.vis.unwrap_or(Visibility::Public),
×
112
            ty,
113
        }
114
    }
115
}
116

117
#[derive(Debug)]
118
struct ParsedImpl<'a> {
119
    path: &'a syn::Path,
120
    rename_methods: RenameRule,
121
    rename_constants: RenameRule,
122
    functions: Vec<FnBuilder>,
123
    constructor: Option<Function<'a>>,
124
    constants: Vec<Constant<'a>>,
125
}
126

127
#[derive(Debug, Eq, Hash, PartialEq)]
128
enum MethodModifier {
129
    Abstract,
130
    Static,
131
}
132

133
impl quote::ToTokens for MethodModifier {
134
    fn to_tokens(&self, tokens: &mut TokenStream) {
×
135
        match *self {
×
136
            Self::Abstract => quote! { ::ext_php_rs::flags::MethodFlags::Abstract },
×
137
            Self::Static => quote! { ::ext_php_rs::flags::MethodFlags::Static },
×
138
        }
139
        .to_tokens(tokens);
×
140
    }
141
}
142

143
#[derive(Debug)]
144
struct FnBuilder {
145
    /// Tokens which represent the `FunctionBuilder` for this function.
146
    pub builder: TokenStream,
147
    /// The visibility of this method.
148
    pub vis: Visibility,
149
    /// Whether this method is abstract.
150
    pub modifiers: HashSet<MethodModifier>,
151
}
152

153
#[derive(Debug)]
154
struct Constant<'a> {
155
    /// Name of the constant in PHP land.
156
    name: String,
157
    /// Identifier of the constant in Rust land.
158
    ident: &'a syn::Ident,
159
    /// Documentation for the constant.
160
    docs: Vec<String>,
161
}
162

163
impl<'a> ParsedImpl<'a> {
164
    /// Create a new, empty parsed impl block.
165
    ///
166
    /// # Parameters
167
    ///
168
    /// * `path` - Path of the type the `impl` block is for.
169
    /// * `rename` - Rename rule for methods.
170
    fn new(path: &'a syn::Path, rename_methods: RenameRule, rename_constants: RenameRule) -> Self {
×
171
        Self {
172
            path,
173
            rename_methods,
174
            rename_constants,
175
            functions: Vec::default(),
×
176
            constructor: Option::default(),
×
177
            constants: Vec::default(),
×
178
        }
179
    }
180

181
    /// Parses an impl block from `items`, populating `self`.
182
    fn parse(&mut self, items: impl Iterator<Item = &'a mut syn::ImplItem>) -> Result<()> {
×
183
        for items in items {
×
184
            match items {
×
185
                syn::ImplItem::Const(c) => {
×
186
                    let attr = PhpConstAttribute::from_attributes(&c.attrs)?;
×
NEW
187
                    let name = attr
×
NEW
188
                        .rename
×
NEW
189
                        .rename(c.ident.to_string(), self.rename_constants);
×
190
                    let docs = get_docs(&attr.attrs)?;
×
191
                    c.attrs.retain(|attr| !attr.path().is_ident("php"));
×
192

193
                    self.constants.push(Constant {
×
194
                        name,
×
195
                        ident: &c.ident,
×
196
                        docs,
×
197
                    });
198
                }
199
                syn::ImplItem::Fn(method) => {
×
200
                    let attr = PhpFunctionImplAttribute::from_attributes(&method.attrs)?;
×
NEW
201
                    let name = attr
×
NEW
202
                        .rename
×
NEW
203
                        .rename_method(method.sig.ident.to_string(), self.rename_methods);
×
204
                    let docs = get_docs(&attr.attrs)?;
×
205
                    method.attrs.retain(|attr| !attr.path().is_ident("php"));
×
206

207
                    let opts = MethodArgs::new(name, attr);
×
208
                    let args = Args::parse_from_fnargs(method.sig.inputs.iter(), opts.defaults)?;
×
209
                    let mut func = Function::new(&method.sig, opts.name, args, opts.optional, docs);
×
210

211
                    let mut modifiers: HashSet<MethodModifier> = HashSet::new();
×
212

213
                    if matches!(opts.ty, MethodTy::Constructor) {
×
214
                        if self.constructor.replace(func).is_some() {
×
215
                            bail!(method => "Only one constructor can be provided per class.");
×
216
                        }
217
                    } else {
218
                        let call_type = CallType::Method {
219
                            class: self.path,
×
220
                            receiver: if func.args.receiver.is_some() {
×
221
                                // `&self` or `&mut self`
222
                                MethodReceiver::Class
223
                            } else if func
224
                                .args
225
                                .typed
226
                                .first()
227
                                .is_some_and(|arg| arg.name == "self_")
228
                            {
229
                                // `self_: &[mut] ZendClassObject<Self>`
230
                                // Need to remove arg from argument list
231
                                func.args.typed.pop();
232
                                MethodReceiver::ZendClassObject
233
                            } else {
234
                                modifiers.insert(MethodModifier::Static);
235
                                // Static method
236
                                MethodReceiver::Static
237
                            },
238
                        };
239
                        if matches!(opts.ty, MethodTy::Abstract) {
×
240
                            modifiers.insert(MethodModifier::Abstract);
×
241
                        }
242

243
                        let builder = func.function_builder(call_type);
×
244

245
                        self.functions.push(FnBuilder {
×
246
                            builder,
×
247
                            vis: opts.vis,
×
248
                            modifiers,
×
249
                        });
250
                    }
251
                }
252
                _ => {}
×
253
            }
254
        }
255
        Ok(())
×
256
    }
257

258
    /// Generates an `impl PhpClassImpl<Self> for PhpClassImplCollector<Self>`
259
    /// block.
260
    fn generate_php_class_impl(&self) -> TokenStream {
×
261
        let path = &self.path;
×
262
        let functions = &self.functions;
×
263
        let constructor = self
×
264
            .constructor
×
265
            .as_ref()
266
            .map(|func| func.constructor_meta(self.path))
×
267
            .option_tokens();
268
        let constants = self.constants.iter().map(|c| {
×
269
            let name = &c.name;
×
270
            let ident = c.ident;
×
271
            let docs = &c.docs;
×
272
            quote! {
×
273
                (#name, &#path::#ident, &[#(#docs),*])
×
274
            }
275
        });
276

277
        quote! {
×
278
            impl ::ext_php_rs::internal::class::PhpClassImpl<#path>
×
279
                for ::ext_php_rs::internal::class::PhpClassImplCollector<#path>
×
280
            {
281
                fn get_methods(self) -> ::std::vec::Vec<
×
282
                    (::ext_php_rs::builders::FunctionBuilder<'static>, ::ext_php_rs::flags::MethodFlags)
×
283
                > {
×
284
                    vec![#(#functions),*]
×
285
                }
286

287
                fn get_method_props<'a>(self) -> ::std::collections::HashMap<&'static str, ::ext_php_rs::props::Property<'a, #path>> {
×
288
                    todo!()
×
289
                }
290

291
                fn get_constructor(self) -> ::std::option::Option<::ext_php_rs::class::ConstructorMeta<#path>> {
×
292
                    #constructor
×
293
                }
294

295
                fn get_constants(self) -> &'static [(&'static str, &'static dyn ::ext_php_rs::convert::IntoZvalDyn, &'static [&'static str])] {
×
296
                    &[#(#constants),*]
×
297
                }
298
            }
299
        }
300
    }
301
}
302

303
impl quote::ToTokens for FnBuilder {
304
    fn to_tokens(&self, tokens: &mut TokenStream) {
×
305
        let builder = &self.builder;
×
306
        // TODO(cole_d): allow more flags via attributes
307
        let mut flags = vec![];
×
308
        flags.push(match self.vis {
×
309
            Visibility::Public => quote! { ::ext_php_rs::flags::MethodFlags::Public },
×
310
            Visibility::Protected => quote! { ::ext_php_rs::flags::MethodFlags::Protected },
×
311
            Visibility::Private => quote! { ::ext_php_rs::flags::MethodFlags::Private },
×
312
        });
313
        for flag in &self.modifiers {
×
314
            flags.push(quote! { #flag });
×
315
        }
316

317
        quote! {
×
318
            (#builder, #(#flags)|*)
×
319
        }
320
        .to_tokens(tokens);
×
321
    }
322
}
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