diff --git a/linera-witty-macros/src/lib.rs b/linera-witty-macros/src/lib.rs index fd30ebe64d3..cbf130ad4e1 100644 --- a/linera-witty-macros/src/lib.rs +++ b/linera-witty-macros/src/lib.rs @@ -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") diff --git a/linera-witty-macros/src/unit_tests/wit_type.rs b/linera-witty-macros/src/unit_tests/wit_type.rs index 42f945fa669..29c1e9038e1 100644 --- a/linera-witty-macros/src/unit_tests/wit_type.rs +++ b/linera-witty-macros/src/unit_tests/wit_type.rs @@ -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 = ::SIZE; type Layout = ::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! { @@ -33,24 +45,48 @@ 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 = ::SIZE; type Layout = ::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(&*::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(&*::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, i64); }; - let output = derive_for_struct(&input.fields); + let output = derive_for_struct(&input.ident, &input.fields); let expected = quote! { const SIZE: u32 = @@ -58,6 +94,37 @@ fn tuple_struct() { type Layout = , i64] as linera_witty::WitType>::Layout; + + type Dependencies = linera_witty::HList![String, Vec, 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(&*::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(&* 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(&*::wit_type_name()); + wit_declaration.push_str(",\n"); + + wit_declaration.push_str(" }\n"); + wit_declaration.into () + } }; assert_eq!(output.to_string(), expected.to_string()); @@ -127,12 +194,57 @@ fn enum_type() { ::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( + &::wit_type_name(), + ); + wit_declaration.push_str(", "); + wit_declaration.push_str( + &::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( + &::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() { @@ -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 = ::SIZE; type Layout = ::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(&*::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(&*::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() { @@ -176,7 +312,7 @@ 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 = @@ -184,6 +320,37 @@ fn tuple_struct_with_skipped_fields() { type Layout = , i64] as linera_witty::WitType>::Layout; + + type Dependencies = linera_witty::HList![String, Vec, 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(&*::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(&* 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(&*::wit_type_name()); + wit_declaration.push_str(",\n"); + + wit_declaration.push_str(" }\n"); + wit_declaration.into () + } }; assert_eq!(output.to_string(), expected.to_string()); @@ -260,6 +427,51 @@ fn enum_type_with_skipped_fields() { ::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( + &::wit_type_name(), + ); + wit_declaration.push_str(", "); + wit_declaration.push_str( + &::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( + &::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()); diff --git a/linera-witty-macros/src/util/fields.rs b/linera-witty-macros/src/util/fields.rs index ff15ebf2966..e02af399cc2 100644 --- a/linera-witty-macros/src/util/fields.rs +++ b/linera-witty-macros/src/util/fields.rs @@ -3,10 +3,11 @@ //! Helper types to process [`Fields`] from `struct`s and `enum` variants. +use heck::ToKebabCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::{borrow::Cow, ops::Deref}; -use syn::{Field, Fields, Ident, Meta, MetaList}; +use syn::{spanned::Spanned, Field, Fields, Ident, LitStr, Meta, MetaList, Type}; /// A helper type with information about a list of [`Fields`]. pub struct FieldsInformation<'input> { @@ -27,10 +28,26 @@ impl<'input> FieldsInformation<'input> { self.fields.iter().map(FieldInformation::name) } + /// Returns an iterator over the types of the non-skipped fields. + pub fn types(&self) -> impl Iterator + Clone + '_ { + self.non_skipped_fields().map(FieldInformation::field_type) + } + + /// Returns an iterator over the WIT compatible names of the non-skipped fields. + pub fn wit_names(&self) -> impl Iterator + '_ { + self.non_skipped_fields().map(FieldInformation::wit_name) + } + + /// Returns an iterator over the code to obtain the WIT type names of the non-skipped fields. + pub fn wit_type_names(&self) -> impl Iterator + '_ { + self.non_skipped_fields() + .map(FieldInformation::wit_type_name) + } + /// Returns the code with a pattern to match a heterogenous list using the `field_names` as /// bindings. pub fn hlist_type(&self) -> TokenStream { - let field_types = self.non_skipped_fields().map(|field| &field.ty); + let field_types = self.types(); quote! { linera_witty::HList![#( #field_types ),*] } } @@ -122,6 +139,7 @@ impl<'input> From<&'input Fields> for FieldsInformation<'input> { pub struct FieldInformation<'input> { field: &'input Field, name: Cow<'input, Ident>, + wit_name: LitStr, is_skipped: bool, } @@ -131,6 +149,23 @@ impl FieldInformation<'_> { &self.name } + /// Returns the type of this field. + pub fn field_type(&self) -> &Type { + &self.field.ty + } + + /// Returns the string literal with the WIT compatible name. + pub fn wit_name(&self) -> &LitStr { + &self.wit_name + } + + /// Returns the code to obtain the field's WIT type name. + pub fn wit_type_name(&self) -> TokenStream { + let field_type = &self.field.ty; + + quote! { <#field_type as linera_witty::WitType>::wit_type_name() } + } + /// Returns `true` if this field was marked to be skipped. pub fn is_skipped(&self) -> bool { self.is_skipped @@ -153,6 +188,16 @@ impl<'input> From<(usize, &'input Field)> for FieldInformation<'input> { .map(Cow::Borrowed) .unwrap_or_else(|| Cow::Owned(format_ident!("field{index}"))); + let wit_name = LitStr::new( + &field + .ident + .as_ref() + .map(Ident::to_string) + .unwrap_or_else(|| format!("inner{index}")) + .to_kebab_case(), + field.span(), + ); + let is_skipped = field.attrs.iter().any(|attribute| { matches!( &attribute.meta, @@ -164,6 +209,7 @@ impl<'input> From<(usize, &'input Field)> for FieldInformation<'input> { FieldInformation { field, name, + wit_name, is_skipped, } } diff --git a/linera-witty-macros/src/wit_type.rs b/linera-witty-macros/src/wit_type.rs index c8d46a76e03..5bd4467140c 100644 --- a/linera-witty-macros/src/wit_type.rs +++ b/linera-witty-macros/src/wit_type.rs @@ -4,23 +4,51 @@ //! Derivation of the `WitType` trait. use crate::util::FieldsInformation; +use heck::ToKebabCase; use proc_macro2::TokenStream; use proc_macro_error::abort; use quote::quote; -use syn::{Ident, Variant}; +use syn::{Ident, LitStr, Variant}; #[path = "unit_tests/wit_type.rs"] mod tests; /// Returns the body of the `WitType` implementation for the Rust `struct` with the specified /// `fields`. -pub fn derive_for_struct<'input>(fields: impl Into>) -> TokenStream { - let fields_hlist = fields.into().hlist_type(); +pub fn derive_for_struct<'input>( + name: &Ident, + fields: impl Into>, +) -> TokenStream { + let wit_name = LitStr::new(&name.to_string().to_kebab_case(), name.span()); + let fields = fields.into(); + let fields_hlist = fields.hlist_type(); + let field_wit_names = fields.wit_names(); + let field_wit_type_names = fields.wit_type_names(); quote! { const SIZE: u32 = <#fields_hlist as linera_witty::WitType>::SIZE; type Layout = <#fields_hlist as linera_witty::WitType>::Layout; + type Dependencies = #fields_hlist; + + fn wit_type_name() -> std::borrow::Cow<'static, str> { + #wit_name.into() + } + + fn wit_type_declaration() -> std::borrow::Cow<'static, str> { + let mut wit_declaration = String::from(concat!(" record ", #wit_name, " {\n")); + + #( + wit_declaration.push_str(" "); + wit_declaration.push_str(#field_wit_names); + wit_declaration.push_str(": "); + wit_declaration.push_str(&*#field_wit_type_names); + wit_declaration.push_str(",\n"); + )* + + wit_declaration.push_str(" }\n"); + wit_declaration.into() + } } } @@ -30,9 +58,17 @@ pub fn derive_for_enum<'variants>( name: &Ident, variants: impl DoubleEndedIterator + Clone, ) -> TokenStream { + let wit_name = LitStr::new(&name.to_string().to_kebab_case(), name.span()); + let variant_count = variants.clone().count(); - let variant_hlists = - variants.map(|variant| FieldsInformation::from(&variant.fields).hlist_type()); + let variant_fields: Vec<_> = variants + .clone() + .map(|variant| FieldsInformation::from(&variant.fields)) + .collect(); + let variant_hlists: Vec<_> = variant_fields + .iter() + .map(FieldsInformation::hlist_type) + .collect(); let discriminant_type = if variant_count <= u8::MAX.into() { quote! { u8 } @@ -46,7 +82,7 @@ pub fn derive_for_enum<'variants>( let discriminant_size = quote! { std::mem::size_of::<#discriminant_type>() as u32 }; - let variant_sizes = variant_hlists.clone().map(|variant_hlist| { + let variant_sizes = variant_hlists.iter().map(|variant_hlist| { quote! { let variant_size = discriminant_size + padding + <#variant_hlist as linera_witty::WitType>::SIZE; @@ -58,6 +94,7 @@ pub fn derive_for_enum<'variants>( }); let variant_layouts = variant_hlists + .iter() .map(|variant_hlist| quote! { <#variant_hlist as linera_witty::WitType>::Layout }) .rev() .reduce(|current, variant_layout| { @@ -66,6 +103,54 @@ pub fn derive_for_enum<'variants>( } }); + let variant_field_types = variant_fields.iter().map(FieldsInformation::types); + let dependencies = variant_field_types.clone().flatten(); + + let enum_or_variant = if dependencies.clone().count() == 0 { + LitStr::new("enum", name.span()) + } else { + LitStr::new("variant", name.span()) + }; + + let variant_wit_names = variants.map(|variant| { + LitStr::new( + &variant.ident.to_string().to_kebab_case(), + variant.ident.span(), + ) + }); + + let variant_wit_payloads = variant_field_types.map(|field_types| { + let mut field_types = field_types.peekable(); + let first_field_type = field_types.next(); + let has_second_field_type = field_types.peek().is_some(); + + match (first_field_type, has_second_field_type) { + (None, _) => quote! {}, + (Some(only_field_type), false) => quote! { + wit_declaration.push('('); + wit_declaration.push_str( + &<#only_field_type as linera_witty::WitType>::wit_type_name(), + ); + wit_declaration.push(')'); + }, + (Some(first_field_type), true) => quote! { + wit_declaration.push_str("(tuple<"); + wit_declaration.push_str( + &<#first_field_type as linera_witty::WitType>::wit_type_name(), + ); + + #( + wit_declaration.push_str(", "); + wit_declaration.push_str( + &<#field_types as linera_witty::WitType>::wit_type_name(), + ); + )* + + wit_declaration.push_str(">)"); + }, + } + }); + quote! { const SIZE: u32 = { let discriminant_size = #discriminant_size; @@ -80,5 +165,26 @@ pub fn derive_for_enum<'variants>( }; type Layout = linera_witty::HCons<#discriminant_type, #variant_layouts>; + type Dependencies = linera_witty::HList![#( #dependencies ),*]; + + fn wit_type_name() -> std::borrow::Cow<'static, str> { + #wit_name.into() + } + + fn wit_type_declaration() -> std::borrow::Cow<'static, str> { + let mut wit_declaration = String::from( + concat!(" ", #enum_or_variant, " ", #wit_name, " {\n"), + ); + + #( + wit_declaration.push_str(" "); + wit_declaration.push_str(#variant_wit_names); + #variant_wit_payloads + wit_declaration.push_str(",\n"); + )* + + wit_declaration.push_str(" }\n"); + wit_declaration.into() + } } } diff --git a/linera-witty/src/lib.rs b/linera-witty/src/lib.rs index fa7d3239e60..06fc439206c 100644 --- a/linera-witty/src/lib.rs +++ b/linera-witty/src/lib.rs @@ -36,7 +36,7 @@ pub use self::{ GuestPointer, Instance, InstanceWithFunction, InstanceWithMemory, Memory, Runtime, RuntimeError, RuntimeMemory, }, - type_traits::{WitLoad, WitStore, WitType}, + type_traits::{RegisterWitTypes, WitLoad, WitStore, WitType}, util::{Merge, Split}, }; pub use frunk::{hlist, hlist::HList, hlist_pat, HCons, HList, HNil}; diff --git a/linera-witty/src/type_traits/implementations/custom_types.rs b/linera-witty/src/type_traits/implementations/custom_types.rs index 9b491e930de..b3e07df0269 100644 --- a/linera-witty/src/type_traits/implementations/custom_types.rs +++ b/linera-witty/src/type_traits/implementations/custom_types.rs @@ -8,11 +8,21 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; impl WitType for GuestPointer { const SIZE: u32 = u32::SIZE; type Layout = HList![i32]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + "guest-pointer".into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + "type guest-pointer = i32".into() + } } impl WitLoad for GuestPointer { diff --git a/linera-witty/src/type_traits/implementations/frunk.rs b/linera-witty/src/type_traits/implementations/frunk.rs index 6dd23beb213..ecb1e60fb51 100644 --- a/linera-witty/src/type_traits/implementations/frunk.rs +++ b/linera-witty/src/type_traits/implementations/frunk.rs @@ -7,13 +7,22 @@ use crate::{ GuestPointer, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory, Split, WitLoad, WitStore, WitType, }; -use frunk::{HCons, HNil}; -use std::ops::Add; +use frunk::{HCons, HList, HNil}; +use std::{borrow::Cow, ops::Add}; impl WitType for HNil { const SIZE: u32 = 0; type Layout = HNil; + type Dependencies = HNil; + + fn wit_type_name() -> Cow<'static, str> { + "hnil".into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + "type hnil = unit".into() + } } impl WitLoad for HNil { @@ -78,6 +87,18 @@ where }; type Layout = >::Output; + type Dependencies = HList![Head, Tail]; + + fn wit_type_name() -> Cow<'static, str> { + format!("hcons-{}-{}", Head::wit_type_name(), Tail::wit_type_name()).into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + let head = Head::wit_type_name(); + let tail = Tail::wit_type_name(); + + format!("type hcons-{head}-{tail} = tuple<{head}, {tail}>").into() + } } impl WitLoad for HCons diff --git a/linera-witty/src/type_traits/implementations/std/floats.rs b/linera-witty/src/type_traits/implementations/std/floats.rs index ba031f16507..16b2a7d245a 100644 --- a/linera-witty/src/type_traits/implementations/std/floats.rs +++ b/linera-witty/src/type_traits/implementations/std/floats.rs @@ -8,13 +8,24 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; macro_rules! impl_wit_traits { - ($float:ty, $size:expr) => { + ($float:ty, $wit_name:literal, $size:expr) => { impl WitType for $float { const SIZE: u32 = $size; type Layout = HList![$float]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + $wit_name.into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // Primitive types don't need to be declared + "".into() + } } impl WitLoad for $float { @@ -71,5 +82,5 @@ macro_rules! impl_wit_traits { }; } -impl_wit_traits!(f32, 4); -impl_wit_traits!(f64, 8); +impl_wit_traits!(f32, "float32", 4); +impl_wit_traits!(f64, "float64", 8); diff --git a/linera-witty/src/type_traits/implementations/std/integers.rs b/linera-witty/src/type_traits/implementations/std/integers.rs index 257a267b42c..2b85d777698 100644 --- a/linera-witty/src/type_traits/implementations/std/integers.rs +++ b/linera-witty/src/type_traits/implementations/std/integers.rs @@ -8,13 +8,23 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; macro_rules! impl_wit_traits { - ($integer:ty, 1) => { + ($integer:ty, $wit_name:literal, 1) => { impl WitType for $integer { const SIZE: u32 = 1; type Layout = HList![$integer]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + $wit_name.into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + "".into() + } } impl WitLoad for $integer { @@ -68,9 +78,11 @@ macro_rules! impl_wit_traits { } }; - ($integer:ty, $size:expr, $flat_type:ty) => { + ($integer:ty, $wit_name:literal, $size:expr, $flat_type:ty) => { impl_wit_traits!( $integer, + $wit_name, + "", $size, ($integer), ($flat_type), @@ -81,6 +93,8 @@ macro_rules! impl_wit_traits { ( $integer:ty, + $wit_name:literal, + $wit_declaration:literal, $size:expr, ($( $simple_types:ty ),*), ($( $flat_types:ty ),*), @@ -91,6 +105,15 @@ macro_rules! impl_wit_traits { const SIZE: u32 = $size; type Layout = HList![$( $simple_types ),*]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + $wit_name.into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + $wit_declaration.into() + } } impl WitLoad for $integer { @@ -147,14 +170,14 @@ macro_rules! impl_wit_traits { }; } -impl_wit_traits!(u8, 1); -impl_wit_traits!(i8, 1); -impl_wit_traits!(u16, 2, i32); -impl_wit_traits!(i16, 2, i32); -impl_wit_traits!(u32, 4, i32); -impl_wit_traits!(i32, 4, i32); -impl_wit_traits!(u64, 8, i64); -impl_wit_traits!(i64, 8, i64); +impl_wit_traits!(u8, "u8", 1); +impl_wit_traits!(i8, "s8", 1); +impl_wit_traits!(u16, "u16", 2, i32); +impl_wit_traits!(i16, "s16", 2, i32); +impl_wit_traits!(u32, "u32", 4, i32); +impl_wit_traits!(i32, "s32", 4, i32); +impl_wit_traits!(u64, "u64", 8, i64); +impl_wit_traits!(i64, "s64", 8, i64); macro_rules! x128_lower { ($this:ident) => { @@ -167,6 +190,8 @@ macro_rules! x128_lower { impl_wit_traits!( u128, + "u128", + " type u128 = tuple;\n", 16, (u64, u64), (i64, i64), @@ -179,6 +204,8 @@ impl_wit_traits!( impl_wit_traits!( i128, + "s128", + " type s128 = tuple;\n", 16, (i64, i64), (i64, i64), diff --git a/linera-witty/src/type_traits/implementations/std/option.rs b/linera-witty/src/type_traits/implementations/std/option.rs index c587bc3867a..a5329c9b584 100644 --- a/linera-witty/src/type_traits/implementations/std/option.rs +++ b/linera-witty/src/type_traits/implementations/std/option.rs @@ -7,7 +7,8 @@ use crate::{ GuestPointer, InstanceWithMemory, JoinFlatLayouts, Layout, Memory, Merge, Runtime, RuntimeError, RuntimeMemory, WitLoad, WitStore, WitType, }; -use frunk::{hlist, hlist_pat, HCons, HNil}; +use frunk::{hlist, hlist_pat, HCons, HList, HNil}; +use std::borrow::Cow; impl WitType for Option where @@ -22,6 +23,16 @@ where }; type Layout = HCons>::Output>; + type Dependencies = HList![T]; + + fn wit_type_name() -> Cow<'static, str> { + format!("option<{}>", T::wit_type_name()).into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // The native `option` type doesn't need to be declared + "".into() + } } impl WitLoad for Option diff --git a/linera-witty/src/type_traits/implementations/std/phantom_data.rs b/linera-witty/src/type_traits/implementations/std/phantom_data.rs index 15d8d3b3d50..eba782a06c4 100644 --- a/linera-witty/src/type_traits/implementations/std/phantom_data.rs +++ b/linera-witty/src/type_traits/implementations/std/phantom_data.rs @@ -8,12 +8,22 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, HList}; -use std::marker::PhantomData; +use std::{borrow::Cow, marker::PhantomData}; impl WitType for PhantomData { const SIZE: u32 = 0; type Layout = HList![]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + "unit".into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // The `unit` type used doesn't need to be declared + "".into() + } } impl WitLoad for PhantomData { diff --git a/linera-witty/src/type_traits/implementations/std/primitives.rs b/linera-witty/src/type_traits/implementations/std/primitives.rs index 06a2f84f0d8..ee237b556b8 100644 --- a/linera-witty/src/type_traits/implementations/std/primitives.rs +++ b/linera-witty/src/type_traits/implementations/std/primitives.rs @@ -8,11 +8,22 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; impl WitType for bool { const SIZE: u32 = 1; type Layout = HList![i8]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + "bool".into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // Primitive types don't need to be declared + "".into() + } } impl WitLoad for bool { @@ -72,6 +83,15 @@ where const SIZE: u32 = T::SIZE; type Layout = T::Layout; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + panic!("Borrowed values can't be used in WIT files generated with Witty"); + } + + fn wit_type_declaration() -> Cow<'static, str> { + panic!("Borrowed values can't be used in WIT files generated with Witty"); + } } impl<'t, T> WitStore for &'t T diff --git a/linera-witty/src/type_traits/implementations/std/result.rs b/linera-witty/src/type_traits/implementations/std/result.rs index bf0eadd81a0..8a525f4346a 100644 --- a/linera-witty/src/type_traits/implementations/std/result.rs +++ b/linera-witty/src/type_traits/implementations/std/result.rs @@ -7,7 +7,8 @@ use crate::{ GuestPointer, InstanceWithMemory, JoinFlatLayouts, Layout, Memory, Merge, Runtime, RuntimeError, RuntimeMemory, WitLoad, WitStore, WitType, }; -use frunk::{hlist, hlist_pat, HCons}; +use frunk::{hlist, hlist_pat, HCons, HList}; +use std::borrow::Cow; impl WitType for Result where @@ -34,6 +35,16 @@ where }; type Layout = HCons>::Output>; + type Dependencies = HList![T, E]; + + fn wit_type_name() -> Cow<'static, str> { + format!("result<{}, {}>", T::wit_type_name(), E::wit_type_name()).into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // The native `result` type doesn't need to be declared + "".into() + } } impl WitLoad for Result diff --git a/linera-witty/src/type_traits/implementations/std/string.rs b/linera-witty/src/type_traits/implementations/std/string.rs index 1aa90f20340..1cf2b88f77b 100644 --- a/linera-witty/src/type_traits/implementations/std/string.rs +++ b/linera-witty/src/type_traits/implementations/std/string.rs @@ -8,11 +8,22 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; impl WitType for String { const SIZE: u32 = 8; type Layout = HList![i32, i32]; + type Dependencies = HList![]; + + fn wit_type_name() -> Cow<'static, str> { + "string".into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // Primitive types don't need to be declared + "".into() + } } impl WitLoad for String { diff --git a/linera-witty/src/type_traits/implementations/std/tuples.rs b/linera-witty/src/type_traits/implementations/std/tuples.rs index 34b1b87f9fa..858445bde1b 100644 --- a/linera-witty/src/type_traits/implementations/std/tuples.rs +++ b/linera-witty/src/type_traits/implementations/std/tuples.rs @@ -8,6 +8,7 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; /// Implement [`WitType`], [`WitLoad`] and [`WitStore`]. /// @@ -48,6 +49,24 @@ macro_rules! impl_wit_traits_with_borrow_store_clause { const SIZE: u32 = ::SIZE; type Layout = ::Layout; + type Dependencies = HList![$( $types ),*]; + + fn wit_type_name() -> Cow<'static, str> { + let elements: &[Cow<'static, str>] = &[ + $( $types::wit_type_name(), )* + ]; + + if elements.is_empty() { + "unit".into() + } else { + format!("tuple<{}>", elements.join(", ")).into() + } + } + + fn wit_type_declaration() -> Cow<'static, str> { + // The native `tuple` type doesn't need to be declared + "".into() + } } impl<$( $types ),*> WitLoad for ($( $types, )*) diff --git a/linera-witty/src/type_traits/implementations/std/vec.rs b/linera-witty/src/type_traits/implementations/std/vec.rs index b249fca9c32..8535bdc5e3c 100644 --- a/linera-witty/src/type_traits/implementations/std/vec.rs +++ b/linera-witty/src/type_traits/implementations/std/vec.rs @@ -8,6 +8,7 @@ use crate::{ WitLoad, WitStore, WitType, }; use frunk::{hlist, hlist_pat, HList}; +use std::borrow::Cow; impl WitType for Vec where @@ -16,6 +17,16 @@ where const SIZE: u32 = 8; type Layout = HList![i32, i32]; + type Dependencies = HList![T]; + + fn wit_type_name() -> Cow<'static, str> { + format!("list<{}>", T::wit_type_name()).into() + } + + fn wit_type_declaration() -> Cow<'static, str> { + // The native `list` type doesn't need to be declared + "".into() + } } impl WitLoad for Vec diff --git a/linera-witty/src/type_traits/mod.rs b/linera-witty/src/type_traits/mod.rs index dfb4b7c022e..f183725d2b1 100644 --- a/linera-witty/src/type_traits/mod.rs +++ b/linera-witty/src/type_traits/mod.rs @@ -4,10 +4,14 @@ //! Traits used to allow complex types to be sent and received between hosts and guests using WIT. mod implementations; +mod register_wit_types; + +pub use self::register_wit_types::RegisterWitTypes; use crate::{ GuestPointer, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory, }; +use std::borrow::Cow; /// A type that is representable by fundamental WIT types. pub trait WitType: Sized { @@ -16,6 +20,15 @@ pub trait WitType: Sized { /// The layout of the type as fundamental types. type Layout: Layout; + + /// Other [`WitType`]s that this type depends on. + type Dependencies: RegisterWitTypes; + + /// Generates the WIT type name for this type. + fn wit_type_name() -> Cow<'static, str>; + + /// Generates the WIT type declaration for this type. + fn wit_type_declaration() -> Cow<'static, str>; } /// A type that can be loaded from a guest Wasm module. diff --git a/linera-witty/src/type_traits/register_wit_types.rs b/linera-witty/src/type_traits/register_wit_types.rs new file mode 100644 index 00000000000..1ef21b6e925 --- /dev/null +++ b/linera-witty/src/type_traits/register_wit_types.rs @@ -0,0 +1,50 @@ +// Copyright (c) Zefchain Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Trait and helper types allow registering a compile-time list of [`WitType`]s. + +use super::WitType; +use frunk::{HCons, HNil}; +use std::collections::HashMap; + +/// Marker trait to prevent [`RegisterWitTypes`] to be implemented for other types. +pub trait Sealed {} + +/// Trait to register a compile-time list of [`WitType`]s into a [`HashMap`]. +pub trait RegisterWitTypes: Sealed { + /// Registers this list of [`WitType`]s into `wit_types`. + fn register_wit_types(wit_types: &mut HashMap); +} + +impl Sealed for HNil {} +impl Sealed for HCons +where + Head: WitType, + Tail: Sealed, +{ +} + +impl RegisterWitTypes for HNil { + fn register_wit_types(_wit_types: &mut HashMap) {} +} + +impl RegisterWitTypes for HCons +where + Head: WitType, + Tail: RegisterWitTypes, +{ + fn register_wit_types(wit_types: &mut HashMap) { + let head_name = Head::wit_type_name(); + + if !wit_types.contains_key(&*head_name) { + wit_types.insert( + head_name.into_owned(), + Head::wit_type_declaration().into_owned(), + ); + + Head::Dependencies::register_wit_types(wit_types); + } + + Tail::register_wit_types(wit_types); + } +} diff --git a/linera-witty/tests/wit_type.rs b/linera-witty/tests/wit_type.rs index 8ff3606ce05..43f9a03c997 100644 --- a/linera-witty/tests/wit_type.rs +++ b/linera-witty/tests/wit_type.rs @@ -10,18 +10,23 @@ use self::types::{ Branch, Enum, Leaf, RecordWithDoublePadding, SimpleWrapper, SpecializedGenericEnum, SpecializedGenericStruct, TupleWithPadding, TupleWithoutPadding, }; -use linera_witty::{HList, Layout, WitType}; +use linera_witty::{HList, Layout, RegisterWitTypes, WitType}; +use std::collections::{BTreeMap, HashMap}; -/// Check the memory size and layout derived for a wrapper type. +/// Check the memory size, layout and WIT type declaration derived for a wrapper type. #[test] fn test_simple_bool_wrapper() { assert_eq!(SimpleWrapper::SIZE, 1); assert_eq!(::Layout::ALIGNMENT, 1); assert_eq!(<::Layout as Layout>::Flat::LEN, 1); + assert_eq!( + wit_type_declaration_of::(), + " record simple-wrapper {\n inner0: bool,\n }\n" + ); } -/// Check the memory size and layout derived for a type with multiple fields ordered in a way that -/// doesn't require any padding. +/// Check the memory size, layout and WIT type declaration derived for a type with multiple fields +/// ordered in a way that doesn't require any padding. #[test] fn test_tuple_struct_without_padding() { assert_eq!(TupleWithoutPadding::SIZE, 16); @@ -30,10 +35,20 @@ fn test_tuple_struct_without_padding() { <::Layout as Layout>::Flat::LEN, 3 ); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " record tuple-without-padding {\n", + " inner0: u64,\n", + " inner1: s32,\n", + " inner2: s16,\n", + " }\n" + ) + ); } -/// Check the memory size and layout derived for a type with multiple fields ordered in a way that -/// requires padding between all fields. +/// Check the memory size, layout and WIT type declaration derived for a type with multiple fields +/// ordered in a way that requires padding between all fields. #[test] fn test_tuple_struct_with_padding() { assert_eq!(TupleWithPadding::SIZE, 16); @@ -42,10 +57,20 @@ fn test_tuple_struct_with_padding() { <::Layout as Layout>::Flat::LEN, 3 ); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " record tuple-with-padding {\n", + " inner0: u16,\n", + " inner1: u32,\n", + " inner2: s64,\n", + " }\n" + ) + ); } -/// Check the memory size and layout derived for a type with multiple named fields ordered in a way -/// that requires padding before two fields. +/// Check the memory size, layout and WIT type declaration derived for a type with multiple named +/// fields ordered in a way that requires padding before two fields. #[test] fn test_named_struct_with_double_padding() { assert_eq!(RecordWithDoublePadding::SIZE, 24); @@ -54,30 +79,79 @@ fn test_named_struct_with_double_padding() { <::Layout as Layout>::Flat::LEN, 4 ); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " record record-with-double-padding {\n", + " first: u16,\n", + " second: u32,\n", + " third: s8,\n", + " fourth: s64,\n", + " }\n" + ) + ); } -/// Check the memory size and layout derived for a type that contains a field with a type that also -/// has `WitType` derived for it. +/// Check the memory size, layout and WIT type declarations derived for a type that contains a +/// field with a type that also has `WitType` derived for it. #[test] fn test_nested_types() { assert_eq!(Leaf::SIZE, 24); assert_eq!(::Layout::ALIGNMENT, 8); assert_eq!(<::Layout as Layout>::Flat::LEN, 3); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " record leaf {\n", + " first: bool,\n", + " second: u128,\n", + " }\n\n", + " type u128 = tuple;\n" + ) + ); assert_eq!(Branch::SIZE, 56); assert_eq!(::Layout::ALIGNMENT, 8); assert_eq!(<::Layout as Layout>::Flat::LEN, 7); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " record branch {\n", + " tag: u16,\n", + " first-leaf: leaf,\n", + " second-leaf: leaf,\n", + " }\n\n", + " record leaf {\n", + " first: bool,\n", + " second: u128,\n", + " }\n\n", + " type u128 = tuple;\n" + ) + ); } -/// Check the memory size and layout derived for an `enum` type. +/// Check the memory size, layout and WIT type declaration derived for an `enum` type. #[test] fn test_enum_type() { assert_eq!(Enum::SIZE, 24); assert_eq!(::Layout::ALIGNMENT, 8); assert_eq!(<::Layout as Layout>::Flat::LEN, 11); + assert_eq!( + wit_type_declaration_of::(), + concat!( + " variant enum {\n", + " empty,\n", + " large-variant-with-loose-alignment(\ + tuple\ + ),\n", + " smaller-variant-with-strict-alignment(u64),\n", + " }\n", + ) + ); } -/// Check the memory size and layout derived for a specialized generic `struct` type. +/// Check the memory size, layout and WIT type declaration derived for a specialized generic +/// `struct` type. #[test] fn test_specialized_generic_struct() { assert_eq!(SpecializedGenericStruct::SIZE, 12); @@ -89,9 +163,20 @@ fn test_specialized_generic_struct() { < as WitType>::Layout as Layout>::Flat::LEN, 4 ); + assert_eq!( + wit_type_declaration_of::>(), + concat!( + " record specialized-generic-struct {\n", + " first: u8,\n", + " second: s16,\n", + " both: list>,\n", + " }\n", + ) + ); } -/// Check the memory size and layout derived for a specialized generic `enum` type. +/// Check the memory size, layout and WIT type declaration derived for a specialized generic `enum` +/// type. #[test] fn test_specialized_generic_enum_type() { assert_eq!(SpecializedGenericEnum::SIZE, 12); @@ -103,4 +188,34 @@ fn test_specialized_generic_enum_type() { <, u32> as WitType>::Layout as Layout>::Flat::LEN, 3 ); + assert_eq!( + wit_type_declaration_of::, u32>>(), + concat!( + " variant specialized-generic-enum {\n", + " none,\n", + " first(option),\n", + " maybe-second(option),\n", + " }\n", + ) + ); +} + +/// Returns the WIT snippet with the type declarations for `T`. +fn wit_type_declaration_of() -> String +where + T: WitType, +{ + let mut wit_types = HashMap::new(); + + ::register_wit_types(&mut wit_types); + + let sorted_wit_types = wit_types + .into_iter() + .filter(|(_, declaration)| !declaration.is_empty()) + .collect::>(); + + sorted_wit_types + .into_values() + .collect::>() + .join("\n") }