bevy/crates/bevy_reflect/src/attributes.rs
Zachary Harrold d70595b667
Add core and alloc over std Lints (#15281)
# Objective

- Fixes #6370
- Closes #6581

## Solution

- Added the following lints to the workspace:
  - `std_instead_of_core`
  - `std_instead_of_alloc`
  - `alloc_instead_of_core`
- Used `cargo +nightly fmt` with [item level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A)
to split all `use` statements into single items.
- Used `cargo clippy --workspace --all-targets --all-features --fix
--allow-dirty` to _attempt_ to resolve the new linting issues, and
intervened where the lint was unable to resolve the issue automatically
(usually due to needing an `extern crate alloc;` statement in a crate
root).
- Manually removed certain uses of `std` where negative feature gating
prevented `--all-features` from finding the offending uses.
- Used `cargo +nightly fmt` with [crate level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A)
to re-merge all `use` statements matching Bevy's previous styling.
- Manually fixed cases where the `fmt` tool could not re-merge `use`
statements due to conditional compilation attributes.

## Testing

- Ran CI locally

## Migration Guide

The MSRV is now 1.81. Please update to this version or higher.

## Notes

- This is a _massive_ change to try and push through, which is why I've
outlined the semi-automatic steps I used to create this PR, in case this
fails and someone else tries again in the future.
- Making this change has no impact on user code, but does mean Bevy
contributors will be warned to use `core` and `alloc` instead of `std`
where possible.
- This lint is a critical first step towards investigating `no_std`
options for Bevy.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-09-27 00:59:59 +00:00

446 lines
14 KiB
Rust

use crate::Reflect;
use bevy_utils::TypeIdMap;
use core::{
any::TypeId,
fmt::{Debug, Formatter},
};
/// A collection of custom attributes for a type, field, or variant.
///
/// These attributes can be created with the [`Reflect` derive macro].
///
/// Attributes are stored by their [`TypeId`].
/// Because of this, there can only be one attribute per type.
///
/// # Example
///
/// ```
/// # use bevy_reflect::{Reflect, Typed, TypeInfo};
/// use std::ops::RangeInclusive;
/// #[derive(Reflect)]
/// struct Slider {
/// #[reflect(@RangeInclusive::<f32>::new(0.0, 1.0))]
/// value: f32
/// }
///
/// let TypeInfo::Struct(info) = <Slider as Typed>::type_info() else {
/// panic!("expected struct info");
/// };
///
/// let range = info.field("value").unwrap().get_attribute::<RangeInclusive<f32>>().unwrap();
/// assert_eq!(0.0..=1.0, *range);
/// ```
///
/// [`Reflect` derive macro]: derive@crate::Reflect
#[derive(Default)]
pub struct CustomAttributes {
attributes: TypeIdMap<CustomAttribute>,
}
impl CustomAttributes {
/// Inserts a custom attribute into the collection.
///
/// Note that this will overwrite any existing attribute of the same type.
pub fn with_attribute<T: Reflect>(mut self, value: T) -> Self {
self.attributes
.insert(TypeId::of::<T>(), CustomAttribute::new(value));
self
}
/// Returns `true` if this collection contains a custom attribute of the specified type.
pub fn contains<T: Reflect>(&self) -> bool {
self.attributes.contains_key(&TypeId::of::<T>())
}
/// Returns `true` if this collection contains a custom attribute with the specified [`TypeId`].
pub fn contains_by_id(&self, id: TypeId) -> bool {
self.attributes.contains_key(&id)
}
/// Gets a custom attribute by type.
pub fn get<T: Reflect>(&self) -> Option<&T> {
self.attributes.get(&TypeId::of::<T>())?.value::<T>()
}
/// Gets a custom attribute by its [`TypeId`].
pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
Some(self.attributes.get(&id)?.reflect_value())
}
/// Returns an iterator over all custom attributes.
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&TypeId, &dyn Reflect)> {
self.attributes
.iter()
.map(|(key, value)| (key, value.reflect_value()))
}
/// Returns the number of custom attributes in this collection.
pub fn len(&self) -> usize {
self.attributes.len()
}
/// Returns `true` if this collection is empty.
pub fn is_empty(&self) -> bool {
self.attributes.is_empty()
}
}
impl Debug for CustomAttributes {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_set().entries(self.attributes.values()).finish()
}
}
struct CustomAttribute {
value: Box<dyn Reflect>,
}
impl CustomAttribute {
pub fn new<T: Reflect>(value: T) -> Self {
Self {
value: Box::new(value),
}
}
pub fn value<T: Reflect>(&self) -> Option<&T> {
self.value.downcast_ref()
}
pub fn reflect_value(&self) -> &dyn Reflect {
&*self.value
}
}
impl Debug for CustomAttribute {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.value.debug(f)
}
}
/// Implements methods for accessing custom attributes.
///
/// Implements the following methods:
///
/// * `fn custom_attributes(&self) -> &CustomAttributes`
/// * `fn get_attribute<T: Reflect>(&self) -> Option<&T>`
/// * `fn get_attribute_by_id(&self, id: TypeId) -> Option<&dyn Reflect>`
/// * `fn has_attribute<T: Reflect>(&self) -> bool`
/// * `fn has_attribute_by_id(&self, id: TypeId) -> bool`
///
/// # Params
///
/// * `$self` - The name of the variable containing the custom attributes (usually `self`).
/// * `$attributes` - The name of the field containing the [`CustomAttributes`].
/// * `$term` - (Optional) The term used to describe the type containing the custom attributes.
/// This is purely used to generate better documentation. Defaults to `"item"`.
macro_rules! impl_custom_attribute_methods {
($self:ident . $attributes:ident, $term:literal) => {
$crate::attributes::impl_custom_attribute_methods!($self, &$self.$attributes, "item");
};
($self:ident, $attributes:expr, $term:literal) => {
#[doc = concat!("Returns the custom attributes for this ", $term, ".")]
pub fn custom_attributes(&$self) -> &$crate::attributes::CustomAttributes {
$attributes
}
/// Gets a custom attribute by type.
///
/// For dynamically accessing an attribute, see [`get_attribute_by_id`](Self::get_attribute_by_id).
pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
$self.custom_attributes().get::<T>()
}
#[allow(rustdoc::redundant_explicit_links)]
/// Gets a custom attribute by its [`TypeId`](std::any::TypeId).
///
/// This is the dynamic equivalent of [`get_attribute`](Self::get_attribute).
pub fn get_attribute_by_id(&$self, id: ::core::any::TypeId) -> Option<&dyn $crate::Reflect> {
$self.custom_attributes().get_by_id(id)
}
#[doc = concat!("Returns `true` if this ", $term, " has a custom attribute of the specified type.")]
#[doc = "\n\nFor dynamically checking if an attribute exists, see [`has_attribute_by_id`](Self::has_attribute_by_id)."]
pub fn has_attribute<T: $crate::Reflect>(&$self) -> bool {
$self.custom_attributes().contains::<T>()
}
#[doc = concat!("Returns `true` if this ", $term, " has a custom attribute with the specified [`TypeId`](::core::any::TypeId).")]
#[doc = "\n\nThis is the dynamic equivalent of [`has_attribute`](Self::has_attribute)"]
pub fn has_attribute_by_id(&$self, id: ::core::any::TypeId) -> bool {
$self.custom_attributes().contains_by_id(id)
}
};
}
pub(crate) use impl_custom_attribute_methods;
#[cfg(test)]
mod tests {
use super::*;
use crate as bevy_reflect;
use crate::{type_info::Typed, TypeInfo, VariantInfo};
use core::ops::RangeInclusive;
#[derive(Reflect, PartialEq, Debug)]
struct Tooltip(String);
impl Tooltip {
fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
}
#[test]
fn should_get_custom_attribute() {
let attributes = CustomAttributes::default().with_attribute(0.0..=1.0);
let value = attributes.get::<RangeInclusive<f64>>().unwrap();
assert_eq!(&(0.0..=1.0), value);
}
#[test]
fn should_get_custom_attribute_dynamically() {
let attributes = CustomAttributes::default().with_attribute(String::from("Hello, World!"));
let value = attributes.get_by_id(TypeId::of::<String>()).unwrap();
assert!(value
.reflect_partial_eq(&String::from("Hello, World!"))
.unwrap());
}
#[test]
fn should_debug_custom_attributes() {
let attributes = CustomAttributes::default().with_attribute("My awesome custom attribute!");
let debug = format!("{:?}", attributes);
assert_eq!(r#"{"My awesome custom attribute!"}"#, debug);
#[derive(Reflect)]
struct Foo {
value: i32,
}
let attributes = CustomAttributes::default().with_attribute(Foo { value: 42 });
let debug = format!("{:?}", attributes);
assert_eq!(
r#"{bevy_reflect::attributes::tests::Foo { value: 42 }}"#,
debug
);
}
#[test]
fn should_derive_custom_attributes_on_struct_container() {
#[derive(Reflect)]
#[reflect(@Tooltip::new("My awesome custom attribute!"))]
struct Slider {
value: f32,
}
let TypeInfo::Struct(info) = Slider::type_info() else {
panic!("expected struct info");
};
let tooltip = info.get_attribute::<Tooltip>().unwrap();
assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
}
#[test]
fn should_derive_custom_attributes_on_struct_fields() {
#[derive(Reflect)]
struct Slider {
#[reflect(@0.0..=1.0)]
#[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
value: f32,
}
let TypeInfo::Struct(info) = Slider::type_info() else {
panic!("expected struct info");
};
let field = info.field("value").unwrap();
let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
assert_eq!(&(0.0..=1.0), range);
let tooltip = field.get_attribute::<Tooltip>().unwrap();
assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
}
#[test]
fn should_derive_custom_attributes_on_tuple_container() {
#[derive(Reflect)]
#[reflect(@Tooltip::new("My awesome custom attribute!"))]
struct Slider(f32);
let TypeInfo::TupleStruct(info) = Slider::type_info() else {
panic!("expected tuple struct info");
};
let tooltip = info.get_attribute::<Tooltip>().unwrap();
assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
}
#[test]
fn should_derive_custom_attributes_on_tuple_struct_fields() {
#[derive(Reflect)]
struct Slider(
#[reflect(@0.0..=1.0)]
#[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
f32,
);
let TypeInfo::TupleStruct(info) = Slider::type_info() else {
panic!("expected tuple struct info");
};
let field = info.field_at(0).unwrap();
let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
assert_eq!(&(0.0..=1.0), range);
let tooltip = field.get_attribute::<Tooltip>().unwrap();
assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
}
#[test]
fn should_derive_custom_attributes_on_enum_container() {
#[derive(Reflect)]
#[reflect(@Tooltip::new("My awesome custom attribute!"))]
enum Color {
Transparent,
Grayscale(f32),
Rgb { r: u8, g: u8, b: u8 },
}
let TypeInfo::Enum(info) = Color::type_info() else {
panic!("expected enum info");
};
let tooltip = info.get_attribute::<Tooltip>().unwrap();
assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
}
#[test]
fn should_derive_custom_attributes_on_enum_variants() {
#[derive(Reflect, Debug, PartialEq)]
enum Display {
Toggle,
Slider,
Picker,
}
#[derive(Reflect)]
enum Color {
#[reflect(@Display::Toggle)]
Transparent,
#[reflect(@Display::Slider)]
Grayscale(f32),
#[reflect(@Display::Picker)]
Rgb { r: u8, g: u8, b: u8 },
}
let TypeInfo::Enum(info) = Color::type_info() else {
panic!("expected enum info");
};
let VariantInfo::Unit(transparent_variant) = info.variant("Transparent").unwrap() else {
panic!("expected unit variant");
};
let display = transparent_variant.get_attribute::<Display>().unwrap();
assert_eq!(&Display::Toggle, display);
let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
panic!("expected tuple variant");
};
let display = grayscale_variant.get_attribute::<Display>().unwrap();
assert_eq!(&Display::Slider, display);
let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
panic!("expected struct variant");
};
let display = rgb_variant.get_attribute::<Display>().unwrap();
assert_eq!(&Display::Picker, display);
}
#[test]
fn should_derive_custom_attributes_on_enum_variant_fields() {
#[derive(Reflect)]
enum Color {
Transparent,
Grayscale(#[reflect(@0.0..=1.0_f32)] f32),
Rgb {
#[reflect(@0..=255u8)]
r: u8,
#[reflect(@0..=255u8)]
g: u8,
#[reflect(@0..=255u8)]
b: u8,
},
}
let TypeInfo::Enum(info) = Color::type_info() else {
panic!("expected enum info");
};
let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
panic!("expected tuple variant");
};
let field = grayscale_variant.field_at(0).unwrap();
let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
assert_eq!(&(0.0..=1.0), range);
let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
panic!("expected struct variant");
};
let field = rgb_variant.field("g").unwrap();
let range = field.get_attribute::<RangeInclusive<u8>>().unwrap();
assert_eq!(&(0..=255), range);
}
#[test]
fn should_allow_unit_struct_attribute_values() {
#[derive(Reflect)]
struct Required;
#[derive(Reflect)]
struct Foo {
#[reflect(@Required)]
value: i32,
}
let TypeInfo::Struct(info) = Foo::type_info() else {
panic!("expected struct info");
};
let field = info.field("value").unwrap();
assert!(field.has_attribute::<Required>());
}
#[test]
fn should_accept_last_attribute() {
#[derive(Reflect)]
struct Foo {
#[reflect(@false)]
#[reflect(@true)]
value: i32,
}
let TypeInfo::Struct(info) = Foo::type_info() else {
panic!("expected struct info");
};
let field = info.field("value").unwrap();
assert!(field.get_attribute::<bool>().unwrap());
}
}