Skip to content

Qualify user-defined type names - #1970

Draft
leighmcculloch wants to merge 11 commits into
handcode-spec-refs-in-derivesfrom
fully-qualified-udt-names
Draft

Qualify user-defined type names#1970
leighmcculloch wants to merge 11 commits into
handcode-spec-refs-in-derivesfrom
fully-qualified-udt-names

Conversation

@leighmcculloch

@leighmcculloch leighmcculloch commented Jul 30, 2026

Copy link
Copy Markdown
Member

Note

Part of a stack of PRs that must merge in this order.

A first group of PRs deliver const-encoded contract specs, so that contract specs are produced at compile time instead of at proc-macro execution time. This provides the foundation for the capability to construct the specs from information that is not known at proc-macro execution and only known at compile time, like the fully qualified name of a type:

  1. Add borrowed Ref variants of generated types rs-stellar-xdr#560
  2. Add const XDR serialization on View types rs-stellar-xdr#562
  3. Encode contract spec XDR at const evaluation time #1965

A second group of PRs deliver fully qualified type names in contract specs. Instead of a type having the name Context it will have the name soroban_sdk::auth::Context. Qualified type names make it possible to uniquely identify types in the spec, even when they have the same name. This resolves several problems with contract specs the type identify problem (#1570), type aliases limitations (#1857 #1063), and optimise spec shaking data section size (#1978):

  1. Widen user-defined type name limit stellar-xdr#312
  2. Regenerate with widened UDT name limit rs-stellar-xdr#566
  3. Qualify user-defined type names #1970 ← this PR
  4. Update stellar-xdr to 28.0.0 rs-stellar-rpc-client#108
  5. Patch xdr and spec crates to support long type names stellar-cli#2674

What

Name a user-defined type in the contract spec by its Rust path rather than by its bare name. Each type gains a spec_type_name const fn returning module_path!() then its own name, and both the type's own definition entry and every reference to it take the name from that fn. Client generation derives an identifier from the last segment of a qualified name.

Examples

Generated Type Qualified Names and Spec XDR

Screenshot 2026-08-07 at 11 31 46 pm

Why

A bare name is not enough to tell two user-defined types apart: two crates, or two modules of one crate, can each define a Flag, and a spec that names both simply Flag cannot say which is which. This was once rare, but as contracts on Soroban are getting larger, and as libraries are showing up in the ecosystem, this is becoming much more common. The module path is the part that distinguishes them, and only the compiler knows it, because a proc macro sees the tokens of the type it expands and not the module those tokens sit in. Emitting module_path!() for expansion where the type is defined is what makes the path reachable at all, and it is why the name is assembled at const evaluation rather than in the macro. That is also why this depends on #1974: once the name is only resolved at const evaluation, a spec shaking marker hashed in the macro no longer matches the entry the wasm actually carries, and every user-defined entry gets shaken out.

Close #1570
Close #1857
Close #1063
Close #1978

Known limitations

A spike in a client demonstrating how to handle the new fully qualified names is required.

@github-actions

Copy link
Copy Markdown
Contributor

semver:minor — the macros now generate two new public const fn items on every #[contracttype], which is an additive change to the public API.

  • spec_type_name() -> &'static str and spec_id() -> [u8; 8] are added to every struct/enum expanded by #[contracttype] (visible in tests-expanded/test_udt_tests.rs and all other expanded files). New inherent items → minor per the "adding inherent items" rule.
  • sha2-const = "0.1.3" is added as a dependency in soroban-sdk/Cargo.toml → minor (adding a Cargo dependency).
  • cargo semver-checks failed to build soroban-sdk and soroban-token-sdk due to the stellar-xdr git dependency requiring a const feature not on crates.io; those crates were classified from the diff alone. soroban-ledger-snapshot and soroban-spec both reported "no semver update required".

Worth reviewer attention: UDT spec names now include the module path (e.g. "test_udt::UdtEnum2" instead of "UdtEnum2"). This is a behavioral change to the contract spec XDR and could break tooling or downstream consumers that match on bare type names.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 32.7 AIC · ⌖ 4.11 AIC · ⊞ 8.1K ·

Comment on lines +111 to +117
type_: ScSpecTypeDef::Udt(ScSpecTypeUdt {
// See module doc comment: resolved through the type
// system to the referenced type's fully qualified
// definition path — the alias `Renamed` never appears.
name: "soroban_sdk::tests::contract_udt_struct_aliased_import::inner::Inner"
.try_into()
.unwrap(),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mootz12 Using type_name means that this PR also fixes the type aliasing issue demonstrated in the following PR that you pointed out:

@github-actions

Copy link
Copy Markdown
Contributor

Minor — the macros now emit new public inherent methods on every #[contracttype] type, which is an additive API change under the "adding inherent items" possibly-breaking rule.

  • spec_type_name() -> &'static str and spec_id() -> [u8; 8] are now generated on every #[contracttype] struct/enum (tests-expanded/test_udt_tests.rs), adding inherent items (Cargo SemVer: minor/possibly-breaking).
  • The contract spec UDT names change from bare ("UdtEnum2") to module-qualified ("test_udt::UdtEnum2"), affecting the emitted contract spec for any contract using #[contracttype]. This is a behavior change to the generated spec — existing consumers of the spec string will see different values.
  • A new dependency sha2-const = "0.1.3" is added to soroban-sdk/Cargo.toml (minor: adding a dependency).
  • cargo semver-checks could not run for soroban-sdk, soroban-token-sdk, and soroban-spec-rust due to build errors (stellar-xdr feature const unavailable on crates.io and SC_SPEC_TYPE_NAME_LIMIT missing in the baseline); classification is from the diff alone.
  • Unsure whether the UDT name change in the emitted spec (bare → module-qualified) is considered breaking for downstream consumers who rely on those spec strings. If those strings are part of the stable contract ABI, it may warrant major; authors should confirm.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 35.4 AIC · ⌖ 4.14 AIC · ⊞ 8.1K ·

@github-actions

Copy link
Copy Markdown
Contributor

semver:minor — the macros now generate new public inherent methods on every #[contracttype]-annotated type, which is an additive API change.

  • spec_type_name() -> &'static str and spec_id() -> [u8; 8] are emitted as pub const fn on every #[contracttype] struct/enum (tests-expanded/test_udt_tests.rs, and pervasively across all other tests-expanded/ files). Adding inherent items is listed as a minor (possibly-breaking) change under the Cargo SemVer rules.
  • New dependency sha2-const = "0.1.3" added to soroban-sdk/Cargo.toml. Adding a dependency is minor.
  • cargo semver-checks could not run for soroban-sdk, soroban-token-sdk, and soroban-spec-rust due to build errors (a stellar-xdr feature flag mismatch and a missing SC_SPEC_TYPE_NAME_LIMIT symbol). The tool's output is therefore incomplete; classification is from the diff alone for those crates.

No removals, renames, or signature changes to existing public items were found.

This classification is advisory; the author and reviewer decide the final release type.

Generated by Semver Impact for #1970 · sonnet46 · 29.4 AIC · ⌖ 6.06 AIC · ⊞ 8.1K ·

@leighmcculloch
leighmcculloch force-pushed the fully-qualified-udt-names branch from 89d152f to c2f3b14 Compare July 31, 2026 08:52
@leighmcculloch
leighmcculloch changed the base branch from handcode-spec-refs-in-derives to const-spec-id-marker July 31, 2026 08:54
@github-actions

Copy link
Copy Markdown
Contributor

semver:minor — the macros now emit two new public methods on every #[contracttype] type, which is an additive change to the public API.

  • spec_type_name() -> &'static str and spec_id() -> [u8; 8] are added as pub const fn methods on every generated type (tests-expanded/ shows them on UdtEnum, UdtEnum2, structs, etc.) — adding public inherent items is a possibly-breaking minor change per the semver rules.
  • soroban-sdk/Cargo.toml adds sha2-const = "0.1.3" — adding a Cargo dependency is minor.
  • cargo semver-checks could not run for soroban-sdk, soroban-token-sdk, or soroban-spec-rust due to a build error (stellar-xdr const feature not available on crates.io); classification is from the diff alone for those crates.

Unsure: whether renaming the UDT name source from a hard-coded b"UdtEnum" literal to UdtEnum::spec_type_name() changes observable behavior for existing contracts (it should be identical for non-aliased imports, but might differ for aliased ones — the new contract_udt_struct_aliased_import test covers this). Reviewers may want to confirm the emitted XDR is equivalent for the common case.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 30.1 AIC · ⌖ 6.9 AIC · ⊞ 8.1K ·

@leighmcculloch
leighmcculloch force-pushed the fully-qualified-udt-names branch from c2f3b14 to f527fc9 Compare July 31, 2026 09:26
@github-actions

Copy link
Copy Markdown
Contributor

semver:minor — the highest-impact single change is adding the public inherent method Bytes::to_string in soroban-sdk/src/bytes.rs, which is an additive API change (adding inherent items).

  • soroban-sdk/src/bytes.rs: new pub fn to_string(&self) -> String — adding an inherent item is minor per the possibly-breaking-changes rule.
  • soroban-sdk-macros/src/map_type.rs: new BN254 type name mappings (Bn254Fp, Bn254G1Affine, Bn254G2Affine, BnScalar) added to map_type — additive macro behavior (minor).
  • soroban-spec-rust/src/lib.rs: new apply_error_udt_override pass rewrites Error spec references to UDT on code generation — additive new behavior. cargo semver-checks could not analyze soroban-sdk, soroban-spec-rust, or soroban-token-sdk due to stellar-xdr const feature resolution failure; those crates were classified from the diff alone.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 69.8 AIC · ⌖ 4.1 AIC · ⊞ 8.1K ·

@leighmcculloch leighmcculloch mentioned this pull request Jul 31, 2026
5 tasks
@leighmcculloch leighmcculloch linked an issue Jul 31, 2026 that may be closed by this pull request
@github-actions

Copy link
Copy Markdown
Contributor

Minor — the macros now generate a new pub const fn spec_type_name() -> &'static str inherent method on every user-defined contract type annotated with #[contracttype], which is an additive change to the generated public API.

  • tests-expanded/test_udt_tests.rs and many others: every #[contracttype] type gains impl Foo { pub const fn spec_type_name() -> &'static str { ... } }. Adding inherent items is a [possibly-breaking minor change]((doc.rustlang.org/redacted) under the Cargo SemVer rules.
  • soroban-sdk-macros/src/map_type.rs (spec_type_name_gen): the new method is emitted by spec_type_name_gen, wired into every derive macro (derive_struct, derive_enum, etc.), confirming the generated API change is universal.
  • cargo semver-checks could not analyze soroban-sdk, soroban-token-sdk, or soroban-spec-rust due to a build error (new stellar-xdr git rev requires a const feature absent from crates.io); soroban-ledger-snapshot and soroban-spec reported "no semver update required".

soroban-spec-rust had a doc-build failure in semver-checks too; its source diff adds new helper functions that appear internal, but this couldn't be confirmed by the tool. Reviewers may want to verify no public items were added there.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 28.8 AIC · ⌖ 6.09 AIC · ⊞ 8.1K ·

@github-actions

Copy link
Copy Markdown
Contributor

Classification: minor — the PR adds new public items to soroban-sdk and changes macro-generated code, which is an additive change requiring a minor version bump.

  • soroban-sdk/src/bytes.rs: Adds BytesBuffer<B> struct and Bytes::to_buffer() method — new public items ([item-new]((doc.rustlang.org/redacted)
  • tests-expanded/: Widespread changes across nearly all expanded test files indicate the macro-generated client/spec code changed. The generated APIs are additive (new struct/impl items, not removals or signature changes), consistent with minor.
  • cargo semver-checks: Could not analyze soroban-sdk, soroban-token-sdk, or soroban-spec-rust due to build errors (missing stellar-xdr feature const and SC_SPEC_TYPE_NAME_LIMIT). soroban-ledger-snapshot and soroban-spec passed with "no semver update required".

Note: BytesBuffer is pub in bytes.rs but bytes is a private module — only to_buffer() (returning BytesBuffer) makes it reachable via soroban_sdk::bytes::BytesBuffer. If BytesBuffer is not reachable from the crate root, those items may be patch. The to_buffer method on the public Bytes type is definitely new public API (minor). I erred toward minor.

This classification is advisory; the author and reviewer decide the release type.

Generated by Semver Impact for #1970 · sonnet46 · 50 AIC · ⌖ 4.17 AIC · ⊞ 8.1K ·

@leighmcculloch
leighmcculloch force-pushed the fully-qualified-udt-names branch from c68e1af to bbf63e6 Compare August 8, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimise spec shaking v2 with the use of a graph

1 participant