bevy_reflect: avoid useless with_{custom_attributes,docs} calls (#19875)

# Objective

`#[derive(Reflect)]` derives `Typed` impls whose `type_info` methods
contain useless calls to `with_custom_attributes` and `with_docs`, e.g.:
```
::bevy::reflect::NamedField:🆕:<f32>("x")
    .with_custom_attributes(
        ::bevy::reflect::attributes::CustomAttributes::default()
    )
    .with_docs(::core::option::Option::None),
```
This hurts compile times and makes the `cargo expand` output harder to
read. It might also hurt runtime speed, depending on whether the
compiler can optimize away the no-op methods.

Avoiding this will help with #19873.

## Solution

Check if the attributes/docs are empty before appending the method
calls.

## Testing

I used `cargo expand` to confirm the useless calls are no longer
produced.

`-Zmacro-stats` outputs tells me this reduces the size of the `Reflect`
impls produced for `bevy_ui` from 1_544_696 bytes to 1_511_214 bytes, a
2.2% drop. Only a small improvement, but it's a start.
This commit is contained in:
Nicholas Nethercote 2025-07-01 09:29:24 +10:00 committed by GitHub
parent 55c7766716
commit 5aa520eac0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 45 deletions

View File

@ -28,6 +28,11 @@ impl CustomAttributes {
Ok(()) Ok(())
} }
/// Is the collection empty?
pub fn is_empty(&self) -> bool {
self.attributes.is_empty()
}
/// Parse `@` (custom attribute) attribute. /// Parse `@` (custom attribute) attribute.
/// ///
/// Examples: /// Examples:

View File

@ -514,25 +514,27 @@ impl<'a> StructField<'a> {
}; };
let ty = self.reflected_type(); let ty = self.reflected_type();
let custom_attributes = self.attrs.custom_attributes.to_tokens(bevy_reflect_path);
#[cfg_attr(
not(feature = "documentation"),
expect(
unused_mut,
reason = "Needs to be mutable if `documentation` feature is enabled.",
)
)]
let mut info = quote! { let mut info = quote! {
#field_info::new::<#ty>(#name).with_custom_attributes(#custom_attributes) #field_info::new::<#ty>(#name)
}; };
let custom_attributes = &self.attrs.custom_attributes;
if !custom_attributes.is_empty() {
let custom_attributes = custom_attributes.to_tokens(bevy_reflect_path);
info.extend(quote! {
.with_custom_attributes(#custom_attributes)
});
}
#[cfg(feature = "documentation")] #[cfg(feature = "documentation")]
{ {
let docs = &self.doc; let docs = &self.doc;
info.extend(quote! { if !docs.is_empty() {
.with_docs(#docs) info.extend(quote! {
}); .with_docs(#docs)
});
}
} }
info info
@ -653,19 +655,20 @@ impl<'a> ReflectStruct<'a> {
.active_fields() .active_fields()
.map(|field| field.to_info_tokens(bevy_reflect_path)); .map(|field| field.to_info_tokens(bevy_reflect_path));
let custom_attributes = self
.meta
.attrs
.custom_attributes()
.to_tokens(bevy_reflect_path);
let mut info = quote! { let mut info = quote! {
#bevy_reflect_path::#info_struct::new::<Self>(&[ #bevy_reflect_path::#info_struct::new::<Self>(&[
#(#field_infos),* #(#field_infos),*
]) ])
.with_custom_attributes(#custom_attributes)
}; };
let custom_attributes = self.meta.attrs.custom_attributes();
if !custom_attributes.is_empty() {
let custom_attributes = custom_attributes.to_tokens(bevy_reflect_path);
info.extend(quote! {
.with_custom_attributes(#custom_attributes)
});
}
if let Some(generics) = generate_generics(self.meta()) { if let Some(generics) = generate_generics(self.meta()) {
info.extend(quote! { info.extend(quote! {
.with_generics(#generics) .with_generics(#generics)
@ -675,9 +678,11 @@ impl<'a> ReflectStruct<'a> {
#[cfg(feature = "documentation")] #[cfg(feature = "documentation")]
{ {
let docs = self.meta().doc(); let docs = self.meta().doc();
info.extend(quote! { if !docs.is_empty() {
.with_docs(#docs) info.extend(quote! {
}); .with_docs(#docs)
});
}
} }
quote! { quote! {
@ -884,19 +889,20 @@ impl<'a> ReflectEnum<'a> {
.iter() .iter()
.map(|variant| variant.to_info_tokens(bevy_reflect_path)); .map(|variant| variant.to_info_tokens(bevy_reflect_path));
let custom_attributes = self
.meta
.attrs
.custom_attributes()
.to_tokens(bevy_reflect_path);
let mut info = quote! { let mut info = quote! {
#bevy_reflect_path::EnumInfo::new::<Self>(&[ #bevy_reflect_path::EnumInfo::new::<Self>(&[
#(#variants),* #(#variants),*
]) ])
.with_custom_attributes(#custom_attributes)
}; };
let custom_attributes = self.meta.attrs.custom_attributes();
if !custom_attributes.is_empty() {
let custom_attributes = custom_attributes.to_tokens(bevy_reflect_path);
info.extend(quote! {
.with_custom_attributes(#custom_attributes)
});
}
if let Some(generics) = generate_generics(self.meta()) { if let Some(generics) = generate_generics(self.meta()) {
info.extend(quote! { info.extend(quote! {
.with_generics(#generics) .with_generics(#generics)
@ -906,9 +912,11 @@ impl<'a> ReflectEnum<'a> {
#[cfg(feature = "documentation")] #[cfg(feature = "documentation")]
{ {
let docs = self.meta().doc(); let docs = self.meta().doc();
info.extend(quote! { if !docs.is_empty() {
.with_docs(#docs) info.extend(quote! {
}); .with_docs(#docs)
});
}
} }
quote! { quote! {
@ -1008,26 +1016,26 @@ impl<'a> EnumVariant<'a> {
} }
}; };
let custom_attributes = self.attrs.custom_attributes.to_tokens(bevy_reflect_path);
#[cfg_attr(
not(feature = "documentation"),
expect(
unused_mut,
reason = "Needs to be mutable if `documentation` feature is enabled.",
)
)]
let mut info = quote! { let mut info = quote! {
#bevy_reflect_path::#info_struct::new(#args) #bevy_reflect_path::#info_struct::new(#args)
.with_custom_attributes(#custom_attributes)
}; };
let custom_attributes = &self.attrs.custom_attributes;
if !custom_attributes.is_empty() {
let custom_attributes = custom_attributes.to_tokens(bevy_reflect_path);
info.extend(quote! {
.with_custom_attributes(#custom_attributes)
});
}
#[cfg(feature = "documentation")] #[cfg(feature = "documentation")]
{ {
let docs = &self.doc; let docs = &self.doc;
info.extend(quote! { if !docs.is_empty() {
.with_docs(#docs) info.extend(quote! {
}); .with_docs(#docs)
});
}
} }
quote! { quote! {

View File

@ -61,6 +61,11 @@ impl Documentation {
) )
} }
/// Is the collection empty?
pub fn is_empty(&self) -> bool {
self.docs.is_empty()
}
/// Push a new docstring to the collection /// Push a new docstring to the collection
pub fn push(&mut self, doc: String) { pub fn push(&mut self, doc: String) {
self.docs.push(doc); self.docs.push(doc);