Skip to content

Commit 5e36de5

Browse files
authored
Normalize raw identifiers (r#) in contract macros (#1837)
### What Normalizes raw identifiers (r#) across the Soroban macros so that every name, generated Rust ident, and downstream comparison/sort key uses the unraw'd form of a user's Ident. For tokens that must refer back to the user's field / variant / fn, the raw Ident is preserved, so it remains valid Rust. ### Why Fixes #1836 ### Known limitations None
1 parent 82f9e53 commit 5e36de5

19 files changed

Lines changed: 573 additions & 101 deletions

soroban-sdk-macros/src/attribute.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use syn::{punctuated::Punctuated, Attribute, Data, Fields, FieldsNamed, FieldsUnnamed};
1+
use syn::{
2+
ext::IdentExt as _, punctuated::Punctuated, Attribute, Data, Fields, FieldsNamed, FieldsUnnamed,
3+
};
24

35
/// Returns true if the attribute is a doc attribute.
46
pub fn is_attr_doc(attr: &Attribute) -> bool {
@@ -31,7 +33,7 @@ pub fn remove_attributes_from_item(data: &mut Data, attrs: &[&str]) {
3133
!attr
3234
.path()
3335
.get_ident()
34-
.is_some_and(|ident| attrs.contains(&ident.to_string().as_str()))
36+
.is_some_and(|ident| attrs.contains(&ident.unraw().to_string().as_str()))
3537
});
3638
}
3739
}

soroban-sdk-macros/src/derive_client.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use proc_macro2::TokenStream;
33
use quote::{format_ident, quote};
44
use syn::{Error, FnArg, LitStr, Path, Type, TypePath, TypeReference};
55

