Skip to content

Commit

Permalink
Add ContractArgs for building function args
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmcculloch committed Oct 24, 2024
1 parent e7eccd3 commit c349f06
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 26 deletions.
21 changes: 4 additions & 17 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ features = ["curr"]
#git = "https://github.com/stellar/rs-stellar-xdr"
#rev = "67be5955a15f1d3a4df83fe86e6ae107f687141b"

#[patch."https://github.com/stellar/rs-soroban-env"]
#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" }
[patch.crates-io]
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" }
#[patch."https://github.com/stellar/rs-stellar-xdr"]
#stellar-xdr = { path = "../rs-stellar-xdr/" }

Expand Down
92 changes: 92 additions & 0 deletions soroban-sdk-macros/src/derive_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Error, FnArg, Lifetime, Path, Type, TypePath, TypeReference};

use crate::syn_ext;

pub fn derive_args_type(ty: &str, name: &str) -> TokenStream {
let ty_str = quote!(#ty).to_string();
// Render the Client.
let args_doc = format!("{name} is a type for buildings function args defined in {ty_str}.");
let args_ident = format_ident!("{}", name);
quote! {
#[doc = #args_doc]
pub struct #args_ident;
}
}

pub fn derive_args_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) -> TokenStream {
// Map the traits methods to methods for the Args.
let mut errors = Vec::<Error>::new();
let fns: Vec<_> = fns
.iter()
.map(|f| {
let fn_ident = &f.ident;

// Check for the Env argument.
let env_input = f.inputs.first().and_then(|a| match a {
FnArg::Typed(pat_type) => {
let mut ty = &*pat_type.ty;
if let Type::Reference(TypeReference { elem, .. }) = ty {
ty = elem;
}
if let Type::Path(TypePath {
path: syn::Path { segments, .. },
..
}) = ty
{
if segments.last().map_or(false, |s| s.ident == "Env") {
Some(())
} else {
None
}
} else {
None
}
}
FnArg::Receiver(_) => None,
});

// Map all remaining inputs.
let fn_input_lifetime = Lifetime::new("'i", Span::call_site());
let (fn_input_names, fn_input_types): (Vec<_>, Vec<_>) = f
.inputs
.iter()
.skip(if env_input.is_some() { 1 } else { 0 })
.map(|t| {
let ident = match syn_ext::fn_arg_ident(t) {
Ok(ident) => ident,
Err(e) => {
errors.push(e);
format_ident!("_")
}
};
(ident, syn_ext::fn_arg_make_ref(t, Some(&fn_input_lifetime)))
})
.unzip();

quote! {
// TODO: Make the fn_input_types an impl Borrow
pub fn #fn_ident<#fn_input_lifetime>(#(#fn_input_types),*)
-> impl #crate_path::IntoVal<#crate_path::Env, #crate_path::Vec<#crate_path::Val>> + #fn_input_lifetime
{
(#(#fn_input_names,)*)
}
}
})
.collect();

// If errors have occurred, render them instead.
if !errors.is_empty() {
let compile_errors = errors.iter().map(Error::to_compile_error);
return quote! { #(#compile_errors)* };
}

// Render the Client.
let args_ident = format_ident!("{}", name);
quote! {
impl #args_ident {
#(#fns)*
}
}
}
2 changes: 1 addition & 1 deletion soroban-sdk-macros/src/derive_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) ->
format_ident!("_")
}
};
(ident, syn_ext::fn_arg_make_ref(t))
(ident, syn_ext::fn_arg_make_ref(t, None))
})
.unzip();
let fn_output = f.output();
Expand Down
52 changes: 52 additions & 0 deletions soroban-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extern crate proc_macro;

mod arbitrary;
mod derive_args;
mod derive_client;
mod derive_enum;
mod derive_enum_int;
Expand All @@ -15,6 +16,7 @@ mod path;
mod symbol;
mod syn_ext;

