Skip to content

Commit

Permalink
Generate WIT type declarations (#1704)
Browse files Browse the repository at this point in the history
* Add a `WitType::wit_name` associated function

Returns the type name to use in the WIT file.

* Fix comments in unit tests

They incorrectly referenced the `WitLoad` trait instead of the correct
`WitType` trait.

* Generate `wit_name` when deriving `WitType`

Generate a static string with the kebab-case name of the type.

* Add `WitType::wit_declaration` method

Generate the WIT snippet to declare the type.

* Allow retrieving WIT names and types of fields

Prepare to use them when generating the WIT declaration snippets of
fields.

* Add a getter for the types of the fields

Allow iterating over the `Type`s of the (non-skipped) fields.

* Generate `WitType::wit_type_declaration` methods

Generate the code with the method to print out a WIT snippet with the
type declaration.

* Create a `RegisterWitTypes` helper trait

A trait for heterogeneous lists that lists the dependencies of a WIT
type. These are other WIT types that should be declared in the same WIT
snippet.

* Add a `Dependencies` associated type to `WitType`

List other WIT types that need to be declared in order for the type's
declaration to be valid.

* Register dependencies recursively

Ensure that all indirect dependencies are registered to generate WIT
type declarations in a WIT snippet.

* Generate `WitType::Dependencies` associated types

Ensure derived `WitType` implementations list their WIT type
dependencies.

* Test generated WIT type declarations

Update the `WitType` unit tests to check that the generated WIT snippets
are correct.
  • Loading branch information
jvff authored Mar 7, 2024
1 parent 85c8367 commit a979911
Show file tree
Hide file tree
Showing 19 changed files with 757 additions and 53 deletions.
2 changes: 1 addition & 1 deletion linera-witty-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub fn derive_wit_type(input: TokenStream) -> TokenStream {
let specializations = apply_specialization_attribute(&mut input);

let body = match &input.data {
Data::Struct(struct_item) => wit_type::derive_for_struct(&struct_item.fields),
Data::Struct(struct_item) => wit_type::derive_for_struct(&input.ident, &struct_item.fields),
Data::Enum(enum_item) => wit_type::derive_for_enum(&input.ident, enum_item.variants.iter()),
Data::Union(_union_item) => {
abort!(input.ident, "Can't derive `WitType` for `union`s")
Expand Down
236 changes: 224 additions & 12 deletions linera-witty-macros/src/unit_tests/wit_type.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Unit tests for the `WitLoad` derive macro.
//! Unit tests for the `WitType` derive macro.

#![cfg(test)]

use super::{derive_for_enum, derive_for_struct};
use quote::quote;
use quote::{format_ident, quote};
use syn::{parse_quote, Fields, ItemEnum, ItemStruct};

/// Check the generated code for the body of the implementation of `WitLoad` for a unit struct.
/// Check the generated code for the body of the implementation of `WitType` for a unit struct.
#[test]
fn zero_sized_type() {
let input = Fields::Unit;
let output = derive_for_struct(&input);
let output = derive_for_struct(&format_ident!("ZeroSizedType"), &input);

let expected = quote! {
const SIZE: u32 = <linera_witty::HList![] as linera_witty::WitType>::SIZE;

type Layout = <linera_witty::HList![] as linera_witty::WitType>::Layout;
type Dependencies = linera_witty::HList![];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"zero-sized-type".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration =
String::from(concat!(" record " , "zero-sized-type" , " {\n"));
wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
}

/// Check the generated code for the body of the implementation of `WitLoad` for a named struct.
/// Check the generated code for the body of the implementation of `WitType` for a named struct.
#[test]
fn named_struct() {
let input: ItemStruct = parse_quote! {
Expand All @@ -33,31 +45,86 @@ fn named_struct() {
second: CustomType,
}
};
let output = derive_for_struct(&input.fields);
let output = derive_for_struct(&input.ident, &input.fields);

let expected = quote! {
const SIZE: u32 = <linera_witty::HList![u8, CustomType] as linera_witty::WitType>::SIZE;

type Layout = <linera_witty::HList![u8, CustomType] as linera_witty::WitType>::Layout;
type Dependencies = linera_witty::HList![u8, CustomType];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"type".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(concat!(" record " , "type" , " {\n"));

wit_declaration.push_str(" ");
wit_declaration.push_str("first");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<u8 as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("second");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<CustomType as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
}

/// Check the generated code for the body of the implementation of `WitLoad` for a tuple struct.
/// Check the generated code for the body of the implementation of `WitType` for a tuple struct.
#[test]
fn tuple_struct() {
let input: ItemStruct = parse_quote! {
struct Type(String, Vec<CustomType>, i64);
};
let output = derive_for_struct(&input.fields);
let output = derive_for_struct(&input.ident, &input.fields);

let expected = quote! {
const SIZE: u32 =
<linera_witty::HList![String, Vec<CustomType>, i64] as linera_witty::WitType>::SIZE;

type Layout =
<linera_witty::HList![String, Vec<CustomType>, i64] as linera_witty::WitType>::Layout;

type Dependencies = linera_witty::HList![String, Vec<CustomType>, i64];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"type".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(concat!(" record " , "type" , " {\n"));

wit_declaration.push_str(" ");
wit_declaration.push_str("inner0");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<String as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("inner1");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<Vec<CustomType> as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("inner2");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<i64 as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
Expand Down Expand Up @@ -127,12 +194,57 @@ fn enum_type() {
<linera_witty::HList![(), String] as linera_witty::WitType>::Layout
>>::Output
>>::Output>;

type Dependencies = linera_witty::HList![i8, CustomType, (), String];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"enum".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(
concat!(" ", "variant", " ", "enum" , " {\n"),
);

wit_declaration.push_str(" ");
wit_declaration.push_str("empty");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("tuple");
wit_declaration.push_str("(tuple<");
wit_declaration.push_str(
&<i8 as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(", ");
wit_declaration.push_str(
&<CustomType as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(">)");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("struct");
wit_declaration.push_str("(tuple<");
wit_declaration.push_str(
&<() as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(", ");
wit_declaration.push_str(
&<String as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(">)");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
}

/// Check the generated code for the body of the implementation of `WitLoad` for a named struct
/// Check the generated code for the body of the implementation of `WitType` for a named struct
/// with some ignored fields.
#[test]
fn named_struct_with_skipped_fields() {
Expand All @@ -150,18 +262,42 @@ fn named_struct_with_skipped_fields() {
ignored4: Vec<()>,
}
};
let output = derive_for_struct(&input.fields);
let output = derive_for_struct(&input.ident, &input.fields);

let expected = quote! {
const SIZE: u32 = <linera_witty::HList![u8, CustomType] as linera_witty::WitType>::SIZE;

type Layout = <linera_witty::HList![u8, CustomType] as linera_witty::WitType>::Layout;
type Dependencies = linera_witty::HList![u8, CustomType];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"type".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(concat!(" record " , "type" , " {\n"));

wit_declaration.push_str(" ");
wit_declaration.push_str("first");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<u8 as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("second");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<CustomType as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
}

/// Check the generated code for the body of the implementation of `WitLoad` for a tuple struct
/// Check the generated code for the body of the implementation of `WitType` for a tuple struct
/// with some ignored fields.
#[test]
fn tuple_struct_with_skipped_fields() {
Expand All @@ -176,14 +312,45 @@ fn tuple_struct_with_skipped_fields() {
i64,
);
};
let output = derive_for_struct(&input.fields);
let output = derive_for_struct(&input.ident, &input.fields);

let expected = quote! {
const SIZE: u32 =
<linera_witty::HList![String, Vec<CustomType>, i64] as linera_witty::WitType>::SIZE;

type Layout =
<linera_witty::HList![String, Vec<CustomType>, i64] as linera_witty::WitType>::Layout;

type Dependencies = linera_witty::HList![String, Vec<CustomType>, i64];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"type".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(concat!(" record " , "type" , " {\n"));

wit_declaration.push_str(" ");
wit_declaration.push_str("inner1");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<String as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("inner2");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<Vec<CustomType> as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("inner4");
wit_declaration.push_str(": ");
wit_declaration.push_str(&*<i64 as linera_witty::WitType>::wit_type_name());
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
Expand Down Expand Up @@ -260,6 +427,51 @@ fn enum_type_with_skipped_fields() {
<linera_witty::HList![(), String] as linera_witty::WitType>::Layout
>>::Output
>>::Output>;

type Dependencies = linera_witty::HList![i8, CustomType, (), String];

fn wit_type_name() -> std::borrow::Cow<'static, str> {
"enum".into()
}

fn wit_type_declaration() -> std::borrow::Cow<'static, str> {
let mut wit_declaration = String::from(
concat!(" ", "variant", " ", "enum" , " {\n"),
);

wit_declaration.push_str(" ");
wit_declaration.push_str("empty");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("tuple");
wit_declaration.push_str("(tuple<");
wit_declaration.push_str(
&<i8 as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(", ");
wit_declaration.push_str(
&<CustomType as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(">)");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" ");
wit_declaration.push_str("struct");
wit_declaration.push_str("(tuple<");
wit_declaration.push_str(
&<() as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(", ");
wit_declaration.push_str(
&<String as linera_witty::WitType>::wit_type_name(),
);
wit_declaration.push_str(">)");
wit_declaration.push_str(",\n");

wit_declaration.push_str(" }\n");
wit_declaration.into ()
}
};

assert_eq!(output.to_string(), expected.to_string());
Expand Down
Loading

0 comments on commit a979911

Please sign in to comment.