bevy/crates/bevy_reflect/derive/src/enum_utility.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

278 lines
9.2 KiB
Rust

use crate::{
derive_data::ReflectEnum, derive_data::StructField, field_attributes::DefaultBehavior,
ident::ident_or_index,
};
use bevy_macro_utils::fq_std::{FQDefault, FQOption};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
pub(crate) struct EnumVariantOutputData {
/// The names of each variant as a string.
///
/// For example, `Some` and `None` for the `Option` enum.
pub variant_names: Vec<String>,
/// The constructor portion of each variant.
///
/// For example, `Option::Some { 0: value }` and `Option::None {}` for the `Option` enum.
pub variant_constructors: Vec<TokenStream>,
}
#[derive(Copy, Clone)]
pub(crate) struct VariantField<'a, 'b> {
/// The alias for the field.
///
/// This should be used whenever the field needs to be referenced in a token stream.
pub alias: &'a Ident,
/// The name of the variant that contains the field.
pub variant_name: &'a str,
/// The field data.
pub field: &'a StructField<'b>,
}
/// Trait used to control how enum variants are built.
pub(crate) trait VariantBuilder: Sized {
/// Returns the enum data.
fn reflect_enum(&self) -> &ReflectEnum;
/// Returns a token stream that accesses a field of a variant as an `Option<dyn Reflect>`.
///
/// The default implementation of this method will return a token stream
/// which gets the field dynamically so as to support `dyn Enum`.
///
/// # Parameters
/// * `this`: The identifier of the enum
/// * `field`: The field to access
fn access_field(&self, this: &Ident, field: VariantField) -> TokenStream {
match &field.field.data.ident {
Some(field_ident) => {
let name = field_ident.to_string();
quote!(#this.field(#name))
}
None => {
if let Some(field_index) = field.field.reflection_index {
quote!(#this.field_at(#field_index))
} else {
quote!(::core::compile_error!(
"internal bevy_reflect error: field should be active"
))
}
}
}
}
/// Returns a token stream that unwraps a field of a variant as a `&dyn Reflect`
/// (from an `Option<dyn Reflect>`).
///
/// # Parameters
/// * `field`: The field to access
fn unwrap_field(&self, field: VariantField) -> TokenStream;
/// Returns a token stream that constructs a field of a variant as a concrete type
/// (from a `&dyn Reflect`).
///
/// # Parameters
/// * `field`: The field to access
fn construct_field(&self, field: VariantField) -> TokenStream;
/// Returns a token stream that constructs an instance of an active field.
///
/// # Parameters
/// * `this`: The identifier of the enum
/// * `field`: The field to access
fn on_active_field(&self, this: &Ident, field: VariantField) -> TokenStream {
let bevy_reflect_path = self.reflect_enum().meta().bevy_reflect_path();
let field_accessor = self.access_field(this, field);
let alias = field.alias;
let field_ty = field.field.reflected_type();
let field_constructor = self.construct_field(field);
let construction = match &field.field.attrs.default {
DefaultBehavior::Func(path) => quote! {
if let #FQOption::Some(#alias) = #field_accessor {
#field_constructor
} else {
#path()
}
},
DefaultBehavior::Default => quote! {
if let #FQOption::Some(#alias) = #field_accessor {
#field_constructor
} else {
#FQDefault::default()
}
},
DefaultBehavior::Required => {
let field_unwrapper = self.unwrap_field(field);
quote! {{
// `#alias` is used by both the unwrapper and constructor
let #alias = #field_accessor;
let #alias = #field_unwrapper;
#field_constructor
}}
}
};
if field.field.attrs().remote.is_some() {
quote! {
<#field_ty as #bevy_reflect_path::ReflectRemote>::into_remote(#construction)
}
} else {
construction
}
}
/// Returns a token stream that constructs an instance of an ignored field.
///
/// # Parameters
/// * `field`: The field to access
fn on_ignored_field(&self, field: VariantField) -> TokenStream {
match &field.field.attrs.default {
DefaultBehavior::Func(path) => quote! { #path() },
_ => quote! { #FQDefault::default() },
}
}
/// Builds the enum variant output data.
fn build(&self, this: &Ident) -> EnumVariantOutputData {
let variants = self.reflect_enum().variants();
let mut variant_names = Vec::with_capacity(variants.len());
let mut variant_constructors = Vec::with_capacity(variants.len());
for variant in variants {
let variant_ident = &variant.data.ident;
let variant_name = variant_ident.to_string();
let variant_path = self.reflect_enum().get_unit(variant_ident);
let fields = variant.fields();
let field_constructors = fields.iter().map(|field| {
let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let alias = format_ident!("_{}", member);
let variant_field = VariantField {
alias: &alias,
variant_name: &variant_name,
field,
};
let value = if field.attrs.ignore.is_ignored() {
self.on_ignored_field(variant_field)
} else {
self.on_active_field(this, variant_field)
};
let constructor = quote! {
#member: #value
};
constructor
});
let constructor = quote! {
#variant_path {
#( #field_constructors ),*
}
};
variant_names.push(variant_name);
variant_constructors.push(constructor);
}
EnumVariantOutputData {
variant_names,
variant_constructors,
}
}
}
/// Generates the enum variant output data needed to build the `FromReflect::from_reflect` implementation.
pub(crate) struct FromReflectVariantBuilder<'a> {
reflect_enum: &'a ReflectEnum<'a>,
}
impl<'a> FromReflectVariantBuilder<'a> {
pub fn new(reflect_enum: &'a ReflectEnum) -> Self {
Self { reflect_enum }
}
}
impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> {
fn reflect_enum(&self) -> &ReflectEnum {
self.reflect_enum
}
fn unwrap_field(&self, field: VariantField) -> TokenStream {
let alias = field.alias;
quote!(#alias?)
}
fn construct_field(&self, field: VariantField) -> TokenStream {
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
let field_ty = field.field.reflected_type();
let alias = field.alias;
quote! {
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#alias)?
}
}
}
/// Generates the enum variant output data needed to build the `PartialReflect::try_apply` implementation.
pub(crate) struct TryApplyVariantBuilder<'a> {
reflect_enum: &'a ReflectEnum<'a>,
}
impl<'a> TryApplyVariantBuilder<'a> {
pub fn new(reflect_enum: &'a ReflectEnum) -> Self {
Self { reflect_enum }
}
}
impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> {
fn reflect_enum(&self) -> &ReflectEnum {
self.reflect_enum
}
fn unwrap_field(&self, field: VariantField) -> TokenStream {
let VariantField {
alias,
variant_name,
field,
..
} = field;
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
let field_name = match &field.data.ident {
Some(ident) => format!("{ident}"),
None => format!(".{}", field.declaration_index),
};
quote! {
#alias.ok_or(#bevy_reflect_path::ApplyError::MissingEnumField {
variant_name: ::core::convert::Into::into(#variant_name),
field_name: ::core::convert::Into::into(#field_name)
})?
}
}
fn construct_field(&self, field: VariantField) -> TokenStream {
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
let alias = field.alias;
let field_ty = field.field.reflected_type();
quote! {
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#alias)
.ok_or(#bevy_reflect_path::ApplyError::MismatchedTypes {
from_type: ::core::convert::Into::into(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(#alias)
),
to_type: ::core::convert::Into::into(<#field_ty as #bevy_reflect_path::TypePath>::type_path())
})?
}
}
}