Skip to content

Commit

Permalink
Add a --rename option to the C generator
Browse files Browse the repository at this point in the history
The generated symbols for types like
`wasi:http/[email protected]` can get quite long so add an
option to rename them explicitly if desired.
  • Loading branch information
alexcrichton committed Dec 3, 2023
1 parent 91070c4 commit f37b651
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ clap = { workspace = true, optional = true }

[dev-dependencies]
test-helpers = { path = '../test-helpers' }
wit-parser = { workspace = true }
70 changes: 64 additions & 6 deletions crates/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct C {
needs_string: bool,
world: String,
sizes: SizeAlign,
renamed_interfaces: HashMap<WorldKey, String>,

world_id: Option<WorldId>,
dtor_funcs: HashMap<TypeId, String>,
Expand All @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -602,13 +635,14 @@ pub fn owner_namespace<'a>(
world: String,
resolve: &Resolve,
id: TypeId,
renamed_interfaces: &HashMap<WorldKey, String>,
) -> 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!(),
Expand All @@ -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<WorldKey, String>,
) -> 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) => {
Expand Down Expand Up @@ -660,10 +710,16 @@ pub fn c_func_name(
world: &str,
interface_id: Option<&WorldKey>,
func: &Function,
renamed_interfaces: &HashMap<WorldKey, String>,
) -> 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("_");
Expand Down Expand Up @@ -1348,6 +1404,7 @@ impl InterfaceGenerator<'_> {
&self.gen.world,
interface_id,
func,
&self.gen.renamed_interfaces,
)
}

Expand Down Expand Up @@ -1682,6 +1739,7 @@ impl InterfaceGenerator<'_> {
self.gen.world.clone(),
self.resolve,
id,
&self.gen.renamed_interfaces,
)
}

Expand Down
66 changes: 66 additions & 0 deletions crates/c/tests/codegen.rs
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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(())
}
3 changes: 3 additions & 0 deletions crates/go/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl InterfaceGenerator<'_> {
self.gen.world.clone(),
self.resolve,
id,
&Default::default(),
)
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -823,6 +825,7 @@ impl InterfaceGenerator<'_> {
&self.gen.world,
self.interface.map(|(_, key)| key),
func,
&Default::default(),
);
src.push_str(&name);
src.push('\n');
Expand Down

0 comments on commit f37b651

Please sign in to comment.