Skip to content

Commit

Permalink
Properly deal with generic bounds in codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
sagacity committed Jul 6, 2022
1 parent 79795cc commit beb1cb9
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 21 deletions.
5 changes: 5 additions & 0 deletions examples/example-deno-runtime/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -28,6 +29,10 @@ import {Result} from "../example-protocol/bindings/ts-runtime/types.ts";
let voidFunctionCalled = false;

const imports: Imports = {
importExplicitBoundPoint: (arg: ExplicitBoundPoint<number>) => {
assertEquals(arg.value, 123);
},

importFpAdjacentlyTagged: (arg: FpAdjacentlyTagged): FpAdjacentlyTagged => {
assertEquals(arg, { type: "Bar", payload: "Hello, plugin!" });
return { type: "Baz", payload: { a: -8, b: 64 } };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::types::*;

#[fp_bindgen_support::fp_import_signature]
pub fn import_explicit_bound_point(arg: ExplicitBoundPoint<u64>);

#[fp_bindgen_support::fp_import_signature]
pub fn import_fp_adjacently_tagged(arg: FpAdjacentlyTagged) -> FpAdjacentlyTagged;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) ,
Expand Down Expand Up @@ -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::<ExplicitBoundPoint<u64>>(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::<FpAdjacentlyTagged>(env, arg);
let result = super::import_fp_adjacently_tagged(arg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
Body,
DocExampleEnum,
DocExampleStruct,
ExplicitBoundPoint,
ExplicitedlyImportedType,
FlattenedStruct,
FloatingPoint,
Expand Down Expand Up @@ -44,6 +45,7 @@ import type {
type FatPtr = bigint;

export type Imports = {
importExplicitBoundPoint: (arg: ExplicitBoundPoint<number>) => void;
importFpAdjacentlyTagged: (arg: FpAdjacentlyTagged) => FpAdjacentlyTagged;
importFpEnum: (arg: FpVariantRenaming) => FpVariantRenaming;
importFpFlatten: (arg: FpFlatten) => FpFlatten;
Expand Down Expand Up @@ -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<ExplicitBoundPoint<number>>(arg_ptr);
importFunctions.importExplicitBoundPoint(arg);
},
__fp_gen_import_fp_adjacently_tagged: (arg_ptr: FatPtr): FatPtr => {
const arg = parseObject<FpAdjacentlyTagged>(arg_ptr);
return serializeObject(importFunctions.importFpAdjacentlyTagged(arg));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export type DocExampleStruct = {
type: string;
};

/**
* A point of an arbitrary type, with an explicit 'Serializable' bound.
*/
export type ExplicitBoundPoint<T> = {
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
Expand Down
1 change: 1 addition & 0 deletions examples/example-protocol/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fp_import! {
//
// See `types/generics.rs` for more info.
fn import_generics(arg: StructWithGenerics<u64>) -> StructWithGenerics<u64>;
fn import_explicit_bound_point(arg: ExplicitBoundPoint<u64>);

// Passing custom types with property/variant renaming.
//
Expand Down
6 changes: 6 additions & 0 deletions examples/example-protocol/src/types/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ pub struct Point<T> {
pub value: T,
}

/// A point of an arbitrary type, with an explicit 'Serializable' bound.
#[derive(Serializable)]
pub struct ExplicitBoundPoint<T: Serializable> {
pub value: T,
}

#[derive(Serializable)]
pub struct StructWithGenerics<T> {
pub list: Vec<T>,
Expand Down
83 changes: 62 additions & 21 deletions macros/src/serializable.rs
Original file line number Diff line number Diff line change
@@ -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<Path> = match item {
syn::Item::Enum(ty) => ty
Expand Down Expand Up @@ -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() {
Expand All @@ -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::<T: MyTrait>::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); )*
}
};

Expand All @@ -92,5 +132,6 @@ pub(crate) fn impl_derive_serializable(item: TokenStream) -> TokenStream {
}
}
};

implementation.into()
}

0 comments on commit beb1cb9

Please sign in to comment.