From 31300b6058aef9615953917b5fd61fc539f623dd Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 20 Apr 2026 01:01:39 -0700 Subject: [PATCH 1/6] Rework Rust method chaining system. --- crates/core/src/chainable_method.rs | 201 +++++++++++++++++++ crates/core/src/lib.rs | 2 + crates/guest-rust/macro/src/lib.rs | 31 ++- crates/rust/src/interface.rs | 78 ++++--- crates/rust/src/lib.rs | 34 +++- crates/rust/tests/codegen.rs | 2 +- tests/runtime/rust/method-chaining/runner.rs | 8 +- tests/runtime/rust/method-chaining/test.rs | 39 +++- tests/runtime/rust/method-chaining/test.wit | 6 + 9 files changed, 347 insertions(+), 54 deletions(-) create mode 100644 crates/core/src/chainable_method.rs diff --git a/crates/core/src/chainable_method.rs b/crates/core/src/chainable_method.rs new file mode 100644 index 000000000..e1e8f9724 --- /dev/null +++ b/crates/core/src/chainable_method.rs @@ -0,0 +1,201 @@ +use anyhow::{Result, bail}; +use std::collections::HashSet; +use std::fmt; +use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; + +/// Structure used to parse the command line argument `--chainable-method` consistently +/// across guest generators. +#[cfg_attr(feature = "clap", derive(clap::Parser))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[derive(Clone, Default, Debug)] +pub struct ChainableMethodFilterSet { + /// Determines which resource methods should have chaining enabled. + /// Chaining takes a WIT method import returning nothing, and modifies bindgen + /// in a language-dependent way to return `self` in the glue code. This does + /// not affect the ABI in any way. + /// + /// This option can be passed multiple times and additionally accepts + /// comma-separated values for each option passed. Each individual argument + /// passed here can be one of: + /// + /// - `all` - all applicable methods will be chainable + /// - `-all` - no methods will be chainable + /// - `foo:bar/baz#my-resource` - enable chaining for all methods in a resource + /// - `foo:bar/baz#my-resource.some-method` - enable chaining for particular method + /// + /// Options are processed in the order they are passed here, so if a method + /// matches two directives passed the least-specific one should be last. + #[cfg_attr( + feature = "clap", + arg( + long = "chainable-methods", + value_parser = parse_chainable_method, + value_delimiter =',', + value_name = "FILTER", + ), + )] + chainable_methods: Vec, + + #[cfg_attr(feature = "clap", arg(skip))] + #[cfg_attr(feature = "serde", serde(skip))] + used_options: HashSet, +} + +#[cfg(feature = "clap")] +fn parse_chainable_method(s: &str) -> Result { + Ok(ChainableMethod::parse(s)) +} + +impl ChainableMethodFilterSet { + /// Returns a set where all functions should be chainable or not depending on + /// `enable` provided. + pub fn all(enable: bool) -> ChainableMethodFilterSet { + ChainableMethodFilterSet { + chainable_methods: vec![ChainableMethod { + enabled: enable, + filter: ChainableMethodFilter::All, + }], + used_options: HashSet::new(), + } + } + + /// Returns whether the `func` provided should be made chainable + pub fn should_be_chainable( + &mut self, + resolve: &Resolve, + interface: Option<&WorldKey>, + func: &Function, + is_import: bool, + ) -> bool { + if !is_import { + return false; + } + + if func.result.is_some() { + return false; + } + + match func.kind { + FunctionKind::AsyncMethod(resource) | FunctionKind::Method(resource) => { + let interface_name = match interface.map(|key| resolve.name_world_key(key)) { + Some(str) => str + "#", + None => "".into(), + }; + + let resource_name_to_test = format!( + "{}{}", + interface_name, + resolve.types[resource].name.as_ref().unwrap() + ); + + let method_name_to_test = format!("{}{}", interface_name, func.name); + + for (i, opt) in self.chainable_methods.iter().enumerate() { + match &opt.filter { + ChainableMethodFilter::All => { + self.used_options.insert(i); + return opt.enabled; + } + ChainableMethodFilter::Resource(s) => { + if *s == resource_name_to_test { + self.used_options.insert(i); + return opt.enabled; + } + } + ChainableMethodFilter::Method(s) => { + if *s == method_name_to_test { + self.used_options.insert(i); + return opt.enabled; + } + } + }; + } + + return false; + } + _ => { + return false; + } + } + } + + /// Intended to be used in the header comment of generated code to help + /// indicate what options were specified. + pub fn debug_opts(&self) -> impl Iterator + '_ { + self.chainable_methods.iter().map(|opt| opt.to_string()) + } + + /// Tests whether all `--chainable-method` options were used throughout bindings + /// generation, returning an error if any were unused. + pub fn ensure_all_used(&self) -> Result<()> { + for (i, opt) in self.chainable_methods.iter().enumerate() { + if self.used_options.contains(&i) { + continue; + } + if !matches!(opt.filter, ChainableMethodFilter::All) { + bail!("unused chainable option: {opt}"); + } + } + Ok(()) + } + + /// Pushes a new option into this set. + pub fn push(&mut self, directive: &str) { + self.chainable_methods + .push(ChainableMethod::parse(directive)); + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +struct ChainableMethod { + enabled: bool, + filter: ChainableMethodFilter, +} + +impl ChainableMethod { + fn parse(s: &str) -> ChainableMethod { + let (s, enabled) = match s.strip_prefix('-') { + Some(s) => (s, false), + None => (s, true), + }; + let filter = match s { + "all" => ChainableMethodFilter::All, + other => { + if other.contains("[method]") { + ChainableMethodFilter::Method(other.to_string()) + } else { + ChainableMethodFilter::Resource(other.to_string()) + } + } + }; + ChainableMethod { enabled, filter } + } +} + +impl fmt::Display for ChainableMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.enabled { + write!(f, "-")?; + } + self.filter.fmt(f) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +enum ChainableMethodFilter { + All, + Resource(String), + Method(String), +} + +impl fmt::Display for ChainableMethodFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ChainableMethodFilter::All => write!(f, "all"), + ChainableMethodFilter::Resource(s) => write!(f, "{s}"), + ChainableMethodFilter::Method(s) => write!(f, "{s}"), + } + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index ee5a63b30..255f46a65 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -14,6 +14,8 @@ mod path; pub use path::name_package_module; mod async_; pub use async_::AsyncFilterSet; +mod chainable_method; +pub use chainable_method::ChainableMethodFilterSet; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 603222a77..7c7cc214b 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -6,9 +6,9 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::{Token, braced, token}; -use wit_bindgen_core::AsyncFilterSet; use wit_bindgen_core::WorldGenerator; use wit_bindgen_core::wit_parser::{PackageId, Resolve, WorldId}; +use wit_bindgen_core::{AsyncFilterSet, ChainableMethodFilterSet}; use wit_bindgen_rust::{Opts, Ownership, WithOption}; #[proc_macro] @@ -66,6 +66,7 @@ impl Parse for Config { let mut source = None; let mut features = Vec::new(); let mut async_configured = false; + let mut method_chaining_configured = false; let mut debug = false; if input.peek(token::Brace) { @@ -169,8 +170,15 @@ impl Parse for Config { async_configured = true; opts.async_ = val; } - Opt::EnableMethodChaining(enable) => { - opts.enable_method_chaining = enable.value(); + Opt::ChainableMethods(val, span) => { + if method_chaining_configured { + return Err(Error::new( + span, + "cannot specify second method chaining config", + )); + } + method_chaining_configured = true; + opts.chainable_methods = val; } Opt::MergeStructurallyEqualTypes(enable) => { opts.merge_structurally_equal_types = Some(Some(enable.value())) @@ -330,7 +338,7 @@ mod kw { syn::custom_keyword!(disable_custom_section_link_helpers); syn::custom_keyword!(imports); syn::custom_keyword!(debug); - syn::custom_keyword!(enable_method_chaining); + syn::custom_keyword!(chainable_methods); syn::custom_keyword!(merge_structurally_equal_types); } @@ -414,7 +422,7 @@ enum Opt { DisableCustomSectionLinkHelpers(syn::LitBool), Async(AsyncFilterSet, Span), Debug(syn::LitBool), - EnableMethodChaining(syn::LitBool), + ChainableMethods(ChainableMethodFilterSet, Span), MergeStructurallyEqualTypes(syn::LitBool), } @@ -600,10 +608,17 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::Debug(input.parse()?)) - } else if l.peek(kw::enable_method_chaining) { - input.parse::()?; + } else if l.peek(kw::chainable_methods) { + let span = input.parse::()?.span; input.parse::()?; - Ok(Opt::EnableMethodChaining(input.parse()?)) + + let mut set = ChainableMethodFilterSet::default(); + let contents; + syn::bracketed!(contents in input); + for val in contents.parse_terminated(|p| p.parse::(), Token![,])? { + set.push(&val.value()); + } + Ok(Opt::ChainableMethods(set, span)) } else if l.peek(Token![async]) { let span = input.parse::()?.span; input.parse::()?; diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index fc809bad0..abd5033d8 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -180,8 +180,13 @@ impl<'i> InterfaceGenerator<'i> { private: true, ..Default::default() }; - sig.update_for_func(&func); - self.print_signature(func, true, &sig); + + let should_return_self = + self.r#gen + .should_return_self(self.resolve, interface.map(|p| p.1), func, false); + + sig.update_for_func(&func, should_return_self); + self.print_signature(func, true, &sig, should_return_self); self.src.push_str(";\n"); let trait_method = mem::replace(&mut self.src, prev); methods.push(trait_method); @@ -792,22 +797,37 @@ pub mod vtable{ordinal} {{ async_, ..Default::default() }; + + let should_return_self = self + .r#gen + .should_return_self(self.resolve, interface, func, true); + if let Some(id) = func.kind.resource() { let name = self.resolve.types[id].name.as_ref().unwrap(); let name = to_upper_camel_case(name); uwriteln!(self.src, "impl {name} {{"); sig.use_item_name = true; - sig.update_for_func(&func); + sig.update_for_func(&func, should_return_self); } self.src.push_str("#[allow(unused_unsafe, clippy::all)]\n"); - let params = self.print_signature(func, async_, &sig); + let params = self.print_signature(func, async_, &sig, should_return_self); self.src.push_str("{\n"); self.src.push_str("unsafe {\n"); if async_ { - self.generate_guest_import_body_async(&self.wasm_import_module, func, params); + self.generate_guest_import_body_async( + &self.wasm_import_module, + func, + params, + should_return_self, + ); } else { - self.generate_guest_import_body_sync(&self.wasm_import_module, func, params); + self.generate_guest_import_body_sync( + &self.wasm_import_module, + func, + params, + should_return_self, + ); } self.src.push_str("}\n"); @@ -859,14 +879,9 @@ pub mod vtable{ordinal} {{ module: &str, func: &Function, params: Vec, + should_return_self: bool, ) { - let mut f = FunctionBindgen::new( - self, - params, - module, - false, - self.r#gen.should_return_self(func), - ); + let mut f = FunctionBindgen::new(self, params, module, false, should_return_self); abi::call( f.r#gen.resolve, AbiVariant::GuestImport, @@ -907,6 +922,7 @@ pub mod vtable{ordinal} {{ module: &str, func: &Function, mut params: Vec, + should_return_self: bool, ) { let param_tys = func .params @@ -1137,11 +1153,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) self.src, "_MySubtask {{ _unused: core::marker::PhantomData }}.call(({})).await{}", params.join(" "), - if self.r#gen.should_return_self(func) { - ";\nself" - } else { - "" - } + if should_return_self { ";\nself" } else { "" } ); } @@ -1438,9 +1450,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) private: true, ..Default::default() }; - sig.update_for_func(&func); + + let should_return_self = + self.r#gen + .should_return_self(self.resolve, interface.map(|p| p.1), func, false); + + sig.update_for_func(&func, should_return_self); self.src.push_str("#[allow(unused_variables)]\n"); - self.print_signature(func, true, &sig); + self.print_signature(func, true, &sig, should_return_self); self.src.push_str("{ unreachable!() }\n"); } @@ -1498,8 +1515,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) // } } - fn print_signature(&mut self, func: &Function, params_owned: bool, sig: &FnSig) -> Vec { - let params = self.print_docs_and_params(func, params_owned, sig); + fn print_signature( + &mut self, + func: &Function, + params_owned: bool, + sig: &FnSig, + should_return_self: bool, + ) -> Vec { + let params = self.print_docs_and_params(func, params_owned, sig, should_return_self); self.push_str(" -> "); if let FunctionKind::Constructor(resource_id) = &func.kind { match classify_constructor_return_type(&self.resolve, *resource_id, &func.result) { @@ -1513,8 +1536,8 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } } } else { - if self.r#gen.should_return_self(func) { - self.push_str("&Self"); + if should_return_self { + self.push_str("Self"); } else { self.print_result_type(&func.result); } @@ -1527,6 +1550,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) func: &Function, params_owned: bool, sig: &FnSig, + should_return_self: bool, ) -> Vec { self.rustdoc(&func.docs); self.rustdoc_params(&func.params, "Parameters"); @@ -1574,7 +1598,11 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) ) in func.params.iter().enumerate() { if i == 0 && sig.self_is_first_param { - params.push("self".to_string()); + params.push(if should_return_self { + "&self".to_string() + } else { + "self".to_string() + }); continue; } let name = to_rust_ident(name); diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index dfe8c2623..d0f737991 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -10,8 +10,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - AsyncFilterSet, Files, InterfaceGenerator as _, Source, Types, WorldGenerator, dealias, - name_package_module, uwrite, uwriteln, wit_parser::*, + AsyncFilterSet, ChainableMethodFilterSet, Files, InterfaceGenerator as _, Source, Types, + WorldGenerator, dealias, name_package_module, uwrite, uwriteln, wit_parser::*, }; mod bindgen; @@ -345,9 +345,9 @@ pub struct Opts { )] pub merge_structurally_equal_types: Option>, - /// If true, methods normally returning `()` instead return `&Self`. This applies to both imported and exported methods. - #[cfg_attr(feature = "clap", arg(long))] - pub enable_method_chaining: bool, + #[cfg_attr(feature = "clap", clap(flatten))] + #[cfg_attr(feature = "serde", serde(flatten))] + pub chainable_methods: ChainableMethodFilterSet, } impl Opts { @@ -1105,10 +1105,17 @@ macro_rules! __export_{world_name}_impl {{ .is_async(resolve, interface, func, is_import) } - fn should_return_self(&self, func: &Function) -> bool { - self.opts.enable_method_chaining - && func.result.is_none() - && matches!(&func.kind, FunctionKind::Method(_)) + fn should_return_self( + &mut self, + resolve: &Resolve, + interface: Option<&WorldKey>, + func: &Function, + is_import: bool, + ) -> bool { + return self + .opts + .chainable_methods + .should_be_chainable(resolve, interface, func, is_import); } } @@ -1616,6 +1623,7 @@ impl WorldGenerator for RustWasm { // Error about unused async configuration to help catch configuration // errors. self.opts.async_.ensure_all_used()?; + self.opts.chainable_methods.ensure_all_used()?; Ok(()) } @@ -1765,9 +1773,13 @@ struct FnSig { } impl FnSig { - fn update_for_func(&mut self, func: &Function) { + fn update_for_func(&mut self, func: &Function, return_self: bool) { if let FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) = &func.kind { - self.self_arg = Some("&self".into()); + self.self_arg = Some(if return_self { + "self".into() + } else { + "&self".into() + }); self.self_is_first_param = true; } } diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index e8046ceaf..de268864e 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -232,7 +232,7 @@ mod method_chaining { } "#, generate_all, - enable_method_chaining: true + chainable_methods: ["all"] }); } diff --git a/tests/runtime/rust/method-chaining/runner.rs b/tests/runtime/rust/method-chaining/runner.rs index cbfaf631e..3a8a89130 100644 --- a/tests/runtime/rust/method-chaining/runner.rs +++ b/tests/runtime/rust/method-chaining/runner.rs @@ -1,8 +1,9 @@ -//@ args = '--enable-method-chaining' +//@ args = '--chainable-methods foo:bar/i#a' include!(env!("BINDINGS")); use crate::foo::bar::i::A; +use crate::foo::bar::i::B; struct Component; export!(Component); @@ -11,5 +12,10 @@ impl Guest for Component { fn run() { let my_a = A::new(); my_a.set_a(42).set_b(true).do_(); + + let my_b = B::new(); + my_b.set_a(42); + my_b.set_b(true); + my_b.do_(); } } diff --git a/tests/runtime/rust/method-chaining/test.rs b/tests/runtime/rust/method-chaining/test.rs index 9e48a05c9..4010cc73f 100644 --- a/tests/runtime/rust/method-chaining/test.rs +++ b/tests/runtime/rust/method-chaining/test.rs @@ -1,14 +1,17 @@ -//@ args = '--enable-method-chaining' +//@ args = '--chainable-methods all' + +// Should have no effect on exports include!(env!("BINDINGS")); -use crate::exports::foo::bar::i::{Guest, GuestA}; +use crate::exports::foo::bar::i::{Guest, GuestA, GuestB}; use std::cell::Cell; struct Component; export!(Component); impl Guest for Component { type A = MyA; + type B = MyB; } struct MyA { @@ -16,6 +19,11 @@ struct MyA { prop_b: Cell, } +struct MyB { + prop_a: Cell, + prop_b: Cell, +} + impl GuestA for MyA { fn new() -> MyA { MyA { @@ -24,17 +32,32 @@ impl GuestA for MyA { } } - fn set_a(&self, a: u32) -> &Self { + fn set_a(&self, a: u32) { self.prop_a.set(a); - self } - fn set_b(&self, b: bool) -> &Self { + fn set_b(&self, b: bool) { self.prop_b.set(b); - self } - fn do_(&self) -> &Self { - self + fn do_(&self) {} +} + +impl GuestB for MyB { + fn new() -> MyB { + MyB { + prop_a: Cell::new(0), + prop_b: Cell::new(false), + } + } + + fn set_a(&self, a: u32) { + self.prop_a.set(a); } + + fn set_b(&self, b: bool) { + self.prop_b.set(b); + } + + fn do_(&self) {} } diff --git a/tests/runtime/rust/method-chaining/test.wit b/tests/runtime/rust/method-chaining/test.wit index f3c18c846..4b7f1dc9d 100644 --- a/tests/runtime/rust/method-chaining/test.wit +++ b/tests/runtime/rust/method-chaining/test.wit @@ -7,6 +7,12 @@ interface i { set-b: func(arg: bool); do: func(); } + resource b { + constructor(); + set-a: func(arg: u32); + set-b: func(arg: bool); + do: func(); + } } world runner { import i; From 46a570d1199e1b027f995fbaaa0e01dfb020a7a3 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 21 Aug 2026 01:11:00 -0700 Subject: [PATCH 2/6] Allow configuring `Owning` vs `Borrowing` chaining --- crates/core/src/chainable_method.rs | 56 +++++++++++++------- crates/core/src/lib.rs | 2 +- crates/rust/src/bindgen.rs | 10 ++-- crates/rust/src/interface.rs | 53 ++++++++++-------- crates/rust/src/lib.rs | 20 +++---- crates/rust/tests/codegen.rs | 21 +++++++- tests/runtime/rust/method-chaining/runner.rs | 17 +++--- tests/runtime/rust/method-chaining/test.rs | 27 +++++++++- tests/runtime/rust/method-chaining/test.wit | 6 +++ 9 files changed, 147 insertions(+), 65 deletions(-) diff --git a/crates/core/src/chainable_method.rs b/crates/core/src/chainable_method.rs index e1e8f9724..c4d3ffe65 100644 --- a/crates/core/src/chainable_method.rs +++ b/crates/core/src/chainable_method.rs @@ -19,10 +19,17 @@ pub struct ChainableMethodFilterSet { /// passed here can be one of: /// /// - `all` - all applicable methods will be chainable - /// - `-all` - no methods will be chainable /// - `foo:bar/baz#my-resource` - enable chaining for all methods in a resource /// - `foo:bar/baz#my-resource.some-method` - enable chaining for particular method /// + /// Each filter may also have one of two modifier prefixes: + /// - `-` - inverts the selection; e.g. `-all` will disable chaining for all + /// - `&` - makes the chainable return `&Self` instead of `Self` (borrowing) + /// + /// For instance, `&foo:bar/baz#my-resource` will make all methods in said resource + /// borrowing chainable, while `-foo:bar/baz#my-resource.some-method` will disable it + /// for that particular method. + /// /// Options are processed in the order they are passed here, so if a method /// matches two directives passed the least-specific one should be last. #[cfg_attr( @@ -46,13 +53,19 @@ fn parse_chainable_method(s: &str) -> Result { Ok(ChainableMethod::parse(s)) } +#[derive(Clone, Copy, Debug)] +pub enum ChainingMode { + Owning, + Borrowing, +} + impl ChainableMethodFilterSet { /// Returns a set where all functions should be chainable or not depending on /// `enable` provided. - pub fn all(enable: bool) -> ChainableMethodFilterSet { + pub fn all(mode: ChainingMode) -> ChainableMethodFilterSet { ChainableMethodFilterSet { chainable_methods: vec![ChainableMethod { - enabled: enable, + mode: Some(mode), filter: ChainableMethodFilter::All, }], used_options: HashSet::new(), @@ -66,13 +79,13 @@ impl ChainableMethodFilterSet { interface: Option<&WorldKey>, func: &Function, is_import: bool, - ) -> bool { + ) -> Option { if !is_import { - return false; + return None; } if func.result.is_some() { - return false; + return None; } match func.kind { @@ -94,27 +107,27 @@ impl ChainableMethodFilterSet { match &opt.filter { ChainableMethodFilter::All => { self.used_options.insert(i); - return opt.enabled; + return opt.mode; } ChainableMethodFilter::Resource(s) => { if *s == resource_name_to_test { self.used_options.insert(i); - return opt.enabled; + return opt.mode; } } ChainableMethodFilter::Method(s) => { if *s == method_name_to_test { self.used_options.insert(i); - return opt.enabled; + return opt.mode; } } }; } - return false; + return None; } _ => { - return false; + return None; } } } @@ -149,15 +162,18 @@ impl ChainableMethodFilterSet { #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] struct ChainableMethod { - enabled: bool, + mode: Option, filter: ChainableMethodFilter, } impl ChainableMethod { fn parse(s: &str) -> ChainableMethod { - let (s, enabled) = match s.strip_prefix('-') { - Some(s) => (s, false), - None => (s, true), + let (s, mode) = match s.strip_prefix('-') { + Some(s) => (s, None), + None => match s.strip_prefix('&') { + Some(s) => (s, Some(ChainingMode::Borrowing)), + None => (s, Some(ChainingMode::Owning)), + }, }; let filter = match s { "all" => ChainableMethodFilter::All, @@ -169,15 +185,17 @@ impl ChainableMethod { } } }; - ChainableMethod { enabled, filter } + ChainableMethod { mode, filter } } } impl fmt::Display for ChainableMethod { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.enabled { - write!(f, "-")?; - } + match self.mode { + Some(ChainingMode::Owning) => {} + Some(ChainingMode::Borrowing) => write!(f, "&")?, + None => write!(f, "-")?, + }; self.filter.fmt(f) } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 255f46a65..b03c173dd 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -15,7 +15,7 @@ pub use path::name_package_module; mod async_; pub use async_::AsyncFilterSet; mod chainable_method; -pub use chainable_method::ChainableMethodFilterSet; +pub use chainable_method::{ChainableMethodFilterSet, ChainingMode}; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 767beb6a9..70aeb67a5 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -6,7 +6,7 @@ use heck::*; use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{Bindgen, Instruction, LiftLower, WasmType}; -use wit_bindgen_core::{Source, dealias, uwrite, uwriteln, wit_parser::*}; +use wit_bindgen_core::{ChainingMode, Source, dealias, uwrite, uwriteln, wit_parser::*}; pub(super) struct FunctionBindgen<'a, 'b> { pub r#gen: &'b mut InterfaceGenerator<'a>, @@ -21,7 +21,7 @@ pub(super) struct FunctionBindgen<'a, 'b> { pub import_return_pointer_area_align: Alignment, pub handle_decls: Vec, always_owned: bool, - return_self: bool, + return_self: Option, } pub const POINTER_SIZE_EXPRESSION: &str = "::core::mem::size_of::<*const u8>()"; @@ -32,7 +32,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { params: Vec, wasm_import_module: &'b str, always_owned: bool, - return_self: bool, + return_self: Option, ) -> FunctionBindgen<'a, 'b> { FunctionBindgen { r#gen, @@ -1054,11 +1054,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::Return { amt, .. } => { - assert!(!self.return_self || *amt == 0); + assert!(self.return_self.is_none() || *amt == 0); match amt { 0 => { - if self.return_self { + if self.return_self.is_some() { self.push_str("self\n"); } } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index abd5033d8..628142d19 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -12,7 +12,8 @@ use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{self, AbiVariant, LiftLower}; use wit_bindgen_core::{ - AnonymousTypeGenerator, Source, TypeInfo, dealias, uwrite, uwriteln, wit_parser::*, + AnonymousTypeGenerator, ChainingMode, Source, TypeInfo, dealias, uwrite, uwriteln, + wit_parser::*, }; pub struct InterfaceGenerator<'a> { @@ -839,7 +840,7 @@ pub mod vtable{ordinal} {{ } fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, Vec::new(), module, true, false); + let mut f = FunctionBindgen::new(self, Vec::new(), module, true, None); abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); format!("unsafe {{ {} }}", String::from(f.src)) } @@ -851,7 +852,7 @@ pub mod vtable{ordinal} {{ indirect: bool, module: &str, ) -> String { - let mut f = FunctionBindgen::new(self, Vec::new(), module, true, false); + let mut f = FunctionBindgen::new(self, Vec::new(), module, true, None); abi::deallocate_lists_in_types(f.r#gen.resolve, types, operands, indirect, &mut f); format!("unsafe {{ {} }}", String::from(f.src)) } @@ -863,13 +864,13 @@ pub mod vtable{ordinal} {{ indirect: bool, module: &str, ) -> String { - let mut f = FunctionBindgen::new(self, Vec::new(), module, true, false); + let mut f = FunctionBindgen::new(self, Vec::new(), module, true, None); abi::deallocate_lists_and_own_in_types(f.r#gen.resolve, types, operands, indirect, &mut f); format!("unsafe {{ {} }}", String::from(f.src)) } fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, Vec::new(), module, true, false); + let mut f = FunctionBindgen::new(self, Vec::new(), module, true, None); let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); format!("unsafe {{ {}\n{result} }}", String::from(f.src)) } @@ -879,7 +880,7 @@ pub mod vtable{ordinal} {{ module: &str, func: &Function, params: Vec, - should_return_self: bool, + should_return_self: Option, ) { let mut f = FunctionBindgen::new(self, params, module, false, should_return_self); abi::call( @@ -922,7 +923,7 @@ pub mod vtable{ordinal} {{ module: &str, func: &Function, mut params: Vec, - should_return_self: bool, + should_return_self: Option, ) { let param_tys = func .params @@ -1105,7 +1106,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } lowers.push("ParamsLower(_ptr,)".to_string()); } else { - let mut f = FunctionBindgen::new(self, Vec::new(), module, true, false); + let mut f = FunctionBindgen::new(self, Vec::new(), module, true, None); let mut results = Vec::new(); for (i, Param { ty, .. }) in func.params.iter().enumerate() { let name = format!("_lower{i}"); @@ -1153,7 +1154,11 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) self.src, "_MySubtask {{ _unused: core::marker::PhantomData }}.call(({})).await{}", params.join(" "), - if should_return_self { ";\nself" } else { "" } + if should_return_self.is_some() { + ";\nself" + } else { + "" + } ); } @@ -1195,7 +1200,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) ); } - let mut f = FunctionBindgen::new(self, params, self.wasm_import_module, false, false); + let mut f = FunctionBindgen::new(self, params, self.wasm_import_module, false, None); let variant = if async_ { AbiVariant::GuestExportAsync } else { @@ -1265,7 +1270,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) let params = self.print_post_return_sig(func); self.src.push_str("{ unsafe {\n"); - let mut f = FunctionBindgen::new(self, params, self.wasm_import_module, false, false); + let mut f = FunctionBindgen::new(self, params, self.wasm_import_module, false, None); abi::post_return(f.r#gen.resolve, func, &mut f); let FunctionBindgen { needs_cleanup_list, @@ -1520,7 +1525,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) func: &Function, params_owned: bool, sig: &FnSig, - should_return_self: bool, + should_return_self: Option, ) -> Vec { let params = self.print_docs_and_params(func, params_owned, sig, should_return_self); self.push_str(" -> "); @@ -1536,11 +1541,11 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } } } else { - if should_return_self { - self.push_str("Self"); - } else { - self.print_result_type(&func.result); - } + match should_return_self { + Some(ChainingMode::Owning) => self.push_str("Self"), + Some(ChainingMode::Borrowing) => self.push_str("&Self"), + None => self.print_result_type(&func.result), + }; } params } @@ -1550,7 +1555,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) func: &Function, params_owned: bool, sig: &FnSig, - should_return_self: bool, + should_return_self: Option, ) -> Vec { self.rustdoc(&func.docs); self.rustdoc_params(&func.params, "Parameters"); @@ -1598,11 +1603,13 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) ) in func.params.iter().enumerate() { if i == 0 && sig.self_is_first_param { - params.push(if should_return_self { - "&self".to_string() - } else { - "self".to_string() - }); + params.push( + match should_return_self { + Some(ChainingMode::Owning) => "&self", + _ => "self", + } + .to_string(), + ); continue; } let name = to_rust_ident(name); diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index d0f737991..8df60e9a5 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -10,8 +10,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - AsyncFilterSet, ChainableMethodFilterSet, Files, InterfaceGenerator as _, Source, Types, - WorldGenerator, dealias, name_package_module, uwrite, uwriteln, wit_parser::*, + AsyncFilterSet, ChainableMethodFilterSet, ChainingMode, Files, InterfaceGenerator as _, Source, + Types, WorldGenerator, dealias, name_package_module, uwrite, uwriteln, wit_parser::*, }; mod bindgen; @@ -1111,7 +1111,7 @@ macro_rules! __export_{world_name}_impl {{ interface: Option<&WorldKey>, func: &Function, is_import: bool, - ) -> bool { + ) -> Option { return self .opts .chainable_methods @@ -1773,13 +1773,15 @@ struct FnSig { } impl FnSig { - fn update_for_func(&mut self, func: &Function, return_self: bool) { + fn update_for_func(&mut self, func: &Function, return_self: Option) { if let FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) = &func.kind { - self.self_arg = Some(if return_self { - "self".into() - } else { - "&self".into() - }); + self.self_arg = Some( + match return_self { + Some(ChainingMode::Owning) => "self", + _ => "&self", + } + .into(), + ); self.self_is_first_param = true; } } diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index de268864e..5cc96cf42 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -218,7 +218,7 @@ mod retyped_list { } #[allow(unused, reason = "testing codegen, not functionality")] -mod method_chaining { +mod owning_method_chaining { wit_bindgen::generate!({ inline: r#" package test:method-chaining; @@ -236,6 +236,25 @@ mod method_chaining { }); } +#[allow(unused, reason = "testing codegen, not functionality")] +mod borrowing_method_chaining { + wit_bindgen::generate!({ + inline: r#" + package test:method-chaining; + world test { + resource a { + constructor(); + set-a: func(arg: u32); + set-b: func(arg: bool); + do: func(); + } + } + "#, + generate_all, + chainable_methods: ["&all"] + }); +} + #[allow(unused, reason = "testing codegen, not functionality")] mod merge_structurally_equal_types { wit_bindgen::generate!({ diff --git a/tests/runtime/rust/method-chaining/runner.rs b/tests/runtime/rust/method-chaining/runner.rs index 3a8a89130..90ca02639 100644 --- a/tests/runtime/rust/method-chaining/runner.rs +++ b/tests/runtime/rust/method-chaining/runner.rs @@ -1,21 +1,26 @@ -//@ args = '--chainable-methods foo:bar/i#a' +//@ args = '--chainable-methods foo:bar/i#a,&foo:bar/i#b' include!(env!("BINDINGS")); use crate::foo::bar::i::A; use crate::foo::bar::i::B; +use crate::foo::bar::i::C; struct Component; export!(Component); impl Guest for Component { + #[allow(unused_assignments)] fn run() { - let my_a = A::new(); - my_a.set_a(42).set_b(true).do_(); + let mut my_a = A::new(); + my_a = my_a.set_a(42).set_b(true).do_(); let my_b = B::new(); - my_b.set_a(42); - my_b.set_b(true); - my_b.do_(); + my_b.set_a(42).set_b(true).do_(); + + let my_c = C::new(); + my_c.set_a(42); + my_c.set_b(true); + my_c.do_(); } } diff --git a/tests/runtime/rust/method-chaining/test.rs b/tests/runtime/rust/method-chaining/test.rs index 4010cc73f..351c52cd7 100644 --- a/tests/runtime/rust/method-chaining/test.rs +++ b/tests/runtime/rust/method-chaining/test.rs @@ -4,7 +4,7 @@ include!(env!("BINDINGS")); -use crate::exports::foo::bar::i::{Guest, GuestA, GuestB}; +use crate::exports::foo::bar::i::{Guest, GuestA, GuestB, GuestC}; use std::cell::Cell; struct Component; @@ -12,6 +12,7 @@ export!(Component); impl Guest for Component { type A = MyA; type B = MyB; + type C = MyC; } struct MyA { @@ -24,6 +25,11 @@ struct MyB { prop_b: Cell, } +struct MyC { + prop_a: Cell, + prop_b: Cell, +} + impl GuestA for MyA { fn new() -> MyA { MyA { @@ -61,3 +67,22 @@ impl GuestB for MyB { fn do_(&self) {} } + +impl GuestC for MyC { + fn new() -> MyC { + MyC { + prop_a: Cell::new(0), + prop_b: Cell::new(false), + } + } + + fn set_a(&self, a: u32) { + self.prop_a.set(a); + } + + fn set_b(&self, b: bool) { + self.prop_b.set(b); + } + + fn do_(&self) {} +} diff --git a/tests/runtime/rust/method-chaining/test.wit b/tests/runtime/rust/method-chaining/test.wit index 4b7f1dc9d..21b64999e 100644 --- a/tests/runtime/rust/method-chaining/test.wit +++ b/tests/runtime/rust/method-chaining/test.wit @@ -13,6 +13,12 @@ interface i { set-b: func(arg: bool); do: func(); } + resource c { + constructor(); + set-a: func(arg: u32); + set-b: func(arg: bool); + do: func(); + } } world runner { import i; From 932f15ee9a2fbdd2e892c0c3ee5a29ac550ba9ca Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 21 Aug 2026 22:01:56 -0700 Subject: [PATCH 3/6] De-dup filter implementations --- crates/c/src/lib.rs | 8 +- crates/core/src/async_.rs | 159 ++++++++++---------- crates/core/src/chainable_method.rs | 218 +++++++++++++--------------- crates/core/src/filter.rs | 96 ++++++++++++ crates/core/src/lib.rs | 2 + crates/go/src/lib.rs | 10 +- crates/guest-rust/macro/src/lib.rs | 2 +- crates/moonbit/src/async_support.rs | 8 +- crates/moonbit/src/lib.rs | 2 + crates/rust/src/lib.rs | 9 +- 10 files changed, 295 insertions(+), 219 deletions(-) create mode 100644 crates/core/src/filter.rs diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 91d30b14e..170e79d8f 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -10,8 +10,8 @@ use wit_bindgen_core::abi::{ self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmSignature, WasmType, }; use wit_bindgen_core::{ - AnonymousTypeGenerator, AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, - WorldGenerator, dealias, uwrite, uwriteln, wit_parser::*, + AnonymousTypeGenerator, AsyncFilterSet, Direction, Files, FilterSet, InterfaceGenerator as _, + Ns, WorldGenerator, dealias, uwrite, uwriteln, wit_parser::*, }; use wit_component::StringEncoding; @@ -2077,7 +2077,7 @@ impl InterfaceGenerator<'_> { .r#gen .opts .async_ - .is_async(self.resolve, interface_name, func, true); + .apply_rules(self.resolve, interface_name, func, true); if async_ { self.r#gen.needs_async = true; } @@ -2246,7 +2246,7 @@ impl InterfaceGenerator<'_> { .r#gen .opts .async_ - .is_async(self.resolve, interface_name, func, false); + .apply_rules(self.resolve, interface_name, func, false); let (variant, prefix) = if async_ { self.r#gen.needs_async = true; diff --git a/crates/core/src/async_.rs b/crates/core/src/async_.rs index e801e2ea3..db3c06711 100644 --- a/crates/core/src/async_.rs +++ b/crates/core/src/async_.rs @@ -1,8 +1,9 @@ -use anyhow::{Result, bail}; -use std::collections::HashSet; use std::fmt; +use std::{collections::HashSet, fmt::Write}; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; +use crate::filter::{FilterMode, FilterRule, FilterSet, FilterTarget}; + /// Structure used to parse the command line argument `--async` consistently /// across guest generators. #[cfg_attr(feature = "clap", derive(clap::Parser))] @@ -33,9 +34,9 @@ pub struct AsyncFilterSet { arg( long = "async", value_parser = parse_async, - value_delimiter =',', + value_delimiter = ',', value_name = "FILTER", - ), + ) )] #[cfg_attr(feature = "serde", serde(rename = "async"))] async_: Vec, @@ -45,26 +46,35 @@ pub struct AsyncFilterSet { used_options: HashSet, } -#[cfg(feature = "clap")] -fn parse_async(s: &str) -> Result { - Ok(Async::parse(s)) -} +impl FilterSet for AsyncFilterSet { + type Mode = bool; + type Filter = AsyncFilter; -impl AsyncFilterSet { - /// Returns a set where all functions should be async or not depending on - /// `async_` provided. - pub fn all(async_: bool) -> AsyncFilterSet { - AsyncFilterSet { - async_: vec![Async { - enabled: async_, - filter: AsyncFilter::All, - }], + fn new(rules: Vec) -> Self { + Self { + async_: rules, used_options: HashSet::new(), } } - /// Returns whether the `func` provided is to be bound `async` or not. - pub fn is_async( + fn rules(&self) -> &[Async] { + &self.async_ + } + fn rules_mut(&mut self) -> &mut Vec { + &mut self.async_ + } + fn used_options(&self) -> &HashSet { + &self.used_options + } + fn used_options_mut(&mut self) -> &mut HashSet { + &mut self.used_options + } + + fn option_name() -> &'static str { + "async" + } + + fn apply_rules( &mut self, resolve: &Resolve, interface: Option<&WorldKey>, @@ -75,11 +85,12 @@ impl AsyncFilterSet { Some(key) => format!("{}#{}", resolve.name_world_key(key), func.name), None => func.name.clone(), }; + for (i, opt) in self.async_.iter().enumerate() { let name = match &opt.filter { AsyncFilter::All => { self.used_options.insert(i); - return opt.enabled; + return opt.mode; } AsyncFilter::Function(s) => s, AsyncFilter::Import(s) => { @@ -97,95 +108,77 @@ impl AsyncFilterSet { }; if *name == name_to_test { self.used_options.insert(i); - return opt.enabled; + return opt.mode; } } - match &func.kind { - FunctionKind::Freestanding - | FunctionKind::Method(_) - | FunctionKind::Static(_) - | FunctionKind::Constructor(_) => false, + matches!( + func.kind, FunctionKind::AsyncFreestanding - | FunctionKind::AsyncMethod(_) - | FunctionKind::AsyncStatic(_) => true, - } - } - - /// Intended to be used in the header comment of generated code to help - /// indicate what options were specified. - pub fn debug_opts(&self) -> impl Iterator + '_ { - self.async_.iter().map(|opt| opt.to_string()) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) + ) } +} - /// Tests whether all `--async` options were used throughout bindings - /// generation, returning an error if any were unused. - pub fn ensure_all_used(&self) -> Result<()> { - for (i, opt) in self.async_.iter().enumerate() { - if self.used_options.contains(&i) { - continue; - } - if !matches!(opt.filter, AsyncFilter::All) { - bail!("unused async option: {opt}"); - } - } - Ok(()) - } +#[cfg(feature = "clap")] +fn parse_async(s: &str) -> Result { + Ok(Async::parse(s)) +} - /// Returns whether any option explicitly requests that async is enabled. +impl AsyncFilterSet { pub fn any_enabled(&self) -> bool { - self.async_.iter().any(|o| o.enabled) + self.async_.iter().any(|o| o.mode) } +} + +type Async = FilterRule; - /// Pushes a new option into this set. - pub fn push(&mut self, directive: &str) { - self.async_.push(Async::parse(directive)); +impl FilterMode for bool { + fn parse(s: &str) -> (Self, &str) { + match s.strip_prefix('-') { + Some(rest) => (false, rest), + None => (true, s), + } + } + fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if *self { + f.write_char('-')?; + } + Ok(()) } } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -struct Async { - enabled: bool, - filter: AsyncFilter, +pub enum AsyncFilter { + All, + Function(String), + Import(String), + Export(String), } -impl Async { - fn parse(s: &str) -> Async { - let (s, enabled) = match s.strip_prefix('-') { - Some(s) => (s, false), - None => (s, true), - }; - let filter = match s { +impl FilterTarget for AsyncFilter { + fn parse(s: &str) -> Self { + match s { "all" => AsyncFilter::All, other => match other.strip_prefix("import:") { - Some(s) => AsyncFilter::Import(s.to_string()), + Some(sub) => AsyncFilter::Import(sub.to_string()), None => match other.strip_prefix("export:") { - Some(s) => AsyncFilter::Export(s.to_string()), - None => AsyncFilter::Function(s.to_string()), + Some(sub) => AsyncFilter::Export(sub.to_string()), + None => AsyncFilter::Function(other.to_string()), }, }, - }; - Async { enabled, filter } + } } -} -impl fmt::Display for Async { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.enabled { - write!(f, "-")?; - } - self.filter.fmt(f) + fn all() -> AsyncFilter { + AsyncFilter::All } -} -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -enum AsyncFilter { - All, - Function(String), - Import(String), - Export(String), + fn is_all(&self) -> bool { + matches!(self, AsyncFilter::All) + } } impl fmt::Display for AsyncFilter { diff --git a/crates/core/src/chainable_method.rs b/crates/core/src/chainable_method.rs index c4d3ffe65..f7b1d0d6d 100644 --- a/crates/core/src/chainable_method.rs +++ b/crates/core/src/chainable_method.rs @@ -1,8 +1,9 @@ -use anyhow::{Result, bail}; -use std::collections::HashSet; use std::fmt; +use std::{collections::HashSet, fmt::Write}; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; +use crate::filter::{FilterMode, FilterRule, FilterSet, FilterTarget}; + /// Structure used to parse the command line argument `--chainable-method` consistently /// across guest generators. #[cfg_attr(feature = "clap", derive(clap::Parser))] @@ -37,9 +38,9 @@ pub struct ChainableMethodFilterSet { arg( long = "chainable-methods", value_parser = parse_chainable_method, - value_delimiter =',', + value_delimiter = ',', value_name = "FILTER", - ), + ) )] chainable_methods: Vec, @@ -48,134 +49,123 @@ pub struct ChainableMethodFilterSet { used_options: HashSet, } -#[cfg(feature = "clap")] -fn parse_chainable_method(s: &str) -> Result { - Ok(ChainableMethod::parse(s)) -} - -#[derive(Clone, Copy, Debug)] -pub enum ChainingMode { - Owning, - Borrowing, -} +impl FilterSet for ChainableMethodFilterSet { + type Mode = Option; + type Filter = ChainableMethodFilter; -impl ChainableMethodFilterSet { - /// Returns a set where all functions should be chainable or not depending on - /// `enable` provided. - pub fn all(mode: ChainingMode) -> ChainableMethodFilterSet { - ChainableMethodFilterSet { - chainable_methods: vec![ChainableMethod { - mode: Some(mode), - filter: ChainableMethodFilter::All, - }], + fn new(rules: Vec) -> Self { + Self { + chainable_methods: rules, used_options: HashSet::new(), } } - /// Returns whether the `func` provided should be made chainable - pub fn should_be_chainable( + fn rules(&self) -> &[ChainableMethod] { + &self.chainable_methods + } + fn rules_mut(&mut self) -> &mut Vec { + &mut self.chainable_methods + } + fn used_options(&self) -> &HashSet { + &self.used_options + } + fn used_options_mut(&mut self) -> &mut HashSet { + &mut self.used_options + } + + fn option_name() -> &'static str { + "chainable" + } + + fn apply_rules( &mut self, resolve: &Resolve, interface: Option<&WorldKey>, func: &Function, is_import: bool, ) -> Option { - if !is_import { + if !is_import || func.result.is_some() { return None; } - if func.result.is_some() { - return None; - } + let resource = match func.kind { + FunctionKind::AsyncMethod(r) | FunctionKind::Method(r) => r, + _ => return None, + }; - match func.kind { - FunctionKind::AsyncMethod(resource) | FunctionKind::Method(resource) => { - let interface_name = match interface.map(|key| resolve.name_world_key(key)) { - Some(str) => str + "#", - None => "".into(), - }; - - let resource_name_to_test = format!( - "{}{}", - interface_name, - resolve.types[resource].name.as_ref().unwrap() - ); - - let method_name_to_test = format!("{}{}", interface_name, func.name); - - for (i, opt) in self.chainable_methods.iter().enumerate() { - match &opt.filter { - ChainableMethodFilter::All => { - self.used_options.insert(i); - return opt.mode; - } - ChainableMethodFilter::Resource(s) => { - if *s == resource_name_to_test { - self.used_options.insert(i); - return opt.mode; - } - } - ChainableMethodFilter::Method(s) => { - if *s == method_name_to_test { - self.used_options.insert(i); - return opt.mode; - } - } - }; - } + let interface_name = match interface.map(|key| resolve.name_world_key(key)) { + Some(str) => str + "#", + None => "".into(), + }; - return None; - } - _ => { - return None; + let resource_name_to_test = format!( + "{}{}", + interface_name, + resolve.types[resource].name.as_ref().unwrap() + ); + let method_name_to_test = format!("{}{}", interface_name, func.name); + + for (i, opt) in self.chainable_methods.iter().enumerate() { + let matched = match &opt.filter { + ChainableMethodFilter::All => true, + ChainableMethodFilter::Resource(s) => *s == resource_name_to_test, + ChainableMethodFilter::Method(s) => *s == method_name_to_test, + }; + + if matched { + self.used_options.insert(i); + return opt.mode; } } - } - /// Intended to be used in the header comment of generated code to help - /// indicate what options were specified. - pub fn debug_opts(&self) -> impl Iterator + '_ { - self.chainable_methods.iter().map(|opt| opt.to_string()) + None } +} - /// Tests whether all `--chainable-method` options were used throughout bindings - /// generation, returning an error if any were unused. - pub fn ensure_all_used(&self) -> Result<()> { - for (i, opt) in self.chainable_methods.iter().enumerate() { - if self.used_options.contains(&i) { - continue; - } - if !matches!(opt.filter, ChainableMethodFilter::All) { - bail!("unused chainable option: {opt}"); - } +#[cfg(feature = "clap")] +fn parse_chainable_method(s: &str) -> Result { + Ok(ChainableMethod::parse(s)) +} + +#[derive(Clone, Copy, Debug)] +pub enum ChainingMode { + Owning, + Borrowing, +} + +type ChainableMethod = FilterRule, ChainableMethodFilter>; + +impl FilterMode for Option { + fn parse(s: &str) -> (Self, &str) { + match s.strip_prefix('-') { + Some(rest) => (None, rest), + None => match s.strip_prefix('&') { + Some(rest) => (Some(ChainingMode::Borrowing), rest), + None => (Some(ChainingMode::Owning), s), + }, } - Ok(()) } - - /// Pushes a new option into this set. - pub fn push(&mut self, directive: &str) { - self.chainable_methods - .push(ChainableMethod::parse(directive)); + fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Some(ChainingMode::Owning) => {} + Some(ChainingMode::Borrowing) => f.write_char('&')?, + None => f.write_char('-')?, + }; + Ok(()) } } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -struct ChainableMethod { - mode: Option, - filter: ChainableMethodFilter, +pub enum ChainableMethodFilter { + All, + Resource(String), + Method(String), } -impl ChainableMethod { - fn parse(s: &str) -> ChainableMethod { - let (s, mode) = match s.strip_prefix('-') { - Some(s) => (s, None), - None => match s.strip_prefix('&') { - Some(s) => (s, Some(ChainingMode::Borrowing)), - None => (s, Some(ChainingMode::Owning)), - }, - }; - let filter = match s { +impl FilterTarget for ChainableMethodFilter { + fn parse(s: &str) -> Self { + match s { "all" => ChainableMethodFilter::All, other => { if other.contains("[method]") { @@ -184,28 +174,16 @@ impl ChainableMethod { ChainableMethodFilter::Resource(other.to_string()) } } - }; - ChainableMethod { mode, filter } + } } -} -impl fmt::Display for ChainableMethod { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.mode { - Some(ChainingMode::Owning) => {} - Some(ChainingMode::Borrowing) => write!(f, "&")?, - None => write!(f, "-")?, - }; - self.filter.fmt(f) + fn all() -> ChainableMethodFilter { + ChainableMethodFilter::All } -} -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -enum ChainableMethodFilter { - All, - Resource(String), - Method(String), + fn is_all(&self) -> bool { + matches!(self, ChainableMethodFilter::All) + } } impl fmt::Display for ChainableMethodFilter { diff --git a/crates/core/src/filter.rs b/crates/core/src/filter.rs new file mode 100644 index 000000000..bb9d2e2e1 --- /dev/null +++ b/crates/core/src/filter.rs @@ -0,0 +1,96 @@ +use anyhow::{Result, bail}; +use std::{ + collections::HashSet, + fmt::{self, Display}, +}; +use wit_parser::{Function, Resolve, WorldKey}; + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct FilterRule { + pub mode: M, + pub filter: F, +} + +impl fmt::Display for FilterRule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.mode.fmt_prefix(f)?; + self.filter.fmt(f) + } +} + +impl FilterRule { + pub fn parse(s: &str) -> Self { + let (mode, rest) = M::parse(s); + + Self { + mode, + filter: F::parse(rest), + } + } +} + +pub trait FilterMode: Sized { + fn parse(s: &str) -> (Self, &str); + fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +pub trait FilterTarget: Display + Sized { + fn parse(s: &str) -> Self; + fn all() -> Self; + fn is_all(&self) -> bool; +} + +pub trait FilterSet: Sized { + type Mode: FilterMode; + type Filter: FilterTarget; + + fn new(rules: Vec>) -> Self; + + fn rules(&self) -> &[FilterRule]; + fn rules_mut(&mut self) -> &mut Vec>; + fn used_options(&self) -> &HashSet; + fn used_options_mut(&mut self) -> &mut HashSet; + + fn option_name() -> &'static str; + + fn all(mode: Self::Mode) -> Self { + Self::new(vec![FilterRule { + mode, + filter: Self::Filter::all(), + }]) + } + + fn push(&mut self, directive: &str) { + self.rules_mut() + .push(FilterRule::::parse(directive)); + } + + fn apply_rules( + &mut self, + resolve: &Resolve, + interface: Option<&WorldKey>, + func: &Function, + is_import: bool, + ) -> Self::Mode; + + /// Tests whether all options were used throughout bindings + /// generation, returning an error if any were unused. + fn ensure_all_used(&self) -> Result<()> { + for (i, opt) in self.rules().iter().enumerate() { + if self.used_options().contains(&i) { + continue; + } + if !opt.filter.is_all() { + bail!("unused {}: {opt}", Self::option_name()); + } + } + Ok(()) + } + + /// Intended to be used in the header comment of generated code to help + /// indicate what options were specified. + fn debug_opts(&self) -> impl Iterator + '_ { + self.rules().iter().map(|opt| opt.to_string()) + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index b03c173dd..849b2aa20 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -16,6 +16,8 @@ mod async_; pub use async_::AsyncFilterSet; mod chainable_method; pub use chainable_method::{ChainableMethodFilterSet, ChainingMode}; +pub mod filter; +pub use filter::FilterSet; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 13800f06a..46c2c89bc 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -19,7 +19,8 @@ use wit_bindgen_core::wit_parser::{ TypeDefKind, TypeId, TypeOwner, Variant, WorldId, WorldKey, }; use wit_bindgen_core::{ - AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, WorldGenerator, uwriteln, + AsyncFilterSet, Direction, Files, FilterSet, InterfaceGenerator as _, Ns, WorldGenerator, + uwriteln, }; const MAX_FLAT_PARAMS: usize = 16; @@ -1030,7 +1031,7 @@ impl Go { ) -> InterfaceData { self.visit_futures_and_streams(true, resolve, func, interface); - let async_ = self.opts.async_.is_async(resolve, interface, func, true); + let async_ = self.opts.async_.apply_rules(resolve, interface, func, true); let (variant, prefix) = if async_ { (AbiVariant::GuestImportAsync, "[async-lower]") @@ -1254,7 +1255,10 @@ func {camel}({go_params}) {go_results} {{ ) -> String { self.visit_futures_and_streams(false, resolve, func, interface); - let async_ = self.opts.async_.is_async(resolve, interface, func, false); + let async_ = self + .opts + .async_ + .apply_rules(resolve, interface, func, false); let (variant, prefix) = if async_ { (AbiVariant::GuestExportAsync, "[async-lift]") diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 7c7cc214b..d54cb39b5 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -8,7 +8,7 @@ use syn::punctuated::Punctuated; use syn::{Token, braced, token}; use wit_bindgen_core::WorldGenerator; use wit_bindgen_core::wit_parser::{PackageId, Resolve, WorldId}; -use wit_bindgen_core::{AsyncFilterSet, ChainableMethodFilterSet}; +use wit_bindgen_core::{AsyncFilterSet, ChainableMethodFilterSet, FilterSet}; use wit_bindgen_rust::{Opts, Ownership, WithOption}; #[proc_macro] diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 30988b245..783afab4e 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, fmt::Write, mem, ops::Range}; use heck::{ToSnakeCase, ToUpperCamelCase}; use wit_bindgen_core::{ - AsyncFilterSet, Direction, Files, Ns, Source, + AsyncFilterSet, Direction, Files, FilterSet, Ns, Source, abi::{self, AbiVariant, WasmSignature, WasmType}, uwrite, uwriteln, wit_parser::{ @@ -408,7 +408,7 @@ impl AsyncSupport { module: Option<&WorldKey>, func: &Function, ) -> AsyncImportPlan { - let is_async = async_filter.is_async(resolve, module, func, true); + let is_async = async_filter.apply_rules(resolve, module, func, true); if is_async { self.runtime_required = true; } @@ -422,7 +422,7 @@ impl AsyncSupport { interface: Option<&WorldKey>, func: &Function, ) -> AsyncExportPlan { - let is_async = async_filter.is_async(resolve, interface, func, false); + let is_async = async_filter.apply_rules(resolve, interface, func, false); if is_async { self.runtime_required = true; } @@ -2370,7 +2370,7 @@ fn wasm{symbol_name}Reject( r#" FixedArray::makei( {length}, - (index) => {{ + (index) => {{ let ptr = ({address}) + (index * {size}) {lift_func}(ptr) }} diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 78d1a0dbf..9cfbb18fc 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -3115,6 +3115,8 @@ fn print_docs(src: &mut String, docs: &Docs) { #[cfg(test)] mod tests { + use wit_bindgen_core::FilterSet; + use super::*; fn try_generate_with_opts(wit: &str, world: &str, opts: Opts) -> Result { diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 8df60e9a5..5683a6b2f 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -10,8 +10,9 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - AsyncFilterSet, ChainableMethodFilterSet, ChainingMode, Files, InterfaceGenerator as _, Source, - Types, WorldGenerator, dealias, name_package_module, uwrite, uwriteln, wit_parser::*, + AsyncFilterSet, ChainableMethodFilterSet, ChainingMode, Files, FilterSet, + InterfaceGenerator as _, Source, Types, WorldGenerator, dealias, name_package_module, uwrite, + uwriteln, wit_parser::*, }; mod bindgen; @@ -1102,7 +1103,7 @@ macro_rules! __export_{world_name}_impl {{ ) -> bool { self.opts .async_ - .is_async(resolve, interface, func, is_import) + .apply_rules(resolve, interface, func, is_import) } fn should_return_self( @@ -1115,7 +1116,7 @@ macro_rules! __export_{world_name}_impl {{ return self .opts .chainable_methods - .should_be_chainable(resolve, interface, func, is_import); + .apply_rules(resolve, interface, func, is_import); } } From 0b26539b6aed73ac96a9bffcf139d7e7f67511b3 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 22 Aug 2026 00:35:30 -0700 Subject: [PATCH 4/6] (Ab)use macros --- crates/core/src/async_.rs | 87 ++++-------------- crates/core/src/chainable_method.rs | 81 ++++------------ crates/core/src/filter.rs | 138 ++++++++++++++++++++-------- 3 files changed, 136 insertions(+), 170 deletions(-) diff --git a/crates/core/src/async_.rs b/crates/core/src/async_.rs index db3c06711..51f01365d 100644 --- a/crates/core/src/async_.rs +++ b/crates/core/src/async_.rs @@ -1,15 +1,14 @@ use std::fmt; -use std::{collections::HashSet, fmt::Write}; +use std::fmt::Write; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; -use crate::filter::{FilterMode, FilterRule, FilterSet, FilterTarget}; +use crate::define_filter_set; +use crate::filter::{FilterMode, FilterRule, FilterTarget}; -/// Structure used to parse the command line argument `--async` consistently -/// across guest generators. -#[cfg_attr(feature = "clap", derive(clap::Parser))] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[derive(Clone, Default, Debug)] -pub struct AsyncFilterSet { +define_filter_set! { + /// Structure used to parse the command line argument `--async` consistently + /// across guest generators. + pub struct AsyncFilterSet, /// Determines which functions to lift or lower `async`, if any. /// /// This option can be passed multiple times and additionally accepts @@ -17,61 +16,30 @@ pub struct AsyncFilterSet { /// passed here can be one of: /// /// - `all` - all imports and exports will be async + /// /// - `-all` - force all imports and exports to be sync + /// /// - `foo:bar/baz#method` - force this method to be async + /// /// - `import:foo:bar/baz#method` - force this method to be async, but only /// as an import + /// /// - `-export:foo:bar/baz#method` - force this export to be sync /// + /// /// If a method is not listed in this option then the WIT's default bindings /// mode will be used. If the WIT function is defined as `async` then async /// bindings will be generated, otherwise sync bindings will be generated. /// /// Options are processed in the order they are passed here, so if a method /// matches two directives passed the least-specific one should be last. - #[cfg_attr( - feature = "clap", - arg( - long = "async", - value_parser = parse_async, - value_delimiter = ',', - value_name = "FILTER", - ) - )] - #[cfg_attr(feature = "serde", serde(rename = "async"))] - async_: Vec, - - #[cfg_attr(feature = "clap", arg(skip))] - #[cfg_attr(feature = "serde", serde(skip))] - used_options: HashSet, + bool, AsyncFilter, + "async" } -impl FilterSet for AsyncFilterSet { - type Mode = bool; - type Filter = AsyncFilter; - - fn new(rules: Vec) -> Self { - Self { - async_: rules, - used_options: HashSet::new(), - } - } - - fn rules(&self) -> &[Async] { - &self.async_ - } - fn rules_mut(&mut self) -> &mut Vec { - &mut self.async_ - } - fn used_options(&self) -> &HashSet { - &self.used_options - } - fn used_options_mut(&mut self) -> &mut HashSet { - &mut self.used_options - } - - fn option_name() -> &'static str { - "async" +impl AsyncFilterSet { + pub fn any_enabled(&self) -> bool { + self.rules.iter().any(|o| o.mode) } fn apply_rules( @@ -86,7 +54,7 @@ impl FilterSet for AsyncFilterSet { None => func.name.clone(), }; - for (i, opt) in self.async_.iter().enumerate() { + for (i, opt) in self.rules.iter().enumerate() { let name = match &opt.filter { AsyncFilter::All => { self.used_options.insert(i); @@ -121,19 +89,6 @@ impl FilterSet for AsyncFilterSet { } } -#[cfg(feature = "clap")] -fn parse_async(s: &str) -> Result { - Ok(Async::parse(s)) -} - -impl AsyncFilterSet { - pub fn any_enabled(&self) -> bool { - self.async_.iter().any(|o| o.mode) - } -} - -type Async = FilterRule; - impl FilterMode for bool { fn parse(s: &str) -> (Self, &str) { match s.strip_prefix('-') { @@ -149,7 +104,7 @@ impl FilterMode for bool { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum AsyncFilter { All, @@ -175,10 +130,6 @@ impl FilterTarget for AsyncFilter { fn all() -> AsyncFilter { AsyncFilter::All } - - fn is_all(&self) -> bool { - matches!(self, AsyncFilter::All) - } } impl fmt::Display for AsyncFilter { diff --git a/crates/core/src/chainable_method.rs b/crates/core/src/chainable_method.rs index f7b1d0d6d..8bdff3060 100644 --- a/crates/core/src/chainable_method.rs +++ b/crates/core/src/chainable_method.rs @@ -1,15 +1,14 @@ use std::fmt; -use std::{collections::HashSet, fmt::Write}; +use std::fmt::Write; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; -use crate::filter::{FilterMode, FilterRule, FilterSet, FilterTarget}; +use crate::define_filter_set; +use crate::filter::{FilterMode, FilterRule, FilterTarget}; -/// Structure used to parse the command line argument `--chainable-method` consistently -/// across guest generators. -#[cfg_attr(feature = "clap", derive(clap::Parser))] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[derive(Clone, Default, Debug)] -pub struct ChainableMethodFilterSet { +define_filter_set! { + /// Structure used to parse the command line argument `--chainable-method` consistently + /// across guest generators. + pub struct ChainableMethodFilterSet, /// Determines which resource methods should have chaining enabled. /// Chaining takes a WIT method import returning nothing, and modifies bindgen /// in a language-dependent way to return `self` in the glue code. This does @@ -20,63 +19,30 @@ pub struct ChainableMethodFilterSet { /// passed here can be one of: /// /// - `all` - all applicable methods will be chainable + /// /// - `foo:bar/baz#my-resource` - enable chaining for all methods in a resource + /// /// - `foo:bar/baz#my-resource.some-method` - enable chaining for particular method /// + /// /// Each filter may also have one of two modifier prefixes: + /// /// - `-` - inverts the selection; e.g. `-all` will disable chaining for all + /// /// - `&` - makes the chainable return `&Self` instead of `Self` (borrowing) /// + /// /// For instance, `&foo:bar/baz#my-resource` will make all methods in said resource /// borrowing chainable, while `-foo:bar/baz#my-resource.some-method` will disable it /// for that particular method. /// /// Options are processed in the order they are passed here, so if a method /// matches two directives passed the least-specific one should be last. - #[cfg_attr( - feature = "clap", - arg( - long = "chainable-methods", - value_parser = parse_chainable_method, - value_delimiter = ',', - value_name = "FILTER", - ) - )] - chainable_methods: Vec, - - #[cfg_attr(feature = "clap", arg(skip))] - #[cfg_attr(feature = "serde", serde(skip))] - used_options: HashSet, + Option, ChainableMethodFilter, + "chainable-methods" } -impl FilterSet for ChainableMethodFilterSet { - type Mode = Option; - type Filter = ChainableMethodFilter; - - fn new(rules: Vec) -> Self { - Self { - chainable_methods: rules, - used_options: HashSet::new(), - } - } - - fn rules(&self) -> &[ChainableMethod] { - &self.chainable_methods - } - fn rules_mut(&mut self) -> &mut Vec { - &mut self.chainable_methods - } - fn used_options(&self) -> &HashSet { - &self.used_options - } - fn used_options_mut(&mut self) -> &mut HashSet { - &mut self.used_options - } - - fn option_name() -> &'static str { - "chainable" - } - +impl ChainableMethodFilterSet { fn apply_rules( &mut self, resolve: &Resolve, @@ -105,7 +71,7 @@ impl FilterSet for ChainableMethodFilterSet { ); let method_name_to_test = format!("{}{}", interface_name, func.name); - for (i, opt) in self.chainable_methods.iter().enumerate() { + for (i, opt) in self.rules.iter().enumerate() { let matched = match &opt.filter { ChainableMethodFilter::All => true, ChainableMethodFilter::Resource(s) => *s == resource_name_to_test, @@ -122,19 +88,12 @@ impl FilterSet for ChainableMethodFilterSet { } } -#[cfg(feature = "clap")] -fn parse_chainable_method(s: &str) -> Result { - Ok(ChainableMethod::parse(s)) -} - #[derive(Clone, Copy, Debug)] pub enum ChainingMode { Owning, Borrowing, } -type ChainableMethod = FilterRule, ChainableMethodFilter>; - impl FilterMode for Option { fn parse(s: &str) -> (Self, &str) { match s.strip_prefix('-') { @@ -155,7 +114,7 @@ impl FilterMode for Option { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum ChainableMethodFilter { All, @@ -180,10 +139,6 @@ impl FilterTarget for ChainableMethodFilter { fn all() -> ChainableMethodFilter { ChainableMethodFilter::All } - - fn is_all(&self) -> bool { - matches!(self, ChainableMethodFilter::All) - } } impl fmt::Display for ChainableMethodFilter { diff --git a/crates/core/src/filter.rs b/crates/core/src/filter.rs index bb9d2e2e1..ae8455ff3 100644 --- a/crates/core/src/filter.rs +++ b/crates/core/src/filter.rs @@ -1,10 +1,96 @@ -use anyhow::{Result, bail}; +use anyhow::Result; use std::{ - collections::HashSet, fmt::{self, Display}, + str::FromStr, }; use wit_parser::{Function, Resolve, WorldKey}; +#[macro_export] +macro_rules! define_filter_set { + ( + $(#[$struct_meta:meta])* + pub struct $struct_name:ident, + $(#[$field_meta:meta])* + $mode_type:ty, + $filter_type:ty, + $option_name:expr + ) => { + #[derive(Clone, Default, Debug)] + #[cfg_attr(feature = "clap", derive(clap::Parser))] + #[cfg_attr(feature = "serde", derive(serde::Deserialize))] + $(#[$struct_meta])* + pub struct $struct_name { + $(#[$field_meta])* + #[cfg_attr( + feature = "clap", + arg( + id = $option_name, + long = $option_name, + value_delimiter = ',', + value_name = "FILTER", + ) + )] + #[cfg_attr(feature = "serde", serde(rename = $option_name))] + rules: Vec>, + + #[cfg_attr(feature = "clap", arg(skip))] + #[cfg_attr(feature = "serde", serde(skip))] + used_options: std::collections::HashSet, + } + + impl $crate::filter::FilterSet for $struct_name { + type Mode = $mode_type; + type Filter = $filter_type; + + + fn all(mode: Self::Mode) -> Self { + Self { + rules: vec![FilterRule { + mode, + filter: Self::Filter::all(), + }], + used_options: std::collections::HashSet::new() + } + } + + fn push(&mut self, filter: &str) { + self.rules + .push( as std::str::FromStr>::from_str(filter).unwrap()); + } + + fn option_name() -> &'static str { + $option_name + } + + fn apply_rules( + &mut self, + resolve: &wit_parser::Resolve, + interface: Option<&wit_parser::WorldKey>, + func: &wit_parser::Function, + is_import: bool, + ) -> Self::Mode { + Self::apply_rules(self, resolve, interface, func, is_import) + } + + fn ensure_all_used(&self) -> anyhow::Result<()> { + for (i, opt) in self.rules.iter().enumerate() { + if self.used_options.contains(&i) { + continue; + } + if opt.filter != Self::Filter::all() { + anyhow::bail!("unused {}: {opt}", Self::option_name()); + } + } + Ok(()) + } + + fn debug_opts(&self) -> impl Iterator + '_ { + self.rules.iter().map(|opt| opt.to_string()) + } + } + }; +} + #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub struct FilterRule { @@ -19,14 +105,16 @@ impl fmt::Display for FilterRule { } } -impl FilterRule { - pub fn parse(s: &str) -> Self { +impl FromStr for FilterRule { + type Err = String; + + fn from_str(s: &str) -> std::prelude::v1::Result { let (mode, rest) = M::parse(s); - Self { + Ok(Self { mode, filter: F::parse(rest), - } + }) } } @@ -35,36 +123,20 @@ pub trait FilterMode: Sized { fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; } -pub trait FilterTarget: Display + Sized { +pub trait FilterTarget: Display + Sized + PartialEq { fn parse(s: &str) -> Self; fn all() -> Self; - fn is_all(&self) -> bool; } pub trait FilterSet: Sized { type Mode: FilterMode; type Filter: FilterTarget; - fn new(rules: Vec>) -> Self; - - fn rules(&self) -> &[FilterRule]; - fn rules_mut(&mut self) -> &mut Vec>; - fn used_options(&self) -> &HashSet; - fn used_options_mut(&mut self) -> &mut HashSet; - fn option_name() -> &'static str; - fn all(mode: Self::Mode) -> Self { - Self::new(vec![FilterRule { - mode, - filter: Self::Filter::all(), - }]) - } + fn all(mode: Self::Mode) -> Self; - fn push(&mut self, directive: &str) { - self.rules_mut() - .push(FilterRule::::parse(directive)); - } + fn push(&mut self, directive: &str); fn apply_rules( &mut self, @@ -76,21 +148,9 @@ pub trait FilterSet: Sized { /// Tests whether all options were used throughout bindings /// generation, returning an error if any were unused. - fn ensure_all_used(&self) -> Result<()> { - for (i, opt) in self.rules().iter().enumerate() { - if self.used_options().contains(&i) { - continue; - } - if !opt.filter.is_all() { - bail!("unused {}: {opt}", Self::option_name()); - } - } - Ok(()) - } + fn ensure_all_used(&self) -> Result<()>; /// Intended to be used in the header comment of generated code to help /// indicate what options were specified. - fn debug_opts(&self) -> impl Iterator + '_ { - self.rules().iter().map(|opt| opt.to_string()) - } + fn debug_opts(&self) -> impl Iterator + '_; } From f183d496decf35a334bace76c413d34173f42190 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 22 Aug 2026 00:40:22 -0700 Subject: [PATCH 5/6] Some fixes --- crates/core/src/async_.rs | 2 +- crates/core/src/filter.rs | 2 +- crates/rust/src/lib.rs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/core/src/async_.rs b/crates/core/src/async_.rs index 51f01365d..a37448c3f 100644 --- a/crates/core/src/async_.rs +++ b/crates/core/src/async_.rs @@ -97,7 +97,7 @@ impl FilterMode for bool { } } fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if *self { + if !*self { f.write_char('-')?; } Ok(()) diff --git a/crates/core/src/filter.rs b/crates/core/src/filter.rs index ae8455ff3..b30521f4f 100644 --- a/crates/core/src/filter.rs +++ b/crates/core/src/filter.rs @@ -78,7 +78,7 @@ macro_rules! define_filter_set { continue; } if opt.filter != Self::Filter::all() { - anyhow::bail!("unused {}: {opt}", Self::option_name()); + anyhow::bail!("unused {} option: {opt}", Self::option_name()); } } Ok(()) diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 5683a6b2f..511117852 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -1235,6 +1235,9 @@ impl WorldGenerator for RustWasm { for opt in self.opts.async_.debug_opts() { uwriteln!(self.src_preamble, "// * async: {opt}"); } + for opt in self.opts.chainable_methods.debug_opts() { + uwriteln!(self.src_preamble, "// * chainable-methods: {opt}"); + } self.types.analyze(resolve); self.types.collect_equal_types(resolve, world, &|a| { // If `--merge-structurally-equal-types` is enabled then any type From 7f0967a5c608d31d3cff3d8b608de9e6b10e554f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 24 Aug 2026 10:14:32 -0700 Subject: [PATCH 6/6] Revert the de-dups --- crates/c/src/lib.rs | 8 +- crates/core/src/async_.rs | 160 +++++++++++++------- crates/core/src/chainable_method.rs | 221 ++++++++++++++++++---------- crates/core/src/filter.rs | 156 -------------------- crates/core/src/lib.rs | 2 - crates/go/src/lib.rs | 10 +- crates/guest-rust/macro/src/lib.rs | 2 +- crates/moonbit/src/async_support.rs | 8 +- crates/moonbit/src/lib.rs | 2 - crates/rust/src/lib.rs | 9 +- 10 files changed, 268 insertions(+), 310 deletions(-) delete mode 100644 crates/core/src/filter.rs diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 170e79d8f..91d30b14e 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -10,8 +10,8 @@ use wit_bindgen_core::abi::{ self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmSignature, WasmType, }; use wit_bindgen_core::{ - AnonymousTypeGenerator, AsyncFilterSet, Direction, Files, FilterSet, InterfaceGenerator as _, - Ns, WorldGenerator, dealias, uwrite, uwriteln, wit_parser::*, + AnonymousTypeGenerator, AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, + WorldGenerator, dealias, uwrite, uwriteln, wit_parser::*, }; use wit_component::StringEncoding; @@ -2077,7 +2077,7 @@ impl InterfaceGenerator<'_> { .r#gen .opts .async_ - .apply_rules(self.resolve, interface_name, func, true); + .is_async(self.resolve, interface_name, func, true); if async_ { self.r#gen.needs_async = true; } @@ -2246,7 +2246,7 @@ impl InterfaceGenerator<'_> { .r#gen .opts .async_ - .apply_rules(self.resolve, interface_name, func, false); + .is_async(self.resolve, interface_name, func, false); let (variant, prefix) = if async_ { self.r#gen.needs_async = true; diff --git a/crates/core/src/async_.rs b/crates/core/src/async_.rs index a37448c3f..e801e2ea3 100644 --- a/crates/core/src/async_.rs +++ b/crates/core/src/async_.rs @@ -1,14 +1,14 @@ +use anyhow::{Result, bail}; +use std::collections::HashSet; use std::fmt; -use std::fmt::Write; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; -use crate::define_filter_set; -use crate::filter::{FilterMode, FilterRule, FilterTarget}; - -define_filter_set! { - /// Structure used to parse the command line argument `--async` consistently - /// across guest generators. - pub struct AsyncFilterSet, +/// Structure used to parse the command line argument `--async` consistently +/// across guest generators. +#[cfg_attr(feature = "clap", derive(clap::Parser))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[derive(Clone, Default, Debug)] +pub struct AsyncFilterSet { /// Determines which functions to lift or lower `async`, if any. /// /// This option can be passed multiple times and additionally accepts @@ -16,33 +16,55 @@ define_filter_set! { /// passed here can be one of: /// /// - `all` - all imports and exports will be async - /// /// - `-all` - force all imports and exports to be sync - /// /// - `foo:bar/baz#method` - force this method to be async - /// /// - `import:foo:bar/baz#method` - force this method to be async, but only /// as an import - /// /// - `-export:foo:bar/baz#method` - force this export to be sync /// - /// /// If a method is not listed in this option then the WIT's default bindings /// mode will be used. If the WIT function is defined as `async` then async /// bindings will be generated, otherwise sync bindings will be generated. /// /// Options are processed in the order they are passed here, so if a method /// matches two directives passed the least-specific one should be last. - bool, AsyncFilter, - "async" + #[cfg_attr( + feature = "clap", + arg( + long = "async", + value_parser = parse_async, + value_delimiter =',', + value_name = "FILTER", + ), + )] + #[cfg_attr(feature = "serde", serde(rename = "async"))] + async_: Vec, + + #[cfg_attr(feature = "clap", arg(skip))] + #[cfg_attr(feature = "serde", serde(skip))] + used_options: HashSet, +} + +#[cfg(feature = "clap")] +fn parse_async(s: &str) -> Result { + Ok(Async::parse(s)) } impl AsyncFilterSet { - pub fn any_enabled(&self) -> bool { - self.rules.iter().any(|o| o.mode) + /// Returns a set where all functions should be async or not depending on + /// `async_` provided. + pub fn all(async_: bool) -> AsyncFilterSet { + AsyncFilterSet { + async_: vec![Async { + enabled: async_, + filter: AsyncFilter::All, + }], + used_options: HashSet::new(), + } } - fn apply_rules( + /// Returns whether the `func` provided is to be bound `async` or not. + pub fn is_async( &mut self, resolve: &Resolve, interface: Option<&WorldKey>, @@ -53,12 +75,11 @@ impl AsyncFilterSet { Some(key) => format!("{}#{}", resolve.name_world_key(key), func.name), None => func.name.clone(), }; - - for (i, opt) in self.rules.iter().enumerate() { + for (i, opt) in self.async_.iter().enumerate() { let name = match &opt.filter { AsyncFilter::All => { self.used_options.insert(i); - return opt.mode; + return opt.enabled; } AsyncFilter::Function(s) => s, AsyncFilter::Import(s) => { @@ -76,62 +97,97 @@ impl AsyncFilterSet { }; if *name == name_to_test { self.used_options.insert(i); - return opt.mode; + return opt.enabled; } } - matches!( - func.kind, + match &func.kind { + FunctionKind::Freestanding + | FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => false, FunctionKind::AsyncFreestanding - | FunctionKind::AsyncMethod(_) - | FunctionKind::AsyncStatic(_) - ) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => true, + } } -} -impl FilterMode for bool { - fn parse(s: &str) -> (Self, &str) { - match s.strip_prefix('-') { - Some(rest) => (false, rest), - None => (true, s), - } + /// Intended to be used in the header comment of generated code to help + /// indicate what options were specified. + pub fn debug_opts(&self) -> impl Iterator + '_ { + self.async_.iter().map(|opt| opt.to_string()) } - fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !*self { - f.write_char('-')?; + + /// Tests whether all `--async` options were used throughout bindings + /// generation, returning an error if any were unused. + pub fn ensure_all_used(&self) -> Result<()> { + for (i, opt) in self.async_.iter().enumerate() { + if self.used_options.contains(&i) { + continue; + } + if !matches!(opt.filter, AsyncFilter::All) { + bail!("unused async option: {opt}"); + } } Ok(()) } + + /// Returns whether any option explicitly requests that async is enabled. + pub fn any_enabled(&self) -> bool { + self.async_.iter().any(|o| o.enabled) + } + + /// Pushes a new option into this set. + pub fn push(&mut self, directive: &str) { + self.async_.push(Async::parse(directive)); + } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub enum AsyncFilter { - All, - Function(String), - Import(String), - Export(String), +struct Async { + enabled: bool, + filter: AsyncFilter, } -impl FilterTarget for AsyncFilter { - fn parse(s: &str) -> Self { - match s { +impl Async { + fn parse(s: &str) -> Async { + let (s, enabled) = match s.strip_prefix('-') { + Some(s) => (s, false), + None => (s, true), + }; + let filter = match s { "all" => AsyncFilter::All, other => match other.strip_prefix("import:") { - Some(sub) => AsyncFilter::Import(sub.to_string()), + Some(s) => AsyncFilter::Import(s.to_string()), None => match other.strip_prefix("export:") { - Some(sub) => AsyncFilter::Export(sub.to_string()), - None => AsyncFilter::Function(other.to_string()), + Some(s) => AsyncFilter::Export(s.to_string()), + None => AsyncFilter::Function(s.to_string()), }, }, - } + }; + Async { enabled, filter } } +} - fn all() -> AsyncFilter { - AsyncFilter::All +impl fmt::Display for Async { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.enabled { + write!(f, "-")?; + } + self.filter.fmt(f) } } +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +enum AsyncFilter { + All, + Function(String), + Import(String), + Export(String), +} + impl fmt::Display for AsyncFilter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/core/src/chainable_method.rs b/crates/core/src/chainable_method.rs index 8bdff3060..c4d3ffe65 100644 --- a/crates/core/src/chainable_method.rs +++ b/crates/core/src/chainable_method.rs @@ -1,14 +1,14 @@ +use anyhow::{Result, bail}; +use std::collections::HashSet; use std::fmt; -use std::fmt::Write; use wit_parser::{Function, FunctionKind, Resolve, WorldKey}; -use crate::define_filter_set; -use crate::filter::{FilterMode, FilterRule, FilterTarget}; - -define_filter_set! { - /// Structure used to parse the command line argument `--chainable-method` consistently - /// across guest generators. - pub struct ChainableMethodFilterSet, +/// Structure used to parse the command line argument `--chainable-method` consistently +/// across guest generators. +#[cfg_attr(feature = "clap", derive(clap::Parser))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[derive(Clone, Default, Debug)] +pub struct ChainableMethodFilterSet { /// Determines which resource methods should have chaining enabled. /// Chaining takes a WIT method import returning nothing, and modifies bindgen /// in a language-dependent way to return `self` in the glue code. This does @@ -19,112 +19,163 @@ define_filter_set! { /// passed here can be one of: /// /// - `all` - all applicable methods will be chainable - /// /// - `foo:bar/baz#my-resource` - enable chaining for all methods in a resource - /// /// - `foo:bar/baz#my-resource.some-method` - enable chaining for particular method /// - /// /// Each filter may also have one of two modifier prefixes: - /// /// - `-` - inverts the selection; e.g. `-all` will disable chaining for all - /// /// - `&` - makes the chainable return `&Self` instead of `Self` (borrowing) /// - /// /// For instance, `&foo:bar/baz#my-resource` will make all methods in said resource /// borrowing chainable, while `-foo:bar/baz#my-resource.some-method` will disable it /// for that particular method. /// /// Options are processed in the order they are passed here, so if a method /// matches two directives passed the least-specific one should be last. - Option, ChainableMethodFilter, - "chainable-methods" + #[cfg_attr( + feature = "clap", + arg( + long = "chainable-methods", + value_parser = parse_chainable_method, + value_delimiter =',', + value_name = "FILTER", + ), + )] + chainable_methods: Vec, + + #[cfg_attr(feature = "clap", arg(skip))] + #[cfg_attr(feature = "serde", serde(skip))] + used_options: HashSet, +} + +#[cfg(feature = "clap")] +fn parse_chainable_method(s: &str) -> Result { + Ok(ChainableMethod::parse(s)) +} + +#[derive(Clone, Copy, Debug)] +pub enum ChainingMode { + Owning, + Borrowing, } impl ChainableMethodFilterSet { - fn apply_rules( + /// Returns a set where all functions should be chainable or not depending on + /// `enable` provided. + pub fn all(mode: ChainingMode) -> ChainableMethodFilterSet { + ChainableMethodFilterSet { + chainable_methods: vec![ChainableMethod { + mode: Some(mode), + filter: ChainableMethodFilter::All, + }], + used_options: HashSet::new(), + } + } + + /// Returns whether the `func` provided should be made chainable + pub fn should_be_chainable( &mut self, resolve: &Resolve, interface: Option<&WorldKey>, func: &Function, is_import: bool, ) -> Option { - if !is_import || func.result.is_some() { + if !is_import { return None; } - let resource = match func.kind { - FunctionKind::AsyncMethod(r) | FunctionKind::Method(r) => r, - _ => return None, - }; + if func.result.is_some() { + return None; + } - let interface_name = match interface.map(|key| resolve.name_world_key(key)) { - Some(str) => str + "#", - None => "".into(), - }; + match func.kind { + FunctionKind::AsyncMethod(resource) | FunctionKind::Method(resource) => { + let interface_name = match interface.map(|key| resolve.name_world_key(key)) { + Some(str) => str + "#", + None => "".into(), + }; + + let resource_name_to_test = format!( + "{}{}", + interface_name, + resolve.types[resource].name.as_ref().unwrap() + ); - let resource_name_to_test = format!( - "{}{}", - interface_name, - resolve.types[resource].name.as_ref().unwrap() - ); - let method_name_to_test = format!("{}{}", interface_name, func.name); - - for (i, opt) in self.rules.iter().enumerate() { - let matched = match &opt.filter { - ChainableMethodFilter::All => true, - ChainableMethodFilter::Resource(s) => *s == resource_name_to_test, - ChainableMethodFilter::Method(s) => *s == method_name_to_test, - }; - - if matched { - self.used_options.insert(i); - return opt.mode; + let method_name_to_test = format!("{}{}", interface_name, func.name); + + for (i, opt) in self.chainable_methods.iter().enumerate() { + match &opt.filter { + ChainableMethodFilter::All => { + self.used_options.insert(i); + return opt.mode; + } + ChainableMethodFilter::Resource(s) => { + if *s == resource_name_to_test { + self.used_options.insert(i); + return opt.mode; + } + } + ChainableMethodFilter::Method(s) => { + if *s == method_name_to_test { + self.used_options.insert(i); + return opt.mode; + } + } + }; + } + + return None; + } + _ => { + return None; } } - - None } -} -#[derive(Clone, Copy, Debug)] -pub enum ChainingMode { - Owning, - Borrowing, -} + /// Intended to be used in the header comment of generated code to help + /// indicate what options were specified. + pub fn debug_opts(&self) -> impl Iterator + '_ { + self.chainable_methods.iter().map(|opt| opt.to_string()) + } -impl FilterMode for Option { - fn parse(s: &str) -> (Self, &str) { - match s.strip_prefix('-') { - Some(rest) => (None, rest), - None => match s.strip_prefix('&') { - Some(rest) => (Some(ChainingMode::Borrowing), rest), - None => (Some(ChainingMode::Owning), s), - }, + /// Tests whether all `--chainable-method` options were used throughout bindings + /// generation, returning an error if any were unused. + pub fn ensure_all_used(&self) -> Result<()> { + for (i, opt) in self.chainable_methods.iter().enumerate() { + if self.used_options.contains(&i) { + continue; + } + if !matches!(opt.filter, ChainableMethodFilter::All) { + bail!("unused chainable option: {opt}"); + } } - } - fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Some(ChainingMode::Owning) => {} - Some(ChainingMode::Borrowing) => f.write_char('&')?, - None => f.write_char('-')?, - }; Ok(()) } + + /// Pushes a new option into this set. + pub fn push(&mut self, directive: &str) { + self.chainable_methods + .push(ChainableMethod::parse(directive)); + } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub enum ChainableMethodFilter { - All, - Resource(String), - Method(String), +struct ChainableMethod { + mode: Option, + filter: ChainableMethodFilter, } -impl FilterTarget for ChainableMethodFilter { - fn parse(s: &str) -> Self { - match s { +impl ChainableMethod { + fn parse(s: &str) -> ChainableMethod { + let (s, mode) = match s.strip_prefix('-') { + Some(s) => (s, None), + None => match s.strip_prefix('&') { + Some(s) => (s, Some(ChainingMode::Borrowing)), + None => (s, Some(ChainingMode::Owning)), + }, + }; + let filter = match s { "all" => ChainableMethodFilter::All, other => { if other.contains("[method]") { @@ -133,14 +184,30 @@ impl FilterTarget for ChainableMethodFilter { ChainableMethodFilter::Resource(other.to_string()) } } - } + }; + ChainableMethod { mode, filter } } +} - fn all() -> ChainableMethodFilter { - ChainableMethodFilter::All +impl fmt::Display for ChainableMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.mode { + Some(ChainingMode::Owning) => {} + Some(ChainingMode::Borrowing) => write!(f, "&")?, + None => write!(f, "-")?, + }; + self.filter.fmt(f) } } +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +enum ChainableMethodFilter { + All, + Resource(String), + Method(String), +} + impl fmt::Display for ChainableMethodFilter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/core/src/filter.rs b/crates/core/src/filter.rs deleted file mode 100644 index b30521f4f..000000000 --- a/crates/core/src/filter.rs +++ /dev/null @@ -1,156 +0,0 @@ -use anyhow::Result; -use std::{ - fmt::{self, Display}, - str::FromStr, -}; -use wit_parser::{Function, Resolve, WorldKey}; - -#[macro_export] -macro_rules! define_filter_set { - ( - $(#[$struct_meta:meta])* - pub struct $struct_name:ident, - $(#[$field_meta:meta])* - $mode_type:ty, - $filter_type:ty, - $option_name:expr - ) => { - #[derive(Clone, Default, Debug)] - #[cfg_attr(feature = "clap", derive(clap::Parser))] - #[cfg_attr(feature = "serde", derive(serde::Deserialize))] - $(#[$struct_meta])* - pub struct $struct_name { - $(#[$field_meta])* - #[cfg_attr( - feature = "clap", - arg( - id = $option_name, - long = $option_name, - value_delimiter = ',', - value_name = "FILTER", - ) - )] - #[cfg_attr(feature = "serde", serde(rename = $option_name))] - rules: Vec>, - - #[cfg_attr(feature = "clap", arg(skip))] - #[cfg_attr(feature = "serde", serde(skip))] - used_options: std::collections::HashSet, - } - - impl $crate::filter::FilterSet for $struct_name { - type Mode = $mode_type; - type Filter = $filter_type; - - - fn all(mode: Self::Mode) -> Self { - Self { - rules: vec![FilterRule { - mode, - filter: Self::Filter::all(), - }], - used_options: std::collections::HashSet::new() - } - } - - fn push(&mut self, filter: &str) { - self.rules - .push( as std::str::FromStr>::from_str(filter).unwrap()); - } - - fn option_name() -> &'static str { - $option_name - } - - fn apply_rules( - &mut self, - resolve: &wit_parser::Resolve, - interface: Option<&wit_parser::WorldKey>, - func: &wit_parser::Function, - is_import: bool, - ) -> Self::Mode { - Self::apply_rules(self, resolve, interface, func, is_import) - } - - fn ensure_all_used(&self) -> anyhow::Result<()> { - for (i, opt) in self.rules.iter().enumerate() { - if self.used_options.contains(&i) { - continue; - } - if opt.filter != Self::Filter::all() { - anyhow::bail!("unused {} option: {opt}", Self::option_name()); - } - } - Ok(()) - } - - fn debug_opts(&self) -> impl Iterator + '_ { - self.rules.iter().map(|opt| opt.to_string()) - } - } - }; -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub struct FilterRule { - pub mode: M, - pub filter: F, -} - -impl fmt::Display for FilterRule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.mode.fmt_prefix(f)?; - self.filter.fmt(f) - } -} - -impl FromStr for FilterRule { - type Err = String; - - fn from_str(s: &str) -> std::prelude::v1::Result { - let (mode, rest) = M::parse(s); - - Ok(Self { - mode, - filter: F::parse(rest), - }) - } -} - -pub trait FilterMode: Sized { - fn parse(s: &str) -> (Self, &str); - fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; -} - -pub trait FilterTarget: Display + Sized + PartialEq { - fn parse(s: &str) -> Self; - fn all() -> Self; -} - -pub trait FilterSet: Sized { - type Mode: FilterMode; - type Filter: FilterTarget; - - fn option_name() -> &'static str; - - fn all(mode: Self::Mode) -> Self; - - fn push(&mut self, directive: &str); - - fn apply_rules( - &mut self, - resolve: &Resolve, - interface: Option<&WorldKey>, - func: &Function, - is_import: bool, - ) -> Self::Mode; - - /// Tests whether all options were used throughout bindings - /// generation, returning an error if any were unused. - fn ensure_all_used(&self) -> Result<()>; - - /// Intended to be used in the header comment of generated code to help - /// indicate what options were specified. - fn debug_opts(&self) -> impl Iterator + '_; -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 849b2aa20..b03c173dd 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -16,8 +16,6 @@ mod async_; pub use async_::AsyncFilterSet; mod chainable_method; pub use chainable_method::{ChainableMethodFilterSet, ChainingMode}; -pub mod filter; -pub use filter::FilterSet; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 46c2c89bc..13800f06a 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -19,8 +19,7 @@ use wit_bindgen_core::wit_parser::{ TypeDefKind, TypeId, TypeOwner, Variant, WorldId, WorldKey, }; use wit_bindgen_core::{ - AsyncFilterSet, Direction, Files, FilterSet, InterfaceGenerator as _, Ns, WorldGenerator, - uwriteln, + AsyncFilterSet, Direction, Files, InterfaceGenerator as _, Ns, WorldGenerator, uwriteln, }; const MAX_FLAT_PARAMS: usize = 16; @@ -1031,7 +1030,7 @@ impl Go { ) -> InterfaceData { self.visit_futures_and_streams(true, resolve, func, interface); - let async_ = self.opts.async_.apply_rules(resolve, interface, func, true); + let async_ = self.opts.async_.is_async(resolve, interface, func, true); let (variant, prefix) = if async_ { (AbiVariant::GuestImportAsync, "[async-lower]") @@ -1255,10 +1254,7 @@ func {camel}({go_params}) {go_results} {{ ) -> String { self.visit_futures_and_streams(false, resolve, func, interface); - let async_ = self - .opts - .async_ - .apply_rules(resolve, interface, func, false); + let async_ = self.opts.async_.is_async(resolve, interface, func, false); let (variant, prefix) = if async_ { (AbiVariant::GuestExportAsync, "[async-lift]") diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index d54cb39b5..7c7cc214b 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -8,7 +8,7 @@ use syn::punctuated::Punctuated; use syn::{Token, braced, token}; use wit_bindgen_core::WorldGenerator; use wit_bindgen_core::wit_parser::{PackageId, Resolve, WorldId}; -use wit_bindgen_core::{AsyncFilterSet, ChainableMethodFilterSet, FilterSet}; +use wit_bindgen_core::{AsyncFilterSet, ChainableMethodFilterSet}; use wit_bindgen_rust::{Opts, Ownership, WithOption}; #[proc_macro] diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 783afab4e..30988b245 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, fmt::Write, mem, ops::Range}; use heck::{ToSnakeCase, ToUpperCamelCase}; use wit_bindgen_core::{ - AsyncFilterSet, Direction, Files, FilterSet, Ns, Source, + AsyncFilterSet, Direction, Files, Ns, Source, abi::{self, AbiVariant, WasmSignature, WasmType}, uwrite, uwriteln, wit_parser::{ @@ -408,7 +408,7 @@ impl AsyncSupport { module: Option<&WorldKey>, func: &Function, ) -> AsyncImportPlan { - let is_async = async_filter.apply_rules(resolve, module, func, true); + let is_async = async_filter.is_async(resolve, module, func, true); if is_async { self.runtime_required = true; } @@ -422,7 +422,7 @@ impl AsyncSupport { interface: Option<&WorldKey>, func: &Function, ) -> AsyncExportPlan { - let is_async = async_filter.apply_rules(resolve, interface, func, false); + let is_async = async_filter.is_async(resolve, interface, func, false); if is_async { self.runtime_required = true; } @@ -2370,7 +2370,7 @@ fn wasm{symbol_name}Reject( r#" FixedArray::makei( {length}, - (index) => {{ + (index) => {{ let ptr = ({address}) + (index * {size}) {lift_func}(ptr) }} diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 9cfbb18fc..78d1a0dbf 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -3115,8 +3115,6 @@ fn print_docs(src: &mut String, docs: &Docs) { #[cfg(test)] mod tests { - use wit_bindgen_core::FilterSet; - use super::*; fn try_generate_with_opts(wit: &str, world: &str, opts: Opts) -> Result { diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 511117852..33bf3806a 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -10,9 +10,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - AsyncFilterSet, ChainableMethodFilterSet, ChainingMode, Files, FilterSet, - InterfaceGenerator as _, Source, Types, WorldGenerator, dealias, name_package_module, uwrite, - uwriteln, wit_parser::*, + AsyncFilterSet, ChainableMethodFilterSet, ChainingMode, Files, InterfaceGenerator as _, Source, + Types, WorldGenerator, dealias, name_package_module, uwrite, uwriteln, wit_parser::*, }; mod bindgen; @@ -1103,7 +1102,7 @@ macro_rules! __export_{world_name}_impl {{ ) -> bool { self.opts .async_ - .apply_rules(resolve, interface, func, is_import) + .is_async(resolve, interface, func, is_import) } fn should_return_self( @@ -1116,7 +1115,7 @@ macro_rules! __export_{world_name}_impl {{ return self .opts .chainable_methods - .apply_rules(resolve, interface, func, is_import); + .should_be_chainable(resolve, interface, func, is_import); } }