Fix name collision with const index or const name colocated with #[table]

This is an old bug that isn't actually related to HTTP handlers at all,
except that I encountered it while testing an HTTP handler named `index` or `name`.
It's a simple enough fix that I'm slipping it in to this PR.
This commit is contained in:
Phoebe Goldman
2026-04-21 10:55:23 -04:00
parent 7e20edaff1
commit 999a7c3175
4 changed files with 71 additions and 5 deletions
+31 -5
View File
@@ -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<S: #krate::sats::typespace::TypespaceBuilder>(__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<fn() -> #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<A: #spacetimedb_lib::de::NamedProductAccess<'de>>(self, mut __prod: A) -> Result<Self::Output, A::Error> {
#(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<A: #spacetimedb_lib::de::NamedProductAccess<'de>>(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<Self::Output, __E> {
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<Self::Output, __E> {
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<fn() -> #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<A: #spacetimedb_lib::de::SumAccess<'de>>(self, __data: A) -> Result<Self::Output, A::Error> {
// __ 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<A: #spacetimedb_lib::de::SumAccess<'de>>(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<E: #spacetimedb_lib::de::Error>(self, __tag: u8) -> Result<Self::Output, E> {
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<E: #spacetimedb_lib::de::Error>(self, __name: &str) -> Result<Self::Output, E> {
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<S: #spacetimedb_lib::ser::Serializer>(&self, __serializer: S) -> Result<S::Ok, S::Error> {
#body
}
@@ -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() {}
@@ -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() {}
+1
View File
@@ -2,4 +2,5 @@
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
t.pass("tests/pass/*.rs");
}