diff --git a/Cargo.lock b/Cargo.lock index 767e4b349..63bcd84c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2190,6 +2190,7 @@ dependencies = [ "wasm-metadata", "wit-bindgen-core", "wit-component", + "wit-parser", ] [[package]] diff --git a/crates/c/Cargo.toml b/crates/c/Cargo.toml index 077e52695..ac407b897 100644 --- a/crates/c/Cargo.toml +++ b/crates/c/Cargo.toml @@ -26,3 +26,4 @@ clap = { workspace = true, optional = true } [dev-dependencies] test-helpers = { path = '../test-helpers' } +wit-parser = { workspace = true } diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index c2271336f..ce4b562b8 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -23,6 +23,7 @@ struct C { needs_string: bool, world: String, sizes: SizeAlign, + renamed_interfaces: HashMap, world_id: Option, dtor_funcs: HashMap, @@ -43,15 +44,34 @@ pub struct Opts { /// Skip emitting component allocation helper functions #[cfg_attr(feature = "clap", arg(long))] pub no_helpers: bool, + /// Set component string encoding #[cfg_attr(feature = "clap", arg(long, default_value_t = StringEncoding::default()))] pub string_encoding: StringEncoding, - // Skip optional null pointer and boolean result argument signature flattening + + /// Skip optional null pointer and boolean result argument signature + /// flattening #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub no_sig_flattening: bool, - // Skip generating C object file + + /// Skip generating an object file which contains type information for the + /// world that is being generated. #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub no_object_file: bool, + + /// Rename the interface `K` to `V` in the generated source code. + #[cfg_attr(feature = "clap", arg(long, name = "K=V", value_parser = parse_rename))] + pub rename: Vec<(String, String)>, +} + +#[cfg(feature = "clap")] +fn parse_rename(name: &str) -> Result<(String, String)> { + let mut parts = name.splitn(2, '='); + let to_rename = parts.next().unwrap(); + match parts.next() { + Some(part) => Ok((to_rename.to_string(), part.to_string())), + None => anyhow::bail!("`--rename` option must have an `=` in it (e.g. `--rename a=b`)"), + } } impl Opts { @@ -90,6 +110,19 @@ impl WorldGenerator for C { self.world = name.to_string(); self.sizes.fill(resolve); self.world_id = Some(world); + + let mut interfaces = HashMap::new(); + let world = &resolve.worlds[world]; + for (key, _item) in world.imports.iter().chain(world.exports.iter()) { + let name = resolve.name_world_key(key); + interfaces.insert(name, key.clone()); + } + + for (from, to) in self.opts.rename.iter() { + if let Some(key) = interfaces.get(from) { + self.renamed_interfaces.insert(key.clone(), to.clone()); + } + } } fn import_interface( @@ -602,13 +635,14 @@ pub fn owner_namespace<'a>( world: String, resolve: &Resolve, id: TypeId, + renamed_interfaces: &HashMap, ) -> String { let ty = &resolve.types[id]; match (ty.owner, interface) { // If this type is owned by an interface, then we must be generating // bindings for that interface to proceed. (TypeOwner::Interface(a), Some((b, key))) if a == b => { - interface_identifier(key, resolve, !in_import) + interface_identifier(key, resolve, !in_import, renamed_interfaces) } (TypeOwner::Interface(_), None) => unreachable!(), (TypeOwner::Interface(_), Some(_)) => unreachable!(), @@ -620,12 +654,28 @@ pub fn owner_namespace<'a>( // If this type has no owner then it's an anonymous type. Here it's // assigned to whatever we happen to be generating bindings for. - (TypeOwner::None, Some((_, key))) => interface_identifier(key, resolve, !in_import), + (TypeOwner::None, Some((_, key))) => { + interface_identifier(key, resolve, !in_import, renamed_interfaces) + } (TypeOwner::None, None) => world.to_snake_case(), } } -pub fn interface_identifier(interface_id: &WorldKey, resolve: &Resolve, in_export: bool) -> String { +fn interface_identifier( + interface_id: &WorldKey, + resolve: &Resolve, + in_export: bool, + renamed_interfaces: &HashMap, +) -> String { + if let Some(rename) = renamed_interfaces.get(interface_id) { + let mut ns = String::new(); + if in_export && matches!(interface_id, WorldKey::Interface(_)) { + ns.push_str("exports_"); + } + ns.push_str(rename); + return ns; + } + match interface_id { WorldKey::Name(name) => name.to_snake_case(), WorldKey::Interface(id) => { @@ -660,10 +710,16 @@ pub fn c_func_name( world: &str, interface_id: Option<&WorldKey>, func: &Function, + renamed_interfaces: &HashMap, ) -> String { let mut name = String::new(); match interface_id { - Some(id) => name.push_str(&interface_identifier(id, resolve, !in_import)), + Some(id) => name.push_str(&interface_identifier( + id, + resolve, + !in_import, + renamed_interfaces, + )), None => name.push_str(&world.to_snake_case()), } name.push_str("_"); @@ -1348,6 +1404,7 @@ impl InterfaceGenerator<'_> { &self.gen.world, interface_id, func, + &self.gen.renamed_interfaces, ) } @@ -1682,6 +1739,7 @@ impl InterfaceGenerator<'_> { self.gen.world.clone(), self.resolve, id, + &self.gen.renamed_interfaces, ) } diff --git a/crates/c/tests/codegen.rs b/crates/c/tests/codegen.rs index c12566758..ca732bf6f 100644 --- a/crates/c/tests/codegen.rs +++ b/crates/c/tests/codegen.rs @@ -1,7 +1,9 @@ +use anyhow::Result; use heck::*; use std::env; use std::path::{Path, PathBuf}; use std::process::Command; +use wit_parser::{Resolve, UnresolvedPackage}; macro_rules! codegen_test { ($id:ident $name:tt $test:tt) => { @@ -69,3 +71,67 @@ fn verify(dir: &Path, name: &str) { cmd.arg(&cpp_src); test_helpers::run_command(&mut cmd); } + +#[test] +fn rename_option() -> Result<()> { + let dir = test_helpers::test_directory("codegen", "guest-c", "rename-option"); + + let mut opts = wit_bindgen_c::Opts::default(); + opts.rename.push(("a".to_string(), "rename1".to_string())); + opts.rename + .push(("foo:bar/b".to_string(), "rename2".to_string())); + opts.rename.push(("c".to_string(), "rename3".to_string())); + + let mut resolve = Resolve::default(); + let pkg = resolve.push(UnresolvedPackage::parse( + "input.wit".as_ref(), + r#" + package foo:bar; + + interface b { + f: func(); + } + + world rename-option { + import a: interface { + f: func(); + } + import b; + + export run: func(); + + export c: interface { + f: func(); + } + export b; + } + "#, + )?)?; + let world = resolve.select_world(pkg, None)?; + let mut files = Default::default(); + opts.build().generate(&resolve, world, &mut files)?; + for (file, contents) in files.iter() { + let dst = dir.join(file); + std::fs::create_dir_all(dst.parent().unwrap()).unwrap(); + std::fs::write(&dst, contents).unwrap(); + } + + std::fs::write( + dir.join("rename_option.c"), + r#" +#include "rename_option.h" + +void rename_option_run(void) { + rename1_f(); + rename2_f(); +} + +void rename3_f() {} + +void exports_rename2_f() {} + "#, + )?; + + verify(&dir, "rename-option"); + Ok(()) +} diff --git a/crates/go/src/interface.rs b/crates/go/src/interface.rs index c4825bb09..4ea90a389 100644 --- a/crates/go/src/interface.rs +++ b/crates/go/src/interface.rs @@ -104,6 +104,7 @@ impl InterfaceGenerator<'_> { self.gen.world.clone(), self.resolve, id, + &Default::default(), ) } @@ -533,6 +534,7 @@ impl InterfaceGenerator<'_> { &self.gen.world, self.interface.map(|(_, key)| key), func, + &Default::default(), ) } else { // do not want to generate public functions @@ -823,6 +825,7 @@ impl InterfaceGenerator<'_> { &self.gen.world, self.interface.map(|(_, key)| key), func, + &Default::default(), ); src.push_str(&name); src.push('\n');