Skip to content

Commit 9932aeb

Browse files
committed
Added remote field assertions
1 parent 802f896 commit 9932aeb

File tree

3 files changed

+139
-11
lines changed

3 files changed

+139
-11
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/remote.rs

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
use crate::{from_reflect, impls, ReflectDerive, REFLECT_ATTRIBUTE_NAME};
1+
use crate::derive_data::EnumVariantFields;
2+
use crate::{
3+
derive_data::StructField, from_reflect, impls, utility::ident_or_index, ReflectDerive,
4+
REFLECT_ATTRIBUTE_NAME,
5+
};
26
use proc_macro::TokenStream;
3-
use quote::quote;
7+
use proc_macro2::Ident;
8+
use quote::{format_ident, quote};
49
use syn::parse::{Parse, ParseStream};
510
use syn::spanned::Spanned;
6-
use syn::{parse_macro_input, DeriveInput, ExprPath, PathArguments, Token, TypePath};
11+
use syn::{parse_macro_input, DeriveInput, ExprPath, Generics, PathArguments, Token, TypePath};
712

813
/// Generates the remote wrapper type and implements all the necessary traits.
914
pub(crate) fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStream {
@@ -31,14 +36,40 @@ pub(crate) fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStre
3136
None
3237
};
3338

34-
let trait_impls = match derive_data {
35-
ReflectDerive::Struct(struct_data) | ReflectDerive::UnitStruct(struct_data) => {
36-
proc_macro2::TokenStream::from(impls::impl_struct(&struct_data))
37-
}
38-
ReflectDerive::TupleStruct(struct_data) => {
39-
proc_macro2::TokenStream::from(impls::impl_tuple_struct(&struct_data))
40-
}
41-
ReflectDerive::Enum(meta) => proc_macro2::TokenStream::from(impls::impl_enum(&meta)),
39+
let (trait_impls, assertions) = match derive_data {
40+
ReflectDerive::Struct(struct_data) | ReflectDerive::UnitStruct(struct_data) => (
41+
proc_macro2::TokenStream::from(impls::impl_struct(&struct_data)),
42+
Some(generate_remote_field_assertions(
43+
struct_data.fields(),
44+
None,
45+
struct_data.meta().generics(),
46+
)),
47+
),
48+
ReflectDerive::TupleStruct(struct_data) => (
49+
proc_macro2::TokenStream::from(impls::impl_tuple_struct(&struct_data)),
50+
Some(generate_remote_field_assertions(
51+
struct_data.fields(),
52+
None,
53+
struct_data.meta().generics(),
54+
)),
55+
),
56+
ReflectDerive::Enum(enum_data) => (
57+
proc_macro2::TokenStream::from(impls::impl_enum(&enum_data)),
58+
enum_data
59+
.variants()
60+
.iter()
61+
.map(|variant| match &variant.fields {
62+
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => {
63+
Some(generate_remote_field_assertions(
64+
fields,
65+
Some(&variant.data.ident),
66+
enum_data.meta().generics(),
67+
))
68+
}
69+
EnumVariantFields::Unit => None,
70+
})
71+
.collect(),
72+
),
4273
_ => {
4374
return syn::Error::new(ast.span(), "cannot reflect a remote value type")
4475
.into_compile_error()
@@ -49,6 +80,8 @@ pub(crate) fn reflect_remote(args: TokenStream, input: TokenStream) -> TokenStre
4980
TokenStream::from(quote! {
5081
#wrapper_definition
5182

83+
#assertions
84+
5285
#from_reflect_impl
5386

5487
#trait_impls
@@ -96,6 +129,74 @@ fn generate_remote_wrapper(input: &DeriveInput, remote_ty: &TypePath) -> proc_ma
96129
}
97130
}
98131

132+
/// Generates compile-time assertions for remote fields.
133+
///
134+
/// # Example
135+
///
136+
/// The following would fail to compile due to an incorrect `#[reflect(remote = "...")]` value.
137+
///
138+
/// ```ignore
139+
/// mod external_crate {
140+
/// pub struct TheirOuter {
141+
/// pub inner: TheirInner,
142+
/// }
143+
/// pub struct TheirInner(pub String);
144+
/// }
145+
///
146+
/// #[reflect_remote(external_crate::TheirOuter)]
147+
/// struct MyOuter {
148+
/// #[reflect(remote = "MyOuter")] // <- Note the mismatched type (it should be `MyInner`)
149+
/// pub inner: external_crate::TheirInner,
150+
/// }
151+
///
152+
/// #[reflect_remote(external_crate::TheirInner)]
153+
/// struct MyInner(pub String);
154+
/// ```
155+
fn generate_remote_field_assertions<'a>(
156+
fields: &[StructField<'a>],
157+
variant: Option<&Ident>,
158+
generics: &Generics,
159+
) -> proc_macro2::TokenStream {
160+
fields
161+
.iter()
162+
.filter(|field| field.attrs.remote.is_some())
163+
.map(|field| {
164+
let ident = if let Some(variant) = variant {
165+
format_ident!(
166+
"{}__{}",
167+
variant,
168+
ident_or_index(field.data.ident.as_ref(), field.index)
169+
)
170+
} else {
171+
field
172+
.data
173+
.ident
174+
.as_ref()
175+
.map(|ident| ident.clone())
176+
.unwrap_or_else(|| format_ident!("field_{}", field.index))
177+
};
178+
179+
let (impl_generics, _, where_clause) = generics.split_for_impl();
180+
let field_ty = &field.data.ty;
181+
let remote_ty = field.attrs.remote.as_ref().unwrap();
182+
let assertion_ident = format_ident!("assert__{}__is_valid_remote", ident);
183+
184+
quote! {
185+
const _: () = {
186+
struct RemoteFieldAssertions;
187+
188+
impl RemoteFieldAssertions {
189+
#[allow(non_snake_case)]
190+
fn #assertion_ident #impl_generics (#ident: #remote_ty) #where_clause {
191+
let _: #field_ty = #ident.0;
192+
}
193+
}
194+
};
195+
}
196+
})
197+
.collect()
198+
}
199+
99200
/// A reflected type's remote type.
100201
///
101202
/// This is a wrapper around [`TypePath`] that allows it to be paired with other remote-specific logic.

crates/bevy_reflect_compile_fail_tests/tests/reflect_remote/nested.fail.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,18 @@ mod incorrect_inner_type {
3131
struct MyInner<T: Reflect>(pub T);
3232
}
3333

34+
mod mismatched_remote_type {
35+
use bevy_reflect::{reflect_remote, Reflect};
36+
37+
#[reflect_remote(super::external_crate::TheirOuter<T>)]
38+
struct MyOuter<T: Reflect> {
39+
// Reason: Should be `MyInner<T>`
40+
#[reflect(remote = "MyOuter<T>")]
41+
pub inner: super::external_crate::TheirInner<T>,
42+
}
43+
44+
#[reflect_remote(super::external_crate::TheirInner<T>)]
45+
struct MyInner<T: Reflect>(pub T);
46+
}
47+
3448
fn main() {}

crates/bevy_reflect_compile_fail_tests/tests/reflect_remote/nested.fail.stderr

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,16 @@ error[E0277]: the trait bound `TheirInner<T>: Reflect` is not satisfied
131131
(A, B, C, D, E, F, G)
132132
and $N others
133133
= note: this error originates in the attribute macro `reflect_remote` (in Nightly builds, run with -Z macro-backtrace for more info)
134+
135+
error[E0308]: mismatched types
136+
--> tests/reflect_remote/nested.fail.rs:37:5
137+
|
138+
37 | #[reflect_remote(super::external_crate::TheirOuter<T>)]
139+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `TheirInner`, found struct `TheirOuter`
140+
...
141+
41 | pub inner: super::external_crate::TheirInner<T>,
142+
| ------------------------------------ expected due to this
143+
|
144+
= note: expected struct `TheirInner<T>`
145+
found struct `TheirOuter<T>`
146+
= note: this error originates in the attribute macro `reflect_remote` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)