Skip to content

Commit 2c1a5f3

Browse files
committed
refactor(macro)!: uinify attributes in #[php] attribute
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 1242e4d commit 2c1a5f3

File tree

12 files changed

+400
-503
lines changed

12 files changed

+400
-503
lines changed

crates/macros/src/class.rs

Lines changed: 48 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,34 @@
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>,
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.
15+
#[darling(flatten)]
16+
rename: PhpRename,
1917
modifier: Option<syn::Ident>,
2018
/// An expression of `ClassFlags` to be applied to the class.
2119
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 {
2820
extends: Option<syn::Expr>,
21+
#[darling(multiple)]
2922
implements: Vec<syn::Expr>,
30-
docs: Vec<String>,
23+
attrs: Vec<Attribute>,
3124
}
3225

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

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

8038
let class_impl = generate_registered_class_impl(
8139
ident,
82-
args.name.as_deref(),
83-
args.modifier.as_ref(),
84-
class_attrs.extends.as_ref(),
85-
&class_attrs.implements,
40+
&name,
41+
attr.modifier.as_ref(),
42+
attr.extends.as_ref(),
43+
&attr.implements,
8644
&fields,
87-
args.flags.as_ref(),
88-
&class_attrs.docs,
45+
attr.flags.as_ref(),
46+
&docs,
8947
);
9048

9149
Ok(quote! {
@@ -96,6 +54,16 @@ pub fn parser(args: TokenStream, mut input: ItemStruct) -> Result<TokenStream> {
9654
})
9755
}
9856

57+
#[derive(FromAttributes, Debug, Default)]
58+
#[darling(attributes(php), forward_attrs(doc), default)]
59+
struct PropAttributes {
60+
prop: Flag,
61+
#[darling(flatten)]
62+
rename: PhpRename,
63+
flags: Option<Expr>,
64+
attrs: Vec<Attribute>,
65+
}
66+
9967
fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<Vec<Property<'a>>> {
10068
#[derive(Debug, Default, FromMeta)]
10169
#[darling(default)]
@@ -105,146 +73,47 @@ fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<
10573

10674
let mut result = vec![];
10775
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-
});
76+
let attr = PropAttributes::from_attributes(&field.attrs)?;
77+
if attr.prop.is_present() {
78+
let ident = field
79+
.ident
80+
.as_ref()
81+
.ok_or_else(|| err!("Only named fields can be properties."))?;
82+
let docs = get_docs(&attr.attrs)?;
83+
field.attrs.retain(|attr| !attr.path().is_ident("php"));
84+
85+
result.push(Property { ident, attr, docs });
13786
}
13887
}
13988

14089
Ok(result)
14190
}
14291

14392
#[derive(Debug)]
144-
pub struct Property<'a> {
93+
struct Property<'a> {
14594
pub ident: &'a syn::Ident,
146-
pub attr: PropertyAttr,
95+
pub attr: PropAttributes,
14796
pub docs: Vec<String>,
14897
}
14998

15099
impl Property<'_> {
151100
pub fn name(&self) -> String {
152-
self.attr
153-
.rename
154-
.to_owned()
155-
.unwrap_or_else(|| self.ident.to_string())
101+
self.attr.rename.rename(self.ident.to_string())
156102
}
157103
}
158104

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-
231105
/// Generates an implementation of `RegisteredClass` for struct `ident`.
232106
#[allow(clippy::too_many_arguments)]
233107
fn generate_registered_class_impl(
234108
ident: &syn::Ident,
235-
class_name: Option<&str>,
109+
class_name: &str,
236110
modifier: Option<&syn::Ident>,
237111
extends: Option<&syn::Expr>,
238112
implements: &[syn::Expr],
239113
fields: &[Property],
240114
flags: Option<&syn::Expr>,
241115
docs: &[String],
242116
) -> 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-
};
248117
let modifier = modifier.option_tokens();
249118
let extends = extends.option_tokens();
250119

@@ -255,7 +124,7 @@ fn generate_registered_class_impl(
255124
.attr
256125
.flags
257126
.as_ref()
258-
.map(|flags| flags.to_token_stream())
127+
.map(ToTokens::to_token_stream)
259128
.unwrap_or(quote! { ::ext_php_rs::flags::PropertyFlags::Public });
260129
let docs = &prop.docs;
261130

crates/macros/src/constant.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,51 @@
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, Visibility};
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+
pub(crate) vis: Option<Visibility>,
19+
pub(crate) attrs: Vec<syn::Attribute>,
20+
}
21+
22+
pub fn parser(mut item: ItemConst) -> Result<TokenStream> {
23+
let attr = PhpConstAttribute::from_attributes(&item.attrs)?;
24+
25+
let name = attr.rename.rename(item.ident.to_string());
26+
let name_ident = format_ident!("{INTERNAL_CONST_NAME_PREFIX}{}", item.ident);
27+
28+
let docs = get_docs(&attr.attrs)?;
1229
let docs_ident = format_ident!("{INTERNAL_CONST_DOC_PREFIX}{}", item.ident);
30+
item.attrs.retain(|attr| !attr.path().is_ident("php"));
1331

14-
quote! {
32+
Ok(quote! {
1533
#item
1634
#[allow(non_upper_case_globals)]
1735
const #docs_ident: &[&str] = &[#(#docs),*];
18-
}
36+
#[allow(non_upper_case_globals)]
37+
const #name_ident: &str = #name;
38+
})
1939
}
2040

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

2748
Ok(quote! {
2849
(#const_name, #input, #doc_const)
29-
3050
})
3151
}

0 commit comments

Comments
 (0)