Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mod mismatched_remote_type {
#[reflect_remote(super::external_crate::TheirOuter<T>)]
//~^ ERROR: mismatched types
//~| ERROR: mismatched types
//~| ERROR: mismatched types
struct MyOuter<T: FromReflect + GetTypeRegistration> {
// Reason: Should be `MyInner<T>`
#[reflect(remote = MyOuter<T>)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod structs {
#[derive(Reflect)]
//~^ ERROR: mismatched types
//~| ERROR: mismatched types
//~| ERROR: mismatched types
struct MyStruct {
// Reason: Should use `MyFoo`
#[reflect(remote = MyBar)]
Expand All @@ -50,6 +51,7 @@ mod tuple_structs {
#[derive(Reflect)]
//~^ ERROR: mismatched types
//~| ERROR: mismatched types
//~| ERROR: mismatched types
struct MyStruct(
// Reason: Should use `MyFoo`
#[reflect(remote = MyBar)] external_crate::TheirFoo,
Expand Down
17 changes: 0 additions & 17 deletions crates/bevy_reflect/derive/src/field_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,21 +252,4 @@ impl FieldAttributes {

Ok(())
}

/// Returns `Some(true)` if the field has a generic remote type.
///
/// If the remote type is not generic, returns `Some(false)`.
///
/// If the field does not have a remote type, returns `None`.
pub fn is_remote_generic(&self) -> Option<bool> {
if let Type::Path(type_path) = self.remote.as_ref()? {
type_path
.path
.segments
.last()
.map(|segment| !segment.arguments.is_empty())
} else {
Some(false)
}
}
}
21 changes: 6 additions & 15 deletions crates/bevy_reflect/derive/src/from_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,23 +265,14 @@ fn get_active_fields(
};

let into_remote = |value: proc_macro2::TokenStream| {
if field.attrs.is_remote_generic().unwrap_or_default() {
if field.attrs().remote.is_some() {
quote! {
#FQOption::Some(
// SAFETY: The remote type should always be a `#[repr(transparent)]` for the actual field type
unsafe {
::core::mem::transmute_copy::<#ty, #real_ty>(
&::core::mem::ManuallyDrop::new(#value?)
)
}
)
}
} else if field.attrs().remote.is_some() {
quote! {
#FQOption::Some(
// SAFETY: The remote type should always be a `#[repr(transparent)]` for the actual field type
unsafe {
::core::mem::transmute::<#ty, #real_ty>(#value?)
{
let wrapper: #ty = #value?;
let remote: #real_ty =
<#ty as #bevy_reflect_path::ReflectRemote>::into_remote(wrapper);
remote
}
)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_reflect/derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden

/// Process the field value to account for remote types.
///
/// If the field is a remote type, then the value will be transmuted accordingly.
/// If the field is a remote type, then the value will be converted accordingly.
fn process_field_value(
ident: &Ident,
field: &StructField,
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_reflect/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@ pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream {
/// Generates a wrapper type that can be used to "derive `Reflect`" for remote types.
///
/// This works by wrapping the remote type in a generated wrapper that has the `#[repr(transparent)]` attribute.
/// This allows the two types to be safely [transmuted] back-and-forth.
/// The generated `ReflectRemote` implementation uses this representation to convert between the
/// wrapper and remote type.
///
/// # Defining the Wrapper
///
Expand Down Expand Up @@ -640,13 +641,12 @@ pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// ## Safety
/// ## Conversion
///
/// When using the `#[reflect(remote = path::to::MyType)]` field attribute, be sure you are defining the correct wrapper type.
/// Internally, this field will be unsafely [transmuted], and is only sound if using a wrapper generated for the remote type.
/// This also means keeping your wrapper definitions up-to-date with the remote types.
/// The wrapper type must implement `ReflectRemote` with `Remote` equal to the field's actual
/// type. Generated reflection code uses the wrapper's conversion methods; in particular,
/// `FromReflect` calls `ReflectRemote::into_remote` to construct the field value.
///
/// [transmuted]: std::mem::transmute
#[proc_macro_attribute]
pub fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStream {
remote::reflect_remote(args, input)
Expand Down
6 changes: 2 additions & 4 deletions crates/bevy_reflect/derive/src/struct_utility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ use crate::ReflectStruct;

/// A helper struct for creating remote-aware field accessors.
///
/// These are "remote-aware" because when a field is a remote field, it uses a [`transmute`] internally
/// to access the field.
///
/// [`transmute`]: std::mem::transmute
/// These are "remote-aware" because when a field is a remote field, they use its
/// `ReflectRemote` implementation to access the field.
pub(crate) struct FieldAccessors {
/// The referenced field accessors, such as `&self.foo`.
pub fields_ref: Vec<proc_macro2::TokenStream>,
Expand Down
46 changes: 46 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3625,6 +3625,52 @@ bevy_reflect::tests::Test {
assert_eq!("Goodbye", data.0.value);
}

#[test]
fn from_reflect_uses_remote_conversion() {
#[derive(Reflect)]
struct BadWrapper(u8);

impl ReflectRemote for BadWrapper {
type Remote = bool;

fn as_remote(&self) -> &Self::Remote {
panic!("not used by this test")
}

fn as_remote_mut(&mut self) -> &mut Self::Remote {
panic!("not used by this test")
}

fn into_remote(self) -> Self::Remote {
false
}

fn as_wrapper(_: &Self::Remote) -> &Self {
panic!("not used by this test")
}

fn as_wrapper_mut(_: &mut Self::Remote) -> &mut Self {
panic!("not used by this test")
}

fn into_wrapper(_: Self::Remote) -> Self {
panic!("not used by this test")
}
}

#[derive(Reflect)]
struct Container {
#[reflect(remote = BadWrapper)]
value: bool,
}

let mut reflected = DynamicStruct::default();
reflected.insert("value", BadWrapper(2));

let container = Container::from_reflect(&reflected).unwrap();
assert!(!container.value);
}

#[test]
fn should_reflect_remote_value_type() {
mod external_crate {
Expand Down
22 changes: 8 additions & 14 deletions crates/bevy_reflect/src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ use crate::Reflect;
///
/// This allows types from external libraries (remote types) to be included in reflection.
///
/// # Safety
/// The [`#[reflect_remote]`](crate::reflect_remote) attribute macro generates a
/// `#[repr(transparent)]` wrapper and an implementation of this trait. Its conversion methods
/// use the wrapper's transparent representation.
///
/// It is highly recommended to avoid implementing this trait manually and instead use the
/// [`#[reflect_remote]`](crate::reflect_remote) attribute macro.
/// This is because the trait tends to rely on [`transmute`], which is [very unsafe].
///
/// The macro will ensure that the following safety requirements are met:
/// - `Self` is a single-field tuple struct (i.e. a newtype) containing the remote type.
/// - `Self` is `#[repr(transparent)]` over the remote type.
///
/// Additionally, the macro will automatically generate [`Reflect`] and [`FromReflect`] implementations,
/// along with compile-time assertions to validate that the safety requirements have been met.
/// Manual implementations may use a different representation and conversion behavior. The
/// associated `Remote` type identifies the remote type represented by this wrapper.
/// When implementing this trait manually, you need to design carefully about the conversion
/// between `Self` and `Remote` type. For example, if you need to resolve the conversion of
/// `u8` and `bool`, you can set the rule that all the values that more than 1 return false.
///
/// # Example
///
Expand All @@ -41,9 +38,6 @@ use crate::Reflect;
/// ```
///
/// [reflectable]: Reflect
/// [`transmute`]: core::mem::transmute
/// [very unsafe]: https://doc.rust-lang.org/1.71.0/nomicon/transmutes.html
/// [`FromReflect`]: crate::FromReflect
pub trait ReflectRemote: Reflect {
/// The remote type this type represents via reflection.
type Remote;
Expand Down