Skip to content

Commit 61f86e0

Browse files
committed
Update derive macro
1 parent ad1a6ca commit 61f86e0

File tree

2 files changed

+127
-59
lines changed

2 files changed

+127
-59
lines changed

crates/bevy_macro_utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ keywords = ["bevy"]
1212
toml = "0.5.8"
1313
syn = "1.0"
1414
quote = "1.0"
15+
proc-macro2 = "1.0"

crates/bevy_macro_utils/src/lib.rs

Lines changed: 126 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ pub use shape::*;
99
pub use symbol::*;
1010

1111
use proc_macro::TokenStream;
12-
use quote::{quote, quote_spanned};
12+
use proc_macro2::{Span, TokenStream as TokenStream2};
13+
use quote::quote;
1314
use std::{env, path::PathBuf};
1415
use syn::spanned::Spanned;
1516
use toml::{map::Map, Value};
@@ -105,6 +106,55 @@ impl BevyManifest {
105106
}
106107
}
107108

109+
/// A set of attributes defined on an item, variant, or field,
110+
/// in the form e.g. `#[system_label(..)]`.
111+
#[derive(Default)]
112+
struct LabelAttrs {
113+
ignore_fields: Option<Span>,
114+
}
115+
116+
impl LabelAttrs {
117+
/// Parses a list of attributes.
118+
///
119+
/// Ignores any that aren't of the form `#[my_label(..)]`.
120+
/// Returns `Ok` if the iterator is empty.
121+
pub fn new<'a>(
122+
iter: impl IntoIterator<Item = &'a syn::Attribute>,
123+
attr_name: &str,
124+
) -> syn::Result<Self> {
125+
let mut this = Self::default();
126+
for attr in iter {
127+
// If it's not of the form `#[my_label(..)]`, skip it.
128+
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
129+
continue;
130+
}
131+
132+
// Parse the argument/s to the attribute.
133+
attr.parse_args_with(|input: syn::parse::ParseStream| {
134+
loop {
135+
syn::custom_keyword!(ignore_fields);
136+
137+
let next = input.lookahead1();
138+
if next.peek(ignore_fields) {
139+
let kw: ignore_fields = input.parse()?;
140+
this.ignore_fields = Some(kw.span);
141+
} else {
142+
return Err(next.error());
143+
}
144+
145+
if input.is_empty() {
146+
break;
147+
}
148+
let _comma: syn::Token![,] = input.parse()?;
149+
}
150+
Ok(())
151+
})?;
152+
}
153+
154+
Ok(this)
155+
}
156+
}
157+
108158
/// Derive a label trait
109159
///
110160
/// # Args
@@ -116,103 +166,120 @@ pub fn derive_label(
116166
trait_path: &syn::Path,
117167
attr_name: &str,
118168
) -> TokenStream {
119-
// return true if the variant specified is an `ignore_fields` attribute
120-
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
121-
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
122-
return false;
123-
}
124-
125-
syn::custom_keyword!(ignore_fields);
126-
attr.parse_args_with(|input: syn::parse::ParseStream| {
127-
let ignore = input.parse::<Option<ignore_fields>>()?.is_some();
128-
Ok(ignore)
129-
})
130-
.unwrap()
131-
}
169+
let item_attrs = match LabelAttrs::new(&input.attrs, attr_name) {
170+
Ok(a) => a,
171+
Err(e) => return e.into_compile_error().into(),
172+
};
132173

133-
let ident = input.ident.clone();
174+
derive_named_label(input, &item_attrs, trait_path, attr_name)
175+
.unwrap_or_else(syn::Error::into_compile_error)
176+
.into()
177+
}
134178