6+
use syn::ext::IdentExt as _;
7+
68
use crate::{
79
attribute::pass_through_attr_to_gen_code, map_type::map_type, stellar_xdr::ScSpecTypeDef,
810
symbol, syn_ext,
@@ -155,13 +157,15 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) ->
155157
// Skip generating client functions for calling contract functions
156158
// that start with '__', because the Soroban Env won't let those
157159
// functions be invoked directly as they're reserved for callbacks
158-
// and hooks.
159-
!f.ident.to_string().starts_with("__")
160+
// and hooks. Check the Soroban-facing name so a raw-identifier
161+
// spelling like `r#__check_auth` can't slip past this filter and then
162+
// still export as `__check_auth`.
163+
!f.ident.unraw().to_string().starts_with("__")
160164
})
161165
.map(|f| {
162166
let fn_ident = &f.ident;
163-
let fn_try_ident = format_ident!("try_{}", &f.ident);
164-
let fn_name = fn_ident.to_string();
167+
let fn_name = fn_ident.unraw().to_string();
168+
let fn_try_ident = format_ident!("try_{}", &fn_name);
165169
let fn_name_symbol = symbol::short_or_long(
166170
crate_path,
167171
quote!(&self.env),

soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use darling::{ast::NestedMeta, Error, FromMeta};
1010
use proc_macro2::{Ident, TokenStream as TokenStream2};
1111
use quote::quote;
1212
use std::collections::HashSet;
13-
use syn::{LitStr, Path, Type};
13+
use syn::{ext::IdentExt as _, LitStr, Path, Type};
1414

1515
// See soroban-sdk/docs/contracttrait.md for documentation on how this works.
1616

@@ -59,7 +59,7 @@ fn derive(args: &Args) -> Result<TokenStream2, Error> {
5959
// overridden in the input fns.
6060
let fns = trait_default_fns
6161
.into_iter()
62-
.filter(|f| !impl_fn_idents.contains(&f.ident.to_string()))
62+
.filter(|f| !impl_fn_idents.contains(&f.ident.unraw().to_string()))
6363
.collect::<Vec<_>>();
6464

6565
let mut output = quote! {};

soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use heck::ToSnakeCase;
44
use proc_macro2::{Ident, TokenStream as TokenStream2};
55
use quote::ToTokens;
66
use quote::{format_ident, quote};
7-
use syn::{parse2, ImplItemFn, ItemTrait, Path, TraitItem, TraitItemFn, Type};
7+
use syn::{ext::IdentExt as _, parse2, ImplItemFn, ItemTrait, Path, TraitItem, TraitItemFn, Type};
88

99
// See soroban-sdk/docs/contracttrait.md for documentation on how this works.
1010

@@ -98,7 +98,7 @@ pub fn generate_call_to_contractimpl_for_trait(
9898
args_ident: &str,
9999
spec_ident: &str,
100100
) -> TokenStream2 {
101-
let impl_fn_idents = pub_methods.iter().map(|f| f.sig.ident.to_string());
101+
let impl_fn_idents = pub_methods.iter().map(|f| f.sig.ident.unraw().to_string());
102102
quote! {
103103
#trait_ident!(
104104
#trait_ident,
@@ -112,6 +112,6 @@ pub fn generate_call_to_contractimpl_for_trait(
112112
}
113113

114114
fn macro_ident(trait_ident: &Ident) -> Ident {
115-
let lower = trait_ident.to_string().to_snake_case();
115+
let lower = trait_ident.unraw().to_string().to_snake_case();
116116
format_ident!("__contractimpl_for_{lower}")
117117
}

soroban-sdk-macros/src/derive_enum.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use itertools::MultiUnzip;
22
use proc_macro2::{Literal, TokenStream as TokenStream2};
33
use quote::{format_ident, quote, ToTokens};
4-
use syn::{spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, Visibility};
4+
use syn::{
5+
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path,
6+
Visibility,
7+
};
58

69
use stellar_xdr::curr as stellar_xdr;
710
use stellar_xdr::{
@@ -48,7 +51,7 @@ pub fn derive_type_enum(
4851
// TODO: Choose discriminant type based on repr type of enum.
4952
// TODO: Use attributes tagged on variant to control whether field is included.
5053
let case_ident = &variant.ident;
51-
let case_name = &case_ident.to_string();
54+
let case_name = &case_ident.unraw().to_string();
5255
let case_name_str_lit = Literal::string(case_name);
5356
let case_num_lit = Literal::usize_unsuffixed(case_num);
5457
if case_name.len() > SCSYMBOL_LIMIT as usize {
@@ -151,7 +154,7 @@ pub fn derive_type_enum(
151154
let spec_entry = ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 {
152155
doc: docs_from_attrs(attrs),
153156
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
154-
name: enum_ident.to_string().try_into().unwrap(),
157+
name: enum_ident.unraw().to_string().try_into().unwrap(),
155158
cases: spec_cases.try_into().unwrap(),
156159
});
157160
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
@@ -163,7 +166,10 @@ pub fn derive_type_enum(
163166
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
164167
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
165168
let spec_xdr_len = spec_xdr.len();
166-
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
169+
let spec_ident = format_ident!(
170+
"__SPEC_XDR_TYPE_{}",
171+
enum_ident.unraw().to_string().to_uppercase()
172+
);
167173
Some(quote! {
168174
#[cfg_attr(target_family = "wasm", link_section = "contractspecv0")]
169175
pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr();

soroban-sdk-macros/src/derive_enum_int.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use proc_macro2::TokenStream as TokenStream2;
33
use quote::{format_ident, quote};
44
use stellar_xdr::curr as stellar_xdr;
55
use stellar_xdr::{ScSpecUdtEnumV0, StringM};
6-
use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, Visibility};
6+
use syn::{
7+
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path,
8+
Visibility,
9+
};
710

811
use stellar_xdr::{ScSpecEntry, ScSpecUdtEnumCaseV0, WriteXdr};
912

@@ -28,7 +31,7 @@ pub fn derive_type_enum_int(
2831
.iter()
2932
.map(|v| {
3033
let ident = &v.ident;
31-
let name = &ident.to_string();
34+
let name = &ident.unraw().to_string();
3235
let discriminant: u32 = if let syn::Expr::Lit(ExprLit {
3336
lit: Lit::Int(ref lit_int),
3437
..
@@ -70,7 +73,7 @@ pub fn derive_type_enum_int(
7073
let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 {
7174
doc: docs_from_attrs(attrs),
7275
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
73-
name: enum_ident.to_string().try_into().unwrap(),
76+
name: enum_ident.unraw().to_string().try_into().unwrap(),
7477
cases: spec_cases.try_into().unwrap(),
7578
});
7679
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
@@ -82,7 +85,10 @@ pub fn derive_type_enum_int(
8285
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
8386
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
8487
let spec_xdr_len = spec_xdr.len();
85-
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
88+
let spec_ident = format_ident!(
89+
"__SPEC_XDR_TYPE_{}",
90+
enum_ident.unraw().to_string().to_uppercase()
91+
);
8692
Some(quote! {
8793
#[cfg_attr(target_family = "wasm", link_section = "contractspecv0")]
8894
pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr();

soroban-sdk-macros/src/derive_error_enum_int.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use proc_macro2::TokenStream as TokenStream2;
33
use quote::{format_ident, quote};
44
use stellar_xdr::curr as stellar_xdr;
55
use stellar_xdr::{ScSpecEntry, ScSpecUdtErrorEnumCaseV0, ScSpecUdtErrorEnumV0, StringM, WriteXdr};
6-
use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path};
6+
use syn::{
7+
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path,
8+
};
79

810
use crate::{doc::docs_from_attrs, shaking, spec_shaking_v2_enabled, DEFAULT_XDR_RW_LIMITS};
911

@@ -23,7 +25,7 @@ pub fn derive_type_error_enum_int(
2325
.iter()
2426
.map(|v| {
2527
let ident = &v.ident;
26-
let name = &ident.to_string();
28+
let name = &ident.unraw().to_string();
2729
let discriminant: u32 = if let syn::Expr::Lit(ExprLit {
2830
lit: Lit::Int(ref lit_int),
2931
..
@@ -68,7 +70,7 @@ pub fn derive_type_error_enum_int(
6870
let spec_entry = ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 {
6971
doc: docs_from_attrs(attrs),
7072
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
71-
name: enum_ident.to_string().try_into().unwrap(),
73+
name: enum_ident.unraw().to_string().try_into().unwrap(),
7274
cases: spec_cases.try_into().unwrap(),
7375
});
7476
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
@@ -80,7 +82,10 @@ pub fn derive_type_error_enum_int(
8082
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
8183
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
8284
let spec_xdr_len = spec_xdr.len();
83-
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
85+
let spec_ident = format_ident!(
86+
"__SPEC_XDR_TYPE_{}",
87+
enum_ident.unraw().to_string().to_uppercase()
88+
);
8489
Some(quote! {
8590
#[cfg_attr(target_family = "wasm", link_section = "contractspecv0")]
8691
pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr();

soroban-sdk-macros/src/derive_event.rs

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ use crate::{
44
};
55
use darling::{ast::NestedMeta, Error, FromMeta};
66
use heck::ToSnakeCase;
7-
use itertools::Itertools as _;
87
use proc_macro2::Span;
98
use proc_macro2::TokenStream as TokenStream2;
109
use quote::{format_ident, quote};
1110
use stellar_xdr::curr::{
1211
ScSpecEntry, ScSpecEventDataFormat, ScSpecEventParamLocationV0, ScSpecEventParamV0,
1312
ScSpecEventV0, ScSymbol, StringM, WriteXdr,
1413
};
15-
use syn::{parse2, spanned::Spanned, Data, DeriveInput, Fields, LitStr, Path};
14+
use syn::{ext::IdentExt as _, parse2, spanned::Spanned, Data, DeriveInput, Fields, LitStr, Path};
1615

1716
#[derive(Debug, FromMeta)]
1817
struct ContractEventArgs {
@@ -88,7 +87,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
8887

8988
// Check event name length
9089
const EVENT_NAME_LENGTH: u32 = 32;
91-
let event_name = input.ident.to_string();
90+
let event_name = input.ident.unraw().to_string();
9291
let event_name_len = event_name.len();
9392
let event_name: StringM<EVENT_NAME_LENGTH> = errors
9493
.handle(event_name.try_into().map_err(|_| {
@@ -102,7 +101,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
102101
let prefix_topics = if let Some(prefix_topics) = &args.topics {
103102
prefix_topics.iter().map(|t| t.value()).collect()
104103
} else {
105-
vec![input.ident.to_string().to_snake_case()]
104+
vec![input.ident.unraw().to_string().to_snake_case()]
106105
};
107106

108107
let fields =
@@ -127,8 +126,11 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
127126
// Collect field types for SpecShakingMarker
128127
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
129128

130-
// Map each field of the struct to a spec for a param.
131-
let params = fields
129+
// Map each field of the struct to a spec for a param, keeping the original Ident
130+
// alongside so it can still be used for `self.#ident` field access in the generated
131+
// code (raw identifiers like `r#type` need to stay raw for Rust, while the spec name
132+
// is the unraw form).
133+
let params_with_idents = fields
132134
.iter()
133135
.map(|field| {
134136
let ident = field.ident.as_ref().unwrap();
@@ -140,7 +142,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
140142
};
141143
let doc = docs_from_attrs(&field.attrs);
142144
const NAME_LENGTH: u32 = 30;
143-
let name = ident.to_string();
145+
let name = ident.unraw().to_string();
144146
let name_len = name.len();
145147
let name: StringM<NAME_LENGTH> = errors
146148
.handle(name.try_into().map_err(|_| {
@@ -153,12 +155,15 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
153155
let type_ = errors
154156
.handle_in(|| Ok(map_type(&field.ty, true, false)?))
155157
.unwrap_or_default();
156-
ScSpecEventParamV0 {
157-
location,
158-
doc,
159-
name,
160-
type_,
161-
}
158+
(
159+
ident.clone(),
160+
ScSpecEventParamV0 {
161+
location,
162+
doc,
163+
name,
164+
type_,
165+
},
166+
)
162167
})
163168
.collect::<Vec<_>>();
164169

@@ -183,9 +188,9 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
183188
.collect::<Vec<_>>()
184189
.try_into()
185190
.unwrap(),
186-
params: params
191+
params: params_with_idents
187192
.iter()
188-
.map(|p| p.clone())
193+
.map(|(_, p)| p.clone())
189194
.collect::<Vec<_>>()
190195
.try_into()
191196
.unwrap(),
@@ -195,7 +200,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
195200
let spec_xdr_len = spec_xdr.len();
196201
let spec_ident = format_ident!(
197202
"__SPEC_XDR_EVENT_{}",
198-
input.ident.to_string().to_uppercase()
203+
input.ident.unraw().to_string().to_uppercase()
199204
);
200205
let spec_shaking_call = if export && spec_shaking_v2_enabled() {
201206
Some(quote! { <Self as #path::SpecShakingMarker>::spec_shaking_marker(); })
@@ -239,10 +244,10 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
239244
&LitStr::new(&t, Span::call_site()),
240245
)
241246
});
242-
let topic_idents = params
247+
let topic_idents = params_with_idents
243248
.iter()
244-
.filter(|p| p.location == ScSpecEventParamLocationV0::TopicList)
245-
.map(|p| format_ident!("{}", p.name.to_string()))
249+
.filter(|(_, p)| p.location == ScSpecEventParamLocationV0::TopicList)
250+
.map(|(ident, _)| ident.clone())
246251
.collect::<Vec<_>>();
247252
let topics_to_vec_val = quote! {
248253
use #path::IntoVal;
@@ -253,14 +258,14 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
253258
};
254259

255260
// Prepare Data Conversion to Val.
256-
let data_params = params
261+
let data_params = params_with_idents
257262
.iter()
258-
.filter(|p| p.location == ScSpecEventParamLocationV0::Data)
263+
.filter(|(_, p)| p.location == ScSpecEventParamLocationV0::Data)
259264
.collect::<Vec<_>>();
260265
let data_params_count = data_params.len();
261266
let data_idents = data_params
262267
.iter()
263-
.map(|p| format_ident!("{}", p.name.to_string()))
268+
.map(|(ident, _)| ident.clone())
264269
.collect::<Vec<_>>();
265270
let data_to_val = match args.data_format {
266271
DataFormat::SingleValue if data_params_count == 0 => quote! {
@@ -288,15 +293,18 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
288293
).into_val(env)
289294
},
290295
DataFormat::Map => {
291-
// Must be sorted for map_new_from_slices
292-
let data_idents_sorted = data_params
296+
// Must be sorted for map_new_from_slices. Sort by the spec name (the
297+
// Soroban-facing Symbol string), and carry the original Ident alongside so
298+
// that `self.#ident` still uses the raw form where needed.
299+
let mut data_params_sorted = data_params.clone();
300+
data_params_sorted.sort_by_key(|(_, p)| p.name.to_string());
301+
let data_idents_sorted = data_params_sorted
293302
.iter()
294-
.sorted_by_key(|p| p.name.to_string())
295-
.map(|p| format_ident!("{}", p.name.to_string()))
303+
.map(|(ident, _)| ident.clone())
296304
.collect::<Vec<_>>();
297-
let data_strs_sorted = data_idents_sorted
305+
let data_strs_sorted = data_params_sorted
298306
.iter()
299-
.map(|i| i.to_string())
307+
.map(|(_, p)| p.name.to_string())
300308
.collect::<Vec<_>>();
301309
quote! {
302310
use #path::{EnvBase,IntoVal,unwrap::UnwrapInfallible};

0 commit comments

Comments
 (0)