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

davidcole1340 / ext-php-rs / 15715261521

17 Jun 2025 06:27PM UTC coverage: 22.034% (-0.04%) from 22.076%
15715261521

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

16 of 35 new or added lines in 5 files covered. (45.71%)

2 existing lines in 2 files now uncovered.

871 of 3953 relevant lines covered (22.03%)

2.35 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),
×
NEW
51
        args.rename_constants.unwrap_or(RenameRule::ScreamingSnake),
×
52
    );
53
    parsed.parse(input.items.iter_mut())?;
×
54

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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