Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/component-macro/test-helpers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn foreach(input: TokenStream) -> TokenStream {
let mut result = Vec::new();
for f in cwd.read_dir().unwrap() {
let f = f.unwrap().path();
if f.extension().and_then(|s| s.to_str()) == Some("wit") {
if f.extension().and_then(|s| s.to_str()) == Some("wit") || f.is_dir() {
let name = f.file_stem().unwrap().to_str().unwrap();
let ident = Ident::new(&name.replace("-", "_"), Span::call_site());
let path = f.to_str().unwrap();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package my:dep@0.1.0;

interface a {
x: func();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package my:dep@0.2.0;

interface a {
x: func();
}
6 changes: 6 additions & 0 deletions crates/component-macro/tests/codegen/multiversion/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo:bar;

world foo {
import my:dep/a@0.1.0;
import my:dep/a@0.2.0;
}
235 changes: 132 additions & 103 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,22 @@ mod source;
mod types;
use source::Source;

struct InterfaceName {
/// True when this interface name has been remapped through the use of `with` in the `bindgen!`
/// macro invocation.
remapped: bool,

/// The string name for this interface.
path: String,
#[derive(Clone)]
enum InterfaceName {
/// This interface was remapped using `with` to some other Rust code.
Remapped {
name_at_root: String,
local_path: Vec<String>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this vector only ever has one or three strings in it, would it make sense to make a more precise type that names those cases?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's probably a bit overkill on types for now since nothing uses this as anything other than .join("::") currently, but if that changes in the future I think it'd be reasonable to add yeah

},
/// This interface is generated in the module hierarchy specified.
Path(Vec<String>),
}

#[derive(Default)]
struct Wasmtime {
src: Source,
opts: Opts,
import_interfaces: BTreeMap<Option<PackageName>, Vec<ImportInterface>>,
import_interfaces: Vec<(String, InterfaceName)>,
import_functions: Vec<ImportFunction>,
exports: Exports,
types: Types,
Expand All @@ -51,10 +53,6 @@ struct Wasmtime {
trappable_errors: IndexMap<TypeId, String>,
}

struct ImportInterface {
snake: String,
module: String,
}
struct ImportFunction {
add_to_linker: String,
sig: String,
Expand All @@ -63,7 +61,7 @@ struct ImportFunction {
#[derive(Default)]
struct Exports {
fields: BTreeMap<String, (String, String)>,
modules: BTreeMap<Option<PackageName>, Vec<String>>,
modules: Vec<(String, InterfaceName)>,
funcs: Vec<String>,
}

Expand Down Expand Up @@ -178,45 +176,85 @@ impl Wasmtime {
is_export: bool,
) -> bool {
let with_name = resolve.name_world_key(name);

let mut path = Vec::new();
if is_export {
path.push("exports".to_string());
}
match name {
WorldKey::Name(name) => {
path.push(name.to_snake_case());
}
WorldKey::Interface(_) => {
let iface = &resolve.interfaces[id];
let pkgname = &resolve.packages[iface.package.unwrap()].name;
path.push(pkgname.namespace.to_snake_case());
path.push(self.name_package_module(resolve, iface.package.unwrap()));
path.push(iface.name.as_ref().unwrap().to_snake_case());
}
}
let entry = if let Some(remapped_path) = self.opts.with.get(&with_name) {
let name = format!("__with_name{}", self.with_name_counter);
self.with_name_counter += 1;
uwriteln!(self.src, "use {remapped_path} as {name};");
InterfaceName {
remapped: true,
path: name,
InterfaceName::Remapped {
name_at_root: name,
local_path: path,
}
} else {
let path = match name {
WorldKey::Name(name) => name.to_snake_case(),
WorldKey::Interface(_) => {
let iface = &resolve.interfaces[id];
let pkgname = &resolve.packages[iface.package.unwrap()].name;
format!(
"{}::{}::{}",
pkgname.namespace.to_snake_case(),
pkgname.name.to_snake_case(),
iface.name.as_ref().unwrap().to_snake_case()
)
}
};
let path = if is_export {
format!("exports::{path}")
} else {
path
};
InterfaceName {
remapped: false,
path,
}
InterfaceName::Path(path)
};

let remapped = entry.remapped;
let remapped = matches!(entry, InterfaceName::Remapped { .. });
self.interface_names.insert(id, entry);

remapped
}

/// If the package `id` is the only package with its namespace/name combo
/// then pass through the name unmodified. If, however, there are multiple
/// versions of this package then the package module is going to get version
/// information.
fn name_package_module(&self, resolve: &Resolve, id: PackageId) -> String {
let pkg = &resolve.packages[id];
let versions_with_same_name = resolve
.packages
.iter()
.filter_map(|(_, p)| {
if p.name.namespace == pkg.name.namespace && p.name.name == pkg.name.name {
Some(&p.name.version)
} else {
None
}
})
.collect::<Vec<_>>();
let base = pkg.name.name.to_snake_case();
if versions_with_same_name.len() == 1 {
return base;
}

let version = match &pkg.name.version {
Some(version) => version,
// If this package didn't have a version then don't mangle its name
// and other packages with the same name but with versions present
// will have their names mangled.
None => return base,
};

// Here there's multiple packages with the same name that differ only in
// version, so the version needs to be mangled into the Rust module name
// that we're generating. This in theory could look at all of
// `versions_with_same_name` and produce a minimal diff, e.g. for 0.1.0
// and 0.2.0 this could generate "foo1" and "foo2", but for now
// a simpler path is chosen to generate "foo0_1_0" and "foo0_2_0".
let version = version
.to_string()
.replace('.', "_")
.replace('-', "_")
.replace('+', "_")
.to_snake_case();
format!("{base}{version}")
}

fn generate(&mut self, resolve: &Resolve, id: WorldId) -> String {
self.types.analyze(resolve, id);
for (i, te) in self.opts.trappable_error_type.iter().enumerate() {
Expand All @@ -235,6 +273,7 @@ impl Wasmtime {
self.import(resolve, id, name, import);
}
}

for (name, export) in world.exports.iter() {
if !self.opts.only_interfaces || matches!(export, WorldItem::Interface(_)) {
self.export(resolve, name, export);
Expand Down Expand Up @@ -291,15 +330,8 @@ impl Wasmtime {
}}
"
);
let pkg = resolve.interfaces[*id].package.unwrap();
let pkgname = match name {
WorldKey::Name(_) => None,
WorldKey::Interface(_) => Some(resolve.packages[pkg].name.clone()),
};
self.import_interfaces
.entry(pkgname)
.or_insert(Vec::new())
.push(ImportInterface { snake, module });
.push((module, self.interface_names[id].clone()));
}
WorldItem::Type(ty) => {
let name = match name {
Expand Down Expand Up @@ -433,9 +465,7 @@ impl Wasmtime {
};
self.exports
.modules
.entry(pkgname.clone())
.or_insert(Vec::new())
.push(module);
.push((module, self.interface_names[id].clone()));

let name = resolve.name_world_key(name);
let (path, method_name) = match pkgname {
Expand Down Expand Up @@ -565,18 +595,10 @@ impl Wasmtime {
}

let imports = mem::take(&mut self.import_interfaces);
self.emit_modules(
&imports
.into_iter()
.map(|(k, v)| (k, v.into_iter().map(|m| m.module).collect()))
.collect(),
);
if !self.exports.modules.is_empty() {
uwriteln!(self.src, "pub mod exports {{");
let exports = mem::take(&mut self.exports.modules);
self.emit_modules(&exports);
uwriteln!(self.src, "}}");
}
self.emit_modules(imports);

let exports = mem::take(&mut self.exports.modules);
self.emit_modules(exports);

let mut src = mem::take(&mut self.src);
if self.opts.rustfmt {
Expand Down Expand Up @@ -606,34 +628,39 @@ impl Wasmtime {
src.into()
}

fn emit_modules(&mut self, modules: &BTreeMap<Option<PackageName>, Vec<String>>) {
let mut map = BTreeMap::new();
for (pkg, modules) in modules {
match pkg {
Some(pkg) => {
let prev = map
.entry(&pkg.namespace)
.or_insert(BTreeMap::new())
.insert(&pkg.name, modules);
assert!(prev.is_none());
}
None => {
for module in modules {
uwriteln!(self.src, "{module}");
}
}
fn emit_modules(&mut self, modules: Vec<(String, InterfaceName)>) {
#[derive(Default)]
struct Module {
submodules: BTreeMap<String, Module>,
contents: Vec<String>,
}
let mut map = Module::default();
for (module, name) in modules {
let path = match name {
InterfaceName::Remapped { local_path, .. } => local_path,
InterfaceName::Path(path) => path,
};
let mut cur = &mut map;
for name in path[..path.len() - 1].iter() {
cur = cur
.submodules
.entry(name.clone())
.or_insert(Module::default());
}
cur.contents.push(module);
}
for (ns, pkgs) in map {
uwriteln!(self.src, "pub mod {} {{", ns.to_snake_case());
for (pkg, modules) in pkgs {
uwriteln!(self.src, "pub mod {} {{", pkg.to_snake_case());
for module in modules {
uwriteln!(self.src, "{module}");
}
uwriteln!(self.src, "}}");

emit(&mut self.src, map);

fn emit(me: &mut Source, module: Module) {
for (name, submodule) in module.submodules {
uwriteln!(me, "pub mod {name} {{");
emit(me, submodule);
uwriteln!(me, "}}");
}
for submodule in module.contents {
uwriteln!(me, "{submodule}");
}
uwriteln!(self.src, "}}");
}
}
}
Expand Down Expand Up @@ -675,19 +702,12 @@ impl Wasmtime {
return;
}
let mut interfaces = Vec::new();
for (pkg, imports) in self.import_interfaces.iter() {
for import in imports {
let mut path = String::new();
if let Some(pkg) = pkg {
path.push_str(&pkg.namespace.to_snake_case());
path.push_str("::");
path.push_str(&pkg.name.to_snake_case());
path.push_str("::");
}

path.push_str(&import.snake);
interfaces.push(path);
}
for (_, name) in self.import_interfaces.iter() {
let path = match name {
InterfaceName::Remapped { .. } => unreachable!("imported a remapped module"),
InterfaceName::Path(path) => path,
};
interfaces.push(path.join("::"));
}

uwrite!(
Expand Down Expand Up @@ -1951,8 +1971,17 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
}
}
let mut path_to_root = self.path_to_root();
let InterfaceName { path, .. } = &self.gen.interface_names[&interface];
path_to_root.push_str(path);
match &self.gen.interface_names[&interface] {
InterfaceName::Remapped { name_at_root, .. } => path_to_root.push_str(name_at_root),
InterfaceName::Path(path) => {
for (i, name) in path.iter().enumerate() {
if i > 0 {
path_to_root.push_str("::");
}
path_to_root.push_str(name);
}
}
}
Some(path_to_root)
}

Expand Down