Skip to content

Commit 90dfe33

Browse files
authored
refactor(macro)!: uinify attributes in #[php] attribute
Use consistent macro across all operations and make it clearer, that those are operations affecting the php side of things. BREAKING CHANGE: Attributes like `#[prop]`, `#[rename]`, etc. have been moved to `#[php]` attributes like `#[php(prop)]`, have been moved to `#[php]` attributes like `#[php(prop)]`, `#[php(name = "Foo")]`, `#[php(rename = CamelCase)]`, etc. Refs: #391
1 parent f7db682 commit 90dfe33

File tree

18 files changed

+752
-624
lines changed

18 files changed

+752
-624
lines changed

crates/macros/src/class.rs

Lines changed: 48 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,37 @@
1-
use darling::ast::NestedMeta;
2-
use darling::{FromMeta, ToTokens};
3-
use proc_macro2::{Ident, TokenStream};
1+
use darling::util::Flag;
2+
use darling::{FromAttributes, FromMeta, ToTokens};
3+
use proc_macro2::TokenStream;
44
use quote::quote;
5-
use syn::parse::ParseStream;
6-
use syn::{Attribute, Expr, Fields, ItemStruct, LitStr, Meta, Token};
5+
use syn::{Attribute, Expr, Fields, ItemStruct};
76

87
use crate::helpers::get_docs;
8+
use crate::parsing::PhpRename;
99
use crate::prelude::*;
1010

11-
#[derive(Debug, Default, FromMeta)]
12-
#[darling(default)]
13-
pub struct StructArgs {
11+
#[derive(FromAttributes, Debug, Default)]
12+
#[darling(attributes(php), forward_attrs(doc), default)]
13+
pub struct StructAttributes {
1414
/// The name of the PHP class. Defaults to the same name as the struct.
15-
name: Option<String>,
15+
#[darling(flatten)]
16+
rename: PhpRename,
1617
/// A modifier function which should accept one argument, a `ClassBuilder`,
1718
/// and return the same object. Allows the user to modify the class before
1819
/// it is built.
1920
modifier: Option<syn::Ident>,
2021
/// An expression of `ClassFlags` to be applied to the class.
2122
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 {
2823
extends: Option<syn::Expr>,
24+
#[darling(multiple)]
2925
implements: Vec<syn::Expr>,
30-
docs: Vec<String>,
26+
attrs: Vec<Attribute>,
3127
}
3228

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> {
29+
pub fn parser(mut input: ItemStruct) -> Result<TokenStream> {
30+
let attr = StructAttributes::from_attributes(&input.attrs)?;
6531
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)?;
32+
let name = attr.rename.rename(ident.to_string());
33+
let docs = get_docs(&attr.attrs)?;
34+
input.attrs.retain(|attr| !attr.path().is_ident("php"));
7435

7536
let fields = match &mut input.fields {
7637
Fields::Named(fields) => parse_fields(fields.named.iter_mut())?,
@@ -79,13 +40,13 @@ pub fn parser(args: TokenStream, mut input: ItemStruct) -> Result<TokenStream> {
7940

8041
let class_impl = generate_registered_class_impl(
8142
ident,
82-
args.name.as_deref(),
83-
args.modifier.as_ref(),
84-
class_attrs.extends.as_ref(),
85-
&class_attrs.implements,
43+
&name,
44+
attr.modifier.as_ref(),
45+
attr.extends.as_ref(),
46+
&attr.implements,
8647
&fields,
87-
args.flags.as_ref(),
88-
&class_attrs.docs,
48+
attr.flags.as_ref(),
49+
&docs,
8950
);
9051

9152
Ok(quote! {
@@ -96,6 +57,16 @@ pub fn parser(args: TokenStream, mut input: ItemStruct) -> Result<TokenStream> {
9657
})
9758
}
9859

60+
#[derive(FromAttributes, Debug, Default)]
61+
#[darling(attributes(php), forward_attrs(doc), default)]
62+
struct PropAttributes {
63+
prop: Flag,
64+
#[darling(flatten)]
65+
rename: PhpRename,
66+
flags: Option<Expr>,
67+
attrs: Vec<Attribute>,
68+
}
69+
9970
fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<Vec<Property<'a>>> {
10071
#[derive(Debug, Default, FromMeta)]
10172
#[darling(default)]
@@ -105,146 +76,47 @@ fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<
10576

10677
let mut result = vec![];
10778
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-
});
79+
let attr = PropAttributes::from_attributes(&field.attrs)?;
80+
if attr.prop.is_present() {
81+
let ident = field
82+
.ident
83+
.as_ref()
84+
.ok_or_else(|| err!("Only named fields can be properties."))?;
85+
let docs = get_docs(&attr.attrs)?;
86+
field.attrs.retain(|attr| !attr.path().is_ident("php"));
87+
88+
result.push(Property { ident, attr, docs });
13789
}
13890
}
13991

14092
Ok(result)
14193
}
14294

14395
#[derive(Debug)]
144-
pub struct Property<'a> {
96+
struct Property<'a> {
14597
pub ident: &'a syn::Ident,
146-
pub attr: PropertyAttr,
98+
pub attr: PropAttributes,
14799
pub docs: Vec<String>,
148100
}
149101

