Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi version support #693

Merged
merged 4 commits into from
Oct 10, 2023
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
1 change: 1 addition & 0 deletions crates/c/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ macro_rules! codegen_test {
(resource_local_alias $name:tt $test:tt) => {};
(resources_with_lists $name:tt $test:tt) => {};
(resources_in_aggregates $name:tt $test:tt) => {};
(multiversion $name:tt $test:tt) => {};

($id:ident $name:tt $test:tt) => {
#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/go/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ macro_rules! codegen_test {
(resource_own_in_other_interface $name:tt $test:tt) => {};
(resources_in_aggregates $name:tt $test:tt) => {};
(issue668 $name:tt $test:tt) => {};
(multiversion $name:tt $test:tt) => {};

($id:ident $name:tt $test:tt) => {
#[test]
Expand Down
38 changes: 10 additions & 28 deletions crates/rust/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,39 +159,21 @@ impl InterfaceGenerator<'_> {
path_to_root
}

pub fn start_append_submodule(&mut self, name: &WorldKey) -> (String, Option<PackageName>) {
pub fn start_append_submodule(&mut self, name: &WorldKey) -> (String, Vec<String>) {
let snake = match name {
WorldKey::Name(name) => to_rust_ident(name),
WorldKey::Interface(id) => {
to_rust_ident(self.resolve.interfaces[*id].name.as_ref().unwrap())
}
};
let pkg = match name {
WorldKey::Name(_) => None,
WorldKey::Interface(id) => {
let pkg = self.resolve.interfaces[*id].package.unwrap();
Some(self.resolve.packages[pkg].name.clone())
}
};
let module_path = crate::compute_module_path(name, &self.resolve, !self.in_import);
if let Identifier::Interface(id, _) = self.identifier {
let mut path = String::new();
if !self.in_import {
path.push_str("exports::");
}
if let Some(name) = &pkg {
path.push_str(&format!(
"{}::{}::",
name.namespace.to_snake_case(),
name.name.to_snake_case()
));
}
path.push_str(&snake);
self.gen.interface_names.insert(id, path);
self.gen.interface_names.insert(id, module_path.join("::"));
}
(snake, pkg)
(snake, module_path)
}

pub fn finish_append_submodule(mut self, snake: &str, pkg: Option<PackageName>) {
pub fn finish_append_submodule(mut self, snake: &str, module_path: Vec<String>) {
let module = self.finish();
let path_to_root = self.path_to_root();
let module = format!(
Expand All @@ -211,7 +193,7 @@ impl InterfaceGenerator<'_> {
} else {
&mut self.gen.export_modules
};
map.entry(pkg).or_insert(Vec::new()).push(module);
map.push((module, module_path))
}

fn generate_guest_import(&mut self, func: &Function) {
Expand Down Expand Up @@ -404,16 +386,16 @@ impl InterfaceGenerator<'_> {
pub fn generate_stub(
&mut self,
resource: Option<TypeId>,
pkg: Option<&PackageName>,
pkg: Option<(String, String)>,
name: &str,
in_interface: bool,
funcs: &[&Function],
) {
let path = if let Some(pkg) = pkg {
let path = if let Some((namespace, pkg_name)) = pkg {
format!(
"{}::{}::{}",
to_rust_ident(&pkg.namespace),
to_rust_ident(&pkg.name),
to_rust_ident(&namespace),
to_rust_ident(&pkg_name),
to_rust_ident(name),
)
} else {
Expand Down
143 changes: 105 additions & 38 deletions crates/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ struct RustWasm {
types: Types,
src: Source,
opts: Opts,
import_modules: BTreeMap<Option<PackageName>, Vec<String>>,
export_modules: BTreeMap<Option<PackageName>, Vec<String>>,
import_modules: Vec<(String, Vec<String>)>,
export_modules: Vec<(String, Vec<String>)>,
skip: HashSet<String>,
interface_names: HashMap<InterfaceId, String>,
resources: HashMap<TypeId, ResourceInfo>,
Expand Down Expand Up @@ -188,34 +188,33 @@ impl RustWasm {
}
}

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, Vec<String>)>) {
#[derive(Default)]
struct Module {
submodules: BTreeMap<String, Module>,
contents: Vec<String>,
}
let mut map = Module::default();
for (module, path) in modules {
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 @@ -251,6 +250,51 @@ impl RustWasm {
}
}

/// 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(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}")
}

impl WorldGenerator for RustWasm {
fn preprocess(&mut self, resolve: &Resolve, _world: WorldId) {
wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION"));
Expand All @@ -271,12 +315,12 @@ impl WorldGenerator for RustWasm {
resolve,
true,
);
let (snake, pkg) = gen.start_append_submodule(name);
let (snake, module_path) = gen.start_append_submodule(name);
gen.types(id);

gen.generate_imports(resolve.interfaces[id].functions.values());

gen.finish_append_submodule(&snake, pkg);
gen.finish_append_submodule(&snake, module_path);
}

fn import_funcs(
Expand Down Expand Up @@ -312,14 +356,14 @@ impl WorldGenerator for RustWasm {
};
let path = resolve.id_of(id).unwrap_or(inner_name.to_string());
let mut gen = self.interface(Identifier::Interface(id, name), None, resolve, false);
let (snake, pkg) = gen.start_append_submodule(name);
let (snake, module_path) = gen.start_append_submodule(name);
gen.types(id);
gen.generate_exports(
&ExportKey::Name(path),
Some(name),
resolve.interfaces[id].functions.values(),
)?;
gen.finish_append_submodule(&snake, pkg);
gen.finish_append_submodule(&snake, module_path);
Ok(())
}

Expand Down Expand Up @@ -363,14 +407,11 @@ impl WorldGenerator for RustWasm {

fn finish(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) {
let name = &resolve.worlds[world].name;

let imports = mem::take(&mut self.import_modules);
self.emit_modules(&imports);
self.emit_modules(imports);
let exports = mem::take(&mut self.export_modules);
if !exports.is_empty() {
self.src.push_str("pub mod exports {\n");
self.emit_modules(&exports);
self.src.push_str("}\n");
}
self.emit_modules(exports);

self.src.push_str("\n#[cfg(target_arch = \"wasm32\")]\n");

Expand Down Expand Up @@ -423,7 +464,7 @@ impl WorldGenerator for RustWasm {
WorldKey::Interface(id) => {
let interface = &resolve.interfaces[*id];
(
Some(&resolve.packages[interface.package.unwrap()].name),
Some(interface.package.unwrap()),
interface.name.as_ref().unwrap(),
)
}
Expand All @@ -438,6 +479,11 @@ impl WorldGenerator for RustWasm {
{
let mut gen =
self.interface(Identifier::World(world_id), None, resolve, false);
let pkg = pkg.map(|pid| {
let namespace = resolve.packages[pid].name.namespace.clone();
let package_module = name_package_module(resolve, pid);
(namespace, package_module)
});
gen.generate_stub(resource, pkg, name, true, &funcs);
let stub = gen.finish();
self.src.push_str(&stub);
Expand Down Expand Up @@ -485,6 +531,27 @@ impl WorldGenerator for RustWasm {
}
}

fn compute_module_path(name: &WorldKey, resolve: &Resolve, is_export: bool) -> Vec<String> {
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(id) => {
let iface = &resolve.interfaces[*id];
let pkg = iface.package.unwrap();
let pkgname = resolve.packages[pkg].name.clone();
path.push(pkgname.namespace.to_snake_case());
path.push(name_package_module(resolve, pkg));
path.push(iface.name.as_ref().unwrap().to_snake_case());
}
}
path
}

enum Identifier<'a> {
World(WorldId),
Interface(InterfaceId, &'a WorldKey),
Expand Down
1 change: 1 addition & 0 deletions crates/teavm-java/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ macro_rules! codegen_test {
(same_names5 $name:tt $test:tt) => {};
(resources_in_aggregates $name:tt $test:tt) => {};
(issue668 $name:tt $test:tt) => {};
(multiversion $name:tt $test:tt) => {};

($id:ident $name:tt $test:tt) => {
#[test]
Expand Down
6 changes: 6 additions & 0 deletions tests/codegen/multiversion/deps/v1/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package my:[email protected];

interface a {
type foo = u8;
x: func();
}
6 changes: 6 additions & 0 deletions tests/codegen/multiversion/deps/v2/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package my:[email protected];

interface a {
use my:dep/[email protected].{foo};
x: func() -> foo;
}
7 changes: 7 additions & 0 deletions tests/codegen/multiversion/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo:bar;

world foo {
import my:dep/[email protected];
import my:dep/[email protected];
export my:dep/[email protected];
}