diff --git a/crates/bevy_reflect/compile_fail/Cargo.toml b/crates/bevy_reflect/compile_fail/Cargo.toml index f8a8a41d67..14e5eb2264 100644 --- a/crates/bevy_reflect/compile_fail/Cargo.toml +++ b/crates/bevy_reflect/compile_fail/Cargo.toml @@ -20,3 +20,7 @@ harness = false [[test]] name = "func" harness = false + +[[test]] +name = "remote" +harness = false diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_fail.rs new file mode 100644 index 0000000000..f25f3fd740 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_fail.rs @@ -0,0 +1,24 @@ +use bevy_reflect::Reflect; + +mod external_crate { + pub struct TheirFoo { + pub value: u32, + } +} + +#[repr(transparent)] +#[derive(Reflect)] +#[reflect(from_reflect = false)] +struct MyFoo(#[reflect(ignore)] pub external_crate::TheirFoo); + +#[derive(Reflect)] +//~^ ERROR: the trait bound `MyFoo: ReflectRemote` is not satisfied +#[reflect(from_reflect = false)] +struct MyStruct { + // Reason: `MyFoo` does not implement `ReflectRemote` (from `#[reflect_remote]` attribute) + #[reflect(remote = MyFoo)] + //~^ ERROR: the trait bound `MyFoo: ReflectRemote` is not satisfied + foo: external_crate::TheirFoo, +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_pass.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_pass.rs new file mode 100644 index 0000000000..798511a85a --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/incorrect_wrapper_pass.rs @@ -0,0 +1,21 @@ +//@check-pass +use bevy_reflect::{reflect_remote, Reflect}; + +mod external_crate { + pub struct TheirFoo { + pub value: u32, + } +} + +#[reflect_remote(external_crate::TheirFoo)] +struct MyFoo { + pub value: u32, +} + +#[derive(Reflect)] +struct MyStruct { + #[reflect(remote = MyFoo)] + foo: external_crate::TheirFoo, +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs new file mode 100644 index 0000000000..d691c824cc --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs @@ -0,0 +1,63 @@ +mod structs { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub struct TheirStruct { + pub value: u32, + } + } + + #[reflect_remote(external_crate::TheirStruct)] + //~^ ERROR: `?` operator has incompatible types + struct MyStruct { + // Reason: Should be `u32` + pub value: bool, + //~^ ERROR: mismatched types + } +} + +mod tuple_structs { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub struct TheirStruct(pub u32); + } + + #[reflect_remote(external_crate::TheirStruct)] + //~^ ERROR: `?` operator has incompatible types + struct MyStruct( + // Reason: Should be `u32` + pub bool, + //~^ ERROR: mismatched types + ); +} + +mod enums { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub enum TheirStruct { + Unit, + Tuple(u32), + Struct { value: usize }, + } + } + + #[reflect_remote(external_crate::TheirStruct)] + //~^ ERROR: variant `enums::external_crate::TheirStruct::Unit` does not have a field named `0` + //~| ERROR: variant `enums::external_crate::TheirStruct::Unit` has no field named `0` + //~| ERROR: `?` operator has incompatible types + //~| ERROR: `?` operator has incompatible types + enum MyStruct { + // Reason: Should be unit variant + Unit(i32), + // Reason: Should be `u32` + Tuple(bool), + //~^ ERROR: mismatched types + // Reason: Should be `usize` + Struct { value: String }, + //~^ ERROR: mismatched types + } +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_pass.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_pass.rs new file mode 100644 index 0000000000..7f2fdb73dd --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_pass.rs @@ -0,0 +1,48 @@ +//@check-pass + +mod structs { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub struct TheirStruct { + pub value: u32, + } + } + + #[reflect_remote(external_crate::TheirStruct)] + struct MyStruct { + pub value: u32, + } +} + +mod tuple_structs { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub struct TheirStruct(pub u32); + } + + #[reflect_remote(external_crate::TheirStruct)] + struct MyStruct(pub u32); +} + +mod enums { + use bevy_reflect::reflect_remote; + + mod external_crate { + pub enum TheirStruct { + Unit, + Tuple(u32), + Struct { value: usize }, + } + } + + #[reflect_remote(external_crate::TheirStruct)] + enum MyStruct { + Unit, + Tuple(u32), + Struct { value: usize }, + } +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_fail.rs new file mode 100644 index 0000000000..2114482816 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_fail.rs @@ -0,0 +1,19 @@ +use bevy_reflect::{reflect_remote, std_traits::ReflectDefault}; + +mod external_crate { + #[derive(Debug, Default)] + pub struct TheirType { + pub value: String, + } +} + +#[derive(Debug, Default)] +#[reflect_remote(external_crate::TheirType)] +#[reflect(Debug, Default)] +struct MyType { + pub value: String, + //~^ ERROR: no field `value` on type `&MyType` + //~| ERROR: struct `MyType` has no field named `value` +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_pass.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_pass.rs new file mode 100644 index 0000000000..58fcb56aaf --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/macro_order_pass.rs @@ -0,0 +1,18 @@ +//@check-pass +use bevy_reflect::{reflect_remote, std_traits::ReflectDefault}; + +mod external_crate { + #[derive(Debug, Default)] + pub struct TheirType { + pub value: String, + } +} + +#[reflect_remote(external_crate::TheirType)] +#[derive(Debug, Default)] +#[reflect(Debug, Default)] +struct MyType { + pub value: String, +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs new file mode 100644 index 0000000000..c9f85c0001 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs @@ -0,0 +1,75 @@ +mod external_crate { + pub struct TheirOuter { + pub inner: TheirInner, + } + pub struct TheirInner(pub T); +} + +mod missing_attribute { + use bevy_reflect::{FromReflect, GetTypeRegistration, reflect_remote}; + + #[reflect_remote(super::external_crate::TheirOuter)] + struct MyOuter { + // Reason: Missing `#[reflect(remote = ...)]` attribute + pub inner: super::external_crate::TheirInner, + } + + #[reflect_remote(super::external_crate::TheirInner)] + struct MyInner(pub T); +} + +mod incorrect_inner_type { + use bevy_reflect::{FromReflect, GetTypeRegistration, reflect_remote}; + + #[reflect_remote(super::external_crate::TheirOuter)] + //~^ ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected + //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected + //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected + //~| ERROR: `TheirInner` can not be used as a dynamic type path + //~| ERROR: `?` operator has incompatible types + struct MyOuter { + // Reason: Should not use `MyInner` directly + pub inner: MyInner, + //~^ ERROR: mismatched types + } + + #[reflect_remote(super::external_crate::TheirInner)] + struct MyInner(pub T); +} + +mod mismatched_remote_type { + use bevy_reflect::{FromReflect, GetTypeRegistration, reflect_remote}; + + #[reflect_remote(super::external_crate::TheirOuter)] + //~^ ERROR: mismatched types + //~| ERROR: mismatched types + struct MyOuter { + // Reason: Should be `MyInner` + #[reflect(remote = MyOuter)] + //~^ ERROR: mismatched types + pub inner: super::external_crate::TheirInner, + } + + #[reflect_remote(super::external_crate::TheirInner)] + struct MyInner(pub T); +} + +mod mismatched_remote_generic { + use bevy_reflect::{FromReflect, GetTypeRegistration, reflect_remote}; + + #[reflect_remote(super::external_crate::TheirOuter)] + //~^ ERROR: `?` operator has incompatible types + //~| ERROR: mismatched types + //~| ERROR: mismatched types + struct MyOuter { + // Reason: `TheirOuter::inner` is not defined as `TheirInner` + #[reflect(remote = MyInner)] + pub inner: super::external_crate::TheirInner, + //~^ ERROR: mismatched types + } + + #[reflect_remote(super::external_crate::TheirInner)] + struct MyInner(pub T); +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_pass.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_pass.rs new file mode 100644 index 0000000000..cd79ede57a --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_pass.rs @@ -0,0 +1,20 @@ +//@check-pass +use bevy_reflect::{FromReflect, GetTypeRegistration, reflect_remote, Reflect, Typed}; + +mod external_crate { + pub struct TheirOuter { + pub inner: TheirInner, + } + pub struct TheirInner(pub T); +} + +#[reflect_remote(external_crate::TheirOuter)] +struct MyOuter { + #[reflect(remote = MyInner)] + pub inner: external_crate::TheirInner, +} + +#[reflect_remote(external_crate::TheirInner)] +struct MyInner(pub T); + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs new file mode 100644 index 0000000000..1983442ab7 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs @@ -0,0 +1,99 @@ +//@no-rustfix + +mod structs { + use bevy_reflect::{reflect_remote, Reflect}; + + mod external_crate { + pub struct TheirFoo { + pub value: u32, + } + pub struct TheirBar { + pub value: i32, + } + } + + #[reflect_remote(external_crate::TheirFoo)] + struct MyFoo { + pub value: u32, + } + #[reflect_remote(external_crate::TheirBar)] + struct MyBar { + pub value: i32, + } + + #[derive(Reflect)] + //~^ ERROR: mismatched types + //~| ERROR: mismatched types + struct MyStruct { + // Reason: Should use `MyFoo` + #[reflect(remote = MyBar)] + //~^ ERROR: mismatched types + foo: external_crate::TheirFoo, + } +} + +mod tuple_structs { + use bevy_reflect::{reflect_remote, Reflect}; + + 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)] + //~^ ERROR: mismatched types + //~| ERROR: mismatched types + struct MyStruct( + // Reason: Should use `MyFoo` + #[reflect(remote = MyBar)] external_crate::TheirFoo, + //~^ ERROR: mismatched types + ); +} + +mod enums { + use bevy_reflect::{reflect_remote, Reflect}; + + mod external_crate { + pub enum TheirFoo { + Value(u32), + } + + pub enum TheirBar { + Value(i32), + } + } + + #[reflect_remote(external_crate::TheirFoo)] + enum MyFoo { + Value(u32), + } + + #[reflect_remote(external_crate::TheirBar)] + //~^ ERROR: `?` operator has incompatible types + enum MyBar { + // Reason: Should use `i32` + Value(u32), + //~^ ERROR: mismatched types + } + + #[derive(Reflect)] + //~^ ERROR: mismatched types + //~| ERROR: mismatched types + //~| ERROR: mismatched types + enum MyStruct { + Value( + // Reason: Should use `MyFoo` + #[reflect(remote = MyBar)] external_crate::TheirFoo, + //~^ ERROR: mismatched types + ), + } +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_pass.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_pass.rs new file mode 100644 index 0000000000..78f9d52c31 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_pass.rs @@ -0,0 +1,79 @@ +//@check-pass + +mod structs { + use bevy_reflect::{reflect_remote, Reflect}; + + mod external_crate { + pub struct TheirFoo { + pub value: u32, + } + pub struct TheirBar { + pub value: i32, + } + } + + #[reflect_remote(external_crate::TheirFoo)] + struct MyFoo { + pub value: u32, + } + #[reflect_remote(external_crate::TheirBar)] + struct MyBar { + pub value: i32, + } + + #[derive(Reflect)] + struct MyStruct { + #[reflect(remote = MyFoo)] + foo: external_crate::TheirFoo, + } +} + +mod tuple_structs { + use bevy_reflect::{reflect_remote, Reflect}; + + 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 = MyFoo)] external_crate::TheirFoo); +} + +mod enums { + use bevy_reflect::{reflect_remote, Reflect}; + + mod external_crate { + pub enum TheirFoo { + Value(u32), + } + + pub enum TheirBar { + Value(i32), + } + } + + #[reflect_remote(external_crate::TheirFoo)] + enum MyFoo { + Value(u32), + } + + #[reflect_remote(external_crate::TheirBar)] + enum MyBar { + Value(i32), + } + + #[derive(Reflect)] + enum MyStruct { + Value(#[reflect(remote = MyFoo)] external_crate::TheirFoo), + } +} + +fn main() {} diff --git a/crates/bevy_reflect/compile_fail/tests/remote.rs b/crates/bevy_reflect/compile_fail/tests/remote.rs new file mode 100644 index 0000000000..3c843853a1 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/remote.rs @@ -0,0 +1,3 @@ +fn main() -> compile_fail_utils::ui_test::Result<()> { + compile_fail_utils::test("reflect_remote", "tests/reflect_remote") +} diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index 8825febfff..edb11f3ef3 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -8,6 +8,7 @@ use crate::utility::{StringExpr, WhereClauseOptions}; use quote::{quote, ToTokens}; use syn::token::Comma; +use crate::remote::RemoteType; use crate::serialization::SerializationDataDef; use crate::{ utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, @@ -46,6 +47,8 @@ pub(crate) struct ReflectMeta<'a> { attrs: ContainerAttributes, /// The path to this type. type_path: ReflectTypePath<'a>, + /// The optional remote type to use instead of the actual type. + remote_ty: Option>, /// A cached instance of the path to the `bevy_reflect` crate. bevy_reflect_path: Path, /// The documentation for this type, if any @@ -146,8 +149,12 @@ enum ReflectMode { /// How the macro was invoked. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum ReflectImplSource { + /// Using `impl_reflect!`. ImplRemoteType, + /// Using `#[derive(...)]`. DeriveLocalType, + /// Using `#[reflect_remote]`. + RemoteReflect, } /// Which trait the macro explicitly implements. @@ -173,7 +180,9 @@ impl fmt::Display for ReflectProvenance { (S::DeriveLocalType, T::Reflect) => "`#[derive(Reflect)]`", (S::DeriveLocalType, T::FromReflect) => "`#[derive(FromReflect)]`", (S::DeriveLocalType, T::TypePath) => "`#[derive(TypePath)]`", - (S::ImplRemoteType, T::FromReflect | T::TypePath) => unreachable!(), + (S::RemoteReflect, T::Reflect) => "`#[reflect_remote]`", + (S::RemoteReflect, T::FromReflect | T::TypePath) + | (S::ImplRemoteType, T::FromReflect | T::TypePath) => unreachable!(), }; f.write_str(str) } @@ -343,13 +352,52 @@ impl<'a> ReflectDerive<'a> { }; } - pub fn meta(&self) -> &ReflectMeta<'a> { + /// Set the remote type for this derived type. + /// + /// # Panics + /// + /// Panics when called on [`ReflectDerive::Value`]. + pub fn set_remote(&mut self, remote_ty: Option>) { match self { - ReflectDerive::Struct(data) - | ReflectDerive::TupleStruct(data) - | ReflectDerive::UnitStruct(data) => data.meta(), - ReflectDerive::Enum(data) => data.meta(), - ReflectDerive::Value(meta) => meta, + Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => { + data.meta.remote_ty = remote_ty; + } + Self::Enum(data) => { + data.meta.remote_ty = remote_ty; + } + Self::Value(meta) => { + meta.remote_ty = remote_ty; + } + } + } + + /// Get the remote type path, if any. + pub fn remote_ty(&self) -> Option { + match self { + Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => { + data.meta.remote_ty() + } + Self::Enum(data) => data.meta.remote_ty(), + Self::Value(meta) => meta.remote_ty(), + } + } + + /// Get the [`ReflectMeta`] for this derived type. + pub fn meta(&self) -> &ReflectMeta { + match self { + Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => data.meta(), + Self::Enum(data) => data.meta(), + Self::Value(meta) => meta, + } + } + + pub fn where_clause_options(&self) -> WhereClauseOptions { + match self { + Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => { + data.where_clause_options() + } + Self::Enum(data) => data.where_clause_options(), + Self::Value(meta) => WhereClauseOptions::new(meta), } } @@ -424,6 +472,7 @@ impl<'a> ReflectMeta<'a> { Self { attrs, type_path, + remote_ty: None, bevy_reflect_path: utility::get_bevy_reflect_path(), #[cfg(feature = "documentation")] docs: Default::default(), @@ -457,6 +506,16 @@ impl<'a> ReflectMeta<'a> { &self.type_path } + /// Get the remote type path, if any. + pub fn remote_ty(&self) -> Option { + self.remote_ty + } + + /// Whether this reflected type represents a remote type or not. + pub fn is_remote_wrapper(&self) -> bool { + self.remote_ty.is_some() + } + /// The cached `bevy_reflect` path. pub fn bevy_reflect_path(&self) -> &Path { &self.bevy_reflect_path @@ -500,7 +559,7 @@ impl<'a> StructField<'a> { } }; - let ty = &self.data.ty; + let ty = self.reflected_type(); let custom_attributes = self.attrs.custom_attributes.to_tokens(bevy_reflect_path); #[allow(unused_mut)] // Needs mutability for the feature gate @@ -518,6 +577,19 @@ impl<'a> StructField<'a> { info } + + /// Returns the reflected type of this field. + /// + /// Normally this is just the field's defined type. + /// However, this can be adjusted to use a different type, like for representing remote types. + /// In those cases, the returned value is the remote wrapper type. + pub fn reflected_type(&self) -> &Type { + self.attrs.remote.as_ref().unwrap_or(&self.data.ty) + } + + pub fn attrs(&self) -> &FieldAttributes { + &self.attrs + } } impl<'a> ReflectStruct<'a> { @@ -549,7 +621,7 @@ impl<'a> ReflectStruct<'a> { /// Get a collection of types which are exposed to the reflection API pub fn active_types(&self) -> Vec { self.active_fields() - .map(|field| field.data.ty.clone()) + .map(|field| field.reflected_type().clone()) .collect() } @@ -631,8 +703,18 @@ impl<'a> ReflectEnum<'a> { } /// Returns the given ident as a qualified unit variant of this enum. + /// + /// This takes into account the remote type, if any. pub fn get_unit(&self, variant: &Ident) -> proc_macro2::TokenStream { - let name = self.meta.type_path(); + let name = self + .meta + .remote_ty + .map(|path| match path.as_expr_path() { + Ok(path) => path.to_token_stream(), + Err(err) => err.into_compile_error(), + }) + .unwrap_or_else(|| self.meta.type_path().to_token_stream()); + quote! { #name::#variant } @@ -646,7 +728,7 @@ impl<'a> ReflectEnum<'a> { /// Get a collection of types which are exposed to the reflection API pub fn active_types(&self) -> Vec { self.active_fields() - .map(|field| field.data.ty.clone()) + .map(|field| field.reflected_type().clone()) .collect() } @@ -670,7 +752,7 @@ impl<'a> ReflectEnum<'a> { self.meta(), where_clause_options, None, - Some(self.active_fields().map(|field| &field.data.ty)), + Some(self.active_fields().map(StructField::reflected_type)), ) } @@ -1121,6 +1203,28 @@ impl<'a> ReflectTypePath<'a> { pub fn type_ident(&self) -> Option { self.get_ident().map(StringExpr::from) } + + /// Returns the true type regardless of whether a custom path is specified. + /// + /// To get the custom path if there is one, use [`Self::get_path`]. + /// + /// For example, the type `Foo` would return `Foo`. + pub fn true_type(&self) -> proc_macro2::TokenStream { + match self { + Self::Primitive(ident) => quote!(#ident), + Self::Internal { + ident, generics, .. + } => { + let (_, ty_generics, _) = generics.split_for_impl(); + quote!(#ident #ty_generics) + } + Self::External { path, generics, .. } => { + let (_, ty_generics, _) = generics.split_for_impl(); + quote!(#path #ty_generics) + } + Self::Anonymous { qualified_type, .. } => qualified_type.to_token_stream(), + } + } } impl<'a> ToTokens for ReflectTypePath<'a> { diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index 50ea343b7a..a279a82237 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -79,12 +79,14 @@ pub(crate) trait VariantBuilder: Sized { /// * `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); - match &field.field.attrs.default { + let construction = match &field.field.attrs.default { DefaultBehavior::Func(path) => quote! { if let #FQOption::Some(#alias) = #field_accessor { #field_constructor @@ -109,6 +111,14 @@ pub(crate) trait VariantBuilder: Sized { #field_constructor }} } + }; + + if field.field.attrs().remote.is_some() { + quote! { + <#field_ty as #bevy_reflect_path::ReflectRemote>::into_remote(#construction) + } + } else { + construction } } @@ -200,7 +210,7 @@ impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { fn construct_field(&self, field: VariantField) -> TokenStream { let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path(); - let field_ty = &field.field.data.ty; + let field_ty = field.field.reflected_type(); let alias = field.alias; quote! { @@ -251,7 +261,7 @@ impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { 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.data.ty; + let field_ty = field.field.reflected_type(); quote! { <#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#alias) diff --git a/crates/bevy_reflect/derive/src/field_attributes.rs b/crates/bevy_reflect/derive/src/field_attributes.rs index d66daf8382..5228f80a67 100644 --- a/crates/bevy_reflect/derive/src/field_attributes.rs +++ b/crates/bevy_reflect/derive/src/field_attributes.rs @@ -7,13 +7,15 @@ use crate::custom_attributes::CustomAttributes; use crate::utility::terminated_parser; use crate::REFLECT_ATTRIBUTE_NAME; +use quote::ToTokens; use syn::parse::ParseStream; -use syn::{Attribute, LitStr, Meta, Token}; +use syn::{Attribute, LitStr, Meta, Token, Type}; mod kw { syn::custom_keyword!(ignore); syn::custom_keyword!(skip_serializing); syn::custom_keyword!(default); + syn::custom_keyword!(remote); } pub(crate) const IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing"; @@ -76,6 +78,8 @@ pub(crate) struct FieldAttributes { pub default: DefaultBehavior, /// Custom attributes created via `#[reflect(@...)]`. pub custom_attributes: CustomAttributes, + /// For defining the remote wrapper type that should be used in place of the field for reflection logic. + pub remote: Option, } impl FieldAttributes { @@ -119,6 +123,8 @@ impl FieldAttributes { self.parse_skip_serializing(input) } else if lookahead.peek(kw::default) { self.parse_default(input) + } else if lookahead.peek(kw::remote) { + self.parse_remote(input) } else { Err(lookahead.error()) } @@ -190,4 +196,41 @@ impl FieldAttributes { fn parse_custom_attribute(&mut self, input: ParseStream) -> syn::Result<()> { self.custom_attributes.parse_custom_attribute(input) } + + /// Parse `remote` attribute. + /// + /// Examples: + /// - `#[reflect(remote = path::to::RemoteType)]` + fn parse_remote(&mut self, input: ParseStream) -> syn::Result<()> { + if let Some(remote) = self.remote.as_ref() { + return Err(input.error(format!( + "remote type already specified as {}", + remote.to_token_stream() + ))); + } + + input.parse::()?; + input.parse::()?; + + self.remote = Some(input.parse()?); + + 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 { + if let Type::Path(type_path) = self.remote.as_ref()? { + type_path + .path + .segments + .last() + .map(|segment| !segment.arguments.is_empty()) + } else { + Some(false) + } + } } diff --git a/crates/bevy_reflect/derive/src/from_reflect.rs b/crates/bevy_reflect/derive/src/from_reflect.rs index 5aa637794a..9ed0d1fcaa 100644 --- a/crates/bevy_reflect/derive/src/from_reflect.rs +++ b/crates/bevy_reflect/derive/src/from_reflect.rs @@ -52,6 +52,16 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream .. } = FromReflectVariantBuilder::new(reflect_enum).build(&ref_value); + let match_branches = if reflect_enum.meta().is_remote_wrapper() { + quote! { + #(#variant_names => #fqoption::Some(Self(#variant_constructors)),)* + } + } else { + quote! { + #(#variant_names => #fqoption::Some(#variant_constructors),)* + } + }; + let (impl_generics, ty_generics, where_clause) = enum_path.generics().split_for_impl(); // Add FromReflect bound for each active field @@ -66,7 +76,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #bevy_reflect_path::PartialReflect::reflect_ref(#ref_value) { match #bevy_reflect_path::Enum::variant_name(#ref_value) { - #(#variant_names => #fqoption::Some(#variant_constructors),)* + #match_branches name => panic!("variant with name `{}` does not exist on enum `{}`", name, ::type_path()), } } else { @@ -94,6 +104,7 @@ fn impl_struct_internal( let fqoption = FQOption.into_token_stream(); let struct_path = reflect_struct.meta().type_path(); + let remote_ty = reflect_struct.meta().remote_ty(); let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); let ref_struct = Ident::new("__ref_struct", Span::call_site()); @@ -107,28 +118,48 @@ fn impl_struct_internal( get_active_fields(reflect_struct, &ref_struct, &ref_struct_type, is_tuple); let is_defaultable = reflect_struct.meta().attrs().contains(REFLECT_DEFAULT); + + // The constructed "Self" ident + let __this = Ident::new("__this", Span::call_site()); + + // The reflected type: either `Self` or a remote type + let (reflect_ty, constructor, retval) = if let Some(remote_ty) = remote_ty { + let constructor = match remote_ty.as_expr_path() { + Ok(path) => path, + Err(err) => return err.into_compile_error(), + }; + let remote_ty = remote_ty.type_path(); + + ( + quote!(#remote_ty), + quote!(#constructor), + quote!(Self(#__this)), + ) + } else { + (quote!(Self), quote!(Self), quote!(#__this)) + }; + let constructor = if is_defaultable { - quote!( - let mut __this: Self = #FQDefault::default(); + quote! { + let mut #__this = <#reflect_ty as #FQDefault>::default(); #( if let #fqoption::Some(__field) = #active_values() { // Iff field exists -> use its value - __this.#active_members = __field; + #__this.#active_members = __field; } )* - #FQOption::Some(__this) - ) + #FQOption::Some(#retval) + } } else { let MemberValuePair(ignored_members, ignored_values) = get_ignored_fields(reflect_struct); - quote!( - #FQOption::Some( - Self { - #(#active_members: #active_values()?,)* - #(#ignored_members: #ignored_values,)* - } - ) - ) + quote! { + let #__this = #constructor { + #(#active_members: #active_values()?,)* + #(#ignored_members: #ignored_values,)* + }; + #FQOption::Some(#retval) + } }; let (impl_generics, ty_generics, where_clause) = reflect_struct @@ -201,34 +232,76 @@ fn get_active_fields( field.reflection_index.expect("field should be active"), is_tuple, ); - let ty = field.data.ty.clone(); + let ty = field.reflected_type().clone(); + let real_ty = &field.data.ty; let get_field = quote! { #bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor) }; + let into_remote = |value: proc_macro2::TokenStream| { + if field.attrs.is_remote_generic().unwrap_or_default() { + 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?) + } + ) + } + } else { + value + } + }; + let value = match &field.attrs.default { - DefaultBehavior::Func(path) => quote! { - (|| - if let #FQOption::Some(field) = #get_field { - <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) - } else { - #FQOption::Some(#path()) - } - ) - }, - DefaultBehavior::Default => quote! { - (|| - if let #FQOption::Some(field) = #get_field { - <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) - } else { - #FQOption::Some(#FQDefault::default()) - } - ) - }, - DefaultBehavior::Required => quote! { - (|| <#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?)) - }, + DefaultBehavior::Func(path) => { + let value = into_remote(quote! { + <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) + }); + quote! { + (|| + if let #FQOption::Some(field) = #get_field { + #value + } else { + #FQOption::Some(#path()) + } + ) + } + } + DefaultBehavior::Default => { + let value = into_remote(quote! { + <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) + }); + quote! { + (|| + if let #FQOption::Some(field) = #get_field { + #value + } else { + #FQOption::Some(#FQDefault::default()) + } + ) + } + } + DefaultBehavior::Required => { + let value = into_remote(quote! { + <#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?) + }); + quote! { + (|| #value) + } + } }; (member, value) diff --git a/crates/bevy_reflect/derive/src/impls/assertions.rs b/crates/bevy_reflect/derive/src/impls/assertions.rs new file mode 100644 index 0000000000..06d835e6cf --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/assertions.rs @@ -0,0 +1,14 @@ +use crate::derive_data::ReflectDerive; +use crate::remote::generate_remote_assertions; +use quote::quote; + +/// Generates an anonymous block containing compile-time assertions. +pub(crate) fn impl_assertions(derive_data: &ReflectDerive) -> proc_macro2::TokenStream { + let mut output = quote!(); + + if let Some(assertions) = generate_remote_assertions(derive_data) { + output.extend(assertions); + } + + output +} diff --git a/crates/bevy_reflect/derive/src/impls/common.rs b/crates/bevy_reflect/derive/src/impls/common.rs index 00c573152c..f01fd96e04 100644 --- a/crates/bevy_reflect/derive/src/impls/common.rs +++ b/crates/bevy_reflect/derive/src/impls/common.rs @@ -14,8 +14,25 @@ pub fn impl_full_reflect( let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); - quote! { - impl #impl_generics #bevy_reflect_path::Reflect for #type_path #ty_generics #where_reflect_clause { + let any_impls = if meta.is_remote_wrapper() { + quote! { + #[inline] + fn into_any(self: #FQBox) -> #FQBox { + #FQBox::new(self.0) + } + + #[inline] + fn as_any(&self) -> &dyn #FQAny { + &self.0 + } + + #[inline] + fn as_any_mut(&mut self) -> &mut dyn #FQAny { + &mut self.0 + } + } + } else { + quote! { #[inline] fn into_any(self: #FQBox) -> #FQBox { self @@ -30,6 +47,12 @@ pub fn impl_full_reflect( fn as_any_mut(&mut self) -> &mut dyn #FQAny { self } + } + }; + + quote! { + impl #impl_generics #bevy_reflect_path::Reflect for #type_path #ty_generics #where_reflect_clause { + #any_impls #[inline] fn into_reflect(self: #FQBox) -> #FQBox { diff --git a/crates/bevy_reflect/derive/src/impls/enums.rs b/crates/bevy_reflect/derive/src/impls/enums.rs index 74c7848afe..15d5c01ffa 100644 --- a/crates/bevy_reflect/derive/src/impls/enums.rs +++ b/crates/bevy_reflect/derive/src/impls/enums.rs @@ -4,11 +4,31 @@ use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_ use bevy_macro_utils::fq_std::{FQBox, FQOption, FQResult}; use proc_macro2::{Ident, Span}; use quote::quote; -use syn::Fields; +use syn::{Fields, Path}; pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream { let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); let enum_path = reflect_enum.meta().type_path(); + let is_remote = reflect_enum.meta().is_remote_wrapper(); + + // For `match self` expressions where self is a reference + let match_this = if is_remote { + quote!(&self.0) + } else { + quote!(self) + }; + // For `match self` expressions where self is a mutable reference + let match_this_mut = if is_remote { + quote!(&mut self.0) + } else { + quote!(self) + }; + // For `*self` assignments + let deref_this = if is_remote { + quote!(self.0) + } else { + quote!(*self) + }; let ref_name = Ident::new("__name_param", Span::call_site()); let ref_index = Ident::new("__index_param", Span::call_site()); @@ -18,7 +38,9 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream let EnumImpls { enum_field, + enum_field_mut, enum_field_at, + enum_field_at_mut, enum_index_of, enum_name_at, enum_field_len, @@ -73,42 +95,42 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause { fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { - match self { + match #match_this { #(#enum_field,)* _ => #FQOption::None, } } fn field_at(&self, #ref_index: usize) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { - match self { + match #match_this { #(#enum_field_at,)* _ => #FQOption::None, } } fn field_mut(&mut self, #ref_name: &str) -> #FQOption<&mut dyn #bevy_reflect_path::PartialReflect> { - match self { - #(#enum_field,)* + match #match_this_mut { + #(#enum_field_mut,)* _ => #FQOption::None, } } fn field_at_mut(&mut self, #ref_index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::PartialReflect> { - match self { - #(#enum_field_at,)* + match #match_this_mut { + #(#enum_field_at_mut,)* _ => #FQOption::None, } } fn index_of(&self, #ref_name: &str) -> #FQOption { - match self { + match #match_this { #(#enum_index_of,)* _ => #FQOption::None, } } fn name_at(&self, #ref_index: usize) -> #FQOption<&str> { - match self { + match #match_this { #(#enum_name_at,)* _ => #FQOption::None, } @@ -120,7 +142,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #[inline] fn field_len(&self) -> usize { - match self { + match #match_this { #(#enum_field_len,)* _ => 0, } @@ -128,7 +150,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #[inline] fn variant_name(&self) -> &str { - match self { + match #match_this { #(#enum_variant_name,)* _ => unreachable!(), } @@ -136,7 +158,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #[inline] fn variant_index(&self) -> usize { - match self { + match #match_this { #(#enum_variant_index,)* _ => unreachable!(), } @@ -144,7 +166,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #[inline] fn variant_type(&self) -> #bevy_reflect_path::VariantType { - match self { + match #match_this { #(#enum_variant_type,)* _ => unreachable!(), } @@ -197,7 +219,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream // New variant -> perform a switch match #bevy_reflect_path::Enum::variant_name(#ref_value) { #(#variant_names => { - *self = #variant_constructors + #deref_this = #variant_constructors })* name => { return #FQResult::Err( @@ -243,7 +265,9 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream struct EnumImpls { enum_field: Vec, + enum_field_mut: Vec, enum_field_at: Vec, + enum_field_at_mut: Vec, enum_index_of: Vec, enum_name_at: Vec, enum_field_len: Vec, @@ -256,7 +280,9 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); let mut enum_field = Vec::new(); + let mut enum_field_mut = Vec::new(); let mut enum_field_at = Vec::new(); + let mut enum_field_at_mut = Vec::new(); let mut enum_index_of = Vec::new(); let mut enum_name_at = Vec::new(); let mut enum_field_len = Vec::new(); @@ -304,6 +330,29 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden field_len } + /// Process the field value to account for remote types. + /// + /// If the field is a remote type, then the value will be transmuted accordingly. + fn process_field_value( + ident: &Ident, + field: &StructField, + is_mutable: bool, + bevy_reflect_path: &Path, + ) -> proc_macro2::TokenStream { + let method = if is_mutable { + quote!(as_wrapper_mut) + } else { + quote!(as_wrapper) + }; + + field + .attrs + .remote + .as_ref() + .map(|ty| quote!(<#ty as #bevy_reflect_path::ReflectRemote>::#method(#ident))) + .unwrap_or_else(|| quote!(#ident)) + } + match &variant.fields { EnumVariantFields::Unit => { let field_len = process_fields(&[], |_| {}); @@ -319,8 +368,16 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden .expect("reflection index should exist for active field"); let declare_field = syn::Index::from(field.declaration_index); + + let __value = Ident::new("__value", Span::call_site()); + let value_ref = process_field_value(&__value, field, false, bevy_reflect_path); + let value_mut = process_field_value(&__value, field, true, bevy_reflect_path); + enum_field_at.push(quote! { - #unit { #declare_field : value, .. } if #ref_index == #reflection_index => #FQOption::Some(value) + #unit { #declare_field : #__value, .. } if #ref_index == #reflection_index => #FQOption::Some(#value_ref) + }); + enum_field_at_mut.push(quote! { + #unit { #declare_field : #__value, .. } if #ref_index == #reflection_index => #FQOption::Some(#value_mut) }); }); @@ -336,11 +393,21 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden .reflection_index .expect("reflection index should exist for active field"); + let __value = Ident::new("__value", Span::call_site()); + let value_ref = process_field_value(&__value, field, false, bevy_reflect_path); + let value_mut = process_field_value(&__value, field, true, bevy_reflect_path); + enum_field.push(quote! { - #unit{ #field_ident, .. } if #ref_name == #field_name => #FQOption::Some(#field_ident) + #unit{ #field_ident: #__value, .. } if #ref_name == #field_name => #FQOption::Some(#value_ref) + }); + enum_field_mut.push(quote! { + #unit{ #field_ident: #__value, .. } if #ref_name == #field_name => #FQOption::Some(#value_mut) }); enum_field_at.push(quote! { - #unit{ #field_ident, .. } if #ref_index == #reflection_index => #FQOption::Some(#field_ident) + #unit{ #field_ident: #__value, .. } if #ref_index == #reflection_index => #FQOption::Some(#value_ref) + }); + enum_field_at_mut.push(quote! { + #unit{ #field_ident: #__value, .. } if #ref_index == #reflection_index => #FQOption::Some(#value_mut) }); enum_index_of.push(quote! { #unit{ .. } if #ref_name == #field_name => #FQOption::Some(#reflection_index) @@ -359,7 +426,9 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden EnumImpls { enum_field, + enum_field_mut, enum_field_at, + enum_field_at_mut, enum_index_of, enum_name_at, enum_field_len, diff --git a/crates/bevy_reflect/derive/src/impls/mod.rs b/crates/bevy_reflect/derive/src/impls/mod.rs index 5974d85d9f..0905d43bd2 100644 --- a/crates/bevy_reflect/derive/src/impls/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/mod.rs @@ -1,3 +1,4 @@ +mod assertions; mod common; mod enums; #[cfg(feature = "functions")] @@ -7,6 +8,7 @@ mod tuple_structs; mod typed; mod values; +pub(crate) use assertions::impl_assertions; pub(crate) use common::{common_partial_reflect_methods, impl_full_reflect}; pub(crate) use enums::impl_enum; #[cfg(feature = "functions")] diff --git a/crates/bevy_reflect/derive/src/impls/structs.rs b/crates/bevy_reflect/derive/src/impls/structs.rs index 964b91e788..15b11d4dd9 100644 --- a/crates/bevy_reflect/derive/src/impls/structs.rs +++ b/crates/bevy_reflect/derive/src/impls/structs.rs @@ -1,5 +1,5 @@ use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed}; -use crate::utility::ident_or_index; +use crate::struct_utility::FieldAccessors; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; @@ -22,12 +22,14 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS .unwrap_or_else(|| field.declaration_index.to_string()) }) .collect::>(); - let field_idents = reflect_struct - .active_fields() - .map(|field| ident_or_index(field.data.ident.as_ref(), field.declaration_index)) - .collect::>(); - let field_count = field_idents.len(); - let field_indices = (0..field_count).collect::>(); + + let FieldAccessors { + fields_ref, + fields_mut, + field_indices, + field_count, + .. + } = FieldAccessors::new(reflect_struct); let where_clause_options = reflect_struct.where_clause_options(); let typed_impl = impl_typed( @@ -74,28 +76,28 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match name { - #(#field_names => #fqoption::Some(&self.#field_idents),)* + #(#field_names => #fqoption::Some(#fields_ref),)* _ => #FQOption::None, } } fn field_mut(&mut self, name: &str) -> #FQOption<&mut dyn #bevy_reflect_path::PartialReflect> { match name { - #(#field_names => #fqoption::Some(&mut self.#field_idents),)* + #(#field_names => #fqoption::Some(#fields_mut),)* _ => #FQOption::None, } } fn field_at(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match index { - #(#field_indices => #fqoption::Some(&self.#field_idents),)* + #(#field_indices => #fqoption::Some(#fields_ref),)* _ => #FQOption::None, } } fn field_at_mut(&mut self, index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::PartialReflect> { match index { - #(#field_indices => #fqoption::Some(&mut self.#field_idents),)* + #(#field_indices => #fqoption::Some(#fields_mut),)* _ => #FQOption::None, } } @@ -118,7 +120,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicStruct { let mut dynamic: #bevy_reflect_path::DynamicStruct = #FQDefault::default(); dynamic.set_represented_type(#bevy_reflect_path::PartialReflect::get_represented_type_info(self)); - #(dynamic.insert_boxed(#field_names, #bevy_reflect_path::PartialReflect::clone_value(&self.#field_idents));)* + #(dynamic.insert_boxed(#field_names, #bevy_reflect_path::PartialReflect::clone_value(#fields_ref));)* dynamic } } diff --git a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs index 50424c5dbf..fc2228e70e 100644 --- a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs @@ -1,8 +1,8 @@ use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed}; +use crate::struct_utility::FieldAccessors; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; -use syn::{Index, Member}; /// Implements `TupleStruct`, `GetTypeRegistration`, and `Reflect` for the given derive data. pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenStream { @@ -11,12 +11,13 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); let struct_path = reflect_struct.meta().type_path(); - let field_idents = reflect_struct - .active_fields() - .map(|field| Member::Unnamed(Index::from(field.declaration_index))) - .collect::>(); - let field_count = field_idents.len(); - let field_indices = (0..field_count).collect::>(); + let FieldAccessors { + fields_ref, + fields_mut, + field_indices, + field_count, + .. + } = FieldAccessors::new(reflect_struct); let where_clause_options = reflect_struct.where_clause_options(); let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options); @@ -63,14 +64,14 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match index { - #(#field_indices => #fqoption::Some(&self.#field_idents),)* + #(#field_indices => #fqoption::Some(#fields_ref),)* _ => #FQOption::None, } } fn field_mut(&mut self, index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::PartialReflect> { match index { - #(#field_indices => #fqoption::Some(&mut self.#field_idents),)* + #(#field_indices => #fqoption::Some(#fields_mut),)* _ => #FQOption::None, } } @@ -86,7 +87,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicTupleStruct { let mut dynamic: #bevy_reflect_path::DynamicTupleStruct = #FQDefault::default(); dynamic.set_represented_type(#bevy_reflect_path::PartialReflect::get_represented_type_info(self)); - #(dynamic.insert_boxed(#bevy_reflect_path::PartialReflect::clone_value(&self.#field_idents));)* + #(dynamic.insert_boxed(#bevy_reflect_path::PartialReflect::clone_value(#fields_ref));)* dynamic } } diff --git a/crates/bevy_reflect/derive/src/impls/values.rs b/crates/bevy_reflect/derive/src/impls/values.rs index e50e0a756a..3a804f3bed 100644 --- a/crates/bevy_reflect/derive/src/impls/values.rs +++ b/crates/bevy_reflect/derive/src/impls/values.rs @@ -31,6 +31,23 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { let full_reflect_impl = impl_full_reflect(meta, &where_clause_options); let common_methods = common_partial_reflect_methods(meta, || None, || None); + let apply_impl = if let Some(remote_ty) = meta.remote_ty() { + let ty = remote_ty.type_path(); + quote! { + if let #FQOption::Some(value) = ::try_downcast_ref::<#ty>(value) { + *self = Self(#FQClone::clone(value)); + return #FQResult::Ok(()); + } + } + } else { + quote! { + if let #FQOption::Some(value) = ::try_downcast_ref::(value) { + *self = #FQClone::clone(value); + return #FQResult::Ok(()); + } + } + }; + #[cfg(not(feature = "functions"))] let function_impls = None::; #[cfg(feature = "functions")] @@ -67,17 +84,14 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { &mut self, value: &dyn #bevy_reflect_path::PartialReflect ) -> #FQResult<(), #bevy_reflect_path::ApplyError> { - if let #FQOption::Some(value) = ::try_downcast_ref::(value) { - *self = #FQClone::clone(value); - } else { - return #FQResult::Err( - #bevy_reflect_path::ApplyError::MismatchedTypes { - from_type: ::core::convert::Into::into(#bevy_reflect_path::DynamicTypePath::reflect_type_path(value)), - to_type: ::core::convert::Into::into(::type_path()), - } - ); - } - #FQResult::Ok(()) + #apply_impl + + #FQResult::Err( + #bevy_reflect_path::ApplyError::MismatchedTypes { + from_type: ::core::convert::Into::into(#bevy_reflect_path::DynamicTypePath::reflect_type_path(value)), + to_type: ::core::convert::Into::into(::type_path()), + } + ) } #[inline] diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 86e0069a88..2fdb055b60 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -27,7 +27,9 @@ mod from_reflect; mod impls; mod reflect_value; mod registration; +mod remote; mod serialization; +mod struct_utility; mod trait_reflection; mod type_path; mod utility; @@ -62,6 +64,8 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre Err(err) => return err.into_compile_error().into(), }; + let assertions = impls::impl_assertions(&derive_data); + let (reflect_impls, from_reflect_impl) = match derive_data { ReflectDerive::Struct(struct_data) | ReflectDerive::UnitStruct(struct_data) => ( impls::impl_struct(&struct_data), @@ -100,7 +104,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre TokenStream::from(quote! { const _: () = { #reflect_impls + #from_reflect_impl + + #assertions }; }) } @@ -511,6 +518,91 @@ pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream { trait_reflection::reflect_trait(&args, input) } +/// 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. +/// +/// # Defining the Wrapper +/// +/// Before defining the wrapper type, please note that it is _required_ that all fields of the remote type are public. +/// The generated code will, at times, need to access or mutate them, +/// and we do not currently have a way to assign getters/setters to each field +/// (but this may change in the future). +/// +/// The wrapper definition should match the remote type 1-to-1. +/// This includes the naming and ordering of the fields and variants. +/// +/// Generics and lifetimes do _not_ need to have the same names, however, they _do_ need to follow the same order. +/// Additionally, whether generics are inlined or placed in a where clause should not matter. +/// +/// Lastly, all macros and doc-comments should be placed __below__ this attribute. +/// If they are placed above, they will not be properly passed to the generated wrapper type. +/// +/// # Example +/// +/// Given a remote type, `RemoteType`: +/// +/// ``` +/// #[derive(Default)] +/// struct RemoteType +/// where +/// T: Default + Clone, +/// { +/// pub foo: T, +/// pub bar: usize +/// } +/// ``` +/// +/// We would define our wrapper type as such: +/// +/// ```ignore +/// use external_crate::RemoteType; +/// +/// #[reflect_remote(RemoteType)] +/// #[derive(Default)] +/// pub struct WrapperType { +/// pub foo: T, +/// pub bar: usize +/// } +/// ``` +/// +/// Apart from all the reflection trait implementations, this generates something like the following: +/// +/// ```ignore +/// use external_crate::RemoteType; +/// +/// #[derive(Default)] +/// #[repr(transparent)] +/// pub struct Wrapper(RemoteType); +/// ``` +/// +/// # Usage as a Field +/// +/// You can tell `Reflect` to use a remote type's wrapper internally on fields of a struct or enum. +/// This allows the real type to be used as usual while `Reflect` handles everything internally. +/// To do this, add the `#[reflect(remote = path::to::MyType)]` attribute to your field: +/// +/// ```ignore +/// #[derive(Reflect)] +/// struct SomeStruct { +/// #[reflect(remote = RemoteTypeWrapper)] +/// data: RemoteType +/// } +/// ``` +/// +/// ## Safety +/// +/// 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. +/// +/// [transmuted]: std::mem::transmute +#[proc_macro_attribute] +pub fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStream { + remote::reflect_remote(args, input) +} + /// A macro used to generate reflection trait implementations for the given type. /// /// This is functionally the same as [deriving `Reflect`] using the `#[reflect_value]` container attribute. diff --git a/crates/bevy_reflect/derive/src/remote.rs b/crates/bevy_reflect/derive/src/remote.rs new file mode 100644 index 0000000000..9a60f930cf --- /dev/null +++ b/crates/bevy_reflect/derive/src/remote.rs @@ -0,0 +1,499 @@ +use crate::derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl}; +use crate::impls::impl_assertions; +use crate::utility::ident_or_index; +use crate::{ + from_reflect, impls, ReflectDerive, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_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}; +use syn::spanned::Spanned; +use syn::token::PathSep; +use syn::{ + parse_macro_input, 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::(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::Value(meta) => ( + impls::impl_value(&meta), + if meta.from_reflect().should_auto_derive() { + Some(from_reflect::impl_value(&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`, then the wrapper type— named `Foo`— would look like: +/// +/// ``` +/// # struct Bar(T); +/// +/// #[repr(transparent)] +/// struct Foo(Bar); +/// ``` +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) + && !attr.path().is_ident(REFLECT_VALUE_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 + // | + // | std::mem::transmute::(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::( + // `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 + // | + // | std::mem::transmute::(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::( + // `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 { + 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> = 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::(); + + 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: +/// found: +/// ``` +/// +/// 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::Value(_) => { + // 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` into `foo::Bar::` 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 { + 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 { + Ok(Self { + remote_ty: input.parse()?, + }) + } +} diff --git a/crates/bevy_reflect/derive/src/struct_utility.rs b/crates/bevy_reflect/derive/src/struct_utility.rs new file mode 100644 index 0000000000..2320e8b86f --- /dev/null +++ b/crates/bevy_reflect/derive/src/struct_utility.rs @@ -0,0 +1,80 @@ +use crate::derive_data::StructField; +use crate::{utility, ReflectStruct}; +use quote::quote; + +/// 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`]: core::mem::transmute +pub(crate) struct FieldAccessors { + /// The referenced field accessors, such as `&self.foo`. + pub fields_ref: Vec, + /// The mutably referenced field accessors, such as `&mut self.foo`. + pub fields_mut: Vec, + /// The ordered set of field indices (basically just the range of [0, `field_count`). + pub field_indices: Vec, + /// The number of fields in the reflected struct. + pub field_count: usize, +} + +impl FieldAccessors { + pub fn new(reflect_struct: &ReflectStruct) -> Self { + let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); + let fields_ref = Self::get_fields(reflect_struct, |field, accessor| { + match &field.attrs.remote { + Some(wrapper_ty) => { + quote! { + <#wrapper_ty as #bevy_reflect_path::ReflectRemote>::as_wrapper(&#accessor) + } + } + None => quote!(& #accessor), + } + }); + let fields_mut = Self::get_fields(reflect_struct, |field, accessor| { + match &field.attrs.remote { + Some(wrapper_ty) => { + quote! { + <#wrapper_ty as #bevy_reflect_path::ReflectRemote>::as_wrapper_mut(&mut #accessor) + } + } + None => quote!(&mut #accessor), + } + }); + + let field_count = fields_ref.len(); + let field_indices = (0..field_count).collect(); + + Self { + fields_ref, + fields_mut, + field_indices, + field_count, + } + } + + fn get_fields( + reflect_struct: &ReflectStruct, + mut wrapper_fn: F, + ) -> Vec + where + F: FnMut(&StructField, proc_macro2::TokenStream) -> proc_macro2::TokenStream, + { + let is_remote = reflect_struct.meta().is_remote_wrapper(); + reflect_struct + .active_fields() + .map(|field| { + let member = + utility::ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let accessor = if is_remote { + quote!(self.0.#member) + } else { + quote!(self.#member) + }; + + wrapper_fn(field, accessor) + }) + .collect::>() + } +} diff --git a/crates/bevy_reflect/derive/src/utility.rs b/crates/bevy_reflect/derive/src/utility.rs index 0670c0fe01..231dc18f2b 100644 --- a/crates/bevy_reflect/derive/src/utility.rs +++ b/crates/bevy_reflect/derive/src/utility.rs @@ -154,17 +154,18 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> { &self, where_clause: Option<&WhereClause>, ) -> proc_macro2::TokenStream { - let type_path = self.meta.type_path(); - let (_, ty_generics, _) = self.meta.type_path().generics().split_for_impl(); + // We would normally just use `Self`, but that won't work for generating things like assertion functions + // and trait impls for a type's reference (e.g. `impl FromArg for &MyType`) + let this = self.meta.type_path().true_type(); let required_bounds = self.required_bounds(); // Maintain existing where clause, if any. let mut generic_where_clause = if let Some(where_clause) = where_clause { let predicates = where_clause.predicates.iter(); - quote! {where #type_path #ty_generics: #required_bounds, #(#predicates,)*} + quote! {where #this: #required_bounds, #(#predicates,)*} } else { - quote!(where #type_path #ty_generics: #required_bounds,) + quote!(where #this: #required_bounds,) }; // Add additional reflection trait bounds diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index de0a9d52c6..87e7704835 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -534,6 +534,7 @@ mod list; mod map; mod path; mod reflect; +mod remote; mod set; mod struct_trait; mod tuple; @@ -584,6 +585,7 @@ pub use list::*; pub use map::*; pub use path::*; pub use reflect::*; +pub use remote::*; pub use set::*; pub use struct_trait::*; pub use tuple::*; @@ -2479,6 +2481,467 @@ bevy_reflect::tests::Test { assert_impl_all!(Enum: Reflect); } + #[test] + fn should_reflect_remote_type() { + mod external_crate { + #[derive(Debug, Default)] + pub struct TheirType { + pub value: String, + } + } + + // === Remote Wrapper === // + #[reflect_remote(external_crate::TheirType)] + #[derive(Debug, Default)] + #[reflect(Debug, Default)] + struct MyType { + pub value: String, + } + + let mut patch = DynamicStruct::default(); + patch.set_represented_type(Some(MyType::type_info())); + patch.insert("value", "Goodbye".to_string()); + + let mut data = MyType(external_crate::TheirType { + value: "Hello".to_string(), + }); + + assert_eq!("Hello", data.0.value); + data.apply(&patch); + assert_eq!("Goodbye", data.0.value); + + // === Struct Container === // + #[derive(Reflect, Debug)] + #[reflect(from_reflect = false)] + struct ContainerStruct { + #[reflect(remote = MyType)] + their_type: external_crate::TheirType, + } + + let mut patch = DynamicStruct::default(); + patch.set_represented_type(Some(ContainerStruct::type_info())); + patch.insert( + "their_type", + MyType(external_crate::TheirType { + value: "Goodbye".to_string(), + }), + ); + + let mut data = ContainerStruct { + their_type: external_crate::TheirType { + value: "Hello".to_string(), + }, + }; + + assert_eq!("Hello", data.their_type.value); + data.apply(&patch); + assert_eq!("Goodbye", data.their_type.value); + + // === Tuple Struct Container === // + #[derive(Reflect, Debug)] + struct ContainerTupleStruct(#[reflect(remote = MyType)] external_crate::TheirType); + + let mut patch = DynamicTupleStruct::default(); + patch.set_represented_type(Some(ContainerTupleStruct::type_info())); + patch.insert(MyType(external_crate::TheirType { + value: "Goodbye".to_string(), + })); + + let mut data = ContainerTupleStruct(external_crate::TheirType { + value: "Hello".to_string(), + }); + + assert_eq!("Hello", data.0.value); + data.apply(&patch); + assert_eq!("Goodbye", data.0.value); + } + + #[test] + fn should_reflect_remote_value_type() { + mod external_crate { + #[derive(Clone, Debug, Default)] + pub struct TheirType { + pub value: String, + } + } + + // === Remote Wrapper === // + #[reflect_remote(external_crate::TheirType)] + #[derive(Clone, Debug, Default)] + #[reflect_value(Debug, Default)] + struct MyType { + pub value: String, + } + + let mut data = MyType(external_crate::TheirType { + value: "Hello".to_string(), + }); + + let patch = MyType(external_crate::TheirType { + value: "Goodbye".to_string(), + }); + + assert_eq!("Hello", data.0.value); + data.apply(&patch); + assert_eq!("Goodbye", data.0.value); + + // === Struct Container === // + #[derive(Reflect, Debug)] + #[reflect(from_reflect = false)] + struct ContainerStruct { + #[reflect(remote = MyType)] + their_type: external_crate::TheirType, + } + + let mut patch = DynamicStruct::default(); + patch.set_represented_type(Some(ContainerStruct::type_info())); + patch.insert( + "their_type", + MyType(external_crate::TheirType { + value: "Goodbye".to_string(), + }), + ); + + let mut data = ContainerStruct { + their_type: external_crate::TheirType { + value: "Hello".to_string(), + }, + }; + + assert_eq!("Hello", data.their_type.value); + data.apply(&patch); + assert_eq!("Goodbye", data.their_type.value); + + // === Tuple Struct Container === // + #[derive(Reflect, Debug)] + struct ContainerTupleStruct(#[reflect(remote = MyType)] external_crate::TheirType); + + let mut patch = DynamicTupleStruct::default(); + patch.set_represented_type(Some(ContainerTupleStruct::type_info())); + patch.insert(MyType(external_crate::TheirType { + value: "Goodbye".to_string(), + })); + + let mut data = ContainerTupleStruct(external_crate::TheirType { + value: "Hello".to_string(), + }); + + assert_eq!("Hello", data.0.value); + data.apply(&patch); + assert_eq!("Goodbye", data.0.value); + } + + #[test] + fn should_reflect_remote_type_from_module() { + mod wrapper { + use super::*; + + // We have to place this module internally to this one to get around the following error: + // ``` + // error[E0433]: failed to resolve: use of undeclared crate or module `external_crate` + // ``` + pub mod external_crate { + pub struct TheirType { + pub value: String, + } + } + + #[reflect_remote(external_crate::TheirType)] + pub struct MyType { + pub value: String, + } + } + + #[derive(Reflect)] + struct ContainerStruct { + #[reflect(remote = wrapper::MyType)] + their_type: wrapper::external_crate::TheirType, + } + } + + #[test] + fn should_reflect_remote_enum() { + mod external_crate { + #[derive(Debug, PartialEq, Eq)] + pub enum TheirType { + Unit, + Tuple(usize), + Struct { value: String }, + } + } + + // === Remote Wrapper === // + #[reflect_remote(external_crate::TheirType)] + #[derive(Debug)] + #[reflect(Debug)] + enum MyType { + Unit, + Tuple(usize), + Struct { value: String }, + } + + let mut patch = DynamicEnum::from(MyType(external_crate::TheirType::Tuple(123))); + + let mut data = MyType(external_crate::TheirType::Unit); + + assert_eq!(external_crate::TheirType::Unit, data.0); + data.apply(&patch); + assert_eq!(external_crate::TheirType::Tuple(123), data.0); + + patch = DynamicEnum::from(MyType(external_crate::TheirType::Struct { + value: "Hello world!".to_string(), + })); + + data.apply(&patch); + assert_eq!( + external_crate::TheirType::Struct { + value: "Hello world!".to_string() + }, + data.0 + ); + + // === Enum Container === // + #[derive(Reflect, Debug, PartialEq)] + enum ContainerEnum { + Foo, + Bar { + #[reflect(remote = MyType)] + their_type: external_crate::TheirType, + }, + } + + let patch = DynamicEnum::from(ContainerEnum::Bar { + their_type: external_crate::TheirType::Tuple(123), + }); + + let mut data = ContainerEnum::Foo; + + assert_eq!(ContainerEnum::Foo, data); + data.apply(&patch); + assert_eq!( + ContainerEnum::Bar { + their_type: external_crate::TheirType::Tuple(123) + }, + data + ); + } + + #[test] + fn should_reflect_nested_remote_type() { + mod external_crate { + pub struct TheirOuter { + pub a: TheirInner, + pub b: TheirInner, + } + + pub struct TheirInner(pub T); + } + + #[reflect_remote(external_crate::TheirOuter)] + struct MyOuter { + #[reflect(remote = MyInner)] + pub a: external_crate::TheirInner, + #[reflect(remote = MyInner)] + pub b: external_crate::TheirInner, + } + + #[reflect_remote(external_crate::TheirInner)] + struct MyInner(pub T); + + let mut patch = DynamicStruct::default(); + patch.set_represented_type(Some(MyOuter::::type_info())); + patch.insert("a", MyInner(external_crate::TheirInner(321_i32))); + patch.insert("b", MyInner(external_crate::TheirInner(true))); + + let mut data = MyOuter(external_crate::TheirOuter { + a: external_crate::TheirInner(123_i32), + b: external_crate::TheirInner(false), + }); + + assert_eq!(123, data.0.a.0); + assert!(!data.0.b.0); + data.apply(&patch); + assert_eq!(321, data.0.a.0); + assert!(data.0.b.0); + } + + #[test] + fn should_reflect_nested_remote_enum() { + mod external_crate { + use std::fmt::Debug; + + #[derive(Debug)] + pub enum TheirOuter { + Unit, + Tuple(TheirInner), + Struct { value: TheirInner }, + } + #[derive(Debug)] + pub enum TheirInner { + Unit, + Tuple(T), + Struct { value: T }, + } + } + + #[reflect_remote(external_crate::TheirOuter)] + #[derive(Debug)] + enum MyOuter { + Unit, + Tuple(#[reflect(remote = MyInner)] external_crate::TheirInner), + Struct { + #[reflect(remote = MyInner)] + value: external_crate::TheirInner, + }, + } + + #[reflect_remote(external_crate::TheirInner)] + #[derive(Debug)] + enum MyInner { + Unit, + Tuple(T), + Struct { value: T }, + } + + let mut patch = DynamicEnum::default(); + let mut value = DynamicStruct::default(); + value.insert("value", MyInner(external_crate::TheirInner::Tuple(123))); + patch.set_variant("Struct", value); + + let mut data = MyOuter(external_crate::TheirOuter::::Unit); + + assert!(matches!( + data, + MyOuter(external_crate::TheirOuter::::Unit) + )); + data.apply(&patch); + assert!(matches!( + data, + MyOuter(external_crate::TheirOuter::Struct { + value: external_crate::TheirInner::Tuple(123) + }) + )); + } + + #[test] + fn should_take_remote_type() { + mod external_crate { + #[derive(Debug, Default, PartialEq, Eq)] + pub struct TheirType { + pub value: String, + } + } + + // === Remote Wrapper === // + #[reflect_remote(external_crate::TheirType)] + #[derive(Debug, Default)] + #[reflect(Debug, Default)] + struct MyType { + pub value: String, + } + + let input: Box = Box::new(MyType(external_crate::TheirType { + value: "Hello".to_string(), + })); + + let output: external_crate::TheirType = input + .take() + .expect("should downcast to `external_crate::TheirType`"); + assert_eq!( + external_crate::TheirType { + value: "Hello".to_string(), + }, + output + ); + } + + #[test] + fn should_try_take_remote_type() { + mod external_crate { + #[derive(Debug, Default, PartialEq, Eq)] + pub struct TheirType { + pub value: String, + } + } + + // === Remote Wrapper === // + #[reflect_remote(external_crate::TheirType)] + #[derive(Debug, Default)] + #[reflect(Debug, Default)] + struct MyType { + pub value: String, + } + + let input: Box = Box::new(MyType(external_crate::TheirType { + value: "Hello".to_string(), + })); + + let output: external_crate::TheirType = input + .try_take() + .expect("should downcast to `external_crate::TheirType`"); + assert_eq!( + external_crate::TheirType { + value: "Hello".to_string(), + }, + output, + ); + } + + #[test] + fn should_take_nested_remote_type() { + mod external_crate { + #[derive(PartialEq, Eq, Debug)] + pub struct TheirOuter { + pub inner: TheirInner, + } + #[derive(PartialEq, Eq, Debug)] + pub struct TheirInner(pub T); + } + + #[reflect_remote(external_crate::TheirOuter)] + struct MyOuter { + #[reflect(remote = MyInner)] + pub inner: external_crate::TheirInner, + } + + #[reflect_remote(external_crate::TheirInner)] + struct MyInner(pub T); + + let input: Box = Box::new(MyOuter(external_crate::TheirOuter { + inner: external_crate::TheirInner(123), + })); + + let output: external_crate::TheirOuter = input + .take() + .expect("should downcast to `external_crate::TheirOuter`"); + assert_eq!( + external_crate::TheirOuter { + inner: external_crate::TheirInner(123), + }, + output + ); + } + + #[test] + fn should_reflect_external_crate_type() { + // This test relies on the external type not implementing `Reflect`, + // so let's just double-check that it does not + assert_not_impl_all!(std::collections::Bound: Reflect); + + #[reflect_remote(std::collections::Bound)] + enum MyBound { + Included(T), + Excluded(T), + Unbounded, + } + + #[derive(Reflect)] + struct MyType { + #[reflect(remote = MyBound)] + bound: std::collections::Bound, + } + } + #[cfg(feature = "glam")] mod glam { use super::*; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 59b8a81d54..0456d209c7 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -410,12 +410,18 @@ where )] pub trait Reflect: PartialReflect + Any { /// Returns the value as a [`Box`][std::any::Any]. + /// + /// For remote wrapper types, this will return the remote type instead. fn into_any(self: Box) -> Box; /// Returns the value as a [`&dyn Any`][std::any::Any]. + /// + /// For remote wrapper types, this will return the remote type instead. fn as_any(&self) -> &dyn Any; /// Returns the value as a [`&mut dyn Any`][std::any::Any]. + /// + /// For remote wrapper types, this will return the remote type instead. fn as_any_mut(&mut self) -> &mut dyn Any; /// Casts this type to a boxed, fully-reflected value. @@ -450,7 +456,9 @@ impl dyn PartialReflect { /// /// If the underlying value does not implement [`Reflect`] /// or is not of type `T`, returns `Err(self)`. - pub fn try_downcast( + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn try_downcast( self: Box, ) -> Result, Box> { self.try_into_reflect()? @@ -462,9 +470,9 @@ impl dyn PartialReflect { /// /// If the underlying value does not implement [`Reflect`] /// or is not of type `T`, returns `Err(self)`. - pub fn try_take( - self: Box, - ) -> Result> { + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn try_take(self: Box) -> Result> { self.try_downcast().map(|value| *value) } @@ -472,7 +480,9 @@ impl dyn PartialReflect { /// /// If the underlying value does not implement [`Reflect`] /// or is not of type `T`, returns [`None`]. - pub fn try_downcast_ref(&self) -> Option<&T> { + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn try_downcast_ref(&self) -> Option<&T> { self.try_as_reflect()?.downcast_ref() } @@ -480,7 +490,9 @@ impl dyn PartialReflect { /// /// If the underlying value does not implement [`Reflect`] /// or is not of type `T`, returns [`None`]. - pub fn try_downcast_mut(&mut self) -> Option<&mut T> { + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn try_downcast_mut(&mut self) -> Option<&mut T> { self.try_as_reflect_mut()?.downcast_mut() } } @@ -508,7 +520,9 @@ impl dyn Reflect { /// Downcasts the value to type `T`, consuming the trait object. /// /// If the underlying value is not of type `T`, returns `Err(self)`. - pub fn downcast(self: Box) -> Result, Box> { + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn downcast(self: Box) -> Result, Box> { if self.is::() { Ok(self.into_any().downcast().unwrap()) } else { @@ -519,7 +533,9 @@ impl dyn Reflect { /// Downcasts the value to type `T`, unboxing and consuming the trait object. /// /// If the underlying value is not of type `T`, returns `Err(self)`. - pub fn take(self: Box) -> Result> { + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + pub fn take(self: Box) -> Result> { self.downcast::().map(|value| *value) } @@ -532,25 +548,31 @@ impl dyn Reflect { /// to determine what type they represent. Represented types cannot be downcasted /// to, but you can use [`FromReflect`] to create a value of the represented type from them. /// + /// For remote types, `T` should be the type itself rather than the wrapper type. + /// /// [`FromReflect`]: crate::FromReflect #[inline] - pub fn is(&self) -> bool { - self.type_id() == TypeId::of::() + pub fn is(&self) -> bool { + self.as_any().type_id() == TypeId::of::() } /// Downcasts the value to type `T` by reference. /// /// If the underlying value is not of type `T`, returns `None`. + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. #[inline] - pub fn downcast_ref(&self) -> Option<&T> { + pub fn downcast_ref(&self) -> Option<&T> { self.as_any().downcast_ref::() } /// Downcasts the value to type `T` by mutable reference. /// /// If the underlying value is not of type `T`, returns `None`. + /// + /// For remote types, `T` should be the type itself rather than the wrapper type. #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + pub fn downcast_mut(&mut self) -> Option<&mut T> { self.as_any_mut().downcast_mut::() } } diff --git a/crates/bevy_reflect/src/remote.rs b/crates/bevy_reflect/src/remote.rs new file mode 100644 index 0000000000..a19d3bdb14 --- /dev/null +++ b/crates/bevy_reflect/src/remote.rs @@ -0,0 +1,64 @@ +use crate::Reflect; + +/// Marks a type as a [reflectable] wrapper for a remote type. +/// +/// This allows types from external libraries (remote types) to be included in reflection. +/// +/// # Safety +/// +/// 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. +/// +/// # Example +/// +/// ``` +/// use bevy_reflect_derive::{reflect_remote, Reflect}; +/// +/// mod some_lib { +/// pub struct TheirType { +/// pub value: u32 +/// } +/// } +/// +/// #[reflect_remote(some_lib::TheirType)] +/// struct MyType { +/// pub value: u32 +/// } +/// +/// #[derive(Reflect)] +/// struct MyStruct { +/// #[reflect(remote = MyType)] +/// data: some_lib::TheirType, +/// } +/// ``` +/// +/// [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; + + /// Converts a reference of this wrapper to a reference of its remote type. + fn as_remote(&self) -> &Self::Remote; + /// Converts a mutable reference of this wrapper to a mutable reference of its remote type. + fn as_remote_mut(&mut self) -> &mut Self::Remote; + /// Converts this wrapper into its remote type. + fn into_remote(self) -> Self::Remote; + + /// Converts a reference of the remote type to a reference of this wrapper. + fn as_wrapper(remote: &Self::Remote) -> &Self; + /// Converts a mutable reference of the remote type to a mutable reference of this wrapper. + fn as_wrapper_mut(remote: &mut Self::Remote) -> &mut Self; + /// Converts the remote type into this wrapper. + fn into_wrapper(remote: Self::Remote) -> Self; +}