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(())
}
/// Is the collection empty?
pub fn is_empty(&self) -> bool {
self.attributes.is_empty()
}
/// Parse `@` (custom attribute) attribute.
///
/// Examples:

View File

@ -514,25 +514,27 @@ impl<'a> StructField<'a> {
};
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! {
#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")]
{
let docs = &self.doc;
info.extend(quote! {
.with_docs(#docs)
});
if !docs.is_empty() {
info.extend(quote! {
.with_docs(#docs)
});
}
}
info
@ -653,19 +655,20 @@ impl<'a> ReflectStruct<'a> {
.active_fields()
.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! {
#bevy_reflect_path::#info_struct::new::<Self>(&[
#(#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()) {
info.extend(quote! {
.with_generics(#generics)
@ -675,9 +678,11 @@ impl<'a> ReflectStruct<'a> {
#[cfg(feature = "documentation")]
{
let docs = self.meta().doc();
info.extend(quote! {
.with_docs(#docs)
});
if !docs.is_empty() {
info.extend(quote! {
.with_docs(#docs)
});
}
}
quote! {
@ -884,19 +889,20 @@ impl<'a> ReflectEnum<'a> {
.iter()
.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! {
#bevy_reflect_path::EnumInfo::new::<Self>(&[
#(#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()) {
info.extend(quote! {
.with_generics(#generics)
@ -906,9 +912,11 @@ impl<'a> ReflectEnum<'a> {
#[cfg(feature = "documentation")]
{
let docs = self.meta().doc();
info.extend(quote! {
.with_docs(#docs)
});
if !docs.is_empty() {
info.extend(quote! {
.with_docs(#docs)
});
}
}
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! {
#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")]
{
let docs = &self.doc;
info.extend(quote! {
.with_docs(#docs)
});
if !docs.is_empty() {
info.extend(quote! {
.with_docs(#docs)
});
}
}
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
pub fn push(&mut self, doc: String) {
self.docs.push(doc);