135-
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
179+
fn with_static_bound(where_clause: Option<&syn::WhereClause>) -> syn::WhereClause {
136180
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
137181
where_token: Default::default(),
138182
predicates: Default::default(),
139183
});
140184
where_clause
141185
.predicates
142186
.push(syn::parse2(quote! { Self: 'static }).unwrap());
187+
where_clause
188+
}
189+
190+
fn derive_named_label(
191+
input: syn::DeriveInput,
192+
item_attrs: &LabelAttrs,
193+
trait_path: &syn::Path,
194+
attr_name: &str,
195+
) -> syn::Result<TokenStream2> {
196+
let ident = input.ident.clone();
197+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
198+
let where_clause = with_static_bound(where_clause);
143199

144-
let as_str = match input.data {
200+
let (data, fmt) = match input.data {
145201
syn::Data::Struct(d) => {
202+
let all_field_attrs =
203+
LabelAttrs::new(d.fields.iter().flat_map(|f| &f.attrs), attr_name)?;
146204
// see if the user tried to ignore fields incorrectly
147-
if let Some(attr) = d
148-
.fields
149-
.iter()
150-
.flat_map(|f| &f.attrs)
151-
.find(|a| is_ignore(a, attr_name))
152-
{
153-
let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration");
154-
return quote_spanned! {
155-
attr.span() => compile_error!(#err_msg);
156-
}
157-
.into();
205+
if let Some(attr) = all_field_attrs.ignore_fields {
206+
let err_msg = format!(
207+
r#"`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually:
208+
try adding it to the struct declaration"#
209+
);
210+
return Err(syn::Error::new(attr, err_msg));
158211
}
159212
// Structs must either be fieldless, or explicitly ignore the fields.
160-
let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name));
161-
if matches!(d.fields, syn::Fields::Unit) || ignore_fields {
213+
let ignore_fields = item_attrs.ignore_fields.is_some();
214+
if d.fields.is_empty() || ignore_fields {
162215
let lit = ident.to_string();
163-
quote! { #lit }
216+
let data = quote! { 0 };
217+
let as_str = quote! { write!(f, #lit) };
218+
(data, as_str)
164219
} else {
165220
let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
166-
return quote_spanned! {
167-
d.fields.span() => compile_error!(#err_msg);
168-
}
169-
.into();
221+
return Err(syn::Error::new(d.fields.span(), err_msg));
170222
}
171223
}
172224
syn::Data::Enum(d) => {
173225
// check if the user put #[label(ignore_fields)] in the wrong place
174-
if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) {
226+
if let Some(attr) = item_attrs.ignore_fields {
175227
let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations");
176-
return quote_spanned! {
177-
attr.span() => compile_error!(#err_msg);
178-
}
179-
.into();
228+
return Err(syn::Error::new(attr, err_msg));
180229
}
181-
let arms = d.variants.iter().map(|v| {
230+
231+
let mut data_arms = Vec::with_capacity(d.variants.len());
232+
let mut fmt_arms = Vec::with_capacity(d.variants.len());
233+
234+
for (i, v) in d.variants.iter().enumerate() {
235+
let v_attrs = LabelAttrs::new(&v.attrs, attr_name)?;
182236
// Variants must either be fieldless, or explicitly ignore the fields.
183-
let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name));
184-
if matches!(v.fields, syn::Fields::Unit) | ignore_fields {
237+
let ignore_fields = v_attrs.ignore_fields.is_some();
238+
if v.fields.is_empty() || ignore_fields {
185239
let mut path = syn::Path::from(ident.clone());
186240
path.segments.push(v.ident.clone().into());
241+
242+
let i = i as u64;
243+
data_arms.push(quote! { #path { .. } => #i });
244+
187245
let lit = format!("{ident}::{}", v.ident.clone());
188-
quote! { #path { .. } => #lit }
246+
fmt_arms.push(quote! { #i => { write!(f, #lit) } });
189247
} else {
190248
let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
191-
quote_spanned! {
192-
v.fields.span() => _ => { compile_error!(#err_msg); }
193-
}
249+
return Err(syn::Error::new(v.fields.span(), err_msg));
194250
}
195-
});
196-
quote! {
251+
}
252+
253+
let data = quote! {
197254
match self {
198-
#(#arms),*
255+
#(#data_arms),*
199256
}
200-
}
257+
};
258+
let fmt = quote! {
259+
match data {
260+
#(#fmt_arms),*
261+
_ => Err(::std::fmt::Error),
262+
}
263+
};
264+
(data, fmt)
201265
}
202266
syn::Data::Union(_) => {
203-
return quote_spanned! {
204-
input.span() => compile_error!("Unions cannot be used as labels.");
205-
}
206-
.into();
267+
let err_msg = format!(
268+
"Unions cannot be used as labels, unless marked with `#[{attr_name}(intern)]`."
269+
);
270+
return Err(syn::Error::new(input.span(), err_msg));
207271
}
208272
};
209273

210-
(quote! {
274+
Ok(quote! {
211275
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
212-
fn as_str(&self) -> &'static str {
213-
#as_str
276+
#[inline]
277+
fn data(&self) -> u64 {
278+
#data
279+
}
280+
fn fmt(data: u64, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
281+
#fmt
214282
}
215283
}
216284
})
217-
.into()
218285
}

0 commit comments

Comments
 (0)