From ab68974be2757f472f70bbad6ac74a35414677e6 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 27 Apr 2026 03:03:24 -0400 Subject: [PATCH] set up cargo workspace, fix derive tests, clippy clean - Add [workspace] with all sub-crates as members - Use workspace.dependencies for internal deps (version + path) - Fix derive_tests: update Value::Record and Value::Variant patterns - Gate derive_tests behind cfg(feature = derive), add CI step for them - Fix clippy warnings across workspace - Fix default-features warning for pack-abi --- .github/workflows/ci.yml | 3 + Cargo.lock | 43 ++- Cargo.toml | 24 +- crates/pack-abi/Cargo.toml | 6 +- crates/pack-abi/src/hash.rs | 8 +- crates/pack-abi/tests/derive_tests.rs | 43 +-- crates/pack-derive/Cargo.toml | 4 +- crates/pack-guest-macros/Cargo.toml | 6 +- crates/pack-guest-macros/src/codegen.rs | 195 +++++++++----- crates/pack-guest-macros/src/lib.rs | 299 +++++++++++++-------- crates/pack-guest-macros/src/metadata.rs | 52 ++-- crates/pack-guest-macros/src/wit_parser.rs | 148 +++++++--- crates/pack-guest/Cargo.toml | 10 +- crates/pack-guest/src/lib.rs | 19 +- examples/transforms/main.rs | 2 +- src/metadata.rs | 18 +- src/runtime/host.rs | 4 +- src/runtime/mod.rs | 4 +- tests/abi_roundtrip.rs | 2 +- tests/composition.rs | 2 +- 20 files changed, 592 insertions(+), 300 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3589be6..5f7e678 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,3 +42,6 @@ jobs: - name: Test run: nix develop --command cargo test --workspace + + - name: Derive tests + run: nix develop --command cargo test -p pack-abi --features derive diff --git a/Cargo.lock b/Cargo.lock index 85ab7a1..4c3a273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,6 +426,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlmalloc" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f5b01c17f85ee988d832c40e549a64bd89ab2c9f8d8a613bdf5122ae507e294" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "either" version = "1.15.0" @@ -793,13 +804,43 @@ dependencies = [ [[package]] name = "pack-abi" -version = "0.1.0" +version = "0.2.0" dependencies = [ "hashbrown 0.14.5", + "pack-derive", "serde", "sha2", ] +[[package]] +name = "pack-derive" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pack-guest" +version = "0.2.0" +dependencies = [ + "dlmalloc", + "pack-abi", + "pack-derive", + "pack-guest-macros", +] + +[[package]] +name = "pack-guest-macros" +version = "0.2.0" +dependencies = [ + "pack-abi", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "paste" version = "1.0.15" diff --git a/Cargo.toml b/Cargo.toml index 78199a8..1ac94ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,25 @@ -[package] -name = "pack" +[workspace] +members = [ + "crates/pack-abi", + "crates/pack-derive", + "crates/pack-guest", + "crates/pack-guest-macros", +] + +[workspace.package] version = "0.2.0" edition = "2021" + +[workspace.dependencies] +pack-abi = { version = "0.2.0", path = "crates/pack-abi", default-features = false } +pack-derive = { version = "0.2.0", path = "crates/pack-derive" } +pack-guest = { version = "0.2.0", path = "crates/pack-guest" } +pack-guest-macros = { version = "0.2.0", path = "crates/pack-guest-macros" } + +[package] +name = "pack" +version.workspace = true +edition.workspace = true description = "A package runtime with extended WIT support for recursive types" [[bin]] @@ -35,7 +53,7 @@ wasm-encoder = "0.219" sha2 = "0.10" # Re-export pack-abi types for unified Value/FromValue/ConversionError -pack-abi = { path = "crates/pack-abi", features = ["std", "serde"] } +pack-abi = { workspace = true, features = ["std", "serde"] } [dev-dependencies] wat = "1.0" # For writing test modules in WAT diff --git a/crates/pack-abi/Cargo.toml b/crates/pack-abi/Cargo.toml index 13e6b66..446de72 100644 --- a/crates/pack-abi/Cargo.toml +++ b/crates/pack-abi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pack-abi" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true description = "Graph-encoded ABI for Pack runtime - supports recursive types" [features] @@ -16,6 +16,6 @@ hashbrown = "0.14" # SHA-256 for type hashing (no_std compatible) sha2 = { version = "0.10", default-features = false } # Optional derive macro -pack-derive = { path = "../pack-derive", optional = true } +pack-derive = { workspace = true, optional = true } # Optional serde support for host-side usage serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true } diff --git a/crates/pack-abi/src/hash.rs b/crates/pack-abi/src/hash.rs index 950bd5c..15a7ad5 100644 --- a/crates/pack-abi/src/hash.rs +++ b/crates/pack-abi/src/hash.rs @@ -60,8 +60,12 @@ impl TypeHash { /// Format as hex string (for debugging). #[cfg(feature = "std")] pub fn to_hex(&self) -> String { - use alloc::format; - self.0.iter().map(|b| format!("{:02x}", b)).collect() + use core::fmt::Write; + let mut s = String::with_capacity(self.0.len() * 2); + for b in &self.0 { + let _ = write!(s, "{:02x}", b); + } + s } } diff --git a/crates/pack-abi/tests/derive_tests.rs b/crates/pack-abi/tests/derive_tests.rs index f48e01c..67abbac 100644 --- a/crates/pack-abi/tests/derive_tests.rs +++ b/crates/pack-abi/tests/derive_tests.rs @@ -1,4 +1,8 @@ //! Tests for the GraphValue derive macro +//! +//! Run with: cargo test -p pack-abi --features derive + +#![cfg(feature = "derive")] use pack_abi::{GraphValue, Value}; @@ -18,7 +22,7 @@ fn struct_to_value() { let value: Value = point.into(); match value { - Value::Record(fields) => { + Value::Record { fields, .. } => { assert_eq!(fields.len(), 2); assert_eq!(fields[0].0, "x"); assert_eq!(fields[0].1, Value::S64(10)); @@ -31,10 +35,13 @@ fn struct_to_value() { #[test] fn value_to_struct() { - let value = Value::Record(vec![ - ("x".to_string(), Value::S64(10)), - ("y".to_string(), Value::S64(20)), - ]); + let value = Value::Record { + type_name: String::new(), + fields: vec![ + ("x".to_string(), Value::S64(10)), + ("y".to_string(), Value::S64(20)), + ], + }; let point: Point = value.try_into().unwrap(); assert_eq!(point.x, 10); @@ -117,9 +124,9 @@ fn enum_unit_variant() { let value: Value = original.clone().into(); match &value { - Value::Variant { tag, payload } => { + Value::Variant { tag, payload, .. } => { assert_eq!(*tag, 2); - assert!(payload.is_none()); + assert!(payload.is_empty()); } _ => panic!("Expected Variant"), } @@ -134,10 +141,10 @@ fn enum_single_payload() { let value: Value = original.clone().into(); match &value { - Value::Variant { tag, payload } => { + Value::Variant { tag, payload, .. } => { assert_eq!(*tag, 0); - assert!(payload.is_some()); - assert_eq!(**payload.as_ref().unwrap(), Value::F64(5.0)); + assert_eq!(payload.len(), 1); + assert_eq!(payload[0], Value::F64(5.0)); } _ => panic!("Expected Variant"), } @@ -152,17 +159,11 @@ fn enum_tuple_payload() { let value: Value = original.clone().into(); match &value { - Value::Variant { tag, payload } => { + Value::Variant { tag, payload, .. } => { assert_eq!(*tag, 1); - assert!(payload.is_some()); - match payload.as_ref().unwrap().as_ref() { - Value::Tuple(items) => { - assert_eq!(items.len(), 2); - assert_eq!(items[0], Value::F64(10.0)); - assert_eq!(items[1], Value::F64(20.0)); - } - _ => panic!("Expected Tuple payload"), - } + assert_eq!(payload.len(), 2); + assert_eq!(payload[0], Value::F64(10.0)); + assert_eq!(payload[1], Value::F64(20.0)); } _ => panic!("Expected Variant"), } @@ -248,7 +249,7 @@ fn rename_attribute() { let value: Value = person.into(); match value { - Value::Record(fields) => { + Value::Record { fields, .. } => { assert!(fields.iter().any(|(name, _)| name == "full_name")); assert!(fields.iter().any(|(name, _)| name == "age")); } diff --git a/crates/pack-derive/Cargo.toml b/crates/pack-derive/Cargo.toml index 3330627..1e69d04 100644 --- a/crates/pack-derive/Cargo.toml +++ b/crates/pack-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pack-derive" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true description = "Derive macros for pack-abi Value conversion" [lib] diff --git a/crates/pack-guest-macros/Cargo.toml b/crates/pack-guest-macros/Cargo.toml index b61d760..4d79675 100644 --- a/crates/pack-guest-macros/Cargo.toml +++ b/crates/pack-guest-macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pack-guest-macros" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true description = "Proc macros for Pack guest packages" license = "MIT OR Apache-2.0" @@ -12,4 +12,4 @@ proc-macro = true proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } -pack-abi = { path = "../pack-abi", features = ["std"] } +pack-abi = { workspace = true, features = ["std"] } diff --git a/crates/pack-guest-macros/src/codegen.rs b/crates/pack-guest-macros/src/codegen.rs index f74656f..f40ca4d 100644 --- a/crates/pack-guest-macros/src/codegen.rs +++ b/crates/pack-guest-macros/src/codegen.rs @@ -6,7 +6,9 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use crate::wit_parser::{Type, TypeDef, VariantCase, World, Function, WorldItem, WitRegistry, Interface}; +use crate::wit_parser::{ + Function, Interface, Type, TypeDef, VariantCase, WitRegistry, World, WorldItem, +}; /// Convert a WIT identifier (kebab-case) to Rust identifier (PascalCase for types, snake_case for functions) fn to_rust_type_name(wit_name: &str) -> syn::Ident { @@ -58,10 +60,12 @@ fn generate_type_ref(ty: &Type, self_type_name: Option<&str>) -> TokenStream { quote! { ::core::option::Option<#inner_ty> } } Type::Result { ok, err } => { - let ok_ty = ok.as_ref() + let ok_ty = ok + .as_ref() .map(|t| generate_type_ref(t, self_type_name)) .unwrap_or_else(|| quote! { () }); - let err_ty = err.as_ref() + let err_ty = err + .as_ref() .map(|t| generate_type_ref(t, self_type_name)) .unwrap_or_else(|| quote! { () }); quote! { ::core::result::Result<#ok_ty, #err_ty> } @@ -70,7 +74,8 @@ fn generate_type_ref(ty: &Type, self_type_name: Option<&str>) -> TokenStream { if items.is_empty() { quote! { () } } else { - let item_tys: Vec<_> = items.iter() + let item_tys: Vec<_> = items + .iter() .map(|t| generate_type_ref(t, self_type_name)) .collect(); quote! { (#(#item_tys),*) } @@ -93,6 +98,7 @@ fn generate_type_ref(ty: &Type, self_type_name: Option<&str>) -> TokenStream { } /// Generate Value conversion expression for a type (Rust value -> Value) +#[allow(clippy::only_used_in_recursion)] fn generate_to_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str>) -> TokenStream { match ty { Type::Bool => quote! { pack_guest::Value::Bool(#expr) }, @@ -125,10 +131,12 @@ fn generate_to_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str>) } } Type::Result { ok, err } => { - let ok_conversion = ok.as_ref() + let ok_conversion = ok + .as_ref() .map(|t| generate_to_value(t, quote! { v }, self_type_name)) .unwrap_or_else(|| quote! { pack_guest::Value::Tuple(::alloc::vec![]) }); - let err_conversion = err.as_ref() + let err_conversion = err + .as_ref() .map(|t| generate_to_value(t, quote! { e }, self_type_name)) .unwrap_or_else(|| quote! { pack_guest::Value::Tuple(::alloc::vec![]) }); quote! { @@ -148,7 +156,9 @@ fn generate_to_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str>) if items.is_empty() { quote! { pack_guest::Value::Tuple(::alloc::vec![]) } } else { - let conversions: Vec<_> = items.iter().enumerate() + let conversions: Vec<_> = items + .iter() + .enumerate() .map(|(i, t)| { let idx = syn::Index::from(i); let item_expr = quote! { #expr.#idx }; @@ -344,7 +354,11 @@ fn generate_from_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str } Type::Result { ok, err } => { // Handle SelfRef specially for ok/err types - let ok_conversion = if ok.as_ref().map(|t| matches!(t.as_ref(), Type::SelfRef)).unwrap_or(false) { + let ok_conversion = if ok + .as_ref() + .map(|t| matches!(t.as_ref(), Type::SelfRef)) + .unwrap_or(false) + { if let Some(name) = self_type_name { let rust_name = to_rust_type_name(name); quote! { Ok(::alloc::boxed::Box::new(<#rust_name>::try_from(*p)?)) } @@ -352,12 +366,17 @@ fn generate_from_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str quote! { Ok(::alloc::boxed::Box::new(Self::try_from(*p)?)) } } } else { - let ok_ty = ok.as_ref() + let ok_ty = ok + .as_ref() .map(|t| generate_type_ref(t, self_type_name)) .unwrap_or_else(|| quote! { () }); quote! { Ok(<#ok_ty>::try_from(*p)?) } }; - let err_conversion = if err.as_ref().map(|t| matches!(t.as_ref(), Type::SelfRef)).unwrap_or(false) { + let err_conversion = if err + .as_ref() + .map(|t| matches!(t.as_ref(), Type::SelfRef)) + .unwrap_or(false) + { if let Some(name) = self_type_name { let rust_name = to_rust_type_name(name); quote! { Err(::alloc::boxed::Box::new(<#rust_name>::try_from(*p)?)) } @@ -365,7 +384,8 @@ fn generate_from_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str quote! { Err(::alloc::boxed::Box::new(Self::try_from(*p)?)) } } } else { - let err_ty = err.as_ref() + let err_ty = err + .as_ref() .map(|t| generate_type_ref(t, self_type_name)) .unwrap_or_else(|| quote! { () }); quote! { Err(<#err_ty>::try_from(*p)?) } @@ -390,8 +410,8 @@ fn generate_from_value(ty: &Type, expr: TokenStream, self_type_name: Option<&str if items.is_empty() { quote! { () } } else { - let extractions: Vec<_> = items.iter().enumerate() - .map(|(_i, t)| { + let extractions: Vec<_> = items.iter() + .map(|t| { // Handle SelfRef specially to avoid Box::try_from issue if matches!(t, Type::SelfRef) { if let Some(name) = self_type_name { @@ -458,7 +478,8 @@ fn generate_alias(name: &str, ty: &Type) -> TokenStream { fn generate_record(name: &str, fields: &[(String, Type)]) -> TokenStream { let rust_name = to_rust_type_name(name); - let field_defs: Vec<_> = fields.iter() + let field_defs: Vec<_> = fields + .iter() .map(|(fname, ftype)| { let rust_fname = to_rust_field_name(fname); let rust_ftype = generate_type_ref(ftype, Some(name)); @@ -466,7 +487,8 @@ fn generate_record(name: &str, fields: &[(String, Type)]) -> TokenStream { }) .collect(); - let field_to_value: Vec<_> = fields.iter() + let field_to_value: Vec<_> = fields + .iter() .map(|(fname, ftype)| { let rust_fname = to_rust_field_name(fname); let wit_fname = fname.clone(); @@ -475,7 +497,8 @@ fn generate_record(name: &str, fields: &[(String, Type)]) -> TokenStream { }) .collect(); - let field_from_value: Vec<_> = fields.iter() + let field_from_value: Vec<_> = fields + .iter() .map(|(fname, ftype)| { let rust_fname = to_rust_field_name(fname); let wit_fname = fname.clone(); @@ -526,7 +549,8 @@ fn generate_record(name: &str, fields: &[(String, Type)]) -> TokenStream { fn generate_variant(name: &str, cases: &[VariantCase]) -> TokenStream { let rust_name = to_rust_type_name(name); - let case_defs: Vec<_> = cases.iter() + let case_defs: Vec<_> = cases + .iter() .map(|case| { let case_name = to_rust_variant_name(&case.name); match &case.payload { @@ -539,7 +563,9 @@ fn generate_variant(name: &str, cases: &[VariantCase]) -> TokenStream { }) .collect(); - let to_value_arms: Vec<_> = cases.iter().enumerate() + let to_value_arms: Vec<_> = cases + .iter() + .enumerate() .map(|(tag, case)| { let case_name = to_rust_variant_name(&case.name); match &case.payload { @@ -562,7 +588,9 @@ fn generate_variant(name: &str, cases: &[VariantCase]) -> TokenStream { }) .collect(); - let from_value_arms: Vec<_> = cases.iter().enumerate() + let from_value_arms: Vec<_> = cases + .iter() + .enumerate() .map(|(tag, case)| { let case_name = to_rust_variant_name(&case.name); match &case.payload { @@ -624,11 +652,14 @@ fn generate_variant(name: &str, cases: &[VariantCase]) -> TokenStream { fn generate_enum(name: &str, cases: &[String]) -> TokenStream { let rust_name = to_rust_type_name(name); - let case_defs: Vec<_> = cases.iter() + let case_defs: Vec<_> = cases + .iter() .map(|case| to_rust_variant_name(case)) .collect(); - let to_value_arms: Vec<_> = cases.iter().enumerate() + let to_value_arms: Vec<_> = cases + .iter() + .enumerate() .map(|(tag, case)| { let case_name = to_rust_variant_name(case); quote! { @@ -640,7 +671,9 @@ fn generate_enum(name: &str, cases: &[String]) -> TokenStream { }) .collect(); - let from_value_arms: Vec<_> = cases.iter().enumerate() + let from_value_arms: Vec<_> = cases + .iter() + .enumerate() .map(|(tag, case)| { let case_name = to_rust_variant_name(case); quote! { #tag => Ok(#rust_name::#case_name) } @@ -689,7 +722,9 @@ fn generate_enum(name: &str, cases: &[String]) -> TokenStream { fn generate_flags(name: &str, flags: &[String]) -> TokenStream { let rust_name = to_rust_type_name(name); - let flag_consts: Vec<_> = flags.iter().enumerate() + let flag_consts: Vec<_> = flags + .iter() + .enumerate() .map(|(i, flag)| { let const_name = format_ident!("{}", flag.to_uppercase().replace('-', "_")); let bit: u64 = 1 << i; @@ -748,9 +783,7 @@ fn generate_flags(name: &str, flags: &[String]) -> TokenStream { /// Generate all types from a world definition pub fn generate_world_types(world: &World) -> TokenStream { - let type_defs: Vec<_> = world.types.iter() - .map(generate_type_def) - .collect(); + let type_defs: Vec<_> = world.types.iter().map(generate_type_def).collect(); quote! { #(#type_defs)* @@ -798,7 +831,11 @@ pub fn get_world_imports(world: &World) -> Vec<(&str, &Function)> { // ============================================================================ /// Format an interface path from its components -fn format_interface_path(namespace: &Option, package: &Option, interface: &str) -> String { +fn format_interface_path( + namespace: &Option, + package: &Option, + interface: &str, +) -> String { match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), (None, Some(pkg)) => format!("{}/{}", pkg, interface), @@ -812,7 +849,11 @@ pub fn generate_imports(registry: &WitRegistry, world: &World) -> TokenStream { for import in &world.imports { match import { - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { // Look up the interface definition let path = format_interface_path(namespace, package, interface); if let Some(iface) = registry.interfaces.get(&path) { @@ -841,14 +882,14 @@ fn generate_import_module(module_path: &str, iface: &Interface) -> TokenStream { let module_name = iface.name.replace('-', "_"); let module_ident = format_ident!("{}", module_name); - let functions: Vec<_> = iface.functions.iter() + let functions: Vec<_> = iface + .functions + .iter() .map(|f| generate_import_function(module_path, f)) .collect(); // Also generate types from the interface - let types: Vec<_> = iface.types.iter() - .map(generate_type_def) - .collect(); + let types: Vec<_> = iface.types.iter().map(generate_type_def).collect(); quote! { pub mod #module_ident { @@ -866,7 +907,8 @@ fn generate_inline_import_module(name: &str, functions: &[Function]) -> TokenStr let module_name = name.replace('-', "_"); let module_ident = format_ident!("{}", module_name); - let funcs: Vec<_> = functions.iter() + let funcs: Vec<_> = functions + .iter() .map(|f| generate_import_function(name, f)) .collect(); @@ -887,15 +929,19 @@ fn generate_import_function(module_path: &str, func: &Function) -> TokenStream { let link_name = &func.name; // Generate parameter list - use &str for string params in imports - let params: Vec<_> = func.params.iter().map(|(name, ty)| { - let param_name = format_ident!("{}", name.replace('-', "_")); - let param_type = if matches!(ty, Type::String) { - quote! { &str } - } else { - generate_type_ref(ty, None) - }; - quote! { #param_name: #param_type } - }).collect(); + let params: Vec<_> = func + .params + .iter() + .map(|(name, ty)| { + let param_name = format_ident!("{}", name.replace('-', "_")); + let param_type = if matches!(ty, Type::String) { + quote! { &str } + } else { + generate_type_ref(ty, None) + }; + quote! { #param_name: #param_type } + }) + .collect(); // Generate return type let return_type = if func.results.is_empty() { @@ -903,7 +949,9 @@ fn generate_import_function(module_path: &str, func: &Function) -> TokenStream { } else if func.results.len() == 1 { generate_type_ref(&func.results[0], None) } else { - let tys: Vec<_> = func.results.iter() + let tys: Vec<_> = func + .results + .iter() .map(|t| generate_type_ref(t, None)) .collect(); quote! { (#(#tys),*) } @@ -917,10 +965,14 @@ fn generate_import_function(module_path: &str, func: &Function) -> TokenStream { let param_name = format_ident!("{}", name.replace('-', "_")); generate_to_value_for_import(ty, quote! { #param_name }) } else { - let conversions: Vec<_> = func.params.iter().map(|(name, ty)| { - let param_name = format_ident!("{}", name.replace('-', "_")); - generate_to_value_for_import(ty, quote! { #param_name }) - }).collect(); + let conversions: Vec<_> = func + .params + .iter() + .map(|(name, ty)| { + let param_name = format_ident!("{}", name.replace('-', "_")); + generate_to_value_for_import(ty, quote! { #param_name }) + }) + .collect(); quote! { pack_guest::Value::Tuple(::alloc::vec![#(#conversions),*]) } }; @@ -993,7 +1045,9 @@ fn generate_to_value_for_import(ty: &Type, expr: TokenStream) -> TokenStream { /// Format a WIT function signature as a string fn format_function_signature(func: &Function) -> String { - let params: Vec = func.params.iter() + let params: Vec = func + .params + .iter() .map(|(name, ty)| format!("{}: {}", name, format_wit_type(ty))) .collect(); @@ -1002,9 +1056,7 @@ fn format_function_signature(func: &Function) -> String { } else if func.results.len() == 1 { format!(" -> {}", format_wit_type(&func.results[0])) } else { - let result_strs: Vec = func.results.iter() - .map(format_wit_type) - .collect(); + let result_strs: Vec = func.results.iter().map(format_wit_type).collect(); format!(" -> ({})", result_strs.join(", ")) }; @@ -1030,8 +1082,14 @@ fn format_wit_type(ty: &Type) -> String { Type::List(inner) => format!("list<{}>", format_wit_type(inner)), Type::Option(inner) => format!("option<{}>", format_wit_type(inner)), Type::Result { ok, err } => { - let ok_str = ok.as_ref().map(|t| format_wit_type(t)).unwrap_or_else(|| "_".to_string()); - let err_str = err.as_ref().map(|t| format_wit_type(t)).unwrap_or_else(|| "_".to_string()); + let ok_str = ok + .as_ref() + .map(|t| format_wit_type(t)) + .unwrap_or_else(|| "_".to_string()); + let err_str = err + .as_ref() + .map(|t| format_wit_type(t)) + .unwrap_or_else(|| "_".to_string()); format!("result<{}, {}>", ok_str, err_str) } Type::Tuple(items) => { @@ -1068,7 +1126,11 @@ pub fn generate_export_metadata(registry: &WitRegistry, world: &World) -> TokenS results: f.results.clone(), }); } - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { let path = format_interface_path(namespace, package, interface); if let Some(iface) = registry.interfaces.get(&path) { for f in &iface.functions { @@ -1098,14 +1160,17 @@ pub fn generate_export_metadata(registry: &WitRegistry, world: &World) -> TokenS } } - let entries: Vec<_> = exports.iter().map(|e| { - let name = &e.name; - let export_name = &e.export_name; - let sig = &e.signature; - quote! { - (#name, #export_name, #sig) - } - }).collect(); + let entries: Vec<_> = exports + .iter() + .map(|e| { + let name = &e.name; + let export_name = &e.export_name; + let sig = &e.signature; + quote! { + (#name, #export_name, #sig) + } + }) + .collect(); quote! { #[doc(hidden)] @@ -1141,7 +1206,11 @@ pub fn collect_exports(registry: &WitRegistry, world: &World) -> Vec results: f.results.clone(), }); } - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { let path = format_interface_path(namespace, package, interface); if let Some(iface) = registry.interfaces.get(&path) { for f in &iface.functions { diff --git a/crates/pack-guest-macros/src/lib.rs b/crates/pack-guest-macros/src/lib.rs index 413f985..09525f7 100644 --- a/crates/pack-guest-macros/src/lib.rs +++ b/crates/pack-guest-macros/src/lib.rs @@ -7,13 +7,13 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemFn, ReturnType, FnArg, Pat, LitStr, Token, Ident}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; +use syn::{parse_macro_input, FnArg, Ident, ItemFn, LitStr, Pat, ReturnType, Token}; -mod wit_parser; mod codegen; mod metadata; +mod wit_parser; /// Arguments for the #[export] attribute. struct ExportArgs { @@ -30,7 +30,11 @@ struct ExportArgs { impl Parse for ExportArgs { fn parse(input: ParseStream) -> syn::Result { - let mut args = ExportArgs { name: None, wit: None, state: None }; + let mut args = ExportArgs { + name: None, + wit: None, + state: None, + }; if input.is_empty() { return Ok(args); @@ -48,7 +52,10 @@ impl Parse for ExportArgs { other => { return Err(syn::Error::new( ident.span(), - format!("unexpected attribute `{}`, expected `name`, `wit`, or `state`", other), + format!( + "unexpected attribute `{}`, expected `name`, `wit`, or `state`", + other + ), )); } } @@ -145,10 +152,9 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { match validate_export_against_wit(wit_path) { Ok(result) => result.derived_name, Err(e) => { - return syn::Error::new( - proc_macro2::Span::call_site(), - e, - ).to_compile_error().into(); + return syn::Error::new(proc_macro2::Span::call_site(), e) + .to_compile_error() + .into(); } } } else { @@ -157,8 +163,7 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Determine the export name: explicit name > derived from wit > function name - let export_name = args.name.clone() - .or(derived_export_name); + let export_name = args.name.clone().or(derived_export_name); let fn_name = &input_fn.sig.ident; let fn_body = &input_fn.block; @@ -179,8 +184,10 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { _ => { return syn::Error::new_spanned( &pat_type.pat, - "parameter must be a simple identifier" - ).to_compile_error().into(); + "parameter must be a simple identifier", + ) + .to_compile_error() + .into(); } }; param_names.push(name); @@ -189,8 +196,10 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { FnArg::Receiver(_) => { return syn::Error::new_spanned( param, - "exported functions cannot have self parameter" - ).to_compile_error().into(); + "exported functions cannot have self parameter", + ) + .to_compile_error() + .into(); } } } @@ -200,7 +209,10 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { // Check if the type is `Value` (simple path check) let ty = ¶m_types[0]; if let syn::Type::Path(type_path) = ty { - type_path.path.segments.last() + type_path + .path + .segments + .last() .map(|seg| seg.ident == "Value") .unwrap_or(false) } else { @@ -213,28 +225,27 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { ReturnType::Default => { return syn::Error::new_spanned( &input_fn.sig, - "exported functions must have a return type" - ).to_compile_error().into(); + "exported functions must have a return type", + ) + .to_compile_error() + .into(); } ReturnType::Type(_, ty) => ty, }; // Generate the inner function name (prefixed with underscore) - let inner_fn_name = syn::Ident::new( - &format!("__{}_inner", fn_name), - fn_name.span() - ); + let inner_fn_name = syn::Ident::new(&format!("__{}_inner", fn_name), fn_name.span()); // Generate the wrapper function name (always a valid Rust identifier) - let wrapper_fn_name = syn::Ident::new( - &format!("__{}_export", fn_name), - fn_name.span() - ); + let wrapper_fn_name = syn::Ident::new(&format!("__{}_export", fn_name), fn_name.span()); // Generate the function parameters for the inner function declaration - let inner_fn_params = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - quote! { #name: #ty } - }); + let inner_fn_params = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + quote! { #name: #ty } + }); // Generate the parameter extraction and function call based on mode let call_body = if args.state.is_some() { @@ -245,8 +256,10 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { if param_names.is_empty() { return syn::Error::new_spanned( &input_fn.sig, - "state mode requires at least one parameter (the state)" - ).to_compile_error().into(); + "state mode requires at least one parameter (the state)", + ) + .to_compile_error() + .into(); } let state_name = ¶m_names[0]; @@ -281,17 +294,21 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { } } else { let indices: Vec<_> = (0..other_param_names.len()).collect(); - let extractions = other_param_names.iter().zip(other_param_types.iter()).zip(indices.iter()).map(|((name, ty), idx)| { - quote! { - let #name: #ty = match param_items.get(#idx).cloned() { - Some(v) => match v.try_into() { - Ok(converted) => converted, - Err(_) => return Err("failed to convert parameter"), - }, - None => return Err("missing parameter in tuple"), - }; - } - }); + let extractions = other_param_names + .iter() + .zip(other_param_types.iter()) + .zip(indices.iter()) + .map(|((name, ty), idx)| { + quote! { + let #name: #ty = match param_items.get(#idx).cloned() { + Some(v) => match v.try_into() { + Ok(converted) => converted, + Err(_) => return Err("failed to convert parameter"), + }, + None => return Err("missing parameter in tuple"), + }; + } + }); quote! { let param_items = match params_value { pack_guest::Value::Tuple(items) => items, @@ -410,17 +427,21 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { let num_params = param_names.len(); let indices: Vec<_> = (0..num_params).collect(); - let extractions = param_names.iter().zip(param_types.iter()).zip(indices.iter()).map(|((name, ty), idx)| { - quote! { - let #name: #ty = match items.get(#idx).cloned() { - Some(v) => match v.try_into() { - Ok(converted) => converted, - Err(_) => return Err("failed to convert parameter"), - }, - None => return Err("missing parameter in tuple"), - }; - } - }); + let extractions = param_names + .iter() + .zip(param_types.iter()) + .zip(indices.iter()) + .map(|((name, ty), idx)| { + quote! { + let #name: #ty = match items.get(#idx).cloned() { + Some(v) => match v.try_into() { + Ok(converted) => converted, + Err(_) => return Err("failed to convert parameter"), + }, + None => return Err("missing parameter in tuple"), + }; + } + }); let call_args = param_names.iter(); @@ -519,8 +540,8 @@ struct WitValidationResult { fn validate_export_against_wit(wit_path: &str) -> Result { // Read and parse WIT files let wit_content = read_wit_files()?; - let registry = wit_parser::parse_wit(&wit_content) - .map_err(|e| format!("Failed to parse WIT: {}", e))?; + let registry = + wit_parser::parse_wit(&wit_content).map_err(|e| format!("Failed to parse WIT: {}", e))?; // Check if this is a full path (contains '.' or '#') if let Some(func_path) = wit_parser::FunctionPath::parse(wit_path) { @@ -554,7 +575,10 @@ fn validate_export_against_wit(wit_path: &str) -> Result { + wit_parser::WorldItem::InlineInterface { + name: iface_name, + functions, + } => { if let Some(f) = functions.iter().find(|f| f.name == func_name) { // Found in inline interface return Ok(WitValidationResult { @@ -563,7 +587,11 @@ fn validate_export_against_wit(wit_path: &str) -> Result { + wit_parser::WorldItem::InterfacePath { + namespace, + package, + interface, + } => { // Check if this interface path is in our registry let iface_path = match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), @@ -633,13 +661,20 @@ fn try_auto_discover_export(fn_name: &str) -> Option { // Found as a bare export - use just the function name return Some(fn_name.to_string()); } - wit_parser::WorldItem::InlineInterface { name: iface_name, functions } => { + wit_parser::WorldItem::InlineInterface { + name: iface_name, + functions, + } => { if functions.iter().any(|f| f.name == fn_name) { // Found in inline interface - use interface.function format return Some(format!("{}.{}", iface_name, fn_name)); } } - wit_parser::WorldItem::InterfacePath { namespace, package, interface } => { + wit_parser::WorldItem::InterfacePath { + namespace, + package, + interface, + } => { // Check if this interface has the function let iface_path = match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), @@ -677,15 +712,16 @@ struct WitImportValidationResult { fn validate_import_against_wit(wit_path: &str) -> Result { // Read and parse WIT files let wit_content = read_wit_files()?; - let registry = wit_parser::parse_wit(&wit_content) - .map_err(|e| format!("Failed to parse WIT: {}", e))?; + let registry = + wit_parser::parse_wit(&wit_content).map_err(|e| format!("Failed to parse WIT: {}", e))?; // Parse the function path - let func_path = wit_parser::FunctionPath::parse(wit_path) - .ok_or_else(|| format!( + let func_path = wit_parser::FunctionPath::parse(wit_path).ok_or_else(|| { + format!( "Invalid WIT path '{}'. Expected format: 'namespace:package/interface.function'", wit_path - ))?; + ) + })?; // Look up the function in the registry if registry.find_import_function(&func_path).is_some() { @@ -739,7 +775,10 @@ impl Parse for ImportArgs { other => { return Err(syn::Error::new( ident.span(), - format!("unexpected attribute `{}`, expected `module`, `name`, or `wit`", other), + format!( + "unexpected attribute `{}`, expected `module`, `name`, or `wit`", + other + ), )); } } @@ -840,10 +879,9 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { match validate_import_against_wit(wit_path) { Ok(result) => (result.module, result.import_name), Err(e) => { - return syn::Error::new( - proc_macro2::Span::call_site(), - e, - ).to_compile_error().into(); + return syn::Error::new(proc_macro2::Span::call_site(), e) + .to_compile_error() + .into(); } } } else { @@ -851,12 +889,16 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Determine module: explicit > derived from wit - let module = args.module.clone() + let module = args + .module + .clone() .or(derived_module) .expect("module should be set by either `module` or `wit` attribute"); // Determine import name: explicit > derived from wit > function name - let import_name = args.name.clone() + let import_name = args + .name + .clone() .or(derived_name) .unwrap_or_else(|| sig.fn_name.to_string()); @@ -865,10 +907,7 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { let output = &sig.output; // Generate a unique name for the raw import - let raw_fn_name = Ident::new( - &format!("__raw_import_{}", fn_name), - fn_name.span() - ); + let raw_fn_name = Ident::new(&format!("__raw_import_{}", fn_name), fn_name.span()); // Extract parameter names and types let params: Vec<_> = sig.inputs.iter().collect(); @@ -883,8 +922,10 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { _ => { return syn::Error::new_spanned( &pat_type.pat, - "parameter must be a simple identifier" - ).to_compile_error().into(); + "parameter must be a simple identifier", + ) + .to_compile_error() + .into(); } }; param_names.push(name.clone()); @@ -893,8 +934,10 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { FnArg::Receiver(_) => { return syn::Error::new_spanned( param, - "imported functions cannot have self parameter" - ).to_compile_error().into(); + "imported functions cannot have self parameter", + ) + .to_compile_error() + .into(); } } } @@ -934,9 +977,12 @@ pub fn import(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Generate the function signature parameters - let fn_params = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - quote! { #name: #ty } - }); + let fn_params = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + quote! { #name: #ty } + }); let expanded = quote! { #[link(wasm_import_module = #module)] @@ -1029,8 +1075,10 @@ pub fn wit(input: TokenStream) -> TokenStream { Err(e) => { return syn::Error::new( proc_macro2::Span::call_site(), - format!("Failed to read WIT files: {}", e) - ).to_compile_error().into(); + format!("Failed to read WIT files: {}", e), + ) + .to_compile_error() + .into(); } } } else { @@ -1045,8 +1093,10 @@ pub fn wit(input: TokenStream) -> TokenStream { Err(e) => { return syn::Error::new( proc_macro2::Span::call_site(), - format!("Failed to parse WIT: {}", e) - ).to_compile_error().into(); + format!("Failed to parse WIT: {}", e), + ) + .to_compile_error() + .into(); } }; @@ -1115,8 +1165,10 @@ pub fn world(input: TokenStream) -> TokenStream { Err(e) => { return syn::Error::new( proc_macro2::Span::call_site(), - format!("Failed to read WIT files: {}", e) - ).to_compile_error().into(); + format!("Failed to read WIT files: {}", e), + ) + .to_compile_error() + .into(); } } } else { @@ -1130,8 +1182,10 @@ pub fn world(input: TokenStream) -> TokenStream { Err(e) => { return syn::Error::new( proc_macro2::Span::call_site(), - format!("Failed to parse WIT: {}", e) - ).to_compile_error().into(); + format!("Failed to parse WIT: {}", e), + ) + .to_compile_error() + .into(); } }; @@ -1141,8 +1195,10 @@ pub fn world(input: TokenStream) -> TokenStream { None => { return syn::Error::new( proc_macro2::Span::call_site(), - "No world definition found in WIT files" - ).to_compile_error().into(); + "No world definition found in WIT files", + ) + .to_compile_error() + .into(); } }; @@ -1150,8 +1206,10 @@ pub fn world(input: TokenStream) -> TokenStream { let types = codegen::generate_world_types(world); // Generate types from top-level definitions in the registry - let registry_types: Vec<_> = registry.types.iter() - .map(|t| codegen::generate_type_def(t)) + let registry_types: Vec<_> = registry + .types + .iter() + .map(codegen::generate_type_def) .collect(); // Generate import modules @@ -1165,14 +1223,15 @@ pub fn world(input: TokenStream) -> TokenStream { #types #imports #export_metadata - }.into() + } + .into() } /// Read all WIT files from the wit/ directory and wit/deps/ subdirectories fn read_wit_files() -> Result { // Get the manifest directory (crate root) - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") - .map_err(|_| "CARGO_MANIFEST_DIR not set")?; + let manifest_dir = + std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set")?; let wit_dir = std::path::Path::new(&manifest_dir).join("wit"); @@ -1194,8 +1253,8 @@ fn read_wit_files() -> Result { /// Recursively read WIT files from a directory fn read_wit_files_recursive(dir: &std::path::Path, content: &mut String) -> Result<(), String> { - let entries = std::fs::read_dir(dir) - .map_err(|e| format!("Failed to read directory {:?}: {}", dir, e))?; + let entries = + std::fs::read_dir(dir).map_err(|e| format!("Failed to read directory {:?}: {}", dir, e))?; for entry in entries { let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; @@ -1314,10 +1373,7 @@ pub fn import_from(attr: TokenStream, item: TokenStream) -> TokenStream { let output = &sig.output; // Generate a unique name for the raw import - let raw_fn_name = Ident::new( - &format!("__raw_pkg_import_{}", fn_name), - fn_name.span() - ); + let raw_fn_name = Ident::new(&format!("__raw_pkg_import_{}", fn_name), fn_name.span()); // Extract parameter names and types let params: Vec<_> = sig.inputs.iter().collect(); @@ -1332,8 +1388,10 @@ pub fn import_from(attr: TokenStream, item: TokenStream) -> TokenStream { _ => { return syn::Error::new_spanned( &pat_type.pat, - "parameter must be a simple identifier" - ).to_compile_error().into(); + "parameter must be a simple identifier", + ) + .to_compile_error() + .into(); } }; param_names.push(name.clone()); @@ -1342,8 +1400,10 @@ pub fn import_from(attr: TokenStream, item: TokenStream) -> TokenStream { FnArg::Receiver(_) => { return syn::Error::new_spanned( param, - "imported functions cannot have self parameter" - ).to_compile_error().into(); + "imported functions cannot have self parameter", + ) + .to_compile_error() + .into(); } } } @@ -1382,9 +1442,12 @@ pub fn import_from(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Generate the function signature parameters - let fn_params = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - quote! { #name: #ty } - }); + let fn_params = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + quote! { #name: #ty } + }); let expanded = quote! { #[link(wasm_import_module = #package)] @@ -1505,12 +1568,14 @@ fn parse_file_reference(input: &str) -> Result { let input = input.trim(); // Strip "file" prefix - let rest = input.strip_prefix("file") + let rest = input + .strip_prefix("file") .ok_or("expected 'file = \"path\"'")? .trim(); // Strip "=" - let rest = rest.strip_prefix('=') + let rest = rest + .strip_prefix('=') .ok_or("expected '=' after 'file'")? .trim(); @@ -1521,8 +1586,8 @@ fn parse_file_reference(input: &str) -> Result { } // Get the manifest directory (crate root) - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") - .map_err(|_| "CARGO_MANIFEST_DIR not set")?; + let manifest_dir = + std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set")?; let full_path = std::path::Path::new(&manifest_dir).join(path); @@ -1606,7 +1671,10 @@ fn parse_import_sigs( /// /// Handles the tricky case where "name: func" needs to NOT consume the colon, /// but "namespace:package/interface.name" SHOULD consume the colon as part of the path. -fn parse_function_path(parser: &mut wit_parser::Parser, default_interface: &str) -> Result<(String, String), String> { +fn parse_function_path( + parser: &mut wit_parser::Parser, + default_interface: &str, +) -> Result<(String, String), String> { let mut path = parser.expect_ident().map_err(|e| e.to_string())?; // Continue collecting path components: namespace:package/interface.funcname @@ -1650,8 +1718,7 @@ fn parse_func_sigs_into( parser.expect_symbol(':').map_err(|e| e.to_string())?; parser.accept_ident("func"); - let func = - wit_parser::parse_func_signature(parser, name).map_err(|e| e.to_string())?; + let func = wit_parser::parse_func_signature(parser, name).map_err(|e| e.to_string())?; let params: Vec<(String, metadata::TypeDesc)> = func .params diff --git a/crates/pack-guest-macros/src/metadata.rs b/crates/pack-guest-macros/src/metadata.rs index 30b5ab6..29b099d 100644 --- a/crates/pack-guest-macros/src/metadata.rs +++ b/crates/pack-guest-macros/src/metadata.rs @@ -4,13 +4,10 @@ //! Also computes Merkle-tree hashes for type compatibility checking. use pack_abi::{ - encode, Value, ValueType, TypeHash, Binding, - HASH_BOOL, HASH_U8, HASH_U16, HASH_U32, HASH_U64, - HASH_S8, HASH_S16, HASH_S32, HASH_S64, - HASH_F32, HASH_F64, HASH_CHAR, HASH_STRING, HASH_FLAGS, - HASH_SELF_REF, - hash_list, hash_option, hash_result, hash_tuple, - hash_record, hash_variant, hash_function, hash_interface, + encode, hash_function, hash_interface, hash_list, hash_option, hash_record, hash_result, + hash_tuple, hash_variant, Binding, TypeHash, Value, ValueType, HASH_BOOL, HASH_CHAR, HASH_F32, + HASH_F64, HASH_FLAGS, HASH_S16, HASH_S32, HASH_S64, HASH_S8, HASH_SELF_REF, HASH_STRING, + HASH_U16, HASH_U32, HASH_U64, HASH_U8, }; use std::collections::HashMap; @@ -92,10 +89,7 @@ impl TypeDesc { tag: 16, payload: vec![Value::Record { type_name: "result-desc".into(), - fields: vec![ - ("ok".into(), ok.to_value()), - ("err".into(), err.to_value()), - ], + fields: vec![("ok".into(), ok.to_value()), ("err".into(), err.to_value())], }], }, TypeDesc::Record { name, fields } => Value::Variant { @@ -206,7 +200,8 @@ impl TypeDesc { } TypeDesc::Record { fields, .. } => { // Sort fields by name for canonical ordering - let mut sorted: Vec<_> = fields.iter() + let mut sorted: Vec<_> = fields + .iter() .map(|(n, t)| (n.as_str(), t.to_hash())) .collect(); sorted.sort_by(|a, b| a.0.cmp(b.0)); @@ -214,7 +209,8 @@ impl TypeDesc { } TypeDesc::Variant { cases, .. } => { // Sort cases by name for canonical ordering - let mut sorted: Vec<_> = cases.iter() + let mut sorted: Vec<_> = cases + .iter() .map(|(n, t)| (n.as_str(), t.as_ref().map(|td| td.to_hash()))) .collect(); sorted.sort_by(|a, b| a.0.cmp(b.0)); @@ -249,10 +245,12 @@ pub fn compute_interface_hashes(funcs: &[FuncSig]) -> Vec { } // Compute hash for each interface - let mut result: Vec = by_interface.into_iter() + let mut result: Vec = by_interface + .into_iter() .map(|(iface_name, funcs)| { // Create bindings for each function (sorted by name) - let mut bindings: Vec<_> = funcs.iter() + let mut bindings: Vec<_> = funcs + .iter() .map(|f| Binding { name: f.name.as_str(), hash: hash_func_sig(f), @@ -328,12 +326,15 @@ fn interface_hash_to_value(ih: &InterfaceHash) -> Value { type_name: "interface-hash".into(), fields: vec![ ("name".into(), Value::String(ih.name.clone())), - ("hash".into(), Value::Tuple(vec![ - Value::U64(a), - Value::U64(b), - Value::U64(c), - Value::U64(d), - ])), + ( + "hash".into(), + Value::Tuple(vec![ + Value::U64(a), + Value::U64(b), + Value::U64(c), + Value::U64(d), + ]), + ), ], } } @@ -416,9 +417,12 @@ pub fn wit_type_to_type_desc( .map_or(TypeDesc::String, |t| wit_type_to_type_desc(t, types)), ), }, - crate::wit_parser::Type::Tuple(items) => { - TypeDesc::Tuple(items.iter().map(|t| wit_type_to_type_desc(t, types)).collect()) - } + crate::wit_parser::Type::Tuple(items) => TypeDesc::Tuple( + items + .iter() + .map(|t| wit_type_to_type_desc(t, types)) + .collect(), + ), crate::wit_parser::Type::Named(name) => { if name == "value" { return TypeDesc::Value; diff --git a/crates/pack-guest-macros/src/wit_parser.rs b/crates/pack-guest-macros/src/wit_parser.rs index a65a8f0..acc197c 100644 --- a/crates/pack-guest-macros/src/wit_parser.rs +++ b/crates/pack-guest-macros/src/wit_parser.rs @@ -42,6 +42,7 @@ impl InterfacePath { } /// Convert to a string representation + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { match (&self.namespace, &self.package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, self.interface), @@ -110,7 +111,8 @@ impl WitRegistry { WorldItem::Function(f) if f.name == path.function => return Some(f), WorldItem::InlineInterface { name, functions } => { // Check if this matches the interface name - if *name == path.interface.interface || path.interface.to_string() == *name { + if *name == path.interface.interface || path.interface.to_string() == *name + { if let Some(f) = functions.iter().find(|f| f.name == path.function) { return Some(f); } @@ -164,7 +166,11 @@ impl WitRegistry { names.push(format!("{}.{}", name, f.name)); } } - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { let path = match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), (None, Some(pkg)) => format!("{}/{}", pkg, interface), @@ -199,7 +205,11 @@ impl WitRegistry { names.push(format!("{}.{}", name, f.name)); } } - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { let path = match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), (None, Some(pkg)) => format!("{}/{}", pkg, interface), @@ -243,13 +253,18 @@ impl WitRegistry { match import { WorldItem::Function(f) if f.name == path.function => return Some(f), WorldItem::InlineInterface { name, functions } => { - if *name == path.interface.interface || path.interface.to_string() == *name { + if *name == path.interface.interface || path.interface.to_string() == *name + { if let Some(f) = functions.iter().find(|f| f.name == path.function) { return Some(f); } } } - WorldItem::InterfacePath { namespace, package, interface } => { + WorldItem::InterfacePath { + namespace, + package, + interface, + } => { let import_path = match (namespace, package) { (Some(ns), Some(pkg)) => format!("{}:{}/{}", ns, pkg, interface), (None, Some(pkg)) => format!("{}/{}", pkg, interface), @@ -276,16 +291,26 @@ impl WitRegistry { pub enum Type { // Primitives Bool, - U8, U16, U32, U64, - S8, S16, S32, S64, - F32, F64, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + F32, + F64, Char, String, // Compound List(Box), Option(Box), - Result { ok: Option>, err: Option> }, + Result { + ok: Option>, + err: Option>, + }, Tuple(Vec), // Named reference (to another type) @@ -302,10 +327,16 @@ pub enum TypeDef { Alias { name: String, ty: Type }, /// record foo { field: type, ... } - Record { name: String, fields: Vec<(String, Type)> }, + Record { + name: String, + fields: Vec<(String, Type)>, + }, /// variant foo { case(payload), ... } - Variant { name: String, cases: Vec }, + Variant { + name: String, + cases: Vec, + }, /// enum foo { a, b, c } Enum { name: String, cases: Vec }, @@ -355,10 +386,17 @@ pub enum WorldItem { Function(Function), /// An interface path: `wasi:cli/stdin` - InterfacePath { namespace: Option, package: Option, interface: String }, + InterfacePath { + namespace: Option, + package: Option, + interface: String, + }, /// Inline interface: `name { func... }` - InlineInterface { name: String, functions: Vec }, + InlineInterface { + name: String, + functions: Vec, + }, } /// A parsed WIT+ world @@ -430,8 +468,10 @@ impl<'a> Lexer<'a> { self.chars.next(); if matches!(self.chars.peek(), Some('/')) { // Line comment - while let Some(c) = self.chars.next() { - if c == '\n' { break; } + for c in self.chars.by_ref() { + if c == '\n' { + break; + } } continue; } @@ -466,7 +506,10 @@ impl<'a> Lexer<'a> { } // Symbols - if matches!(ch, '{' | '}' | '(' | ')' | '<' | '>' | ':' | ',' | '=' | ';' | '-' | '.' | '@' | '*') { + if matches!( + ch, + '{' | '}' | '(' | ')' | '<' | '>' | ':' | ',' | '=' | ';' | '-' | '.' | '@' | '*' + ) { tokens.push(Token::Symbol(ch)); self.chars.next(); continue; @@ -522,7 +565,10 @@ impl Parser { pub(crate) fn expect_symbol(&mut self, expected: char) -> Result<(), ParseError> { match self.next() { Token::Symbol(c) if c == expected => Ok(()), - other => Err(ParseError::new(format!("expected '{}', got {:?}", expected, other))), + other => Err(ParseError::new(format!( + "expected '{}', got {:?}", + expected, other + ))), } } @@ -538,7 +584,10 @@ impl Parser { pub(crate) fn expect_ident(&mut self) -> Result { match self.next() { Token::Ident(s) => Ok(s), - other => Err(ParseError::new(format!("expected identifier, got {:?}", other))), + other => Err(ParseError::new(format!( + "expected identifier, got {:?}", + other + ))), } } @@ -618,13 +667,23 @@ pub fn parse_world(src: &str) -> Result { match keyword.as_str() { "import" => imports.push(parse_world_item(&mut parser)?), "export" => exports.push(parse_world_item(&mut parser)?), - _ => return Err(ParseError::new(format!("expected 'import' or 'export', got '{}'", keyword))), + _ => { + return Err(ParseError::new(format!( + "expected 'import' or 'export', got '{}'", + keyword + ))) + } } } parser.expect_symbol('}')?; - Ok(World { name, types, imports, exports }) + Ok(World { + name, + types, + imports, + exports, + }) } /// Parse WIT content and return a complete registry @@ -757,7 +816,11 @@ fn parse_interface(parser: &mut Parser) -> Result { parser.expect_symbol('}')?; - Ok(Interface { name, types, functions }) + Ok(Interface { + name, + types, + functions, + }) } /// Parse just the body of a world (after 'world' keyword has been consumed) @@ -793,13 +856,23 @@ fn parse_world_body(parser: &mut Parser) -> Result { match keyword.as_str() { "import" => imports.push(parse_world_item(parser)?), "export" => exports.push(parse_world_item(parser)?), - _ => return Err(ParseError::new(format!("expected 'import' or 'export', got '{}'", keyword))), + _ => { + return Err(ParseError::new(format!( + "expected 'import' or 'export', got '{}'", + keyword + ))) + } } } parser.expect_symbol('}')?; - Ok(World { name, types, imports, exports }) + Ok(World { + name, + types, + imports, + exports, + }) } /// Try to parse a type definition from the current parser position. @@ -850,7 +923,10 @@ fn try_parse_typedef(parser: &mut Parser) -> Result, ParseError> } else { None }; - cases.push(VariantCase { name: case_name, payload }); + cases.push(VariantCase { + name: case_name, + payload, + }); parser.accept_symbol(','); } Ok(Some(TypeDef::Variant { name, cases })) @@ -908,7 +984,10 @@ fn parse_world_item(parser: &mut Parser) -> Result { if parser.accept_symbol('{') { let functions = parse_function_block(parser)?; parser.expect_symbol('}')?; - return Ok(WorldItem::InlineInterface { name: first, functions }); + return Ok(WorldItem::InlineInterface { + name: first, + functions, + }); } // Check for package/interface (no namespace) @@ -938,9 +1017,11 @@ fn parse_function_block(parser: &mut Parser) -> Result, ParseError } // Try name: func(...) pattern - if let (Token::Ident(name), Token::Symbol(':'), Token::Ident(func_kw)) = - (parser.peek().clone(), parser.peek_n(1).clone(), parser.peek_n(2).clone()) - { + if let (Token::Ident(name), Token::Symbol(':'), Token::Ident(func_kw)) = ( + parser.peek().clone(), + parser.peek_n(1).clone(), + parser.peek_n(2).clone(), + ) { if func_kw == "func" { parser.next(); // name parser.next(); // : @@ -963,7 +1044,10 @@ fn parse_function_block(parser: &mut Parser) -> Result, ParseError Ok(functions) } -pub(crate) fn parse_func_signature(parser: &mut Parser, name: String) -> Result { +pub(crate) fn parse_func_signature( + parser: &mut Parser, + name: String, +) -> Result { parser.expect_symbol('(')?; let params = parse_params(parser)?; parser.expect_symbol(')')?; @@ -975,7 +1059,11 @@ pub(crate) fn parse_func_signature(parser: &mut Parser, name: String) -> Result< Vec::new() }; - Ok(Function { name, params, results }) + Ok(Function { + name, + params, + results, + }) } fn parse_params(parser: &mut Parser) -> Result, ParseError> { diff --git a/crates/pack-guest/Cargo.toml b/crates/pack-guest/Cargo.toml index d891746..de94155 100644 --- a/crates/pack-guest/Cargo.toml +++ b/crates/pack-guest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pack-guest" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true description = "Guest-side helpers for Pack WASM packages" license = "MIT OR Apache-2.0" @@ -10,7 +10,7 @@ default = [] derive = ["dep:pack-derive"] [dependencies] -pack-abi = { path = "../pack-abi", default-features = false } -pack-guest-macros = { path = "../pack-guest-macros" } -pack-derive = { path = "../pack-derive", optional = true } +pack-abi = { workspace = true, default-features = false } +pack-guest-macros.workspace = true +pack-derive = { workspace = true, optional = true } dlmalloc = { version = "0.2", features = ["global"] } diff --git a/crates/pack-guest/src/lib.rs b/crates/pack-guest/src/lib.rs index c661c99..5886ffb 100644 --- a/crates/pack-guest/src/lib.rs +++ b/crates/pack-guest/src/lib.rs @@ -75,13 +75,7 @@ pub use alloc as __alloc; /// /// **Do not call this directly** - use the `#[export]` macro instead. #[doc(hidden)] -pub fn __export_impl( - in_ptr: i32, - in_len: i32, - out_ptr_ptr: i32, - out_len_ptr: i32, - f: F, -) -> i32 +pub fn __export_impl(in_ptr: i32, in_len: i32, out_ptr_ptr: i32, out_len_ptr: i32, f: F) -> i32 where F: FnOnce(Value) -> Result, { @@ -227,17 +221,14 @@ where } // Read the result from the location the callee specified - let output_bytes = unsafe { - core::slice::from_raw_parts(out_ptr as *const u8, out_len as usize) - }; + let output_bytes = + unsafe { core::slice::from_raw_parts(out_ptr as *const u8, out_len as usize) }; // Decode the result - let result = match decode(output_bytes) { + match decode(output_bytes) { Ok(v) => v, Err(_) => panic!("failed to decode import result"), - }; - - result + } } /// A simple bump allocator for guest packages. diff --git a/examples/transforms/main.rs b/examples/transforms/main.rs index 136e02e..c7b47ec 100644 --- a/examples/transforms/main.rs +++ b/examples/transforms/main.rs @@ -67,7 +67,7 @@ fn main() -> Result<(), Box> { let scope = type_registry.resolve_scope_with_transforms(caller, &transform_registry)?; println!("Types in scope:"); - for (name, _) in &scope.types { + for name in scope.types.keys() { println!(" - {}", name); } println!("\nTransformed interfaces:"); diff --git a/src/metadata.rs b/src/metadata.rs index 94102b4..8f7f1bd 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -59,16 +59,22 @@ impl TypeHash { /// Format as hex string (for display). pub fn to_hex(&self) -> String { - self.0.iter().map(|b| format!("{:02x}", b)).collect() + use core::fmt::Write; + let mut s = String::with_capacity(self.0.len() * 2); + for b in &self.0 { + let _ = write!(s, "{:02x}", b); + } + s } /// Format as short hex (first 8 chars). pub fn to_short_hex(&self) -> String { - self.0 - .iter() - .take(4) - .map(|b| format!("{:02x}", b)) - .collect() + use core::fmt::Write; + let mut s = String::with_capacity(8); + for b in self.0.iter().take(4) { + let _ = write!(s, "{:02x}", b); + } + s } /// Const function to create from bytes (for compile-time constants). diff --git a/src/runtime/host.rs b/src/runtime/host.rs index 31ec0b7..db56720 100644 --- a/src/runtime/host.rs +++ b/src/runtime/host.rs @@ -406,7 +406,7 @@ pub struct InterfaceBuilder<'a, 'b, T> { interceptor: Option>, } -impl<'a, 'b, T: 'static> InterfaceBuilder<'a, 'b, T> { +impl InterfaceBuilder<'_, '_, T> { /// Register a raw host function with direct WASM-level parameters. /// /// Use this for functions that need direct memory access or don't @@ -821,7 +821,7 @@ impl<'a, 'b, T: 'static> InterfaceBuilder<'a, 'b, T> { // Async Host Functions (require T: Send) // ============================================================================ -impl<'a, 'b, T: Send + Clone + 'static> InterfaceBuilder<'a, 'b, T> { +impl InterfaceBuilder<'_, '_, T> { /// Register an async host function with automatic Graph ABI encode/decode. /// /// The closure receives an `AsyncCtx` containing a cloned copy of the store diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index e896ce5..e044774 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -238,7 +238,7 @@ pub struct AsyncCompiledModule<'a> { engine: &'a Engine, } -impl<'a> AsyncCompiledModule<'a> { +impl AsyncCompiledModule<'_> { /// Instantiate the module with no imports (async). pub async fn instantiate_async(&self) -> Result, RuntimeError> { let mut store = Store::new(self.engine, ()); @@ -660,7 +660,7 @@ pub struct CompiledModule<'a> { engine: &'a Engine, } -impl<'a> CompiledModule<'a> { +impl CompiledModule<'_> { /// Instantiate the module with no imports pub fn instantiate(&self) -> Result, RuntimeError> { let mut store = Store::new(self.engine, ()); diff --git a/tests/abi_roundtrip.rs b/tests/abi_roundtrip.rs index aad95d6..ab48be4 100644 --- a/tests/abi_roundtrip.rs +++ b/tests/abi_roundtrip.rs @@ -102,7 +102,7 @@ fn roundtrip_array_s32() { // List should also use compact Array encoding let value = Value::List { elem_type: ValueType::S32, - items: vec![Value::S32(-1), Value::S32(0), Value::S32(i32::MAX as i32)], + items: vec![Value::S32(-1), Value::S32(0), Value::S32(i32::MAX)], }; let bytes = encode(&value).expect("encode"); diff --git a/tests/composition.rs b/tests/composition.rs index d8f7f05..f69bace 100644 --- a/tests/composition.rs +++ b/tests/composition.rs @@ -100,7 +100,7 @@ fn composition_multiple_calls() { for (input, expected) in test_cases { let result = composition .call("adder", "process", &Value::S64(input)) - .expect(&format!("failed to call process with {}", input)); + .unwrap_or_else(|_| panic!("failed to call process with {}", input)); assert_eq!( result, Value::S64(expected),