diff --git a/CHANGELOG.md b/CHANGELOG.md index 7128aeed..e6e7c363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## main + +- Fix custom types in generic positions in export function signatures. + ## 2.0.0 ### Breaking changes diff --git a/examples/example-protocol/src/assets/rust_plugin_test/expected_export.rs b/examples/example-protocol/src/assets/rust_plugin_test/expected_export.rs index eb3c60fa..3cb62bdc 100644 --- a/examples/example-protocol/src/assets/rust_plugin_test/expected_export.rs +++ b/examples/example-protocol/src/assets/rust_plugin_test/expected_export.rs @@ -24,6 +24,9 @@ pub fn export_fp_untagged(arg: FpUntagged) -> FpUntagged; #[fp_bindgen_support::fp_export_signature] pub fn export_generics(arg: StructWithGenerics) -> StructWithGenerics; +#[fp_bindgen_support::fp_export_signature] +pub fn export_get_bytes() -> Result; + #[fp_bindgen_support::fp_export_signature] pub fn export_multiple_primitives(arg1: i8, arg2: String) -> i64; diff --git a/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_bindings.rs b/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_bindings.rs index de2a169e..bc5ec510 100644 --- a/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_bindings.rs +++ b/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_bindings.rs @@ -238,6 +238,27 @@ impl Runtime { Ok(result) } + pub fn export_get_bytes( + &self, + ) -> Result, InvocationError> { + let result = self.export_get_bytes_raw(); + let result = result.map(|ref data| deserialize_from_slice(data)); + result + } + pub fn export_get_bytes_raw(&self) -> Result, InvocationError> { + let mut env = RuntimeInstanceData::default(); + let import_object = create_import_object(self.module.store(), &env); + let instance = Instance::new(&self.module, &import_object).unwrap(); + env.init_with_instance(&instance).unwrap(); + let function = instance + .exports + .get_native_function::<(), FatPtr>("__fp_gen_export_get_bytes") + .map_err(|_| InvocationError::FunctionNotExported)?; + let result = function.call()?; + let result = import_from_guest_raw(&env, result); + Ok(result) + } + pub fn export_multiple_primitives( &self, arg1: i8, @@ -695,7 +716,7 @@ impl Runtime { Ok(result) } - #[doc = " Example how plugin could expose async data-fetching capabilities."] + /// Example how plugin could expose async data-fetching capabilities. pub async fn fetch_data( &self, r#type: String, @@ -721,7 +742,7 @@ impl Runtime { Ok(result) } - #[doc = " Called on the plugin to give it a chance to initialize."] + /// Called on the plugin to give it a chance to initialize. pub fn init(&self) -> Result<(), InvocationError> { let result = self.init_raw(); result @@ -740,7 +761,7 @@ impl Runtime { Ok(result) } - #[doc = " Example how plugin could expose a reducer."] + /// Example how plugin could expose a reducer. pub fn reducer_bridge(&self, action: ReduxAction) -> Result { let action = serialize_to_vec(&action); let result = self.reducer_bridge_raw(action); @@ -765,42 +786,42 @@ impl Runtime { fn create_import_object(store: &Store, env: &RuntimeInstanceData) -> ImportObject { imports! { - "fp" => { - "__fp_host_resolve_async_value" => Function :: new_native_with_env (store , env . clone () , resolve_async_value) , - "__fp_gen_import_explicit_bound_point" => Function :: new_native_with_env (store , env . clone () , _import_explicit_bound_point) , - "__fp_gen_import_fp_adjacently_tagged" => Function :: new_native_with_env (store , env . clone () , _import_fp_adjacently_tagged) , - "__fp_gen_import_fp_enum" => Function :: new_native_with_env (store , env . clone () , _import_fp_enum) , - "__fp_gen_import_fp_flatten" => Function :: new_native_with_env (store , env . clone () , _import_fp_flatten) , - "__fp_gen_import_fp_internally_tagged" => Function :: new_native_with_env (store , env . clone () , _import_fp_internally_tagged) , - "__fp_gen_import_fp_struct" => Function :: new_native_with_env (store , env . clone () , _import_fp_struct) , - "__fp_gen_import_fp_untagged" => Function :: new_native_with_env (store , env . clone () , _import_fp_untagged) , - "__fp_gen_import_generics" => Function :: new_native_with_env (store , env . clone () , _import_generics) , - "__fp_gen_import_get_bytes" => Function :: new_native_with_env (store , env . clone () , _import_get_bytes) , - "__fp_gen_import_multiple_primitives" => Function :: new_native_with_env (store , env . clone () , _import_multiple_primitives) , - "__fp_gen_import_primitive_bool" => Function :: new_native_with_env (store , env . clone () , _import_primitive_bool) , - "__fp_gen_import_primitive_f32" => Function :: new_native_with_env (store , env . clone () , _import_primitive_f32) , - "__fp_gen_import_primitive_f64" => Function :: new_native_with_env (store , env . clone () , _import_primitive_f64) , - "__fp_gen_import_primitive_i16" => Function :: new_native_with_env (store , env . clone () , _import_primitive_i16) , - "__fp_gen_import_primitive_i32" => Function :: new_native_with_env (store , env . clone () , _import_primitive_i32) , - "__fp_gen_import_primitive_i64" => Function :: new_native_with_env (store , env . clone () , _import_primitive_i64) , - "__fp_gen_import_primitive_i8" => Function :: new_native_with_env (store , env . clone () , _import_primitive_i8) , - "__fp_gen_import_primitive_u16" => Function :: new_native_with_env (store , env . clone () , _import_primitive_u16) , - "__fp_gen_import_primitive_u32" => Function :: new_native_with_env (store , env . clone () , _import_primitive_u32) , - "__fp_gen_import_primitive_u64" => Function :: new_native_with_env (store , env . clone () , _import_primitive_u64) , - "__fp_gen_import_primitive_u8" => Function :: new_native_with_env (store , env . clone () , _import_primitive_u8) , - "__fp_gen_import_serde_adjacently_tagged" => Function :: new_native_with_env (store , env . clone () , _import_serde_adjacently_tagged) , - "__fp_gen_import_serde_enum" => Function :: new_native_with_env (store , env . clone () , _import_serde_enum) , - "__fp_gen_import_serde_flatten" => Function :: new_native_with_env (store , env . clone () , _import_serde_flatten) , - "__fp_gen_import_serde_internally_tagged" => Function :: new_native_with_env (store , env . clone () , _import_serde_internally_tagged) , - "__fp_gen_import_serde_struct" => Function :: new_native_with_env (store , env . clone () , _import_serde_struct) , - "__fp_gen_import_serde_untagged" => Function :: new_native_with_env (store , env . clone () , _import_serde_untagged) , - "__fp_gen_import_string" => Function :: new_native_with_env (store , env . clone () , _import_string) , - "__fp_gen_import_timestamp" => Function :: new_native_with_env (store , env . clone () , _import_timestamp) , - "__fp_gen_import_void_function" => Function :: new_native_with_env (store , env . clone () , _import_void_function) , - "__fp_gen_import_void_function_empty_result" => Function :: new_native_with_env (store , env . clone () , _import_void_function_empty_result) , - "__fp_gen_import_void_function_empty_return" => Function :: new_native_with_env (store , env . clone () , _import_void_function_empty_return) , - "__fp_gen_log" => Function :: new_native_with_env (store , env . clone () , _log) , - "__fp_gen_make_http_request" => Function :: new_native_with_env (store , env . clone () , _make_http_request) , + "fp" => { + "__fp_host_resolve_async_value" => Function::new_native_with_env(store, env.clone(), resolve_async_value), + "__fp_gen_import_explicit_bound_point" => Function::new_native_with_env(store, env.clone(), _import_explicit_bound_point), + "__fp_gen_import_fp_adjacently_tagged" => Function::new_native_with_env(store, env.clone(), _import_fp_adjacently_tagged), + "__fp_gen_import_fp_enum" => Function::new_native_with_env(store, env.clone(), _import_fp_enum), + "__fp_gen_import_fp_flatten" => Function::new_native_with_env(store, env.clone(), _import_fp_flatten), + "__fp_gen_import_fp_internally_tagged" => Function::new_native_with_env(store, env.clone(), _import_fp_internally_tagged), + "__fp_gen_import_fp_struct" => Function::new_native_with_env(store, env.clone(), _import_fp_struct), + "__fp_gen_import_fp_untagged" => Function::new_native_with_env(store, env.clone(), _import_fp_untagged), + "__fp_gen_import_generics" => Function::new_native_with_env(store, env.clone(), _import_generics), + "__fp_gen_import_get_bytes" => Function::new_native_with_env(store, env.clone(), _import_get_bytes), + "__fp_gen_import_multiple_primitives" => Function::new_native_with_env(store, env.clone(), _import_multiple_primitives), + "__fp_gen_import_primitive_bool" => Function::new_native_with_env(store, env.clone(), _import_primitive_bool), + "__fp_gen_import_primitive_f32" => Function::new_native_with_env(store, env.clone(), _import_primitive_f32), + "__fp_gen_import_primitive_f64" => Function::new_native_with_env(store, env.clone(), _import_primitive_f64), + "__fp_gen_import_primitive_i16" => Function::new_native_with_env(store, env.clone(), _import_primitive_i16), + "__fp_gen_import_primitive_i32" => Function::new_native_with_env(store, env.clone(), _import_primitive_i32), + "__fp_gen_import_primitive_i64" => Function::new_native_with_env(store, env.clone(), _import_primitive_i64), + "__fp_gen_import_primitive_i8" => Function::new_native_with_env(store, env.clone(), _import_primitive_i8), + "__fp_gen_import_primitive_u16" => Function::new_native_with_env(store, env.clone(), _import_primitive_u16), + "__fp_gen_import_primitive_u32" => Function::new_native_with_env(store, env.clone(), _import_primitive_u32), + "__fp_gen_import_primitive_u64" => Function::new_native_with_env(store, env.clone(), _import_primitive_u64), + "__fp_gen_import_primitive_u8" => Function::new_native_with_env(store, env.clone(), _import_primitive_u8), + "__fp_gen_import_serde_adjacently_tagged" => Function::new_native_with_env(store, env.clone(), _import_serde_adjacently_tagged), + "__fp_gen_import_serde_enum" => Function::new_native_with_env(store, env.clone(), _import_serde_enum), + "__fp_gen_import_serde_flatten" => Function::new_native_with_env(store, env.clone(), _import_serde_flatten), + "__fp_gen_import_serde_internally_tagged" => Function::new_native_with_env(store, env.clone(), _import_serde_internally_tagged), + "__fp_gen_import_serde_struct" => Function::new_native_with_env(store, env.clone(), _import_serde_struct), + "__fp_gen_import_serde_untagged" => Function::new_native_with_env(store, env.clone(), _import_serde_untagged), + "__fp_gen_import_string" => Function::new_native_with_env(store, env.clone(), _import_string), + "__fp_gen_import_timestamp" => Function::new_native_with_env(store, env.clone(), _import_timestamp), + "__fp_gen_import_void_function" => Function::new_native_with_env(store, env.clone(), _import_void_function), + "__fp_gen_import_void_function_empty_result" => Function::new_native_with_env(store, env.clone(), _import_void_function_empty_result), + "__fp_gen_import_void_function_empty_return" => Function::new_native_with_env(store, env.clone(), _import_void_function_empty_return), + "__fp_gen_log" => Function::new_native_with_env(store, env.clone(), _log), + "__fp_gen_make_http_request" => Function::new_native_with_env(store, env.clone(), _make_http_request), } } } diff --git a/examples/example-protocol/src/assets/ts_runtime_test/expected_index.ts b/examples/example-protocol/src/assets/ts_runtime_test/expected_index.ts index 02f027d3..2c186a35 100644 --- a/examples/example-protocol/src/assets/ts_runtime_test/expected_index.ts +++ b/examples/example-protocol/src/assets/ts_runtime_test/expected_index.ts @@ -57,6 +57,7 @@ export type Exports = { exportFpStruct?: (arg: types.FpPropertyRenaming) => types.FpPropertyRenaming; exportFpUntagged?: (arg: types.FpUntagged) => types.FpUntagged; exportGenerics?: (arg: types.StructWithGenerics) => types.StructWithGenerics; + exportGetBytes?: () => types.Result; exportMultiplePrimitives?: (arg1: number, arg2: string) => bigint; exportPrimitiveBool?: (arg: boolean) => boolean; exportPrimitiveF32?: (arg: number) => number; @@ -89,6 +90,7 @@ export type Exports = { exportFpStructRaw?: (arg: Uint8Array) => Uint8Array; exportFpUntaggedRaw?: (arg: Uint8Array) => Uint8Array; exportGenericsRaw?: (arg: Uint8Array) => Uint8Array; + exportGetBytesRaw?: () => Uint8Array; exportMultiplePrimitivesRaw?: (arg1: number, arg2: Uint8Array) => bigint; exportPrimitiveBoolRaw?: (arg: boolean) => boolean; exportPrimitiveI16Raw?: (arg: number) => number; @@ -439,6 +441,12 @@ export async function createRuntime( return parseObject>(export_fn(arg_ptr)); }; })(), + exportGetBytes: (() => { + const export_fn = instance.exports.__fp_gen_export_get_bytes as any; + if (!export_fn) return; + + return () => parseObject>(export_fn()); + })(), exportMultiplePrimitives: (() => { const export_fn = instance.exports.__fp_gen_export_multiple_primitives as any; if (!export_fn) return; @@ -648,6 +656,12 @@ export async function createRuntime( return importFromMemory(export_fn(arg_ptr)); }; })(), + exportGetBytesRaw: (() => { + const export_fn = instance.exports.__fp_gen_export_get_bytes as any; + if (!export_fn) return; + + return () => importFromMemory(export_fn()); + })(), exportMultiplePrimitivesRaw: (() => { const export_fn = instance.exports.__fp_gen_export_multiple_primitives as any; if (!export_fn) return; diff --git a/examples/example-protocol/src/main.rs b/examples/example-protocol/src/main.rs index 8f3afdd8..fe4f1b21 100644 --- a/examples/example-protocol/src/main.rs +++ b/examples/example-protocol/src/main.rs @@ -154,6 +154,9 @@ fp_export! { // See `types/generics.rs` for more info. fn export_generics(arg: StructWithGenerics) -> StructWithGenerics; + // Custom type in a generic position. + fn export_get_bytes() -> Result; + // Passing custom types with property/variant renaming. // // See `types/renaming.rs` for more info. diff --git a/fp-bindgen/src/functions.rs b/fp-bindgen/src/functions.rs index ab11f5c0..217c0c07 100644 --- a/fp-bindgen/src/functions.rs +++ b/fp-bindgen/src/functions.rs @@ -1,8 +1,8 @@ use crate::utils::normalize_return_type; use crate::{docs::get_doc_lines, types::TypeIdent}; -use quote::{format_ident, quote, ToTokens}; +use quote::ToTokens; use std::{collections::BTreeSet, convert::TryFrom}; -use syn::{token::Async, FnArg, ForeignItemFn}; +use syn::{FnArg, ForeignItemFn}; /// Maps from function name to the stringified function declaration. #[derive(Debug, Default)] @@ -100,78 +100,8 @@ impl PartialOrd for Function { } } -impl ToTokens for Function { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let Self { - name, - args, - doc_lines, - is_async, - return_type, - } = self; - - let name = format_ident!("{}", name); - - let asyncness = is_async.then(|| Async { - ..Default::default() - }); - - (quote! { - #(#[doc = #doc_lines])* - #asyncness fn #name(#(#args),*) -> #return_type - }) - .to_tokens(tokens); - } -} - #[derive(Debug, Eq, PartialEq)] pub struct FunctionArg { pub name: String, pub ty: TypeIdent, } - -impl ToTokens for FunctionArg { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let Self { name, ty } = self; - let name = format_ident!("{}", name); - - (quote! { #name: #ty }).to_tokens(tokens) - } -} - -#[cfg(test)] -mod test { - use super::{Function, FunctionArg}; - use crate::types::TypeIdent; - use quote::ToTokens; - - #[test] - fn test_function_arg_to_tokens() { - let arg = FunctionArg { - name: "foobar".into(), - ty: TypeIdent::from("i64"), - }; - - let stringified = arg.into_token_stream().to_string(); - - pretty_assertions::assert_eq!(&stringified, "foobar : i64"); - } - - #[test] - fn test_function_to_tokens() { - let func = Function { - name: "foobar".into(), - is_async: false, - doc_lines: vec![], - return_type: Some(TypeIdent::from("String")), - args: vec![FunctionArg { - name: "a1".into(), - ty: TypeIdent::from("u64"), - }], - }; - - let string = func.into_token_stream().to_string(); - - pretty_assertions::assert_eq!(&string, "fn foobar (a1 : u64) -> String"); - } -} diff --git a/fp-bindgen/src/generators/rust_plugin/mod.rs b/fp-bindgen/src/generators/rust_plugin/mod.rs index f394c5ea..494e18b0 100644 --- a/fp-bindgen/src/generators/rust_plugin/mod.rs +++ b/fp-bindgen/src/generators/rust_plugin/mod.rs @@ -1,3 +1,4 @@ +use crate::functions::Function; use crate::types::is_runtime_bound; use crate::{ functions::FunctionList, @@ -189,18 +190,25 @@ pub fn generate_type_bindings(types: &TypeMap, path: &str, module_key: &str) { ); } +pub fn format_doc_lines(doc_lines: &[String]) -> String { + doc_lines + .iter() + .map(|line| format!("///{}\n", line)) + .collect::>() + .join("") +} + +pub fn format_modifiers(function: &Function) -> String { + if function.is_async { "async " } else { "" }.to_owned() +} + fn format_functions(export_functions: FunctionList, types: &TypeMap, macro_path: &str) -> String { export_functions .iter() .map(|func| { let name = &func.name; - let doc = func - .doc_lines - .iter() - .map(|line| format!("///{}\n", line)) - .collect::>() - .join(""); - let modifiers = if func.is_async { "async " } else { "" }; + let doc = format_doc_lines(&func.doc_lines); + let modifiers = format_modifiers(func); let args_with_types = func .args .iter() @@ -220,7 +228,7 @@ fn format_functions(export_functions: FunctionList, types: &TypeMap, macro_path: .join("\n\n") } -fn format_ident(ident: &TypeIdent, types: &TypeMap) -> String { +pub fn format_ident(ident: &TypeIdent, types: &TypeMap) -> String { match types.get(ident) { Some(ty) => format_type_with_ident(ty, ident, types), None => ident.to_string(), // Must be a generic. diff --git a/fp-bindgen/src/generators/rust_wasmer_runtime/mod.rs b/fp-bindgen/src/generators/rust_wasmer_runtime/mod.rs index bd803bf7..0379e4b7 100644 --- a/fp-bindgen/src/generators/rust_wasmer_runtime/mod.rs +++ b/fp-bindgen/src/generators/rust_wasmer_runtime/mod.rs @@ -1,13 +1,11 @@ use crate::{ functions::{Function, FunctionArg, FunctionList}, - generators::rust_plugin::generate_type_bindings, - primitives::Primitive, + generators::rust_plugin::{ + format_doc_lines, format_ident, format_modifiers, generate_type_bindings, + }, types::{TypeIdent, TypeMap}, }; -use proc_macro2::{Punct, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use std::{fs, str::FromStr}; -use syn::token::Async; +use std::fs; pub(crate) fn generate_bindings( import_functions: FunctionList, @@ -21,372 +19,291 @@ pub(crate) fn generate_bindings( // serializable and deserializable types inverted: generate_type_bindings(&types, path, "rust_wasmer_runtime"); - generate_function_bindings(import_functions, export_functions, path); + generate_function_bindings(import_functions, export_functions, &types, path); } -fn generate_create_import_object_func(import_functions: &FunctionList) -> TokenStream { - // Yes, this is pretty ugly but fortunately *only* required here to get - // proper formatting with quote!, since rustfmt doesn't format inside macro - // invocations :( - let newline = Punct::new('\n', proc_macro2::Spacing::Alone); - let space = Punct::new(' ', proc_macro2::Spacing::Joint); - let spaces4: Vec<_> = (0..3).map(|_| &space).collect(); - let spaces8: Vec<_> = (0..7).map(|_| &space).collect(); - let spaces8 = quote! {#(#spaces8)*}; - - let fp_gen_names = import_functions - .iter() - .map(|function| format!("__fp_gen_{}", function.name)); - let names = import_functions +fn generate_create_import_object_func(import_functions: &FunctionList) -> String { + let imports = import_functions .iter() - .map(|function| format_ident!("_{}", function.name)); - - quote! { - fn create_import_object(store: &Store, env: &RuntimeInstanceData) -> ImportObject { - imports! { - #newline - #(#spaces4)* "fp" => { - #newline - #spaces8 "__fp_host_resolve_async_value" => Function::new_native_with_env(store, env.clone(), resolve_async_value), - #newline - #( - #spaces8 #fp_gen_names => Function::new_native_with_env(store, env.clone(), #names), - #newline - )* - #(#spaces4)* } - #newline - } - } - } + .map(|function| { + let name = &function.name; + format!( + "\"__fp_gen_{name}\" => Function::new_native_with_env(store, env.clone(), _{name})," + ) + }) + .collect::>() + .join("\n "); + + format!( + r#"fn create_import_object(store: &Store, env: &RuntimeInstanceData) -> ImportObject {{ + imports! {{ + "fp" => {{ + "__fp_host_resolve_async_value" => Function::new_native_with_env(store, env.clone(), resolve_async_value), + {imports} + }} + }} +}}"# + ) } -struct WasmType<'a>(&'a TypeIdent); - -impl ToTokens for WasmType<'_> { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - if let Ok(p) = Primitive::from_str(&self.0.name) { - quote! { <#p as WasmAbi>::AbiType } - } else { - quote! { FatPtr } - } - .to_tokens(tokens) +fn format_raw_ident(ty: &TypeIdent, types: &TypeMap) -> String { + if ty.is_primitive() { + format_ident(ty, types) + } else { + "Vec".to_owned() } } -struct WasmArg<'a>(&'a FunctionArg); - -impl ToTokens for WasmArg<'_> { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let name = format_ident!("{}", self.0.name); - let ty = WasmType(&self.0.ty); - quote!(#name: #ty).to_tokens(tokens) +fn format_wasm_ident(ty: &TypeIdent) -> String { + if ty.is_primitive() { + format!("<{} as WasmAbi>::AbiType", ty.name) + } else { + "FatPtr".to_owned() } } -struct RawType<'a>(&'a TypeIdent); +fn format_import_function(function: &Function, types: &TypeMap) -> String { + let doc = format_doc_lines(&function.doc_lines); + let modifiers = format_modifiers(function); -impl ToTokens for RawType<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - if let Ok(p) = Primitive::from_str(&self.0.name) { - quote! { #p } - } else { - quote! { Vec } - } - .to_tokens(tokens) - } -} + let name = &function.name; -struct RawArg<'a>(&'a FunctionArg); + let args = function + .args + .iter() + .map(|FunctionArg { name, ty }| format!(", {name}: {}", format_ident(ty, types))) + .collect::>() + .join(""); + let raw_args = function + .args + .iter() + .map(|FunctionArg { name, ty }| format!(", {name}: {}", format_raw_ident(ty, types))) + .collect::>() + .join(""); + let wasm_args = function + .args + .iter() + .map(|arg| format_wasm_ident(&arg.ty)) + .collect::>(); + let wasm_args = if wasm_args.len() == 1 { + let mut wasm_args = wasm_args; + wasm_args.remove(0) + } else { + format!("({})", wasm_args.join(", ")) + }; + + let return_type = match &function.return_type { + Some(ty) => format_ident(ty, types), + None => "()".to_owned(), + }; + let raw_return_type = match &function.return_type { + Some(ty) => format_raw_ident(ty, types), + None => "()".to_owned(), + }; + let wasm_return_type = match &function.return_type { + Some(ty) => format_wasm_ident(ty), + None => "()".to_owned(), + }; + + let serialize_args = function + .args + .iter() + .filter(|arg| !arg.ty.is_primitive()) + .map(|FunctionArg { name, .. }| format!("let {name} = serialize_to_vec(&{name});")) + .collect::>() + .join("\n"); + let serialize_raw_args = function + .args + .iter() + .filter(|arg| !arg.ty.is_primitive()) + .map(|FunctionArg { name, .. }| format!("let {name} = export_to_guest_raw(&env, {name});")) + .collect::>() + .join("\n"); -impl ToTokens for RawArg<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = format_ident!("{}", self.0.name); - let ty = RawType(&self.0.ty); - (quote! { #name: #ty }).to_tokens(tokens) - } + let arg_names = function + .args + .iter() + .map(|arg| arg.name.as_ref()) + .collect::>() + .join(", "); + let wasm_arg_names = function + .args + .iter() + .map(|arg| format!("{}.to_abi()", arg.name)) + .collect::>() + .join(", "); + + let (raw_return_wrapper, return_wrapper) = if function.is_async { + ( + "let result = ModuleRawFuture::new(env.clone(), result).await;", + "let result = result.await;\nlet result = result.map(|ref data| deserialize_from_slice(data));", + ) + } else if !function + .return_type + .as_ref() + .map(TypeIdent::is_primitive) + .unwrap_or(true) + { + ( + "let result = import_from_guest_raw(&env, result);", + "let result = result.map(|ref data| deserialize_from_slice(data));", + ) + } else { + ("let result = WasmAbi::from_abi(result);", "") + }; + + format!( + r#"{doc}pub {modifiers}fn {name}(&self{args}) -> Result<{return_type}, InvocationError> {{ + {serialize_args} + let result = self.{name}_raw({arg_names}); + {return_wrapper}result +}} +pub {modifiers}fn {name}_raw(&self{raw_args}) -> Result<{raw_return_type}, InvocationError> {{ + let mut env = RuntimeInstanceData::default(); + let import_object = create_import_object(self.module.store(), &env); + let instance = Instance::new(&self.module, &import_object).unwrap(); + env.init_with_instance(&instance).unwrap(); + {serialize_raw_args}let function = instance + .exports + .get_native_function::<{wasm_args}, {wasm_return_type}>("__fp_gen_{name}") + .map_err(|_| InvocationError::FunctionNotExported)?; + let result = function.call({wasm_arg_names})?; + {raw_return_wrapper}Ok(result) +}}"# + ) } -struct RuntimeImportedFunction<'a>(&'a Function); - -impl ToTokens for RuntimeImportedFunction<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let newline = Punct::new('\n', proc_macro2::Spacing::Alone); - - let Function { - name, - doc_lines, - args, - return_type, - is_async, - } = self.0; - - let fp_gen_name = format!("__fp_gen_{}", name); - let raw_name = format_ident!("{}_raw", name); - let name = format_ident!("{}", name); - - let arg_names: Vec<_> = args - .iter() - .map(|arg| format_ident!("{}", arg.name)) - .collect(); - let serialize_names: Vec<_> = args - .iter() - .filter(|arg| !&arg.ty.is_primitive()) - .map(|arg| format_ident!("{}", arg.name)) - .collect(); - let wasm_arg_types: Vec<_> = args.iter().map(|arg| WasmType(&arg.ty)).collect(); - let wasm_arg_types = match wasm_arg_types.len() { - 1 => { - let wasm_arg_type = &wasm_arg_types[0]; - quote! { #wasm_arg_type } - } - _ => quote! { (#(#wasm_arg_types),*) }, - }; - let wasm_return_type = match return_type { - Some(ty) => { - let ty = WasmType(ty); - quote! { #ty } - } - None => quote! { () }, - }; - let raw_format_args = args.iter().map(RawArg); - let raw_format_return_type = match return_type { - Some(ty) => { - let raw = RawType(ty); - quote! { #raw } - } - None => quote! { () }, - }; - - let asyncness = is_async.then(Async::default); - - let (raw_return_wrapper, return_wrapper) = if *is_async { - ( - quote! { - let result = ModuleRawFuture::new(env.clone(), result).await; - }, - quote! { - let result = result.await; - let result = result.map(|ref data| deserialize_from_slice(data)); - }, - ) - } else if !return_type - .as_ref() - .map(TypeIdent::is_primitive) - .unwrap_or(true) - { - ( - quote! { - let result = import_from_guest_raw(&env, result); - }, - quote! { - let result = result.map(|ref data| deserialize_from_slice(data)); - }, - ) - } else { - ( - quote! {let result = WasmAbi::from_abi(result);}, - TokenStream::default(), - ) - }; - - let return_type = match return_type { - Some(ident) => quote! { #ident }, - None => quote! { () }, - }; - - (quote! { - #(#[doc = #doc_lines])* - pub #asyncness fn #name(&self #(,#args)*) -> Result<#return_type, InvocationError> { - #(let #serialize_names = serialize_to_vec(&#serialize_names);)* - - let result = self.#raw_name(#(#arg_names),*); - - #return_wrapper - - result - } - - pub #asyncness fn #raw_name(&self #(,#raw_format_args)*) -> Result<#raw_format_return_type, InvocationError> { - let mut env = RuntimeInstanceData::default(); - let import_object = create_import_object(self.module.store(), &env); - let instance = Instance::new(&self.module, &import_object).unwrap(); - env.init_with_instance(&instance).unwrap(); - - #(let #serialize_names = export_to_guest_raw(&env, #serialize_names);)* - - let function = instance - .exports - .get_native_function::<#wasm_arg_types, #wasm_return_type>(#fp_gen_name) - .map_err(|_| InvocationError::FunctionNotExported)?; - - let result = function.call(#(#arg_names.to_abi()),*)?; - - #raw_return_wrapper - - Ok(result) - } - #newline - #newline - #newline - }) - .to_tokens(tokens) +fn format_import_arg(name: &str, ty: &TypeIdent, types: &TypeMap) -> String { + if ty.is_primitive() { + format!("let {name} = WasmAbi::from_abi({name});") + } else { + let ty = format_ident(ty, types); + format!("let {name} = import_from_guest::<{ty}>(env, {name});") } } -struct RuntimeArgImport<'a>(&'a FunctionArg); - -impl ToTokens for RuntimeArgImport<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = format_ident!("{}", self.0.name); - let ty_name = &self.0.ty; - - let importer = if self.0.ty.is_primitive() { - quote! { WasmAbi::from_abi(#name) } - } else { - quote! { import_from_guest::<#ty_name>(env, #name) } - }; - - quote! { - let #name = #importer; +fn format_export_function(function: &Function, types: &TypeMap) -> String { + let name = &function.name; + let wasm_args = function + .args + .iter() + .map(|FunctionArg { name, ty }| format!(", {name}: {}", format_wasm_ident(ty))) + .collect::>() + .join(""); + + let wrapper_return_type = if function.is_async { + " -> FatPtr".to_owned() + } else { + match &function.return_type { + Some(ty) => format!(" -> {}", format_wasm_ident(ty)), + None => "".to_owned(), } - .to_tokens(tokens) - } -} - -struct RuntimeExportedFunction<'a>(&'a Function); - -impl ToTokens for RuntimeExportedFunction<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Function { - name, - args, - is_async, - return_type, - .. - } = self.0; - - let underscore_name = format_ident!("_{}", name); - let input_args = args.iter().map(WasmArg); - let wrapper_return_type = if *is_async { - quote! { -> FatPtr } - } else { - match return_type { - Some(ty) => { - let ty = WasmType(ty); - quote! { -> #ty } - } - None => TokenStream::default(), - } - }; - - let import_args = args.iter().map(RuntimeArgImport); + }; - let impl_func_name = format_ident!("{}", name); - let arg_idents = args.iter().map(|a| format_ident!("{}", a.name)); - let func_call = quote!(super::#impl_func_name(#(#arg_idents),*)); - - let wrapper = if *is_async { - quote! { - let env = env.clone(); - let async_ptr = create_future_value(&env); - let handle = tokio::runtime::Handle::current(); - handle.spawn(async move { - let result = result.await; - let result_ptr = export_to_guest(&env, &result); - env.guest_resolve_async_value(async_ptr, result_ptr); - }); - async_ptr - } - } else { - match return_type { - None => TokenStream::default(), - Some(ty) if ty.is_primitive() => quote! { result.to_abi() }, - _ => quote! { export_to_guest(env, &result) }, - } - }; - - let newline = Punct::new('\n', proc_macro2::Spacing::Alone); - - (quote! { - pub fn #underscore_name(env: &RuntimeInstanceData #(,#input_args)*) #wrapper_return_type { - #(#import_args)* + let import_args = function + .args + .iter() + .map(|arg| format_import_arg(&arg.name, &arg.ty, types)) + .collect::>() + .join("\n"); - let result = #func_call; - #wrapper - } - #newline - #newline - }).to_tokens(tokens) - } + let arg_names = function + .args + .iter() + .map(|arg| arg.name.as_ref()) + .collect::>() + .join(", "); + + let return_wrapper = if function.is_async { + r#"let env = env.clone(); + let async_ptr = create_future_value(&env); + let handle = tokio::runtime::Handle::current(); + handle.spawn(async move { + let result = result.await; + let result_ptr = export_to_guest(&env, &result); + env.guest_resolve_async_value(async_ptr, result_ptr); + }); + async_ptr"# + } else { + match &function.return_type { + None => "", + Some(ty) if ty.is_primitive() => "result.to_abi()", + _ => "export_to_guest(env, &result)", + } + }; + + format!( + r#"pub fn _{name}(env: &RuntimeInstanceData{wasm_args}){wrapper_return_type} {{ + {import_args} + let result = super::{name}({arg_names}); + {return_wrapper} +}}"# + ) } fn generate_function_bindings( import_functions: FunctionList, export_functions: FunctionList, + types: &TypeMap, path: &str, ) { - let newline = Punct::new('\n', proc_macro2::Spacing::Alone); let create_import_object_func = generate_create_import_object_func(&import_functions); - let imports = import_functions.iter().map(RuntimeExportedFunction); - let exports = export_functions.iter().map(RuntimeImportedFunction); - - let full = rustfmt_wrapper::rustfmt(quote! { - use super::types::*; - use fp_bindgen_support::{ - common::{mem::FatPtr, abi::WasmAbi}, - host::{ - errors::{InvocationError, RuntimeError}, - mem::{export_to_guest, export_to_guest_raw, import_from_guest, import_from_guest_raw, deserialize_from_slice, serialize_to_vec}, - r#async::{create_future_value, future::ModuleRawFuture, resolve_async_value}, - runtime::RuntimeInstanceData, - }, - }; - use wasmer::{imports, Function, ImportObject, Instance, Module, Store, WasmerEnv}; - #newline - #newline - pub struct Runtime { - module: Module, - } - #newline - #newline - impl Runtime { - pub fn new(wasm_module: impl AsRef<[u8]>) -> Result { - let store = Self::default_store(); - let module = Module::new(&store, wasm_module)?; - - Ok(Self { module }) - } - #newline - #newline - #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] - fn default_store() -> wasmer::Store { - let compiler = wasmer_compiler_cranelift::Cranelift::default(); - let engine = wasmer_engine_universal::Universal::new(compiler).engine(); - Store::new(&engine) - } - #newline - #newline - #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] - fn default_store() -> wasmer::Store { - let compiler = wasmer_compiler_singlepass::Singlepass::default(); - let engine = wasmer_engine_universal::Universal::new(compiler).engine(); - Store::new(&engine) - } - #newline - #newline - #(#exports)* - } - #newline - #newline - - #create_import_object_func - - #newline - #newline - #(#imports)* - - }) + let imports = import_functions + .iter() + .map(|function| format_export_function(function, types)) + .collect::>() + .join("\n\n"); + let exports = export_functions + .iter() + .map(|function| format_import_function(function, types)) + .collect::>() + .join("\n\n"); + + let full = rustfmt_wrapper::rustfmt(format!(r#"use super::types::*; +use fp_bindgen_support::{{ + common::{{mem::FatPtr, abi::WasmAbi}}, + host::{{ + errors::{{InvocationError, RuntimeError}}, + mem::{{export_to_guest, export_to_guest_raw, import_from_guest, import_from_guest_raw, deserialize_from_slice, serialize_to_vec}}, + r#async::{{create_future_value, future::ModuleRawFuture, resolve_async_value}}, + runtime::RuntimeInstanceData, + }}, +}}; +use wasmer::{{imports, Function, ImportObject, Instance, Module, Store, WasmerEnv}}; + +pub struct Runtime {{ + module: Module, +}} + +impl Runtime {{ + pub fn new(wasm_module: impl AsRef<[u8]>) -> Result {{ + let store = Self::default_store(); + let module = Module::new(&store, wasm_module)?; + Ok(Self {{ module }}) + }} + + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + fn default_store() -> wasmer::Store {{ + let compiler = wasmer_compiler_cranelift::Cranelift::default(); + let engine = wasmer_engine_universal::Universal::new(compiler).engine(); + Store::new(&engine) + }} + + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] + fn default_store() -> wasmer::Store {{ + let compiler = wasmer_compiler_singlepass::Singlepass::default(); + let engine = wasmer_engine_universal::Universal::new(compiler).engine(); + Store::new(&engine) + }} + + {exports} +}} + +{create_import_object_func} + +{imports} +"#)) .unwrap(); write_bindings_file(format!("{}/bindings.rs", path), full); @@ -398,23 +315,3 @@ where { fs::write(&file_path, &contents).expect("Could not write bindings file"); } - -#[cfg(test)] -mod test { - use super::WasmArg; - use crate::{functions::FunctionArg, types::TypeIdent}; - use quote::ToTokens; - - #[test] - fn test_function_arg_to_tokens() { - let arg = FunctionArg { - name: "foobar".into(), - ty: TypeIdent::from("String"), - }; - let arg = WasmArg(&arg); - - let stringified = arg.into_token_stream().to_string(); - - pretty_assertions::assert_eq!(&stringified, "foobar : FatPtr"); - } -} diff --git a/fp-bindgen/src/types/mod.rs b/fp-bindgen/src/types/mod.rs index a5a5114f..75c40be3 100644 --- a/fp-bindgen/src/types/mod.rs +++ b/fp-bindgen/src/types/mod.rs @@ -1,5 +1,4 @@ use crate::primitives::Primitive; -use quote::{quote, ToTokens}; use std::{collections::BTreeMap, hash::Hash}; use syn::{Item, TypeParam, TypeParamBound}; @@ -69,33 +68,6 @@ impl Type { } } -impl ToTokens for Type { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - (match self { - Type::Alias(name, _) | Type::Custom(CustomType { rs_ty: name, .. }) => { - let ty = syn::parse_str::(name).unwrap(); - quote! { #ty } - } - Type::Container(name, ident) | Type::List(name, ident) => { - let name = syn::parse_str::(name).unwrap(); - quote! { #name<#ident> } - } - Type::Struct(Struct { ident, .. }) | Type::Enum(Enum { ident, .. }) => { - quote! { #ident } - } - Type::Map(name, k, v) => { - let name = syn::parse_str::(name).unwrap(); - quote! { #name<#k, #v> } - } - Type::Primitive(primitive) => quote! { #primitive }, - Type::String => quote! { String }, - Type::Tuple(items) => quote! { (#(#items),*) }, - Type::Unit => quote! { () }, - }) - .to_tokens(tokens) - } -} - pub(crate) fn format_bounds(ty: &TypeParam) -> Vec { ty.bounds .iter() diff --git a/fp-bindgen/src/types/type_ident.rs b/fp-bindgen/src/types/type_ident.rs index 9d8e9c91..922cd522 100644 --- a/fp-bindgen/src/types/type_ident.rs +++ b/fp-bindgen/src/types/type_ident.rs @@ -1,6 +1,4 @@ use crate::primitives::Primitive; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; use std::{ convert::{Infallible, TryFrom}, fmt::Display, @@ -130,38 +128,6 @@ impl PartialOrd for TypeIdent { } } -impl ToTokens for TypeIdent { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = syn::parse_str::(&self.name).unwrap(); - if self.generic_args.is_empty() { - quote! { #name } - } else { - let args = self - .generic_args - .iter() - .map(|(arg, _)| arg) - .collect::>(); - let bounds = self - .generic_args - .iter() - .map(|(_, bounds)| { - let ident_bounds = bounds - .iter() - .map(|b| Ident::new(b, Span::call_site())) - .collect::>(); - if ident_bounds.is_empty() { - quote! {} - } else { - quote! { : #(#ident_bounds)+* } - } - }) - .collect::>(); - quote! { #name<#(#args#bounds),*> } - } - .to_tokens(tokens) - } -} - impl TryFrom<&syn::Type> for TypeIdent { type Error = String; @@ -243,42 +209,6 @@ fn is_runtime_bound(bound: &str) -> bool { mod tests { use super::*; - #[test] - fn type_ident_to_tokens() { - let t = TypeIdent::new("foo", vec![]); - assert_eq!(format!("{}", quote! { #t }), "foo"); - - let t = TypeIdent::new("foo", vec![(TypeIdent::new("T", vec![]), vec![])]); - assert_eq!(format!("{}", quote! { #t }), "foo < T >"); - - let t = TypeIdent::new( - "foo", - vec![(TypeIdent::new("T", vec![]), vec!["Debug".into()])], - ); - assert_eq!(format!("{}", quote! { #t }), "foo < T : Debug >"); - - let t = TypeIdent::new( - "foo", - vec![( - TypeIdent::new("T", vec![]), - vec!["Debug".into(), "Display".into()], - )], - ); - assert_eq!(format!("{}", quote! { #t }), "foo < T : Debug + Display >"); - - let t = TypeIdent::new( - "foo", - vec![ - (TypeIdent::new("K", vec![]), vec!["Debug".into()]), - (TypeIdent::new("V", vec![]), vec!["Display".into()]), - ], - ); - assert_eq!( - format!("{}", quote! { #t }), - "foo < K : Debug , V : Display >" - ); - } - #[test] fn type_ident_from_syn_type() { let ty = syn::parse_str::("u32").unwrap();