Normalize raw identifiers (r#) in contract macros#1837
Conversation
There was a problem hiding this comment.
Pull request overview
This PR normalizes Rust raw identifiers (r#...) in Soroban SDK macros so that all Soroban-facing names (spec XDR, exported WASM names, client symbols, generated internal idents, and sort keys) use the unraw form, preventing invalid identifiers and runtime failures when contracts use keyword-colliding names.
Changes:
- Introduces
IdentExt::soroban_name()insoroban-sdk-macrosand routes Soroban-facing naming through it. - Updates multiple derive paths (spec generation, export names, client filtering, internal generated identifiers, and sort keys) to use normalized names while preserving the original
Identwhere Rust token emission requires it. - Adds a new raw-identifier-focused test module and corresponding test snapshot.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| soroban-sdk-macros/src/syn_ext.rs | Adds IdentExt::soroban_name() helper for unraw identifier normalization. |
| soroban-sdk-macros/src/derive_struct.rs | Normalizes struct spec/type names and sort keys to use unraw names. |
| soroban-sdk-macros/src/derive_struct_tuple.rs | Normalizes tuple-struct spec/type names and generated spec ident names. |
| soroban-sdk-macros/src/derive_enum.rs | Normalizes enum spec/type names and case names for Soroban-facing output. |
| soroban-sdk-macros/src/derive_enum_int.rs | Normalizes int-enum spec/type names and case names. |
| soroban-sdk-macros/src/derive_error_enum_int.rs | Normalizes error-enum spec/type names and case names. |
| soroban-sdk-macros/src/derive_event.rs | Normalizes event names/params and ensures generated code uses original idents for field access. |
| soroban-sdk-macros/src/derive_spec_fn.rs | Normalizes function/arg names in spec, special-cases __check_auth via unraw comparison, and fixes generated spec identifiers. |
| soroban-sdk-macros/src/derive_fn.rs | Normalizes export names and generated internal identifiers for contract functions. |
| soroban-sdk-macros/src/derive_client.rs | Filters/reserves __* functions based on Soroban-facing name and normalizes try_* method generation. |
| soroban-sdk/src/tests.rs | Registers new raw-identifier test module. |
| soroban-sdk/src/tests/contract_udt_raw_identifier.rs | Adds functional and spec assertions covering raw identifiers across types, events, and functions. |
| soroban-sdk/test_snapshots/tests/contract_udt_raw_identifier/test_functional.1.json | Adds snapshot capturing observable behavior for the new test. |
There was a problem hiding this comment.
This change is focused on functions, and other soroban spec emtities, and introduces the bespoke 'soroban_name', but I think what it is addressing is the more general problem that whenever composing Ident's or getting their string value we should be .unraw(). And that applies to more than just soroban types but Ident usage in general.
So I think we should start calling .unraw() instead of .to_string() everywhere Ident's are used.
Thoughts?
Yeah, good call. Updated this to just be a blanket update to Ident usage to ensure we properly use |
There was a problem hiding this comment.
Pull request overview
Normalizes handling of Rust raw identifiers (r#...) across Soroban SDK macros so Soroban-facing names (spec entries, symbols, sort keys) consistently use the unraw’d form, fixing runtime mismatches/panics when contracts use raw idents.
Changes:
- Update multiple macro generators to compare/sort/export using
IdentExt::unraw()while preserving raw idents where needed for valid Rust field/method access. - Harden a couple of special-cases (
__check_auth, client generation skipping__*) against bypass via raw-identifier spellings. - Add a dedicated regression test contract + snapshot covering raw identifiers across contract, events, errors, structs, enums, and traits.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| soroban-sdk/test_snapshots/tests/contract_udt_raw_identifier/test_functional.1.json | Adds snapshot output for new raw-identifier regression test. |
| soroban-sdk/src/tests/contract_udt_raw_identifier.rs | New functional + spec-level tests exercising raw identifier normalization end-to-end. |
| soroban-sdk/src/tests.rs | Registers the new raw-identifier test module. |
| soroban-sdk-macros/src/syn_ext.rs | Normalizes trait names via unraw(); adds r# stripping for safe ident strings. |
| soroban-sdk-macros/src/map_type.rs | Avoids stringify+reparse for idents (fixes raw-keyword idents) and uses unraw() for name comparisons. |
| soroban-sdk-macros/src/lib.rs | Normalizes contract type string; adjusts client/args naming construction. |
| soroban-sdk-macros/src/derive_trait.rs | Updates default derived names for Spec/Args/Client in #[contracttrait]. |
| soroban-sdk-macros/src/derive_struct_tuple.rs | Ensures tuple-struct spec names / exported statics use unraw’d identifiers. |
| soroban-sdk-macros/src/derive_struct.rs | Sorts struct fields by unraw’d names and emits unraw’d field/spec names. |
| soroban-sdk-macros/src/derive_spec_fn.rs | Uses unraw’d function names for spec entries and special-case checks. |
| soroban-sdk-macros/src/derive_fn.rs | Uses unraw’d function names for exported invocation wrappers and special-case checks. |
| soroban-sdk-macros/src/derive_event.rs | Uses unraw’d event/field names in spec while preserving raw idents for generated Rust field access. |
| soroban-sdk-macros/src/derive_error_enum_int.rs | Emits error-enum case/spec names from unraw’d identifiers. |
| soroban-sdk-macros/src/derive_enum_int.rs | Emits enum case/spec names from unraw’d identifiers. |
| soroban-sdk-macros/src/derive_enum.rs | Emits union/enum case/spec names from unraw’d identifiers. |
| soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs | Uses unraw’d identifiers when generating trait-impl helper macro names/calls. |
| soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs | Compares overridden default fns using unraw’d names. |
| soroban-sdk-macros/src/derive_client.rs | Filters/generates client methods using unraw’d function names. |
| soroban-sdk-macros/src/attribute.rs | Matches attribute names using unraw’d identifiers. |
Comments suppressed due to low confidence (1)
soroban-sdk-macros/src/syn_ext.rs:162
HasFnsItem::name()now normalizes trait idents viaunraw(), but theImplarm still stringifiesself_tydirectly, which can preserver#spellings in the returned name. Sinceitem.name()feeds into generated Args/Client docs (and potentially other naming logic), consider stripping raw prefixes here as well to keep naming consistently normalized across Trait vs Impl inputs.
HasFnsItem::Trait(t) => t.ident.unraw().to_string(),
HasFnsItem::Impl(i) => {
let ty = &i.self_ty;
quote!(#ty).to_string()
}
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