diff --git a/crates/bindings-macro/src/sats.rs b/crates/bindings-macro/src/sats.rs index 1902592fcb..140bb36f94 100644 --- a/crates/bindings-macro/src/sats.rs +++ b/crates/bindings-macro/src/sats.rs @@ -1,3 +1,7 @@ +//! When editing generated code in this module, use `__`-prefixed reserved names +//! for macro-emitted local bindings and helper items to avoid collisions with +//! user-defined items at the expansion site. + extern crate core; extern crate proc_macro; @@ -252,11 +256,13 @@ pub(crate) fn derive_satstype(ty: &SatsType<'_>) -> TokenStream { #[automatically_derived] impl #impl_generics #krate::SpacetimeType for #name #ty_generics #where_clause { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn make_type(__typespace: &mut S) -> #krate::sats::AlgebraicType { #krate::sats::typespace::TypespaceBuilder::add( __typespace, core::any::TypeId::of::<#name #typeid_ty_generics>(), Some(#ty_name), + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. |__typespace| #typ, ) } @@ -407,6 +413,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { } } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. struct __ProductVisitor #impl_generics #where_clause { _marker: std::marker::PhantomData #name #ty_generics>, } @@ -435,8 +442,10 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { )* Ok(()) } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn visit_named_product>(self, mut __prod: A) -> Result { #(let mut #field_names = None;)* + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self { _marker: std::marker::PhantomData, })? { @@ -454,8 +463,10 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { #field_names.ok_or_else(|| #spacetimedb_lib::de::Error::missing_field(#iter_n4, Some(#field_strings), &self))?,)* }) } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn validate_named_product>(self, mut __prod: A) -> Result<(), A::Error> { #(let mut #field_names = false;)* + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self { _marker: std::marker::PhantomData, })? { @@ -485,15 +496,18 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { [#(#field_strings),*].into_iter().map(Some) } - fn visit<__E: #spacetimedb_lib::de::Error>(self, name: &str) -> Result { - match name { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. + fn visit<__E: #spacetimedb_lib::de::Error>(self, __name: &str) -> Result { + match __name { #(#field_strings => Ok(__ProductFieldIdent::#field_names),)* - _ => Err(#spacetimedb_lib::de::Error::unknown_field_name(name, &self)), + _ => Err(#spacetimedb_lib::de::Error::unknown_field_name(__name, &self)), } } - fn visit_seq(self, index: usize) -> Self::Output { - match index { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. + fn visit_seq(self, __index: usize) -> Self::Output { + match __index { #(#iter_n7 => __ProductFieldIdent::#field_names,)* _ => core::unreachable!(), } @@ -501,6 +515,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { } #[allow(non_camel_case_types)] + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. enum __ProductFieldIdent { #(#field_names,)* } @@ -555,6 +570,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { } } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. struct __SumVisitor #impl_generics #where_clause { _marker: std::marker::PhantomData #name #ty_generics>, } @@ -566,14 +582,18 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { Some(#tuple_name) } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn visit_sum>(self, __data: A) -> Result { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. let (__variant, __access) = __data.variant(self)?; match __variant { #(#arms)* } } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn validate_sum>(self, __data: A) -> Result<(), A::Error> { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. let (__variant, __access) = __data.variant(self)?; match __variant { #(#arms_validate)* @@ -583,6 +603,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { } #[allow(non_camel_case_types)] + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. enum __Variant { #(#variant_idents,)* } @@ -594,12 +615,14 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream { [#(#variant_names,)*].into_iter() } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn visit_tag(self, __tag: u8) -> Result { match __tag { #(#tags => Ok(__Variant::#variant_idents),)* _ => Err(#spacetimedb_lib::de::Error::unknown_variant_tag(__tag, &self)), } } + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn visit_name(self, __name: &str) -> Result { match __name { #(#variant_names => Ok(__Variant::#variant_idents),)* @@ -664,6 +687,7 @@ pub(crate) fn derive_serialize(ty: &SatsType) -> TokenStream { let fieldnamestrings = fields.iter().map(|field| field.name.as_ref().unwrap()); let nfields = fields.len(); quote! { + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. let mut __prod = __serializer.serialize_named_product(#nfields)?; #(#spacetimedb_lib::ser::SerializeNamedProduct::serialize_element::<#tys>(&mut __prod, Some(#fieldnamestrings), &self.#fieldnames)?;)* #spacetimedb_lib::ser::SerializeNamedProduct::end(__prod) @@ -675,6 +699,7 @@ pub(crate) fn derive_serialize(ty: &SatsType) -> TokenStream { let tag = i as u8; if let (Some(member), Some(ty)) = (&var.member, var.ty) { quote_spanned! {ty.span()=> + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. Self::#name { #member: __variant } => __serializer.serialize_variant::<#ty>(#tag, Some(#name_str), __variant), } } else { @@ -692,6 +717,7 @@ pub(crate) fn derive_serialize(ty: &SatsType) -> TokenStream { quote! { impl #impl_generics #spacetimedb_lib::ser::Serialize for #name #ty_generics #where_clause { #fast_body + // __ reserved name for binding to prevent name conflicts. See module-level doc comment. fn serialize(&self, __serializer: S) -> Result { #body } diff --git a/crates/bindings/tests/pass/table_index_name_conflict.rs b/crates/bindings/tests/pass/table_index_name_conflict.rs new file mode 100644 index 0000000000..421e2729e5 --- /dev/null +++ b/crates/bindings/tests/pass/table_index_name_conflict.rs @@ -0,0 +1,20 @@ +// This file tests that it's possible to have a value item (`fn`, `const`, or `static`) named `index` +// without introducing a name conflict due to a binding introduced by the `#[table]` macro. +// Prior to a fix, the SATS derive macros (which were invoked by `table`) introduced some bindings +// which were not in the `__` reserved namespace and had common names, +// resulting in name collisions with user code. + +use spacetimedb::http::{Request, Response}; + +#[spacetimedb::http::handler] +fn index(_ctx: &mut spacetimedb::HandlerContext, _req: Request) -> Response { + Response::new(().into()) +} + +#[spacetimedb::table(accessor = things)] +struct Thing { + #[index(btree)] + value: u32, +} + +fn main() {} diff --git a/crates/bindings/tests/pass/table_name_name_conflict.rs b/crates/bindings/tests/pass/table_name_name_conflict.rs new file mode 100644 index 0000000000..1019b3e62c --- /dev/null +++ b/crates/bindings/tests/pass/table_name_name_conflict.rs @@ -0,0 +1,19 @@ +// This file tests that it's possible to have a value item (`fn`, `const`, or `static`) named `name` +// without introducing a name conflict due to a binding introduced by the `#[table]` macro. +// Prior to a fix, the SATS derive macros (which were invoked by `table`) introduced some bindings +// which were not in the `__` reserved namespace and had common names, +// resulting in name collisions with user code. + +use spacetimedb::http::{Request, Response}; + +#[spacetimedb::http::handler] +fn name(_ctx: &mut spacetimedb::HandlerContext, _req: Request) -> Response { + Response::new(().into()) +} + +#[spacetimedb::table(accessor = things)] +struct Thing { + value: u32, +} + +fn main() {} diff --git a/crates/bindings/tests/ui.rs b/crates/bindings/tests/ui.rs index 870c2f95ec..c13cfdfaeb 100644 --- a/crates/bindings/tests/ui.rs +++ b/crates/bindings/tests/ui.rs @@ -2,4 +2,5 @@ fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); + t.pass("tests/pass/*.rs"); }