use derive_args::{derive_args_impl, derive_args_type};
use derive_client::{derive_client_impl, derive_client_type};
use derive_enum::derive_type_enum;
use derive_enum_int::derive_type_enum_int;
Expand Down Expand Up @@ -136,8 +138,11 @@ pub fn contract(metadata: TokenStream, input: TokenStream) -> TokenStream {
let fn_set_registry_ident = format_ident!("__{}_fn_set_registry", ty_str.to_lowercase());
let crate_path = &args.crate_path;
let client = derive_client_type(&args.crate_path, &ty_str, &client_ident);
let args_ident = format!("{ty_str}Args");
let contract_args = derive_args_type(&ty_str, &args_ident);
let mut output = quote! {
#input2
#contract_args
#client
};
if cfg!(feature = "testutils") {
Expand Down Expand Up @@ -206,6 +211,18 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream {
let ty = &imp.self_ty;
let ty_str = quote!(#ty).to_string();

// TODO: Use imp.trait_ in generating the args ident, to create a unique
// args for each trait impl for a contract, to avoid conflicts.
let args_ident = if let Type::Path(path) = &**ty {
path.path
.segments
.last()
.map(|name| format!("{}Args", name.ident))
} else {
None
}
.unwrap_or_else(|| "Args".to_string());

// TODO: Use imp.trait_ in generating the client ident, to create a unique
// client for each trait impl for a contract, to avoid conflicts.
let client_ident = if let Type::Path(path) = &**ty {
Expand Down Expand Up @@ -239,6 +256,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream {
match derived {
Ok(derived_ok) => {
let mut output = quote! {
#[#crate_path::contractargs(crate_path = #crate_path_str, 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
Expand Down Expand Up @@ -533,6 +551,40 @@ pub fn contractfile(metadata: TokenStream) -> TokenStream {
quote! { #contents_lit }.into()
}

#[derive(Debug, FromMeta)]
struct ContractArgsArgs {
#[darling(default = "default_crate_path")]
crate_path: Path,
name: String,
#[darling(default)]
impl_only: bool,
}

#[proc_macro_attribute]
pub fn contractargs(metadata: TokenStream, input: TokenStream) -> TokenStream {
let args = match NestedMeta::parse_meta_list(metadata.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(darling::Error::from(e).write_errors());
}
};
let args = match ContractArgsArgs::from_list(&args) {
Ok(v) => v,
Err(e) => return e.write_errors().into(),
};
let input2: TokenStream2 = input.clone().into();
let item = parse_macro_input!(input as HasFnsItem);
let methods: Vec<_> = item.fns();
let args_type = (!args.impl_only).then(|| derive_args_type(&item.name(), &args.name));
let args_impl = derive_args_impl(&args.crate_path, &args.name, &methods);
quote! {
#input2
#args_type
#args_impl
}
.into()
}

#[derive(Debug, FromMeta)]
struct ContractClientArgs {
#[darling(default = "default_crate_path")]
Expand Down
6 changes: 3 additions & 3 deletions soroban-sdk-macros/src/syn_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use syn::{
};
use syn::{
spanned::Spanned, token::And, Error, FnArg, Ident, ImplItem, ImplItemFn, ItemImpl, ItemTrait,
Pat, PatType, TraitItem, TraitItemFn, Type, TypeReference, Visibility,
Lifetime, Pat, PatType, TraitItem, TraitItemFn, Type, TypeReference, Visibility,
};

/// Gets methods from the implementation that have public visibility. For
Expand Down Expand Up @@ -49,7 +49,7 @@ pub fn fn_arg_ident(arg: &FnArg) -> Result<Ident, Error> {

/// Returns a clone of FnArg with the type as a reference if the arg is a typed
/// arg and its type is not already a reference.
pub fn fn_arg_make_ref(arg: &FnArg) -> FnArg {
pub fn fn_arg_make_ref(arg: &FnArg, lifetime: Option<&Lifetime>) -> FnArg {
if let FnArg::Typed(pat_type) = arg {
if !matches!(*pat_type.ty, Type::Reference(_)) {
return FnArg::Typed(PatType {
Expand All @@ -58,7 +58,7 @@ pub fn fn_arg_make_ref(arg: &FnArg) -> FnArg {
colon_token: pat_type.colon_token,
ty: Box::new(Type::Reference(TypeReference {
and_token: And::default(),
lifetime: None,
lifetime: lifetime.cloned(),
mutability: None,
elem: pat_type.ty.clone(),
})),
Expand Down
5 changes: 5 additions & 0 deletions soroban-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,11 @@ pub use soroban_sdk_macros::contractmeta;
/// ```
pub use soroban_sdk_macros::contracttype;

/// Generates a type that helps build function args for a contract trait.
///
/// TODO: Docs and examples.
pub use soroban_sdk_macros::contractargs;

/// Generates a client for a contract trait.
///
/// Can be used to create clients for contracts that live outside the current
Expand Down
1 change: 1 addition & 0 deletions soroban-spec-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub fn generate_without_file(specs: &[ScSpecEntry]) -> TokenStream {
let error_enums = spec_error_enums.iter().map(|s| generate_error_enum(s));

quote! {
#[soroban_sdk::contractargs(name = "Args")]
#[soroban_sdk::contractclient(name = "Client")]
#trait_

Expand Down
2 changes: 1 addition & 1 deletion tests/constructor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl Contract {
#[test]
fn test_constructor() {
let env = Env::default();
let contract_id = env.register(Contract, (100_u32, 1000_i64));
let contract_id = env.register(Contract, ContractArgs::__constructor(100_u32, 1000_i64));
let client = ContractClient::new(&env, &contract_id);
assert_eq!(client.get_data(&DataKey::Persistent(100)), Some(1000));
assert_eq!(client.get_data(&DataKey::Temp(200)), Some(2000));
Expand Down

0 comments on commit c349f06

Please sign in to comment.