Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ default-features = false
# Pending https://github.com/stellar/rs-stellar-xdr/pull/562, which adds the
# borrowing `*Ref` types and their const XDR serializers that the contract spec
# generation in soroban-sdk-macros builds on.
stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "0c7bcaa9145d40de52d6592a1df785c2a4d0f1dc" }
stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "71021aea8210d75eab7290cc176f27df8e0a432f" }
#soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" }
#soroban-env-guest = { path = "../rs-soroban-env/soroban-env-guest" }
#soroban-env-host = { path = "../rs-soroban-env/soroban-env-host" }
Expand Down
151 changes: 86 additions & 65 deletions soroban-sdk-macros/src/derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use itertools::MultiUnzip;
use proc_macro2::{Literal, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens};
use syn::{
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path,
ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, Type,
Visibility,
};

Expand All @@ -13,7 +13,10 @@ use stellar_xdr::{

use crate::{
doc::docs_from_attrs,
map_type::{const_ref_string, const_ref_type_def, map_type},
map_type::{
const_ref_string, const_ref_type_def, const_ref_type_def_canonical, map_type,
spec_type_id_gen,
},
shaking, DEFAULT_XDR_RW_LIMITS,
};

Expand Down Expand Up @@ -149,53 +152,75 @@ pub fn derive_type_enum(
return quote! { #(#compile_errors)* };
}

// Build the spec entry once if spec is enabled.
let spec_entry = if spec {
Some(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(),
})
} else {
None
// Build the spec entry. Built even when spec is not enabled, because the
// type's identity is derived from it and other types' references to this
// type need that identity regardless.
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 ScSpecEntry::UdtUnionV0(spec_union) = &spec_entry else {
unreachable!()
};

// Generated code spec. The spec entry is rendered as the equivalent const
// ScSpecEntryRef, which the contract crate encodes to XDR at compile time.
let spec_gen = spec_entry.as_ref().map(|spec_entry| {
let doc = const_ref_string(path, &spec_entry.doc);
let lib = const_ref_string(path, &spec_entry.lib);
let name = const_ref_string(path, &spec_entry.name);
let cases = spec_entry.cases.iter().map(|c| match c {
ScSpecUdtUnionCaseV0::VoidV0(c) => {
let doc = const_ref_string(path, &c.doc);
let name = const_ref_string(path, &c.name);
quote!(#path::xdr::ScSpecUdtUnionCaseV0Ref::VoidV0(
#path::xdr::ScSpecUdtUnionCaseVoidV0Ref { doc: #doc, name: #name }
))
}
ScSpecUdtUnionCaseV0::TupleV0(c) => {
let doc = const_ref_string(path, &c.doc);
let name = const_ref_string(path, &c.name);
let type_ = c.type_.iter().map(|t| const_ref_type_def(path, t));
quote!(#path::xdr::ScSpecUdtUnionCaseV0Ref::TupleV0(
#path::xdr::ScSpecUdtUnionCaseTupleV0Ref {
doc: #doc,
name: #name,
type_: #path::xdr::VecMRef::new(&[#(#type_),*]),
// The spec entry rendered as the equivalent const ScSpecEntryRef, which the
// contract crate encodes to XDR at compile time. `case_type` renders each
// tuple case's types, and is all that differs between the exported form and
// the canonical form the type's identity is computed over.
let entry_ref = |case_type: &dyn Fn(&ScSpecTypeDef, &Type) -> TokenStream2| {
let doc = const_ref_string(path, &spec_union.doc);
let lib = const_ref_string(path, &spec_union.lib);
let name = const_ref_string(path, &spec_union.name);
// Each case's Rust field types, so a reference to a user-defined type in
// a case resolves to that type's id.
let cases =
spec_union
.cases
.iter()
.zip(&variant_field_types)
.map(|(c, field_types)| match c {
ScSpecUdtUnionCaseV0::VoidV0(c) => {
let doc = const_ref_string(path, &c.doc);
let name = const_ref_string(path, &c.name);
quote!(#path::xdr::ScSpecUdtUnionCaseV0Ref::VoidV0(
#path::xdr::ScSpecUdtUnionCaseVoidV0Ref { doc: #doc, name: #name }
))
}
))
}
});
let spec_ref = quote! {
ScSpecUdtUnionCaseV0::TupleV0(c) => {
let doc = const_ref_string(path, &c.doc);
let name = const_ref_string(path, &c.name);
let type_ = c
.type_
.iter()
.zip(field_types.iter().copied())
.map(|(t, rust)| case_type(t, rust));
quote!(#path::xdr::ScSpecUdtUnionCaseV0Ref::TupleV0(
#path::xdr::ScSpecUdtUnionCaseTupleV0Ref {
doc: #doc,
name: #name,
type_: #path::xdr::VecMRef::new(&[#(#type_),*]),
}
))
}
});
quote! {
#path::xdr::ScSpecEntryRef::UdtUnionV0(#path::xdr::ScSpecUdtUnionV0Ref {
doc: #doc,
lib: #lib,
name: #name,
cases: #path::xdr::VecMRef::new(&[#(#cases),*]),
})
};
}
};
let spec_id_gen = spec_type_id_gen(
path,
enum_ident,
&entry_ref(&|t, _| const_ref_type_def_canonical(path, t)),
);

