Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly deal with generic bounds in codegen #125

Merged
merged 3 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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