diff --git a/examples/example-deno-runtime/tests.ts b/examples/example-deno-runtime/tests.ts index a0cee463..2bd7b20d 100644 --- a/examples/example-deno-runtime/tests.ts +++ b/examples/example-deno-runtime/tests.ts @@ -7,6 +7,7 @@ import { import { loadPlugin } from "./loader.ts"; import type { Exports, Imports } from "../example-protocol/bindings/ts-runtime/index.ts"; import type { + ExplicitBoundPoint, FpAdjacentlyTagged, FpFlatten, FpInternallyTagged, @@ -28,6 +29,10 @@ import {Result} from "../example-protocol/bindings/ts-runtime/types.ts"; let voidFunctionCalled = false; const imports: Imports = { + importExplicitBoundPoint: (arg: ExplicitBoundPoint) => { + assertEquals(arg.value, 123); + }, + importFpAdjacentlyTagged: (arg: FpAdjacentlyTagged): FpAdjacentlyTagged => { assertEquals(arg, { type: "Bar", payload: "Hello, plugin!" }); return { type: "Baz", payload: { a: -8, b: 64 } }; diff --git a/examples/example-protocol/src/assets/rust_plugin_test/expected_import.rs b/examples/example-protocol/src/assets/rust_plugin_test/expected_import.rs index bce4b02f..cd665e88 100644 --- a/examples/example-protocol/src/assets/rust_plugin_test/expected_import.rs +++ b/examples/example-protocol/src/assets/rust_plugin_test/expected_import.rs @@ -1,5 +1,8 @@ use crate::types::*; +#[fp_bindgen_support::fp_import_signature] +pub fn import_explicit_bound_point(arg: ExplicitBoundPoint); + #[fp_bindgen_support::fp_import_signature] pub fn import_fp_adjacently_tagged(arg: FpAdjacentlyTagged) -> FpAdjacentlyTagged; diff --git a/examples/example-protocol/src/assets/rust_plugin_test/expected_types.rs b/examples/example-protocol/src/assets/rust_plugin_test/expected_types.rs index ad60fca6..cb768211 100644 --- a/examples/example-protocol/src/assets/rust_plugin_test/expected_types.rs +++ b/examples/example-protocol/src/assets/rust_plugin_test/expected_types.rs @@ -31,6 +31,12 @@ pub struct DocExampleStruct { pub r#type: String, } +/// A point of an arbitrary type, with an explicit 'Serializable' bound. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct ExplicitBoundPoint { + pub value: T, +} + /// This struct is also not referenced by any function or data structure, but /// it will show up because there is an explicit `use` statement for it in the /// `fp_import!` macro. 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 4a0e2b17..4de3cc50 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 @@ -767,6 +767,7 @@ fn create_import_object(store: &Store, env: &RuntimeInstanceData) -> ImportObjec 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) , @@ -803,6 +804,11 @@ fn create_import_object(store: &Store, env: &RuntimeInstanceData) -> ImportObjec } } +pub fn _import_explicit_bound_point(env: &RuntimeInstanceData, arg: FatPtr) { + let arg = import_from_guest::>(env, arg); + let result = super::import_explicit_bound_point(arg); +} + pub fn _import_fp_adjacently_tagged(env: &RuntimeInstanceData, arg: FatPtr) -> FatPtr { let arg = import_from_guest::(env, arg); let result = super::import_fp_adjacently_tagged(arg); diff --git a/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_types.rs b/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_types.rs index ad60fca6..cb768211 100644 --- a/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_types.rs +++ b/examples/example-protocol/src/assets/rust_wasmer_runtime_test/expected_types.rs @@ -31,6 +31,12 @@ pub struct DocExampleStruct { pub r#type: String, } +/// A point of an arbitrary type, with an explicit 'Serializable' bound. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct ExplicitBoundPoint { + pub value: T, +} + /// This struct is also not referenced by any function or data structure, but /// it will show up because there is an explicit `use` statement for it in the /// `fp_import!` macro. 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 e8e073dd..4b06d539 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 @@ -11,6 +11,7 @@ import type { Body, DocExampleEnum, DocExampleStruct, + ExplicitBoundPoint, ExplicitedlyImportedType, FlattenedStruct, FloatingPoint, @@ -44,6 +45,7 @@ import type { type FatPtr = bigint; export type Imports = { + importExplicitBoundPoint: (arg: ExplicitBoundPoint) => void; importFpAdjacentlyTagged: (arg: FpAdjacentlyTagged) => FpAdjacentlyTagged; importFpEnum: (arg: FpVariantRenaming) => FpVariantRenaming; importFpFlatten: (arg: FpFlatten) => FpFlatten; @@ -247,6 +249,10 @@ export async function createRuntime( const { instance } = await WebAssembly.instantiate(plugin, { fp: { + __fp_gen_import_explicit_bound_point: (arg_ptr: FatPtr) => { + const arg = parseObject>(arg_ptr); + importFunctions.importExplicitBoundPoint(arg); + }, __fp_gen_import_fp_adjacently_tagged: (arg_ptr: FatPtr): FatPtr => { const arg = parseObject(arg_ptr); return serializeObject(importFunctions.importFpAdjacentlyTagged(arg)); diff --git a/examples/example-protocol/src/assets/ts_runtime_test/expected_types.ts b/examples/example-protocol/src/assets/ts_runtime_test/expected_types.ts index 36662786..78006268 100644 --- a/examples/example-protocol/src/assets/ts_runtime_test/expected_types.ts +++ b/examples/example-protocol/src/assets/ts_runtime_test/expected_types.ts @@ -42,6 +42,13 @@ export type DocExampleStruct = { type: string; }; +/** + * A point of an arbitrary type, with an explicit 'Serializable' bound. + */ +export type ExplicitBoundPoint = { + value: T; +}; + /** * This struct is also not referenced by any function or data structure, but * it will show up because there is an explicit `use` statement for it in the diff --git a/examples/example-protocol/src/main.rs b/examples/example-protocol/src/main.rs index e67e081a..37f5afc1 100644 --- a/examples/example-protocol/src/main.rs +++ b/examples/example-protocol/src/main.rs @@ -77,6 +77,7 @@ fp_import! { // // See `types/generics.rs` for more info. fn import_generics(arg: StructWithGenerics) -> StructWithGenerics; + fn import_explicit_bound_point(arg: ExplicitBoundPoint); // Passing custom types with property/variant renaming. // diff --git a/examples/example-protocol/src/types/generics.rs b/examples/example-protocol/src/types/generics.rs index 978939c9..f1ad2a34 100644 --- a/examples/example-protocol/src/types/generics.rs +++ b/examples/example-protocol/src/types/generics.rs @@ -13,6 +13,12 @@ pub struct Point { pub value: T, } +/// A point of an arbitrary type, with an explicit 'Serializable' bound. +#[derive(Serializable)] +pub struct ExplicitBoundPoint { + pub value: T, +} + #[derive(Serializable)] pub struct StructWithGenerics { pub list: Vec, diff --git a/macros/src/serializable.rs b/macros/src/serializable.rs index 173d53de..26d723ef 100644 --- a/macros/src/serializable.rs +++ b/macros/src/serializable.rs @@ -1,12 +1,13 @@ use crate::utils::{extract_path_from_type, parse_type_item}; use proc_macro::TokenStream; use quote::quote; -use std::collections::HashSet; -use syn::Path; +use std::collections::{HashMap, HashSet}; +use syn::punctuated::Punctuated; +use syn::{Path, TypeParamBound}; pub(crate) fn impl_derive_serializable(item: TokenStream) -> TokenStream { let item_str = item.to_string(); - let (item_name, item, generics) = parse_type_item(item); + let (item_name, item, mut generics) = parse_type_item(item); let field_types: HashSet = match item { syn::Item::Enum(ty) => ty @@ -37,21 +38,6 @@ pub(crate) fn impl_derive_serializable(item: TokenStream) -> TokenStream { _ => HashSet::default(), }; - let collect_types = if field_types.is_empty() { - quote! { types.entry(Self::ident()).or_insert_with(Self::ty); } - } else { - let field_types = field_types.iter(); - let generic_params = generics.type_params(); - quote! { - if let std::collections::btree_map::Entry::Vacant(entry) = types.entry(Self::ident()) { - entry.insert(Self::ty()); - #( #field_types::collect_types(types); )* - } - - #( #generic_params::collect_types(types); )* - } - }; - let ident = { let item_name = item_name.to_string(); if generics.params.is_empty() { @@ -67,13 +53,67 @@ pub(crate) fn impl_derive_serializable(item: TokenStream) -> TokenStream { } }; - let where_clause = if generics.params.is_empty() { + // Remove any bounds from the generic types and store them separately. + // Otherwise, collect_types will be called like `Foo::::collect_types()` and where clauses + // will be incorrect, too. + let mut bounds = HashMap::new(); + for param in generics.type_params_mut() { + // For every parameter we want to either extract the existing trait bounds, or, if there + // were no existing bounds, we will mark the parameter as having no bounds. + + param.bounds = if param.bounds.is_empty() { + // No existing bound found, so mark this parameter as having 'None' as a bound + bounds.insert(param.ident.clone(), None); + Punctuated::new() + } else { + param + .clone() + .bounds + .into_iter() + .filter(|bound| { + match &bound { + TypeParamBound::Trait(tr) => { + // Extract the bound and remove it from the param + bounds.insert(param.ident.clone(), Some(tr.clone())); + false + } + // Ignore other bound types (lifetimes) for now + _ => true, + } + }) + .collect() + }; + } + + let where_clause = if bounds.is_empty() { quote! {} } else { - let params = generics.type_params(); + let params = bounds.keys(); + + // Add the appropriate bounds to the where clause + // If no existing bounds were present, we will add the 'Serializable' bound. + let param_bounds = bounds.values().map(|bound| match bound { + Some(bound) => quote! { : #bound }, + None => quote! { : Serializable }, + }); quote! { where - #( #params: Serializable ),* + #( #params#param_bounds ),* + } + }; + + let collect_types = if field_types.is_empty() { + quote! { types.entry(Self::ident()).or_insert_with(Self::ty); } + } else { + let field_types = field_types.iter(); + let generic_params = generics.type_params(); + quote! { + if let std::collections::btree_map::Entry::Vacant(entry) = types.entry(Self::ident()) { + entry.insert(Self::ty()); + #( #field_types::collect_types(types); )* + } + + #( #generic_params::collect_types(types); )* } }; @@ -92,5 +132,6 @@ pub(crate) fn impl_derive_serializable(item: TokenStream) -> TokenStream { } } }; + implementation.into() }