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

503 lines
18 KiB
Rust

use crate::{
derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl},
from_reflect,
ident::ident_or_index,
impls,
impls::impl_assertions,
ReflectDerive, REFLECT_ATTRIBUTE_NAME,
};
use bevy_macro_utils::fq_std::FQOption;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{format_ident, quote, quote_spanned};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
token::PathSep,
DeriveInput, ExprPath, Generics, Member, PathArguments, Type, TypePath,
};
/// Generates the remote wrapper type and implements all the necessary traits.
pub(crate) fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStream {
let remote_args = match syn::parse::<RemoteArgs>(args) {
Ok(path) => path,
Err(err) => return err.to_compile_error().into(),
};
let remote_ty = remote_args.remote_ty;
let ast = parse_macro_input!(input as DeriveInput);
let wrapper_definition = generate_remote_wrapper(&ast, &remote_ty);
let mut derive_data = match ReflectDerive::from_input(
&ast,
ReflectProvenance {
source: ReflectImplSource::RemoteReflect,
trait_: ReflectTraitToImpl::Reflect,
},
) {
Ok(data) => data,
Err(err) => return err.into_compile_error().into(),
};
derive_data.set_remote(Some(RemoteType::new(&remote_ty)));
let assertions = impl_assertions(&derive_data);
let definition_assertions = generate_remote_definition_assertions(&derive_data);
let reflect_remote_impl = impl_reflect_remote(&derive_data, &remote_ty);
let (reflect_impls, from_reflect_impl) = match derive_data {
ReflectDerive::Struct(struct_data) | ReflectDerive::UnitStruct(struct_data) => (
impls::impl_struct(&struct_data),
if struct_data.meta().from_reflect().should_auto_derive() {
Some(from_reflect::impl_struct(&struct_data))
} else {
None
},
),
ReflectDerive::TupleStruct(struct_data) => (
impls::impl_tuple_struct(&struct_data),
if struct_data.meta().from_reflect().should_auto_derive() {
Some(from_reflect::impl_tuple_struct(&struct_data))
} else {
None
},
),
ReflectDerive::Enum(enum_data) => (
impls::impl_enum(&enum_data),
if enum_data.meta().from_reflect().should_auto_derive() {
Some(from_reflect::impl_enum(&enum_data))
} else {
None
},
),
ReflectDerive::Opaque(meta) => (
impls::impl_opaque(&meta),
if meta.from_reflect().should_auto_derive() {
Some(from_reflect::impl_opaque(&meta))
} else {
None
},
),
};
TokenStream::from(quote! {
#wrapper_definition
const _: () = {
#reflect_remote_impl
#reflect_impls
#from_reflect_impl
#definition_assertions
#assertions
};
})
}
/// Generates the remote wrapper type.
///
/// # Example
///
/// If the supplied remote type is `Bar<T>`, then the wrapper type— named `Foo<T>`— would look like:
///
/// ```
/// # struct Bar<T>(T);
///
/// #[repr(transparent)]
/// struct Foo<T>(Bar<T>);
/// ```
fn generate_remote_wrapper(input: &DeriveInput, remote_ty: &TypePath) -> proc_macro2::TokenStream {
let ident = &input.ident;
let vis = &input.vis;
let ty_generics = &input.generics;
let where_clause = &input.generics.where_clause;
let attrs = input
.attrs
.iter()
.filter(|attr| !attr.path().is_ident(REFLECT_ATTRIBUTE_NAME));
quote! {
#(#attrs)*
#[repr(transparent)]
#[doc(hidden)]
#vis struct #ident #ty_generics (pub #remote_ty) #where_clause;
}
}
/// Generates the implementation of the `ReflectRemote` trait for the given derive data and remote type.
///
/// # Note to Developers
///
/// The `ReflectRemote` trait could likely be made with default method implementations.
/// However, this makes it really easy for a user to accidentally implement this trait in an unsafe way.
/// To prevent this, we instead generate the implementation through a macro using this function.
fn impl_reflect_remote(input: &ReflectDerive, remote_ty: &TypePath) -> proc_macro2::TokenStream {
let bevy_reflect_path = input.meta().bevy_reflect_path();
let type_path = input.meta().type_path();
let (impl_generics, ty_generics, where_clause) =
input.meta().type_path().generics().split_for_impl();
let where_reflect_clause = input
.where_clause_options()
.extend_where_clause(where_clause);
quote! {
// SAFE: The generated wrapper type is guaranteed to be valid and repr(transparent) over the remote type.
impl #impl_generics #bevy_reflect_path::ReflectRemote for #type_path #ty_generics #where_reflect_clause {
type Remote = #remote_ty;
fn as_remote(&self) -> &Self::Remote {
&self.0
}
fn as_remote_mut(&mut self) -> &mut Self::Remote {
&mut self.0
}
fn into_remote(self) -> Self::Remote
{
// SAFE: The wrapper type should be repr(transparent) over the remote type
unsafe {
// Unfortunately, we have to use `transmute_copy` to avoid a compiler error:
// ```
// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
// |
// | core::mem::transmute::<A, B>(a)
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// |
// = note: source type: `A` (this type does not have a fixed size)
// = note: target type: `B` (this type does not have a fixed size)
// ```
::core::mem::transmute_copy::<Self, Self::Remote>(
// `ManuallyDrop` is used to prevent double-dropping `self`
&::core::mem::ManuallyDrop::new(self)
)
}
}
fn as_wrapper(remote: &Self::Remote) -> &Self {
// SAFE: The wrapper type should be repr(transparent) over the remote type
unsafe { ::core::mem::transmute::<&Self::Remote, &Self>(remote) }
}
fn as_wrapper_mut(remote: &mut Self::Remote) -> &mut Self {
// SAFE: The wrapper type should be repr(transparent) over the remote type
unsafe { ::core::mem::transmute::<&mut Self::Remote, &mut Self>(remote) }
}
fn into_wrapper(remote: Self::Remote) -> Self
{
// SAFE: The wrapper type should be repr(transparent) over the remote type
unsafe {
// Unfortunately, we have to use `transmute_copy` to avoid a compiler error:
// ```
// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
// |
// | core::mem::transmute::<A, B>(a)
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// |
// = note: source type: `A` (this type does not have a fixed size)
// = note: target type: `B` (this type does not have a fixed size)
// ```
::core::mem::transmute_copy::<Self::Remote, Self>(
// `ManuallyDrop` is used to prevent double-dropping `self`
&::core::mem::ManuallyDrop::new(remote)
)
}
}
}
}
}
/// Generates compile-time assertions for remote fields.
///
/// This prevents types from using an invalid remote type.
/// this works by generating a struct, `RemoteFieldAssertions`, with methods that
/// will result in compile-time failure if types are mismatched.
/// The output of this function is best placed within an anonymous context to maintain hygiene.
///
/// # Example
///
/// The following would fail to compile due to an incorrect `#[reflect(remote = ...)]` value.
///
/// ```ignore
/// mod external_crate {
/// pub struct TheirFoo(pub u32);
/// pub struct TheirBar(pub i32);
/// }
///
/// #[reflect_remote(external_crate::TheirFoo)]
/// struct MyFoo(pub u32);
/// #[reflect_remote(external_crate::TheirBar)]
/// struct MyBar(pub i32);
///
/// #[derive(Reflect)]
/// struct MyStruct {
/// #[reflect(remote = MyBar)] // ERROR: expected type `TheirFoo` but found struct `TheirBar`
/// foo: external_crate::TheirFoo
/// }
/// ```
pub(crate) fn generate_remote_assertions(
derive_data: &ReflectDerive,
) -> Option<proc_macro2::TokenStream> {
struct RemoteAssertionData<'a> {
ident: Member,
variant: Option<&'a Ident>,
ty: &'a Type,
generics: &'a Generics,
remote_ty: &'a Type,
}
let bevy_reflect_path = derive_data.meta().bevy_reflect_path();
let fields: Box<dyn Iterator<Item = RemoteAssertionData>> = match derive_data {
ReflectDerive::Struct(data)
| ReflectDerive::TupleStruct(data)
| ReflectDerive::UnitStruct(data) => Box::new(data.active_fields().filter_map(|field| {
field
.attrs
.remote
.as_ref()
.map(|remote_ty| RemoteAssertionData {
ident: ident_or_index(field.data.ident.as_ref(), field.declaration_index),
variant: None,
ty: &field.data.ty,
generics: data.meta().type_path().generics(),
remote_ty,
})
})),
ReflectDerive::Enum(data) => Box::new(data.variants().iter().flat_map(|variant| {
variant.active_fields().filter_map(|field| {
field
.attrs
.remote
.as_ref()
.map(|remote_ty| RemoteAssertionData {
ident: ident_or_index(field.data.ident.as_ref(), field.declaration_index),
variant: Some(&variant.data.ident),
ty: &field.data.ty,
generics: data.meta().type_path().generics(),
remote_ty,
})
})
})),
_ => return None,
};
let assertions = fields
.map(move |field| {
let ident = if let Some(variant) = field.variant {
format_ident!("{}__{}", variant, field.ident)
} else {
match field.ident {
Member::Named(ident) => ident,
Member::Unnamed(index) => format_ident!("field_{}", index),
}
};
let (impl_generics, _, where_clause) = field.generics.split_for_impl();
let where_reflect_clause = derive_data
.where_clause_options()
.extend_where_clause(where_clause);
let ty = &field.ty;
let remote_ty = field.remote_ty;
let assertion_ident = format_ident!("assert__{}__is_valid_remote", ident);
let span = create_assertion_span(remote_ty.span());
quote_spanned! {span=>
#[allow(non_snake_case)]
#[allow(clippy::multiple_bound_locations)]
fn #assertion_ident #impl_generics () #where_reflect_clause {
let _: <#remote_ty as #bevy_reflect_path::ReflectRemote>::Remote = (|| -> #FQOption<#ty> {
None
})().unwrap();
}
}
})
.collect::<proc_macro2::TokenStream>();
if assertions.is_empty() {
None
} else {
Some(quote! {
struct RemoteFieldAssertions;
impl RemoteFieldAssertions {
#assertions
}
})
}
}
/// Generates compile-time assertions that ensure a remote wrapper definition matches up with the
/// remote type it's wrapping.
///
/// Note: This currently results in "backwards" error messages like:
///
/// ```ignore
/// expected: <WRAPPER_FIELD_TYPE>
/// found: <REMOTE_FIELD_TYPE>
/// ```
///
/// Ideally it would be the other way around, but there's no easy way of doing this without
/// generating a copy of the struct/enum definition and using that as the base instead of the remote type.
fn generate_remote_definition_assertions(derive_data: &ReflectDerive) -> proc_macro2::TokenStream {
let meta = derive_data.meta();
let self_ident = format_ident!("__remote__");
let self_ty = derive_data.remote_ty().unwrap().type_path();
let self_expr_path = derive_data.remote_ty().unwrap().as_expr_path().unwrap();
let (impl_generics, _, where_clause) = meta.type_path().generics().split_for_impl();
let where_reflect_clause = derive_data
.where_clause_options()
.extend_where_clause(where_clause);
let assertions = match derive_data {
ReflectDerive::Struct(data)
| ReflectDerive::TupleStruct(data)
| ReflectDerive::UnitStruct(data) => {
let mut output = proc_macro2::TokenStream::new();
for field in data.fields() {
let field_member =
ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let field_ty = &field.data.ty;
let span = create_assertion_span(field_ty.span());
output.extend(quote_spanned! {span=>
#self_ident.#field_member = (|| -> #FQOption<#field_ty> {None})().unwrap();
});
}
output
}
ReflectDerive::Enum(data) => {
let variants = data.variants().iter().map(|variant| {
let ident = &variant.data.ident;
let mut output = proc_macro2::TokenStream::new();
if variant.fields().is_empty() {
return quote!(#self_expr_path::#ident => {});
}
for field in variant.fields() {
let field_member =
ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let field_ident = format_ident!("field_{}", field_member);
let field_ty = &field.data.ty;
let span = create_assertion_span(field_ty.span());
output.extend(quote_spanned! {span=>
#self_expr_path::#ident {#field_member: mut #field_ident, ..} => {
#field_ident = (|| -> #FQOption<#field_ty> {None})().unwrap();
}
});
}
output
});
quote! {
match #self_ident {
#(#variants)*
}
}
}
ReflectDerive::Opaque(_) => {
// No assertions needed since there are no fields to check
proc_macro2::TokenStream::new()
}
};
quote! {
const _: () = {
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(unused_assignments)]
#[allow(unreachable_patterns)]
#[allow(clippy::multiple_bound_locations)]
fn assert_wrapper_definition_matches_remote_type #impl_generics (mut #self_ident: #self_ty) #where_reflect_clause {
#assertions
}
};
}
}
/// Creates a span located around the given one, but resolves to the assertion's context.
///
/// This should allow the compiler to point back to the line and column in the user's code,
/// while still attributing the error to the macro.
fn create_assertion_span(span: Span) -> Span {
Span::call_site().located_at(span)
}
/// A reflected type's remote type.
///
/// This is a wrapper around [`TypePath`] that allows it to be paired with other remote-specific logic.
#[derive(Copy, Clone)]
pub(crate) struct RemoteType<'a> {
path: &'a TypePath,
}
impl<'a> RemoteType<'a> {
pub fn new(path: &'a TypePath) -> Self {
Self { path }
}
/// Returns the [type path](TypePath) of this remote type.
pub fn type_path(&self) -> &'a TypePath {
self.path
}
/// Attempts to convert the [type path](TypePath) of this remote type into an [expression path](ExprPath).
///
/// For example, this would convert `foo::Bar<T>` into `foo::Bar::<T>` to be used as part of an expression.
///
/// This will return an error for types that are parenthesized, such as in `Fn() -> Foo`.
pub fn as_expr_path(&self) -> Result<ExprPath, syn::Error> {
let mut expr_path = self.path.clone();
if let Some(segment) = expr_path.path.segments.last_mut() {
match &mut segment.arguments {
PathArguments::None => {}
PathArguments::AngleBracketed(arg) => {
arg.colon2_token = Some(PathSep::default());
}
PathArguments::Parenthesized(arg) => {
return Err(syn::Error::new(
arg.span(),
"cannot use parenthesized type as remote type",
))
}
}
}
Ok(ExprPath {
path: expr_path.path,
qself: expr_path.qself,
attrs: Vec::new(),
})
}
}
/// Metadata from the arguments defined in the `reflect_remote` attribute.
///
/// The syntax for the arguments is: `#[reflect_remote(REMOTE_TYPE_PATH)]`.
struct RemoteArgs {
remote_ty: TypePath,
}
impl Parse for RemoteArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
remote_ty: input.parse()?,
})
}
}