let spec_gen = spec.then(|| {
let spec_ref = entry_ref(&|t, rust| const_ref_type_def(path, t, Some(rust)));
let spec_ident = format_ident!(
"__SPEC_XDR_TYPE_{}",
enum_ident.unraw().to_string().to_uppercase()
Expand All @@ -216,35 +241,31 @@ pub fn derive_type_enum(

// 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_entry.as_ref().map(|spec_entry| {
let spec_xdr = ScSpecEntry::UdtUnionV0(spec_entry.clone())
.to_xdr(DEFAULT_XDR_RW_LIMITS)
.unwrap();
// 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,
)
})
} else {
None
};
let spec_shaking_impl = (spec && cfg!(feature = "experimental_spec_shaking_v2")).then(|| {
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
// 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,
)
});

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

#spec_id_gen

#spec_shaking_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
Expand Down
85 changes: 46 additions & 39 deletions soroban-sdk-macros/src/derive_enum_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ use syn::{

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

use crate::{doc::docs_from_attrs, map_type::const_ref_string, shaking, DEFAULT_XDR_RW_LIMITS};
use crate::{
doc::docs_from_attrs,
map_type::{const_ref_string, spec_type_id_gen},
shaking, DEFAULT_XDR_RW_LIMITS,
};

// TODO: Add conversions to/from ScVal types.

Expand Down Expand Up @@ -67,38 +71,45 @@ pub fn derive_type_enum_int(
return quote! { #(#compile_errors)* };
}

// Build the spec entry once if spec is enabled.
let spec_entry = if spec {
Some(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(),
})
} else {
None
// Build the spec entry. Built even when spec is not enabled, because the
// type's identity is derived from it and other types' references to this
// type need that identity regardless.
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 ScSpecEntry::UdtEnumV0(spec_enum) = &spec_entry else {
unreachable!()
};

// Generated code spec. The spec entry is rendered as the equivalent const
// ScSpecEntryRef, which the contract crate encodes to XDR at compile time.
let spec_gen = spec_entry.as_ref().map(|spec_entry| {
let doc = const_ref_string(path, &spec_entry.doc);
let lib = const_ref_string(path, &spec_entry.lib);
let name = const_ref_string(path, &spec_entry.name);
let cases = spec_entry.cases.iter().map(|c| {
// The spec entry rendered as the equivalent const ScSpecEntryRef, which the
// contract crate encodes to XDR at compile time. An enum holds no types, so
// it contains no reference to carry an id and this is already the canonical
// form the type's identity is computed over.
let entry_ref = || {
let doc = const_ref_string(path, &spec_enum.doc);
let lib = const_ref_string(path, &spec_enum.lib);
let name = const_ref_string(path, &spec_enum.name);
let cases = spec_enum.cases.iter().map(|c| {
let doc = const_ref_string(path, &c.doc);
let name = const_ref_string(path, &c.name);
let value = c.value;
quote!(#path::xdr::ScSpecUdtEnumCaseV0Ref { doc: #doc, name: #name, value: #value })
});
let spec_ref = quote! {
quote! {
#path::xdr::ScSpecEntryRef::UdtEnumV0(#path::xdr::ScSpecUdtEnumV0Ref {
doc: #doc,
lib: #lib,
name: #name,
cases: #path::xdr::VecMRef::new(&[#(#cases),*]),
})
};
}
};
let spec_id_gen = spec_type_id_gen(path, enum_ident, &entry_ref());

let spec_gen = spec.then(|| {
let spec_ref = entry_ref();
let spec_ident = format_ident!(
"__SPEC_XDR_TYPE_{}",
enum_ident.unraw().to_string().to_uppercase()
Expand All @@ -119,29 +130,25 @@ pub fn derive_type_enum_int(

// 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_entry.as_ref().map(|spec_entry| {
let spec_xdr = ScSpecEntry::UdtEnumV0(spec_entry.clone())
.to_xdr(DEFAULT_XDR_RW_LIMITS)
.unwrap();
shaking::generate_marker_impl(
path,
quote!(#enum_ident),
&spec_xdr,
std::iter::empty(),
None,
None,
None,
)
})
} else {
None
};
let spec_shaking_impl = (spec && cfg!(feature = "experimental_spec_shaking_v2")).then(|| {
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
shaking::generate_marker_impl(
path,
quote!(#enum_ident),
&spec_xdr,
std::iter::empty(),
None,
None,
None,
)
});

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

#spec_id_gen

#spec_shaking_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
Expand Down
Loading
Loading