Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ae44bad
feat: keep inner spec markers alive via reference
mootz12 Apr 15, 2026
5d76a59
test: add nested and recursive types to udt tests
mootz12 Apr 15, 2026
661b984
test: use spec_shaking_v2 for unit tests
mootz12 Apr 15, 2026
0a0bd62
chore: expand tests
mootz12 Apr 15, 2026
9bac2fc
feat: reduce keep_reachable to Vec, Map, and refs
mootz12 Apr 16, 2026
f889467
chore: test and expand-tests
mootz12 Apr 16, 2026
dbdffc1
Merge branch 'main' into spec-shaking-marker-recursive-guard
mootz12 Apr 16, 2026
79006e3
test: add testcase with nested vec elements
mootz12 Apr 21, 2026
ecd6838
Merge branch 'main' into spec-shaking-marker-recursive-guard
mootz12 Apr 22, 2026
f74af65
Merge branch 'main' into spec-shaking-marker-recursive-guard
mootz12 Apr 27, 2026
a6171fb
chore: remove unncessary test and make expand-tests
mootz12 Apr 27, 2026
36ee83b
feat: add custom wasm section for tracking full type graphs
mootz12 Apr 29, 2026
a378356
Merge remote-tracking branch 'origin/main' into spec-shaking-marker-f…
mootz12 Apr 29, 2026
fbece7a
chore: make expand-tests
mootz12 Apr 29, 2026
d5f53fe
Retain panic-only error enums in spec shaking v2 (#1852)
leighmcculloch Apr 28, 2026
21f7b6c
Add Claude auto-review workflow for pull requests (#1853)
leighmcculloch Apr 28, 2026
a616ecd
chore: update panic-only error enums to use spec graph
mootz12 Apr 30, 2026
43c02e6
fix: support error refs
mootz12 Apr 30, 2026
e0b160d
chore: doc improvements
mootz12 Apr 30, 2026
d0dd159
chore: align type_id_refs with map_type
mootz12 Apr 30, 2026
9d0c280
fix: validate spec shaking v2 meta before shaking
mootz12 Apr 30, 2026
be55ab5
chore: make shaking logic fallible and error if graph is malformed
mootz12 May 1, 2026
2955d9e
fix: remove used to keep graph markers out of wasm data
mootz12 May 1, 2026
65e90ed
chore: make expand-tests
mootz12 May 1, 2026
160081b
chore: refactor marker definitions into shared crate
mootz12 May 1, 2026
7caf8e7
chore: reduce duplicated spec shaking generation code
mootz12 May 1, 2026
b094fdf
chore: make expand-tests
mootz12 May 1, 2026
af64973
chore: doc and test updates
mootz12 May 1, 2026
9b5d006
chore: relax spec graph checks
mootz12 May 1, 2026
5a9c631
chore: improve spec shaking error logs
mootz12 May 4, 2026
3302b21
fix: treat sdk defined contracttypes as udts
mootz12 May 4, 2026
c667d7d
chore: make test and make expand-tests
mootz12 May 4, 2026
3c07959
fix: include duplicate name udts references
mootz12 May 4, 2026
a88ddcb
chore: agent review feedback
mootz12 May 4, 2026
8587000
Merge remote-tracking branch 'origin/main' into spec-shaking-marker-f…
mootz12 May 4, 2026
88b77ba
chore: fix missed merge conflict
mootz12 May 4, 2026
c3d3396
chore: claude feedback
mootz12 May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"soroban-sdk-macros",
"soroban-meta",
"soroban-spec",
"soroban-spec-markers",
"soroban-spec-rust",
"soroban-ledger-snapshot",
"soroban-token-sdk",
Expand All @@ -23,6 +24,7 @@ soroban-sdk = { version = "26.0.0-rc.1", path = "soroban-sdk" }
soroban-sdk-macros = { version = "26.0.0-rc.1", path = "soroban-sdk-macros" }
soroban-meta = { version = "26.0.0-rc.1", path = "soroban-meta" }
soroban-spec = { version = "26.0.0-rc.1", path = "soroban-spec" }
soroban-spec-markers = { version = "26.0.0-rc.1", path = "soroban-spec-markers", default-features = false }
soroban-spec-rust = { version = "26.0.0-rc.1", path = "soroban-spec-rust" }
soroban-ledger-snapshot = { version = "26.0.0-rc.1", path = "soroban-ledger-snapshot" }
soroban-token-sdk = { version = "26.0.0-rc.1", path = "soroban-token-sdk" }
Expand Down
11 changes: 6 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ test: fmt build-test-wasms test-only
# hazmat granular features are excluded because all hazmat features are tested
# together with the umbrella hazmat feature.
test-only:
cargo hack --feature-powerset --ignore-unknown-features --features testutils \
--exclude-features docs \
--exclude-features hazmat-crypto \
--exclude-features hazmat-address \
test
SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 \
cargo hack --feature-powerset --ignore-unknown-features --features testutils \
--exclude-features docs \
--exclude-features hazmat-crypto \
--exclude-features hazmat-address \
test

build: build-libs build-test-wasms

Expand Down
1 change: 1 addition & 0 deletions soroban-sdk-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ doctest = false

[dependencies]
soroban-spec = { workspace = true }
soroban-spec-markers = { workspace = true, features = ["alloc", "hash"] }
soroban-spec-rust = { workspace = true }
soroban-env-common = { workspace = true }
stellar-xdr = { workspace = true, features = ["curr", "std"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ fn derive(args: &Args) -> Result<TokenStream2, Error> {
Some(trait_ident),
&args.client_name,
));
output.extend(derive_fns_spec(&args.spec_name, &fns, spec_export));
output.extend(derive_fns_spec(
&args.crate_path,
&args.spec_name,
&fns,
spec_export,
));
output.extend(derive_client_impl(
&args.crate_path,
&args.client_name,
Expand Down
85 changes: 40 additions & 45 deletions soroban-sdk-macros/src/derive_enum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use itertools::MultiUnzip;
use proc_macro2::{Literal, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens};
use quote::{format_ident, quote};
use syn::{
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path,
Visibility,
Expand Down Expand Up @@ -36,12 +36,12 @@ pub fn derive_type_enum(
let (
spec_cases,
case_name_str_lits,
variant_field_types,
try_froms,
try_intos,
try_from_xdrs,
into_xdrs,
): (Vec<_>, Vec<_>, Vec<Vec<_>>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = variants
case_type_id_refs,
): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = variants
.iter()
.enumerate()
.map(|(case_num, variant)| {
Expand Down Expand Up @@ -80,9 +80,6 @@ pub fn derive_type_enum(
_ => {}
}

// Collect field types for SpecShakingMarker
let field_types: Vec<_> = variant.fields.iter().map(|f| &f.ty).collect();

let is_unit_variant = variant.fields == Fields::Unit;
if !is_unit_variant {
let VariantTokens {
Expand All @@ -91,6 +88,7 @@ pub fn derive_type_enum(
try_into,
try_from_xdr,
into_xdr,
type_id_refs,
} = map_tuple_variant(
path,
enum_ident,
Expand All @@ -105,11 +103,11 @@ pub fn derive_type_enum(
(
spec_case,
case_name_str_lit,
field_types,
try_from,
try_into,
try_from_xdr,
into_xdr,
type_id_refs,
)
} else {
let VariantTokens {
Expand All @@ -118,6 +116,7 @@ pub fn derive_type_enum(
try_into,
try_from_xdr,
into_xdr,
type_id_refs,
} = map_empty_variant(
path,
enum_ident,
Expand All @@ -130,11 +129,11 @@ pub fn derive_type_enum(
(
spec_case,
case_name_str_lit,
field_types,
try_from,
try_into,
try_from_xdr,
into_xdr,
type_id_refs,
)
}
})
Expand All @@ -146,21 +145,31 @@ pub fn derive_type_enum(
return quote! { #(#compile_errors)* };
}

// Compute spec XDR once if spec is enabled.
let spec_xdr = if spec {
let spec_entry = ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.unraw().to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
// Compute spec XDR once. Spec shaking v2 emits SpecTypeId even for
// `export = false` types so graph records can be built without exporting
// those types to `contractspecv0`.
let spec_entry = ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.unraw().to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
let spec_shaking_gen = if cfg!(feature = "experimental_spec_shaking_v2") {
shaking::generate_udt_shaking(
path,
enum_ident,
&spec_xdr,
case_type_id_refs.into_iter().flatten().collect(),
spec,
false,
)
} else {
None
};

// Generated code spec.
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
let spec_gen = if spec {
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
let spec_xdr_len = spec_xdr.len();
let spec_ident = format_ident!(
Expand All @@ -176,41 +185,17 @@ pub fn derive_type_enum(
*#spec_xdr_lit
}
}
})
} else {
None
};

// SpecShakingMarker impl - only generated when spec is true and the
// experimental_spec_shaking_v2 feature is enabled.
let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") {
spec_xdr.as_ref().map(|spec_xdr| {
// Flatten all variant field types for shaking calls, deduplicating
// to avoid redundant calls for types that appear in multiple variants.
let all_field_types =
itertools::Itertools::unique_by(variant_field_types.iter().flatten(), |t| {
t.to_token_stream().to_string()
});
shaking::generate_marker_impl(
path,
quote!(#enum_ident),
spec_xdr,
all_field_types.cloned(),
None,
None,
None,
)
#spec_shaking_gen
})
} else {
None
spec_shaking_gen
};

// Output.
let mut output = quote! {
#spec_gen

#spec_shaking_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
type Error = #path::ConversionError;
#[inline(always)]
Expand Down Expand Up @@ -329,6 +314,7 @@ struct VariantTokens {
try_into: TokenStream2,
try_from_xdr: TokenStream2,
into_xdr: TokenStream2,
type_id_refs: Vec<TokenStream2>,
}

fn map_empty_variant(
Expand Down Expand Up @@ -380,6 +366,7 @@ fn map_empty_variant(
try_into,
try_from_xdr,
into_xdr,
type_id_refs: Vec::new(),
}
}

Expand Down Expand Up @@ -433,7 +420,14 @@ fn map_tuple_variant(
type_: field_types,
})
};

let type_id_refs = if cfg!(feature = "experimental_spec_shaking_v2") {
fields
.iter()
.flat_map(|field| shaking::type_id_refs(path, &field.ty))
.collect::<Vec<_>>()
} else {
Vec::new()
};
let num_fields = fields.iter().len();
let try_from = {
let field_convs = fields
Expand Down Expand Up @@ -519,5 +513,6 @@ fn map_tuple_variant(
try_into,
try_from_xdr,
into_xdr,
type_id_refs,
}
}
45 changes: 15 additions & 30 deletions soroban-sdk-macros/src/derive_enum_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,24 @@ pub fn derive_type_enum_int(
return quote! { #(#compile_errors)* };
}

// Compute spec XDR once if spec is enabled.
let spec_xdr = if spec {
let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.unraw().to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
// Compute spec XDR once. Spec shaking v2 emits SpecTypeId even for
// `export = false` types so graph records can be built without exporting
// those types to `contractspecv0`.
let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.unraw().to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
let spec_shaking_gen = if cfg!(feature = "experimental_spec_shaking_v2") {
shaking::generate_udt_shaking(path, enum_ident, &spec_xdr, Vec::new(), spec, false)
} else {
None
};

// Generated code spec.
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
let spec_gen = if spec {
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
let spec_xdr_len = spec_xdr.len();
let spec_ident = format_ident!(
Expand All @@ -98,35 +101,17 @@ pub fn derive_type_enum_int(
*#spec_xdr_lit
}
}
})
} else {
None
};

// SpecShakingMarker impl - only generated when spec is true and the
// experimental_spec_shaking_v2 feature is enabled.
let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") {
spec_xdr.as_ref().map(|spec_xdr| {
shaking::generate_marker_impl(
path,
quote!(#enum_ident),
spec_xdr,
std::iter::empty(),
None,
None,
None,
)
#spec_shaking_gen
})
} else {
None
spec_shaking_gen
};

// Output.
let mut output = quote! {
#spec_gen

#spec_shaking_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
type Error = #path::ConversionError;
#[inline(always)]
Expand Down
Loading
Loading