bevy/crates/bevy_macro_utils/src/label.rs
Tim 4924cf5828
Remove upcasting methods + Cleanup interned label code (#18984)
Hiya!

# Objective

- Remove upcasting methods that are no longer necessary since Rust 1.86.
- Cleanup the interned label code.
 
## Notes
- I didn't try to remove the upcasting methods from `bevy_reflect`, as
there appears to be some complexity related to remote type reflection.
- There are likely some other upcasting methods floating around.

## Testing
I ran the `breakout` example to check that the hashing/eq
implementations of the labels are still correct.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-05-26 15:38:12 +00:00

96 lines
3.4 KiB
Rust

use proc_macro::{TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use std::collections::HashSet;
use syn::{spanned::Spanned, Ident};
/// Finds an identifier that will not conflict with the specified set of tokens.
///
/// If the identifier is present in `haystack`, extra characters will be added
/// to it until it no longer conflicts with anything.
///
/// Note that the returned identifier can still conflict in niche cases,
/// such as if an identifier in `haystack` is hidden behind an un-expanded macro.
pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident {
// Collect all the identifiers in `haystack` into a set.
let idents = {
// List of token streams that will be visited in future loop iterations.
let mut unvisited = vec![haystack];
// Identifiers we have found while searching tokens.
let mut found = HashSet::new();
while let Some(tokens) = unvisited.pop() {
for t in tokens {
match t {
// Collect any identifiers we encounter.
TokenTree::Ident(ident) => {
found.insert(ident.to_string());
}
// Queue up nested token streams to be visited in a future loop iteration.
TokenTree::Group(g) => unvisited.push(g.stream()),
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
}
}
}
found
};
let span = value.span();
// If there's a collision, add more characters to the identifier
// until it doesn't collide with anything anymore.
let mut value = value.to_string();
while idents.contains(&value) {
value.push('X');
}
Ident::new(&value, span)
}
/// Derive a label trait
///
/// # Args
///
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
/// - `trait_name`: Name of the label trait
/// - `trait_path`: The [path](`syn::Path`) to the label trait
/// - `dyn_eq_path`: The [path](`syn::Path`) to the `DynEq` trait
pub fn derive_label(
input: syn::DeriveInput,
trait_name: &str,
trait_path: &syn::Path,
) -> TokenStream {
if let syn::Data::Union(_) = &input.data {
let message = format!("Cannot derive {trait_name} for unions.");
return quote_spanned! {
input.span() => compile_error!(#message);
}
.into();
}
let ident = input.ident.clone();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
where_token: Default::default(),
predicates: Default::default(),
});
where_clause.predicates.push(
syn::parse2(quote! {
Self: 'static + Send + Sync + Clone + Eq + ::core::fmt::Debug + ::core::hash::Hash
})
.unwrap(),
);
quote! {
// To ensure alloc is available, but also prevent its name from clashing, we place the implementation inside an anonymous constant
const _: () = {
extern crate alloc;
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
fn dyn_clone(&self) -> alloc::boxed::Box<dyn #trait_path> {
alloc::boxed::Box::new(::core::clone::Clone::clone(self))
}
}
};
}
.into()
}