150102
impl Property<'_> {
151103
pub fn name(&self) -> String {
152-
self.attr
153-
.rename
154-
.to_owned()
155-
.unwrap_or_else(|| self.ident.to_string())
104+
self.attr.rename.rename(self.ident.to_string())
156105
}
157106
}
158107

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-
231108
/// Generates an implementation of `RegisteredClass` for struct `ident`.
232109
#[allow(clippy::too_many_arguments)]
233110
fn generate_registered_class_impl(
234111
ident: &syn::Ident,
235-
class_name: Option<&str>,
112+
class_name: &str,
236113
modifier: Option<&syn::Ident>,
237114
extends: Option<&syn::Expr>,
238115
implements: &[syn::Expr],
239116
fields: &[Property],
240117
flags: Option<&syn::Expr>,
241118
docs: &[String],
242119
) -> 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-
};
248120
let modifier = modifier.option_tokens();
249121
let extends = extends.option_tokens();
250122

@@ -255,7 +127,7 @@ fn generate_registered_class_impl(
255127
.attr
256128
.flags
257129
.as_ref()
258-
.map(|flags| flags.to_token_stream())
130+
.map(ToTokens::to_token_stream)
259131
.unwrap_or(quote! { ::ext_php_rs::flags::PropertyFlags::Public });
260132
let docs = &prop.docs;
261133

crates/macros/src/constant.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,52 @@
1+
use darling::FromAttributes;
12
use proc_macro2::TokenStream;
23
use quote::{format_ident, quote};
34
use syn::ItemConst;
45

56
use crate::helpers::get_docs;
7+
use crate::parsing::PhpRename;
68
use crate::prelude::*;
79

810
const INTERNAL_CONST_DOC_PREFIX: &str = "_internal_const_docs_";
11+
const INTERNAL_CONST_NAME_PREFIX: &str = "_internal_const_name_";
912

10-
pub fn parser(item: ItemConst) -> TokenStream {
11-
let docs = get_docs(&item.attrs);
13+
#[derive(FromAttributes, Default, Debug)]
14+
#[darling(default, attributes(php), forward_attrs(doc))]
15+
pub(crate) struct PhpConstAttribute {
16+
#[darling(flatten)]
17+
pub(crate) rename: PhpRename,
18+
// TODO: Implement const Visibility
19+
// pub(crate) vis: Option<Visibility>,
20+
pub(crate) attrs: Vec<syn::Attribute>,
21+
}
22+
23+
pub fn parser(mut item: ItemConst) -> Result<TokenStream> {
24+
let attr = PhpConstAttribute::from_attributes(&item.attrs)?;
25+
26+
let name = attr.rename.rename(item.ident.to_string());
27+
let name_ident = format_ident!("{INTERNAL_CONST_NAME_PREFIX}{}", item.ident);
28+
29+
let docs = get_docs(&attr.attrs)?;
1230
let docs_ident = format_ident!("{INTERNAL_CONST_DOC_PREFIX}{}", item.ident);
31+
item.attrs.retain(|attr| !attr.path().is_ident("php"));
1332

14-
quote! {
33+
Ok(quote! {
1534
#item
1635
#[allow(non_upper_case_globals)]
1736
const #docs_ident: &[&str] = &[#(#docs),*];
18-
}
37+
#[allow(non_upper_case_globals)]
38+
const #name_ident: &str = #name;
39+
})
1940
}
2041

21-
pub fn wrap(input: syn::Path) -> Result<TokenStream> {
22-
let Some(const_name) = input.get_ident().map(|i| i.to_string()) else {
42+
pub fn wrap(input: &syn::Path) -> Result<TokenStream> {
43+
let Some(const_name) = input.get_ident().map(ToString::to_string) else {
2344
bail!(input => "Pass a PHP const into `wrap_constant!()`.");
2445
};
2546
let doc_const = format_ident!("{INTERNAL_CONST_DOC_PREFIX}{const_name}");
47+
let const_name = format_ident!("{INTERNAL_CONST_NAME_PREFIX}{const_name}");
2648

2749
Ok(quote! {
2850
(#const_name, #input, #doc_const)
29-
3051
})
3152
}

0 commit comments

Comments
 (0)