diff --git a/Cargo.lock b/Cargo.lock index 1818670fd..2d5a397ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1816,6 +1816,7 @@ name = "test_contracttrait_impl_partial" version = "26.0.0-rc.1" dependencies = [ "soroban-sdk", + "stellar-xdr", "test_contracttrait_trait", ] diff --git a/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs b/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs index 1c6f316da..9af405fc3 100644 --- a/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs +++ b/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs @@ -1,4 +1,5 @@ use crate::{ + attribute::is_attr_doc, default_crate_path, derive_args::derive_args_impl, derive_client::derive_client_impl, @@ -9,7 +10,7 @@ use crate::{ use darling::{ast::NestedMeta, Error, FromMeta}; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use syn::{LitStr, Path, Type}; // See soroban-sdk/docs/contracttrait.md for documentation on how this works. @@ -20,8 +21,10 @@ struct Args { crate_path: Path, trait_ident: Path, trait_default_fns: Vec, + trait_all_fns: Vec, impl_ident: Ident, impl_fns: Vec, + impl_fns_with_docs: Vec, client_name: String, args_name: String, spec_name: Type, @@ -84,5 +87,37 @@ fn derive(args: &Args) -> Result { fns.iter().map(|f| &f.ident), )); + // Generate spec for overridden functions, inheriting trait docs for + // functions that lack their own doc comments. + let trait_all_fns = syn_ext::strs_to_fns(&args.trait_all_fns)?; + let trait_fn_docs: HashMap> = trait_all_fns + .into_iter() + .map(|f| (f.ident.to_string(), f.attrs)) + .collect(); + + let impl_fns_parsed = syn_ext::strs_to_fns(&args.impl_fns_with_docs)?; + let impl_fns_for_spec: Vec = impl_fns_parsed + .into_iter() + .map(|f| { + let has_docs = f.attrs.iter().any(|a| is_attr_doc(a)); + if !has_docs { + if let Some(trait_attrs) = trait_fn_docs.get(&f.ident.to_string()) { + return syn_ext::Fn { + ident: f.ident, + attrs: trait_attrs.clone(), + inputs: f.inputs, + output: f.output, + }; + } + } + f + }) + .collect(); + output.extend(derive_fns_spec( + &args.spec_name, + &impl_fns_for_spec, + spec_export, + )); + Ok(output) } diff --git a/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs b/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs index c81724a03..ca61fc4c2 100644 --- a/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs +++ b/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs @@ -56,6 +56,16 @@ fn derive(args: &Args, input: &ItemTrait) -> Result { _ => None, }); + // Serialize all trait functions' docs+sigs (including non-default ones) so + // that overridden functions can inherit trait docs when they lack their own. + let all_fns = input.items.iter().filter_map(|i| match i { + TraitItem::Fn(TraitItemFn { sig, attrs, .. }) => { + let doc_attrs: Vec<_> = attrs.iter().filter(|a| is_attr_doc(a)).collect(); + Some(quote!(#(#doc_attrs)* #sig).to_token_stream().to_string()) + } + _ => None, + }); + let macro_ident = macro_ident(&input.ident); let output = quote! { @@ -67,6 +77,7 @@ fn derive(args: &Args, input: &ItemTrait) -> Result { $trait_ident:path, $impl_ident:ty, $impl_fns:expr, + $impl_fns_with_docs:expr, $client_name:literal, $args_name:literal, $spec_name:literal $(,)? @@ -74,8 +85,10 @@ fn derive(args: &Args, input: &ItemTrait) -> Result { #path::contractimpl_trait_default_fns_not_overridden!( trait_ident = $trait_ident, trait_default_fns = [#(#fns),*], + trait_all_fns = [#(#all_fns),*], impl_ident = $impl_ident, impl_fns = $impl_fns, + impl_fns_with_docs = $impl_fns_with_docs, client_name = $client_name, args_name = $args_name, spec_name = $spec_name, @@ -99,11 +112,23 @@ pub fn generate_call_to_contractimpl_for_trait( spec_ident: &str, ) -> TokenStream2 { let impl_fn_idents = pub_methods.iter().map(|f| f.sig.ident.to_string()); + // Serialize impl functions with their doc attrs so the macro bridge can + // generate spec entries, falling back to trait docs when the impl function + // lacks its own. + let impl_fn_strs: Vec = pub_methods + .iter() + .map(|f| { + let doc_attrs: Vec<_> = f.attrs.iter().filter(|a| is_attr_doc(a)).collect(); + let sig = &f.sig; + quote!(#(#doc_attrs)* #sig).to_token_stream().to_string() + }) + .collect(); quote! { #trait_ident!( #trait_ident, #impl_ident, [#(#impl_fn_idents),*], + [#(#impl_fn_strs),*], #client_ident, #args_ident, #spec_ident, diff --git a/soroban-sdk-macros/src/lib.rs b/soroban-sdk-macros/src/lib.rs index fa5728b43..d69e3aec1 100644 --- a/soroban-sdk-macros/src/lib.rs +++ b/soroban-sdk-macros/src/lib.rs @@ -269,12 +269,24 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { match derived { Ok(derived_ok) => { - let mut output = quote! { - #[#crate_path::contractargs(name = #args_ident, impl_only = true)] - #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)] - #[#crate_path::contractspecfn(name = #ty_str)] - #imp - #derived_ok + // When contracttrait is true, spec generation for overridden + // functions is handled by the macro bridge (so that trait docs can + // be used as fallback), so contractspecfn is not applied here. + let mut output = if args.contracttrait { + quote! { + #[#crate_path::contractargs(name = #args_ident, impl_only = true)] + #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)] + #imp + #derived_ok + } + } else { + quote! { + #[#crate_path::contractargs(name = #args_ident, impl_only = true)] + #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)] + #[#crate_path::contractspecfn(name = #ty_str)] + #imp + #derived_ok + } }; // See soroban-sdk/docs/contracttrait.md for documentation on how diff --git a/tests-expanded/test_contracttrait_impl_partial_tests.rs b/tests-expanded/test_contracttrait_impl_partial_tests.rs index f0b8ff548..4ad86641d 100644 --- a/tests-expanded/test_contracttrait_impl_partial_tests.rs +++ b/tests-expanded/test_contracttrait_impl_partial_tests.rs @@ -147,6 +147,7 @@ impl AllTypes for Contract { fn test_string(v: String) -> String { v } + /// Custom env param docs. fn test_env_param(_env: &Env) -> u32 { 100 } @@ -157,63 +158,6 @@ impl AllTypes for Contract { } } } -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_u32__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - pub static __SPEC_XDR_FN_TEST_U32: [u8; 48usize] = super::Contract::spec_xdr_test_u32(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_u32() -> [u8; 48usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x08test_u32\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x04\0\0\0\x01\0\0\0\x04" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_string__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - pub static __SPEC_XDR_FN_TEST_STRING: [u8; 52usize] = super::Contract::spec_xdr_test_string(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_string() -> [u8; 52usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_string\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x10\0\0\0\x01\0\0\0\x10" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_env_param__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - pub static __SPEC_XDR_FN_TEST_ENV_PARAM: [u8; 40usize] = - super::Contract::spec_xdr_test_env_param(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_env_param() -> [u8; 40usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0etest_env_param\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_struct__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - pub static __SPEC_XDR_FN_TEST_STRUCT: [u8; 76usize] = super::Contract::spec_xdr_test_struct(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_struct() -> [u8; 76usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_struct\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\x07\xd0\0\0\0\x08MyStruct\0\0\0\x01\0\0\x07\xd0\0\0\0\x08MyStruct" - } -} impl<'a> ContractClient<'a> { pub fn test_u32(&self, v: &u32) -> u32 { use core::ops::Not; @@ -370,6 +314,7 @@ impl<'a> ContractClient<'a> { } res } + /// Custom env param docs. pub fn test_env_param(&self) -> u32 { use core::ops::Not; let old_auth_manager = self @@ -403,6 +348,7 @@ impl<'a> ContractClient<'a> { } res } + /// Custom env param docs. pub fn try_test_env_param( &self, ) -> Result< @@ -636,6 +582,7 @@ pub extern "C" fn __Contract__test_string__invoke_raw_extern( __Contract__test_string__invoke_raw(soroban_sdk::Env::default(), arg_0) } #[doc(hidden)] +/// Custom env param docs. #[allow(non_snake_case)] #[deprecated(note = "use `ContractClient::new(&env, &contract_id).test_env_param` instead")] #[allow(deprecated)] @@ -646,6 +593,7 @@ pub fn __Contract__test_env_param__invoke_raw(env: soroban_sdk::Env) -> soroban_ ) } #[doc(hidden)] +/// Custom env param docs. #[allow(non_snake_case)] #[deprecated(note = "use `ContractClient::new(&env, &contract_id).test_env_param` instead")] pub fn __Contract__test_env_param__invoke_raw_slice( @@ -665,6 +613,7 @@ pub fn __Contract__test_env_param__invoke_raw_slice( __Contract__test_env_param__invoke_raw(env) } #[doc(hidden)] +/// Custom env param docs. #[allow(non_snake_case)] #[deprecated(note = "use `ContractClient::new(&env, &contract_id).test_env_param` instead")] pub extern "C" fn __Contract__test_env_param__invoke_raw_extern() -> soroban_sdk::Val { @@ -3463,6 +3412,72 @@ fn __Contract__AllTypes__447a3d427d821f62365afd21ac9b6fa9597c9d71324b5cba7631f73 } } #[doc(hidden)] +/// Test u32 values. +/// Returns the input unchanged. +#[allow(non_snake_case)] +pub mod __Contract__test_u32__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + /// Test u32 values. + /// Returns the input unchanged. + pub static __SPEC_XDR_FN_TEST_U32: [u8; 96usize] = super::Contract::spec_xdr_test_u32(); +} +impl Contract { + #[allow(non_snake_case)] + /// Test u32 values. + /// Returns the input unchanged. + pub const fn spec_xdr_test_u32() -> [u8; 96usize] { + *b"\0\0\0\0\0\0\0-Test u32 values.\nReturns the input unchanged.\0\0\0\0\0\0\x08test_u32\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x04\0\0\0\x01\0\0\0\x04" + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__test_string__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + pub static __SPEC_XDR_FN_TEST_STRING: [u8; 52usize] = super::Contract::spec_xdr_test_string(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_test_string() -> [u8; 52usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_string\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x10\0\0\0\x01\0\0\0\x10" + } +} +#[doc(hidden)] +/// Custom env param docs. +#[allow(non_snake_case)] +pub mod __Contract__test_env_param__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + /// Custom env param docs. + pub static __SPEC_XDR_FN_TEST_ENV_PARAM: [u8; 64usize] = + super::Contract::spec_xdr_test_env_param(); +} +impl Contract { + #[allow(non_snake_case)] + /// Custom env param docs. + pub const fn spec_xdr_test_env_param() -> [u8; 64usize] { + *b"\0\0\0\0\0\0\0\x16Custom env param docs.\0\0\0\0\0\x0etest_env_param\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04" + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__test_struct__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + pub static __SPEC_XDR_FN_TEST_STRUCT: [u8; 76usize] = super::Contract::spec_xdr_test_struct(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_test_struct() -> [u8; 76usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_struct\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\x07\xd0\0\0\0\x08MyStruct\0\0\0\x01\0\0\x07\xd0\0\0\0\x08MyStruct" + } +} +#[doc(hidden)] #[allow(non_snake_case)] #[allow(unused)] fn __Contract__AllTypes__1eb9a6a69c5f732bd78e03e0fa5ea9d0a5c925757f7a5e53cd10ccd57b3e027d_ctor() { @@ -3511,6 +3526,117 @@ mod test { use super::*; use soroban_sdk::{map, symbol_short, testutils::Address as _, vec, Env}; extern crate test; + #[rustc_test_marker = "test::test_spec_docs_inherited_from_trait"] + #[doc(hidden)] + pub const test_spec_docs_inherited_from_trait: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("test::test_spec_docs_inherited_from_trait"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "tests/contracttrait_impl_partial/src/lib.rs", + start_line: 42usize, + start_col: 8usize, + end_line: 42usize, + end_col: 43usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::UnitTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(test_spec_docs_inherited_from_trait()), + ), + }; + fn test_spec_docs_inherited_from_trait() { + use stellar_xdr::curr as stellar_xdr; + use stellar_xdr::{Limits, ReadXdr, ScSpecEntry}; + let entry = ScSpecEntry::from_xdr(Contract::spec_xdr_test_u32(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + { + ::core::panicking::panic_fmt(format_args!("expected FunctionV0")); + }; + }; + match ( + &func.doc.to_utf8_string().unwrap(), + &"Test u32 values.\nReturns the input unchanged.", + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + let entry = ScSpecEntry::from_xdr(Contract::spec_xdr_test_i32(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + { + ::core::panicking::panic_fmt(format_args!("expected FunctionV0")); + }; + }; + match (&func.doc.to_utf8_string().unwrap(), &"Test i32 values.") { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + let entry = + ScSpecEntry::from_xdr(Contract::spec_xdr_test_string(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + { + ::core::panicking::panic_fmt(format_args!("expected FunctionV0")); + }; + }; + match (&func.doc.to_utf8_string().unwrap(), &"") { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + let entry = + ScSpecEntry::from_xdr(Contract::spec_xdr_test_env_param(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + { + ::core::panicking::panic_fmt(format_args!("expected FunctionV0")); + }; + }; + match ( + &func.doc.to_utf8_string().unwrap(), + &"Custom env param docs.", + ) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + } + extern crate test; #[rustc_test_marker = "test::test_partial_override"] #[doc(hidden)] pub const test_partial_override: test::TestDescAndFn = test::TestDescAndFn { @@ -3519,9 +3645,9 @@ mod test { ignore: false, ignore_message: ::core::option::Option::None, source_file: "tests/contracttrait_impl_partial/src/lib.rs", - start_line: 41usize, + start_line: 83usize, start_col: 8usize, - end_line: 41usize, + end_line: 83usize, end_col: 29usize, compile_fail: false, no_run: false, @@ -3847,5 +3973,5 @@ mod test { #[doc(hidden)] pub fn main() -> () { extern crate test; - test::test_main_static(&[&test_partial_override]) + test::test_main_static(&[&test_partial_override, &test_spec_docs_inherited_from_trait]) } diff --git a/tests-expanded/test_contracttrait_impl_partial_wasm32v1-none.rs b/tests-expanded/test_contracttrait_impl_partial_wasm32v1-none.rs index f35f7e083..9a91e741a 100644 --- a/tests-expanded/test_contracttrait_impl_partial_wasm32v1-none.rs +++ b/tests-expanded/test_contracttrait_impl_partial_wasm32v1-none.rs @@ -35,6 +35,7 @@ impl AllTypes for Contract { fn test_string(v: String) -> String { v } + /// Custom env param docs. fn test_env_param(_env: &Env) -> u32 { 100 } @@ -45,67 +46,6 @@ impl AllTypes for Contract { } } } -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_u32__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - #[link_section = "contractspecv0"] - pub static __SPEC_XDR_FN_TEST_U32: [u8; 48usize] = super::Contract::spec_xdr_test_u32(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_u32() -> [u8; 48usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x08test_u32\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x04\0\0\0\x01\0\0\0\x04" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_string__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - #[link_section = "contractspecv0"] - pub static __SPEC_XDR_FN_TEST_STRING: [u8; 52usize] = super::Contract::spec_xdr_test_string(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_string() -> [u8; 52usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_string\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x10\0\0\0\x01\0\0\0\x10" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_env_param__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - #[link_section = "contractspecv0"] - pub static __SPEC_XDR_FN_TEST_ENV_PARAM: [u8; 40usize] = - super::Contract::spec_xdr_test_env_param(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_env_param() -> [u8; 40usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0etest_env_param\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04" - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub mod __Contract__test_struct__spec { - #[doc(hidden)] - #[allow(non_snake_case)] - #[allow(non_upper_case_globals)] - #[link_section = "contractspecv0"] - pub static __SPEC_XDR_FN_TEST_STRUCT: [u8; 76usize] = super::Contract::spec_xdr_test_struct(); -} -impl Contract { - #[allow(non_snake_case)] - pub const fn spec_xdr_test_struct() -> [u8; 76usize] { - *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_struct\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\x07\xd0\0\0\0\x08MyStruct\0\0\0\x01\0\0\x07\xd0\0\0\0\x08MyStruct" - } -} impl<'a> ContractClient<'a> { pub fn test_u32(&self, v: &u32) -> u32 { use core::ops::Not; @@ -168,6 +108,7 @@ impl<'a> ContractClient<'a> { ); res } + /// Custom env param docs. pub fn test_env_param(&self) -> u32 { use core::ops::Not; use soroban_sdk::{FromVal, IntoVal}; @@ -178,6 +119,7 @@ impl<'a> ContractClient<'a> { ); res } + /// Custom env param docs. pub fn try_test_env_param( &self, ) -> Result< @@ -304,6 +246,7 @@ pub extern "C" fn __Contract__test_string__invoke_raw_extern( __Contract__test_string__invoke_raw(soroban_sdk::Env::default(), arg_0) } #[doc(hidden)] +/// Custom env param docs. #[allow(non_snake_case)] #[deprecated(note = "use `ContractClient::new(&env, &contract_id).test_env_param` instead")] #[allow(deprecated)] @@ -314,6 +257,7 @@ pub fn __Contract__test_env_param__invoke_raw(env: soroban_sdk::Env) -> soroban_ ) } #[doc(hidden)] +/// Custom env param docs. #[allow(non_snake_case)] #[deprecated(note = "use `ContractClient::new(&env, &contract_id).test_env_param` instead")] #[export_name = "test_env_param"] @@ -1826,3 +1770,73 @@ impl ContractArgs { (v,) } } +#[doc(hidden)] +/// Test u32 values. +/// Returns the input unchanged. +#[allow(non_snake_case)] +pub mod __Contract__test_u32__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + /// Test u32 values. + /// Returns the input unchanged. + #[link_section = "contractspecv0"] + pub static __SPEC_XDR_FN_TEST_U32: [u8; 96usize] = super::Contract::spec_xdr_test_u32(); +} +impl Contract { + #[allow(non_snake_case)] + /// Test u32 values. + /// Returns the input unchanged. + pub const fn spec_xdr_test_u32() -> [u8; 96usize] { + *b"\0\0\0\0\0\0\0-Test u32 values.\nReturns the input unchanged.\0\0\0\0\0\0\x08test_u32\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x04\0\0\0\x01\0\0\0\x04" + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__test_string__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + #[link_section = "contractspecv0"] + pub static __SPEC_XDR_FN_TEST_STRING: [u8; 52usize] = super::Contract::spec_xdr_test_string(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_test_string() -> [u8; 52usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_string\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\0\x10\0\0\0\x01\0\0\0\x10" + } +} +#[doc(hidden)] +/// Custom env param docs. +#[allow(non_snake_case)] +pub mod __Contract__test_env_param__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + /// Custom env param docs. + #[link_section = "contractspecv0"] + pub static __SPEC_XDR_FN_TEST_ENV_PARAM: [u8; 64usize] = + super::Contract::spec_xdr_test_env_param(); +} +impl Contract { + #[allow(non_snake_case)] + /// Custom env param docs. + pub const fn spec_xdr_test_env_param() -> [u8; 64usize] { + *b"\0\0\0\0\0\0\0\x16Custom env param docs.\0\0\0\0\0\x0etest_env_param\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04" + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__test_struct__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + #[link_section = "contractspecv0"] + pub static __SPEC_XDR_FN_TEST_STRUCT: [u8; 76usize] = super::Contract::spec_xdr_test_struct(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_test_struct() -> [u8; 76usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x0btest_struct\0\0\0\0\x01\0\0\0\0\0\0\0\x01v\0\0\0\0\0\x07\xd0\0\0\0\x08MyStruct\0\0\0\x01\0\0\x07\xd0\0\0\0\x08MyStruct" + } +} diff --git a/tests/contracttrait_impl_partial/Cargo.toml b/tests/contracttrait_impl_partial/Cargo.toml index 203f46c3d..a03822774 100644 --- a/tests/contracttrait_impl_partial/Cargo.toml +++ b/tests/contracttrait_impl_partial/Cargo.toml @@ -18,3 +18,4 @@ test_contracttrait_trait = {path = "../contracttrait_trait"} [dev-dependencies] soroban-sdk = {path = "../../soroban-sdk", features = ["testutils"]} test_contracttrait_trait = {path = "../contracttrait_trait"} +stellar-xdr.workspace = true diff --git a/tests/contracttrait_impl_partial/src/lib.rs b/tests/contracttrait_impl_partial/src/lib.rs index 1eaddc45f..73d877791 100644 --- a/tests/contracttrait_impl_partial/src/lib.rs +++ b/tests/contracttrait_impl_partial/src/lib.rs @@ -20,6 +20,7 @@ impl AllTypes for Contract { v } + /// Custom env param docs. fn test_env_param(_env: &Env) -> u32 { 100 } @@ -37,6 +38,47 @@ mod test { use super::*; use soroban_sdk::{map, symbol_short, testutils::Address as _, vec, Env}; + #[test] + fn test_spec_docs_inherited_from_trait() { + use stellar_xdr::curr as stellar_xdr; + use stellar_xdr::{Limits, ReadXdr, ScSpecEntry}; + + // An overridden function without its own docs should inherit trait docs. + let entry = ScSpecEntry::from_xdr(Contract::spec_xdr_test_u32(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + panic!("expected FunctionV0"); + }; + assert_eq!( + func.doc.to_utf8_string().unwrap(), + "Test u32 values.\nReturns the input unchanged." + ); + + // A non-overridden default function should also have trait docs. + let entry = ScSpecEntry::from_xdr(Contract::spec_xdr_test_i32(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + panic!("expected FunctionV0"); + }; + assert_eq!(func.doc.to_utf8_string().unwrap(), "Test i32 values."); + + // An overridden function without docs and a trait function without docs + // should have empty docs. + let entry = + ScSpecEntry::from_xdr(Contract::spec_xdr_test_string(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + panic!("expected FunctionV0"); + }; + assert_eq!(func.doc.to_utf8_string().unwrap(), ""); + + // An overridden function with its own docs should use its own docs, + // not the trait docs. + let entry = + ScSpecEntry::from_xdr(Contract::spec_xdr_test_env_param(), Limits::none()).unwrap(); + let ScSpecEntry::FunctionV0(func) = entry else { + panic!("expected FunctionV0"); + }; + assert_eq!(func.doc.to_utf8_string().unwrap(), "Custom env param docs."); + } + #[test] fn test_partial_override() { let e = Env::default();