Skip to content

Commit

Permalink
Properly deal with generic bounds in codegen (#125)
Browse files Browse the repository at this point in the history
* Properly deal with generic bounds in codegen

* Use BTreeMap to preserve bound order

* Store bounds in TypeIdent so they can be propagated to generated types
  • Loading branch information
sagacity authored Jul 18, 2022
1 parent 79795cc commit 7fa8e0b
Show file tree
Hide file tree
Showing 20 changed files with 459 additions and 95 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
- Various smaller bugfixes.
- **Deprecation**: `BindingsType::TsRuntime` is now deprecated in favor of
`BindingsType::TsRuntimeWithExtendedConfig`.
- Fix #88: Bounds are propagated correctly to generated types (with the exception of the compile-time only `Serializable` bound).
- Fix #88: Deal with the Unit (`()`) type.
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 explicit trait bounds.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ExplicitBoundPoint<T: std::fmt::Debug + std::fmt::Display> {
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 explicit trait bounds.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ExplicitBoundPoint<T: std::fmt::Debug + std::fmt::Display> {
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 explicit trait bounds.
*/
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 explicit trait bounds.
#[derive(Serializable)]
pub struct ExplicitBoundPoint<T: Serializable + std::fmt::Debug + std::fmt::Display> {
pub value: T,
}

#[derive(Serializable)]
pub struct StructWithGenerics<T> {
pub list: Vec<T>,
Expand Down
3 changes: 3 additions & 0 deletions examples/example-rust-runtime/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ fn import_void_function_empty_result() -> Result<(), u32> {
}
fn import_void_function_empty_return() -> () {}

fn import_explicit_bound_point(arg: ExplicitBoundPoint<u64>) {
todo!()
}
fn import_primitive_bool(arg: bool) -> bool {
todo!()
}
Expand Down
2 changes: 1 addition & 1 deletion fp-bindgen/src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ fn display_warnings(
.flat_map(|ty| ty.fields.iter().map(|field| &field.ty)),
);
warn_about_custom_serializer_usage(
all_idents.flat_map(|ident| ident.generic_args.iter()),
all_idents.flat_map(|ident| ident.generic_args.iter().map(|(arg, _)| arg)),
"generic argument",
types,
);
Expand Down
49 changes: 31 additions & 18 deletions fp-bindgen/src/generators/rust_plugin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::types::is_runtime_bound;
use crate::{
functions::FunctionList,
types::{CargoDependency, Enum, Field, Struct, Type, TypeIdent, TypeMap},
Expand Down Expand Up @@ -230,27 +231,33 @@ fn format_type_with_ident(ty: &Type, ident: &TypeIdent, types: &TypeMap) -> Stri
match ty {
Type::Alias(name, _) => name.clone(),
Type::Container(name, _) | Type::List(name, _) => {
let arg = ident
let (arg, bounds) = ident
.generic_args
.first()
.expect("Identifier was expected to contain a generic argument");
format!("{}<{}>", name, format_ident(arg, types))
format!(
"{}<{}{}>",
name,
format_ident(arg, types),
format_bounds(bounds)
)
}
Type::Custom(custom) => custom.rs_ty.clone(),
Type::Map(name, _, _) => {
let arg1 = ident
.generic_args
.first()
.expect("Identifier was expected to contain a generic argument");
let arg2 = ident
let (arg1, bounds1) = ident.generic_args.first().expect(
"Identifier was expected to contain two generic arguments, but none were provided",
);
let (arg2, bounds2) = ident
.generic_args
.get(1)
.expect("Identifier was expected to contain two arguments");
.expect("Identifier was expected to contain two generic arguments, but only one was provided");
format!(
"{}<{}, {}>",
"{}<{}{}, {}{}>",
name,
format_ident(arg1, types),
format_ident(arg2, types)
format_bounds(bounds1),
format_ident(arg2, types),
format_bounds(bounds2),
)
}
Type::Tuple(items) => format!(
Expand All @@ -266,6 +273,15 @@ fn format_type_with_ident(ty: &Type, ident: &TypeIdent, types: &TypeMap) -> Stri
}
}

fn format_bounds(bounds: &[String]) -> String {
bounds
.iter()
.filter(|bound| is_runtime_bound(bound))
.cloned()
.collect::<Vec<_>>()
.join(" + ")
}

fn generate_imported_function_bindings(
import_functions: FunctionList,
types: &TypeMap,
Expand Down Expand Up @@ -474,27 +490,24 @@ fn create_struct_definition(ty: &Struct, types: &TypeMap) -> String {
serde_annotation
);

// Format ident, include bounds and skip compile-time only bounds
let ident = ty.ident.format(true);
if is_tuple_struct {
if fields.len() > 1 {
format!(
"{}pub struct {}(\n{}\n);",
annotations,
ty.ident,
ident,
fields.join("\n").trim_start_matches('\n')
)
} else {
format!(
"{}pub struct {}({});",
annotations,
ty.ident,
fields.join(" ")
)
format!("{}pub struct {}({});", annotations, ident, fields.join(" "))
}
} else {
format!(
"{}pub struct {} {{\n{}\n}}",
annotations,
ty.ident,
ident,
fields.join("\n").trim_start_matches('\n')
)
}
Expand Down
16 changes: 8 additions & 8 deletions fp-bindgen/src/generators/ts_runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ fn create_enum_definition(ty: &Enum, types: &TypeMap) -> String {
format!(
"{}export type {} =\n{};",
join_lines(&format_docs(&ty.doc_lines), String::to_owned),
ty.ident,
ty.ident.format(false),
variants.trim_end()
)
}
Expand All @@ -862,7 +862,7 @@ fn create_struct_definition(ty: &Struct, types: &TypeMap) -> String {
format!(
"{}export type {} = {{\n{}}}{};",
join_lines(&format_docs(&ty.doc_lines), String::to_owned),
ty.ident,
ty.ident.format(false),
join_lines(
&format_struct_fields(
&fields.into_iter().cloned().collect::<Vec<_>>(),
Expand Down Expand Up @@ -904,7 +904,7 @@ fn format_struct_fields(fields: &[Field], types: &TypeMap, casing: Casing) -> Ve
let field_decl = match types.get(&field.ty) {
Some(Type::Container(name, _)) => {
let optional = if name == "Option" { "?" } else { "" };
let arg = field
let (arg, _) = field
.ty
.generic_args
.first()
Expand Down Expand Up @@ -955,7 +955,7 @@ fn format_type_with_ident(ty: &Type, ident: &TypeIdent, types: &TypeMap) -> Stri
match ty {
Type::Alias(name, _) => name.clone(),
Type::Container(name, _) => {
let arg = ident
let (arg, _) = ident
.generic_args
.first()
.expect("Identifier was expected to contain a generic argument");
Expand All @@ -971,7 +971,7 @@ fn format_type_with_ident(ty: &Type, ident: &TypeIdent, types: &TypeMap) -> Stri
let args: Vec<_> = ident
.generic_args
.iter()
.map(|arg| format_ident(arg, types))
.map(|(arg, _)| format_ident(arg, types))
.collect();
if args.is_empty() {
ident.name.clone()
Expand All @@ -980,18 +980,18 @@ fn format_type_with_ident(ty: &Type, ident: &TypeIdent, types: &TypeMap) -> Stri
}
}
Type::List(_, _) => {
let arg = ident
let (arg, _) = ident
.generic_args
.first()
.expect("Identifier was expected to contain a generic argument");
format!("Array<{}>", format_ident(arg, types))
}
Type::Map(_, _, _) => {
let arg1 = ident
let (arg1, _) = ident
.generic_args
.first()
.expect("Identifier was expected to contain a generic argument");
let arg2 = ident
let (arg2, _) = ident
.generic_args
.get(1)
.expect("Identifier was expected to contain two arguments");
Expand Down
Loading

0 comments on commit 7fa8e0b

Please sign in to comment.