Skip to content

Commit 28c9087

Browse files
authored
Add a --rename option to the C generator (#783)
* Add a `--rename` option to the C generator The generated symbols for types like `wasi:http/[email protected]` can get quite long so add an option to rename them explicitly if desired. * Warn when renames don't match
1 parent 91070c4 commit 28c9087

File tree

5 files changed

+140
-6
lines changed

5 files changed

+140
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/c/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ clap = { workspace = true, optional = true }
2626

2727
[dev-dependencies]
2828
test-helpers = { path = '../test-helpers' }
29+
wit-parser = { workspace = true }

crates/c/src/lib.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct C {
2323
needs_string: bool,
2424
world: String,
2525
sizes: SizeAlign,
26+
renamed_interfaces: HashMap<WorldKey, String>,
2627

2728
world_id: Option<WorldId>,
2829
dtor_funcs: HashMap<TypeId, String>,
@@ -43,15 +44,34 @@ pub struct Opts {
4344
/// Skip emitting component allocation helper functions
4445
#[cfg_attr(feature = "clap", arg(long))]
4546
pub no_helpers: bool,
47+
4648
/// Set component string encoding
4749
#[cfg_attr(feature = "clap", arg(long, default_value_t = StringEncoding::default()))]
4850
pub string_encoding: StringEncoding,
49-
// Skip optional null pointer and boolean result argument signature flattening
51+
52+
/// Skip optional null pointer and boolean result argument signature
53+
/// flattening
5054
#[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
5155
pub no_sig_flattening: bool,
52-
// Skip generating C object file
56+
57+
/// Skip generating an object file which contains type information for the
58+
/// world that is being generated.
5359
#[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
5460
pub no_object_file: bool,
61+
62+
/// Rename the interface `K` to `V` in the generated source code.
63+
#[cfg_attr(feature = "clap", arg(long, name = "K=V", value_parser = parse_rename))]
64+
pub rename: Vec<(String, String)>,
65+
}
66+
67+
#[cfg(feature = "clap")]
68+
fn parse_rename(name: &str) -> Result<(String, String)> {
69+
let mut parts = name.splitn(2, '=');
70+
let to_rename = parts.next().unwrap();
71+
match parts.next() {
72+
Some(part) => Ok((to_rename.to_string(), part.to_string())),
73+
None => anyhow::bail!("`--rename` option must have an `=` in it (e.g. `--rename a=b`)"),
74+
}
5575
}
5676

5777
impl Opts {
@@ -90,6 +110,24 @@ impl WorldGenerator for C {
90110
self.world = name.to_string();
91111
self.sizes.fill(resolve);
92112
self.world_id = Some(world);
113+
114+
let mut interfaces = HashMap::new();
115+
let world = &resolve.worlds[world];
116+
for (key, _item) in world.imports.iter().chain(world.exports.iter()) {
117+
let name = resolve.name_world_key(key);
118+
interfaces.insert(name, key.clone());
119+
}
120+
121+
for (from, to) in self.opts.rename.iter() {
122+
match interfaces.get(from) {
123+
Some(key) => {
124+
self.renamed_interfaces.insert(key.clone(), to.clone());
125+
}
126+
None => {
127+
eprintln!("warning: rename of `{from}` did not match any interfaces");
128+
}
129+
}
130+
}
93131
}
94132

95133
fn import_interface(
@@ -602,13 +640,14 @@ pub fn owner_namespace<'a>(
602640
world: String,
603641
resolve: &Resolve,
604642
id: TypeId,
643+
renamed_interfaces: &HashMap<WorldKey, String>,
605644
) -> String {
606645
let ty = &resolve.types[id];
607646
match (ty.owner, interface) {
608647
// If this type is owned by an interface, then we must be generating
609648
// bindings for that interface to proceed.
610649
(TypeOwner::Interface(a), Some((b, key))) if a == b => {
611-
interface_identifier(key, resolve, !in_import)
650+
interface_identifier(key, resolve, !in_import, renamed_interfaces)
612651
}
613652
(TypeOwner::Interface(_), None) => unreachable!(),
614653
(TypeOwner::Interface(_), Some(_)) => unreachable!(),
@@ -620,12 +659,28 @@ pub fn owner_namespace<'a>(
620659

621660
// If this type has no owner then it's an anonymous type. Here it's
622661
// assigned to whatever we happen to be generating bindings for.
623-
(TypeOwner::None, Some((_, key))) => interface_identifier(key, resolve, !in_import),
662+
(TypeOwner::None, Some((_, key))) => {
663+
interface_identifier(key, resolve, !in_import, renamed_interfaces)
664+
}
624665
(TypeOwner::None, None) => world.to_snake_case(),
625666
}
626667
}
627668

628-
pub fn interface_identifier(interface_id: &WorldKey, resolve: &Resolve, in_export: bool) -> String {
669+
fn interface_identifier(
670+
interface_id: &WorldKey,
671+
resolve: &Resolve,
672+
in_export: bool,
673+
renamed_interfaces: &HashMap<WorldKey, String>,
674+
) -> String {
675+
if let Some(rename) = renamed_interfaces.get(interface_id) {
676+
let mut ns = String::new();
677+
if in_export && matches!(interface_id, WorldKey::Interface(_)) {
678+
ns.push_str("exports_");
679+
}
680+
ns.push_str(rename);
681+
return ns;
682+
}
683+
629684
match interface_id {
630685
WorldKey::Name(name) => name.to_snake_case(),
631686
WorldKey::Interface(id) => {
@@ -660,10 +715,16 @@ pub fn c_func_name(
660715
world: &str,
661716
interface_id: Option<&WorldKey>,
662717
func: &Function,
718+
renamed_interfaces: &HashMap<WorldKey, String>,
663719
) -> String {
664720
let mut name = String::new();
665721
match interface_id {
666-
Some(id) => name.push_str(&interface_identifier(id, resolve, !in_import)),
722+
Some(id) => name.push_str(&interface_identifier(
723+
id,
724+
resolve,
725+
!in_import,
726+
renamed_interfaces,
727+
)),
667728
None => name.push_str(&world.to_snake_case()),
668729
}
669730
name.push_str("_");
@@ -1348,6 +1409,7 @@ impl InterfaceGenerator<'_> {
13481409
&self.gen.world,
13491410
interface_id,
13501411
func,
1412+
&self.gen.renamed_interfaces,
13511413
)
13521414
}
13531415

@@ -1682,6 +1744,7 @@ impl InterfaceGenerator<'_> {
16821744
self.gen.world.clone(),
16831745
self.resolve,
16841746
id,
1747+
&self.gen.renamed_interfaces,
16851748
)
16861749
}
16871750

crates/c/tests/codegen.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use anyhow::Result;
12
use heck::*;
23
use std::env;
34
use std::path::{Path, PathBuf};
45
use std::process::Command;
6+
use wit_parser::{Resolve, UnresolvedPackage};
57

68
macro_rules! codegen_test {
79
($id:ident $name:tt $test:tt) => {
@@ -69,3 +71,67 @@ fn verify(dir: &Path, name: &str) {
6971
cmd.arg(&cpp_src);
7072
test_helpers::run_command(&mut cmd);
7173
}
74+
75+
#[test]
76+
fn rename_option() -> Result<()> {
77+
let dir = test_helpers::test_directory("codegen", "guest-c", "rename-option");
78+
79+
let mut opts = wit_bindgen_c::Opts::default();
80+
opts.rename.push(("a".to_string(), "rename1".to_string()));
81+
opts.rename
82+
.push(("foo:bar/b".to_string(), "rename2".to_string()));
83+
opts.rename.push(("c".to_string(), "rename3".to_string()));
84+
85+
let mut resolve = Resolve::default();
86+
let pkg = resolve.push(UnresolvedPackage::parse(
87+
"input.wit".as_ref(),
88+
r#"
89+
package foo:bar;
90+
91+
interface b {
92+
f: func();
93+
}
94+
95+
world rename-option {
96+
import a: interface {
97+
f: func();
98+
}
99+
import b;
100+
101+
export run: func();
102+
103+
export c: interface {
104+
f: func();
105+
}
106+
export b;
107+
}
108+
"#,
109+
)?)?;
110+
let world = resolve.select_world(pkg, None)?;
111+
let mut files = Default::default();
112+
opts.build().generate(&resolve, world, &mut files)?;
113+
for (file, contents) in files.iter() {
114+
let dst = dir.join(file);
115+
std::fs::create_dir_all(dst.parent().unwrap()).unwrap();
116+
std::fs::write(&dst, contents).unwrap();
117+
}
118+
119+
std::fs::write(
120+
dir.join("rename_option.c"),
121+
r#"
122+
#include "rename_option.h"
123+
124+
void rename_option_run(void) {
125+
rename1_f();
126+
rename2_f();
127+
}
128+
129+
void rename3_f() {}
130+
131+
void exports_rename2_f() {}
132+
"#,
133+
)?;
134+
135+
verify(&dir, "rename-option");
136+
Ok(())
137+
}

crates/go/src/interface.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl InterfaceGenerator<'_> {
104104
self.gen.world.clone(),
105105
self.resolve,
106106
id,
107+
&Default::default(),
107108
)
108109
}
109110

@@ -533,6 +534,7 @@ impl InterfaceGenerator<'_> {
533534
&self.gen.world,
534535
self.interface.map(|(_, key)| key),
535536
func,
537+
&Default::default(),
536538
)
537539
} else {
538540
// do not want to generate public functions
@@ -823,6 +825,7 @@ impl InterfaceGenerator<'_> {
823825
&self.gen.world,
824826
self.interface.map(|(_, key)| key),
825827
func,
828+
&Default::default(),
826829
);
827830
src.push_str(&name);
828831
src.push('\n');

0 commit comments

Comments
 (0)