From 231f1f0034fc81b230e534b3a39645cdd4dcc114 Mon Sep 17 00:00:00 2001 From: karthik2804 Date: Thu, 12 Sep 2024 17:37:10 +0200 Subject: [PATCH 1/3] Add importize method to Resolve A new method `importize` is added to the Resolve. This is to allow to mutate the Resolve to state where it would resemble what a consuming component would expect to see during composition. Signed-off-by: karthik2804 --- crates/wit-parser/src/resolve.rs | 50 ++++++++++++++++++++ src/bin/wasm-tools/component.rs | 23 +++++++-- src/lib.rs | 14 ++++-- tests/cli/importize-component.wit | 37 +++++++++++++++ tests/cli/importize-component.wit.stdout | 60 ++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/cli/importize-component.wit create mode 100644 tests/cli/importize-component.wit.stdout diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 91d74ca29c..acf504bcb2 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -931,6 +931,56 @@ package {name} is defined in two different locations:\n\ Some(self.id_of_name(interface.package.unwrap(), interface.name.as_ref()?)) } + pub fn importize(&mut self, world_id: WorldId) { + let exports = &self.worlds[world_id].exports.clone(); + // Make a copy of the imports as we will wholesale clear the imports in the world being importized + let ref_imports = mem::take(&mut self.worlds[world_id].imports); + + let mut temp = Vec::new(); + + for (key, item) in exports.clone() { + match key { + WorldKey::Name(_) => { + temp.push((key, item)); + } + WorldKey::Interface(id) => self.resolve_interface_deps(id, world_id, &ref_imports), + } + } + let world = &mut self.worlds[world_id]; + world.imports.append(&mut world.exports); + + for (key, item) in ref_imports { + if let WorldItem::Type(_) = item { + world.imports.insert(key, item); + } + } + + for (key, item) in temp { + world.imports.insert(key, item); + } + + world.exports.clear(); + } + + fn resolve_interface_deps( + &mut self, + interface_id: InterfaceId, + world_id: WorldId, + ref_imports: &IndexMap, + ) { + let direct_deps = self.interface_direct_deps(interface_id).collect::>(); + for dep_id in direct_deps { + let world_key = WorldKey::Interface(dep_id); + let world_item = ref_imports.get(&world_key).unwrap(); + + let world = &mut self.worlds[world_id]; + if !world.imports.contains_key(&world_key) { + world.imports.insert(world_key, world_item.clone()); + self.resolve_interface_deps(dep_id, world_id, ref_imports); + } + } + } + /// Returns the ID of the specified `name` within the `pkg`. pub fn id_of_name(&self, pkg: PackageId, name: &str) -> String { let package = &self.packages[pkg]; diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 3fa9eefdf9..22d33345ae 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -499,6 +499,16 @@ pub struct WitOpts { )] json: bool, + /// Importize the WIT document by removing the imports and + /// moving the exports of a component to be its imports. + #[clap( + long, + conflicts_with = "wasm", + conflicts_with = "wat", + conflicts_with = "json" + )] + importize: bool, + /// Features to enable when parsing the `wit` option. /// /// This flag enables the `@unstable` feature in WIT documents where the @@ -621,7 +631,13 @@ impl WitOpts { fn emit_wit(&self, decoded: &DecodedWasm) -> Result<()> { assert!(!self.wasm && !self.wat); - let resolve = decoded.resolve(); + let mut resolve = decoded.resolve().clone(); + + let main = decoded.package(); + if self.importize { + let world_id = resolve.select_world(main, None)?; + resolve.importize(world_id) + } let mut printer = WitPrinter::default(); printer.emit_docs(!self.no_docs); @@ -647,7 +663,7 @@ impl WitOpts { let main = decoded.package(); for (id, pkg) in resolve.packages.iter() { let is_main = id == main; - let output = printer.print(resolve, id, &[])?; + let output = printer.print(&resolve, id, &[])?; let out_dir = if is_main { dir.clone() } else { @@ -685,8 +701,9 @@ impl WitOpts { self.output.output( &self.general, Output::Wit { - wit: &decoded, printer, + resolve: &resolve, + package: main, }, )?; } diff --git a/src/lib.rs b/src/lib.rs index e4caf81c0a..c3274492fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,8 @@ pub struct OutputArg { pub enum Output<'a> { #[cfg(feature = "component")] Wit { - wit: &'a wit_component::DecodedWasm, + resolve: &'a wit_parser::Resolve, + package: wit_parser::PackageId, printer: wit_component::WitPrinter, }, Wasm(&'a [u8]), @@ -236,15 +237,18 @@ impl OutputArg { } Output::Json(s) => self.output_str(s), #[cfg(feature = "component")] - Output::Wit { wit, mut printer } => { - let resolve = wit.resolve(); + Output::Wit { + mut printer, + resolve, + package, + } => { let ids = resolve .packages .iter() .map(|(id, _)| id) - .filter(|id| *id != wit.package()) + .filter(|id| *id != package) .collect::>(); - let output = printer.print(resolve, wit.package(), &ids)?; + let output = printer.print(resolve, package, &ids)?; self.output_str(&output) } } diff --git a/tests/cli/importize-component.wit b/tests/cli/importize-component.wit new file mode 100644 index 0000000000..02320f9aa8 --- /dev/null +++ b/tests/cli/importize-component.wit @@ -0,0 +1,37 @@ +// RUN: component embed --world foo --dummy % | component wit --importize + +package importize:importize; + +interface t { + resource r; +} +interface bar { + use t.{r}; + record foo { + x: string + } + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +world foo { + export qux; + export foo: func(); + type s = u32; + export bar: func() -> s; + export something: interface { + foo: func(); + } + export something-else: interface { + use something-else-dep.{t}; + bar: func() -> t; + } +} \ No newline at end of file diff --git a/tests/cli/importize-component.wit.stdout b/tests/cli/importize-component.wit.stdout new file mode 100644 index 0000000000..ef86527b93 --- /dev/null +++ b/tests/cli/importize-component.wit.stdout @@ -0,0 +1,60 @@ +package root:root; + +world root { + import importize:importize/bar; + import importize:importize/t; + import foo: func(); + import bar: func() -> s; + import importize:importize/qux; + import something: interface { + foo: func(); + } + import something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } + + type s = u32; +} +package importize:importize { + interface t { + resource r; + } + interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); + } + interface something-else-dep { + type t = u32; + } + interface qux { + use bar.{foo}; + + blah: func(boo: foo); + } + world foo { + import t; + import bar; + import something-else-dep; + + type s = u32; + + export foo: func(); + export bar: func() -> s; + export qux; + export something: interface { + foo: func(); + } + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } + } +} From 43121d67d0de0a64774c8a6f77bd0a2efbd709d7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Sep 2024 12:34:34 -0700 Subject: [PATCH 2/3] Updates to importize * Update the CLI to have `--importize` and `--importize-world` * Rewrite the test to use these flags and have multiple tests in one file, each with a smaller world. * Update the implementation to preserve allow-listed imports instead of removing all imports and recreating what needs to be preserved. --- crates/wit-parser/src/resolve.rs | 119 ++++++++++++------ src/bin/wasm-tools/component.rs | 76 ++++++++--- src/lib.rs | 14 +-- tests/cli.rs | 35 +++--- tests/cli/importize-component.wit | 37 ------ tests/cli/importize-component.wit.stdout | 60 --------- tests/cli/importize.wit | 80 ++++++++++++ tests/cli/importize.wit.fail1.stderr | 4 + .../cli/importize.wit.simple-component.stdout | 13 ++ .../cli/importize.wit.simple-toplevel.stdout | 93 ++++++++++++++ tests/cli/importize.wit.simple.stdout | 93 ++++++++++++++ tests/cli/importize.wit.toplevel-deps.stdout | 92 ++++++++++++++ tests/cli/importize.wit.tricky-import.stdout | 92 ++++++++++++++ tests/cli/importize.wit.trim-imports.stdout | 88 +++++++++++++ tests/cli/importize.wit.with-deps.stdout | 92 ++++++++++++++ 15 files changed, 811 insertions(+), 177 deletions(-) delete mode 100644 tests/cli/importize-component.wit delete mode 100644 tests/cli/importize-component.wit.stdout create mode 100644 tests/cli/importize.wit create mode 100644 tests/cli/importize.wit.fail1.stderr create mode 100644 tests/cli/importize.wit.simple-component.stdout create mode 100644 tests/cli/importize.wit.simple-toplevel.stdout create mode 100644 tests/cli/importize.wit.simple.stdout create mode 100644 tests/cli/importize.wit.toplevel-deps.stdout create mode 100644 tests/cli/importize.wit.tricky-import.stdout create mode 100644 tests/cli/importize.wit.trim-imports.stdout create mode 100644 tests/cli/importize.wit.with-deps.stdout diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index acf504bcb2..975e86222e 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -931,53 +931,98 @@ package {name} is defined in two different locations:\n\ Some(self.id_of_name(interface.package.unwrap(), interface.name.as_ref()?)) } - pub fn importize(&mut self, world_id: WorldId) { - let exports = &self.worlds[world_id].exports.clone(); - // Make a copy of the imports as we will wholesale clear the imports in the world being importized - let ref_imports = mem::take(&mut self.worlds[world_id].imports); - - let mut temp = Vec::new(); - - for (key, item) in exports.clone() { - match key { - WorldKey::Name(_) => { - temp.push((key, item)); + /// Convert a world to an "importized" version where the world is updated + /// in-place to reflect what it would look like to be imported. + /// + /// This is a transformation which is used as part of the process of + /// importing a component today. For example when a component depends on + /// another component this is useful for generating WIT which can be use to + /// represent the component being imported. The general idea is that this + /// function will update the `world_id` specified such it imports the + /// functionality that it previously exported. The world will be left with + /// no exports. + /// + /// This world is then suitable for merging into other worlds or generating + /// bindings in a context that is importing the original world. This + /// is intended to be used as part of language tooling when depending on + /// other components. + pub fn importize(&mut self, world_id: WorldId) -> Result<()> { + // Collect the set of interfaces which are depended on by exports. Also + // all imported types are assumed to stay so collect any interfaces + // they depend on. + let mut live_through_exports = IndexSet::default(); + for (_, export) in self.worlds[world_id].exports.iter() { + if let WorldItem::Interface { id, .. } = export { + self.collect_interface_deps(*id, &mut live_through_exports); + } + } + for (_, import) in self.worlds[world_id].imports.iter() { + if let WorldItem::Type(ty) = import { + if let Some(dep) = self.type_interface_dep(*ty) { + self.collect_interface_deps(dep, &mut live_through_exports); } - WorldKey::Interface(id) => self.resolve_interface_deps(id, world_id, &ref_imports), } } + + // Rename the world to avoid having it get confused with the original + // name of the world. Add `-importized` to it for now. Precisely how + // this new world is created may want to be updated over time if this + // becomes problematic. let world = &mut self.worlds[world_id]; - world.imports.append(&mut world.exports); + let pkg = &mut self.packages[world.package.unwrap()]; + pkg.worlds.shift_remove(&world.name); + world.name.push_str("-importized"); + pkg.worlds.insert(world.name.clone(), world_id); + + // Trim all unnecessary imports first. + world.imports.retain(|name, item| match (name, item) { + // Remove imports which can't be used by import such as: + // + // * `import foo: interface { .. }` + // * `import foo: func();` + (WorldKey::Name(_), WorldItem::Interface { .. } | WorldItem::Function(_)) => false, + + // Coarsely say that all top-level types are required to avoid + // calculating precise liveness of them right now. + (WorldKey::Name(_), WorldItem::Type(_)) => true, + + // Only retain interfaces if they're needed somehow transitively + // for the exports. + (WorldKey::Interface(id), _) => live_through_exports.contains(id), + }); - for (key, item) in ref_imports { - if let WorldItem::Type(_) = item { - world.imports.insert(key, item); - } - } + // After all unnecessary imports are gone remove all exports and move + // them all to imports, failing if there's an overlap. + for (name, export) in mem::take(&mut world.exports) { + match (name.clone(), world.imports.insert(name, export)) { + // no previous item? this insertion was ok + (_, None) => {} + + // cannot overwrite an import with an export + (WorldKey::Name(name), _) => { + bail!("world export `{name}` conflicts with import of same name"); + } + + // interface overlap is ok and is always allowed. + (WorldKey::Interface(id), Some(WorldItem::Interface { id: other, .. })) => { + assert_eq!(id, other); + } - for (key, item) in temp { - world.imports.insert(key, item); + (WorldKey::Interface(_), _) => unreachable!(), + } } - world.exports.clear(); + #[cfg(debug_assertions)] + self.assert_valid(); + Ok(()) } - fn resolve_interface_deps( - &mut self, - interface_id: InterfaceId, - world_id: WorldId, - ref_imports: &IndexMap, - ) { - let direct_deps = self.interface_direct_deps(interface_id).collect::>(); - for dep_id in direct_deps { - let world_key = WorldKey::Interface(dep_id); - let world_item = ref_imports.get(&world_key).unwrap(); - - let world = &mut self.worlds[world_id]; - if !world.imports.contains_key(&world_key) { - world.imports.insert(world_key, world_item.clone()); - self.resolve_interface_deps(dep_id, world_id, ref_imports); - } + fn collect_interface_deps(&self, interface: InterfaceId, deps: &mut IndexSet) { + if !deps.insert(interface) { + return; + } + for dep in self.interface_direct_deps(interface) { + self.collect_interface_deps(dep, deps); } } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 22d33345ae..e8e1b40819 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -4,6 +4,7 @@ use anyhow::{bail, Context, Result}; use clap::Parser; use std::collections::HashMap; use std::io::Read; +use std::mem; use std::path::{Path, PathBuf}; use wasm_encoder::reencode::{Error, Reencode, ReencodeComponent, RoundtripReencoder}; use wasm_encoder::ModuleType; @@ -499,16 +500,30 @@ pub struct WitOpts { )] json: bool, - /// Importize the WIT document by removing the imports and - /// moving the exports of a component to be its imports. - #[clap( - long, - conflicts_with = "wasm", - conflicts_with = "wat", - conflicts_with = "json" - )] + /// Generates WIT to import the component specified to this command. + /// + /// This flags requires that the input is a binary component, not a + /// wasm-encoded WIT package. This will then generate a WIT world and output + /// that. The returned world will have imports corresponding to the exports + /// of the component which is input. + /// + /// This is similar to `--importize-world`, but is used with components. + #[clap(long, conflicts_with = "importize_world")] importize: bool, + /// Generates a WIT world to import a component which corresponds to the + /// selected world. + /// + /// This flag is used to indicate that the input is a WIT package and the + /// world passed here is the name of a WIT `world` within the package. The + /// output of the command will be the same WIT world but one that's + /// importing the selected world. This effectively moves the world's exports + /// to imports. + /// + /// This is similar to `--importize`, but is used with WIT packages. + #[clap(long, conflicts_with = "importize", value_name = "WORLD")] + importize_world: Option, + /// Features to enable when parsing the `wit` option. /// /// This flag enables the `@unstable` feature in WIT documents where the @@ -531,7 +546,13 @@ impl WitOpts { /// Executes the application. fn run(self) -> Result<()> { - let decoded = self.decode_input()?; + let mut decoded = self.decode_input()?; + + if self.importize { + self.importize(&mut decoded, None)?; + } else if self.importize_world.is_some() { + self.importize(&mut decoded, self.importize_world.as_deref())?; + } // Now that the WIT document has been decoded, it's time to emit it. // This interprets all of the output options and performs such a task. @@ -615,6 +636,30 @@ impl WitOpts { } } + fn importize(&self, decoded: &mut DecodedWasm, world: Option<&str>) -> Result<()> { + let (resolve, world_id) = match (&mut *decoded, world) { + (DecodedWasm::Component(resolve, world), None) => (resolve, *world), + (DecodedWasm::Component(..), Some(_)) => { + bail!( + "the `--importize-world` flag is not compatible with a \ + component input, use `--importize` instead" + ); + } + (DecodedWasm::WitPackage(resolve, id), world) => { + let world = resolve.select_world(*id, world)?; + (resolve, world) + } + }; + // let pkg = decoded.package(); + // let world_id = decoded.resolve().select_world(main, None)?; + resolve + .importize(world_id) + .context("failed to move world exports to imports")?; + let resolve = mem::take(resolve); + *decoded = DecodedWasm::Component(resolve, world_id); + Ok(()) + } + fn emit_wasm(&self, decoded: &DecodedWasm) -> Result<()> { assert!(self.wasm || self.wat); assert!(self.out_dir.is_none()); @@ -631,13 +676,7 @@ impl WitOpts { fn emit_wit(&self, decoded: &DecodedWasm) -> Result<()> { assert!(!self.wasm && !self.wat); - let mut resolve = decoded.resolve().clone(); - - let main = decoded.package(); - if self.importize { - let world_id = resolve.select_world(main, None)?; - resolve.importize(world_id) - } + let resolve = decoded.resolve(); let mut printer = WitPrinter::default(); printer.emit_docs(!self.no_docs); @@ -663,7 +702,7 @@ impl WitOpts { let main = decoded.package(); for (id, pkg) in resolve.packages.iter() { let is_main = id == main; - let output = printer.print(&resolve, id, &[])?; + let output = printer.print(resolve, id, &[])?; let out_dir = if is_main { dir.clone() } else { @@ -701,9 +740,8 @@ impl WitOpts { self.output.output( &self.general, Output::Wit { + wit: &decoded, printer, - resolve: &resolve, - package: main, }, )?; } diff --git a/src/lib.rs b/src/lib.rs index c3274492fe..e4caf81c0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,8 +147,7 @@ pub struct OutputArg { pub enum Output<'a> { #[cfg(feature = "component")] Wit { - resolve: &'a wit_parser::Resolve, - package: wit_parser::PackageId, + wit: &'a wit_component::DecodedWasm, printer: wit_component::WitPrinter, }, Wasm(&'a [u8]), @@ -237,18 +236,15 @@ impl OutputArg { } Output::Json(s) => self.output_str(s), #[cfg(feature = "component")] - Output::Wit { - mut printer, - resolve, - package, - } => { + Output::Wit { wit, mut printer } => { + let resolve = wit.resolve(); let ids = resolve .packages .iter() .map(|(id, _)| id) - .filter(|id| *id != package) + .filter(|id| *id != wit.package()) .collect::>(); - let output = printer.print(resolve, package, &ids)?; + let output = printer.print(resolve, wit.package(), &ids)?; self.output_str(&output) } } diff --git a/tests/cli.rs b/tests/cli.rs index 6ecca33fe0..cf6dd14d99 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -149,27 +149,32 @@ fn execute(cmd: &mut Command, stdin: Option<&[u8]>, should_fail: bool) -> Result let output = p.wait_with_output()?; - if !output.status.success() { - if !should_fail { - bail!( - "{cmd:?} failed: - status: {} - stdout: {} - stderr: {}", - output.status, - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); + let mut failure = None; + match output.status.code() { + Some(0) => { + if should_fail { + failure = Some("succeeded instead of failed"); + } } - } else if should_fail { + Some(1) | Some(2) => { + if !should_fail { + failure = Some("failed"); + } + } + _ => failure = Some("unknown exit code"), + } + if let Some(msg) = failure { bail!( - "{cmd:?} succeeded instead of failed - stdout: {} - stderr: {}", + "{cmd:?} {msg}: + status: {} + stdout: {} + stderr: {}", + output.status, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } + Ok(output) } diff --git a/tests/cli/importize-component.wit b/tests/cli/importize-component.wit deleted file mode 100644 index 02320f9aa8..0000000000 --- a/tests/cli/importize-component.wit +++ /dev/null @@ -1,37 +0,0 @@ -// RUN: component embed --world foo --dummy % | component wit --importize - -package importize:importize; - -interface t { - resource r; -} -interface bar { - use t.{r}; - record foo { - x: string - } - importize: func(name: r); -} - -interface qux { - use bar.{foo}; - blah: func(boo: foo); -} - -interface something-else-dep { - type t = u32; -} - -world foo { - export qux; - export foo: func(); - type s = u32; - export bar: func() -> s; - export something: interface { - foo: func(); - } - export something-else: interface { - use something-else-dep.{t}; - bar: func() -> t; - } -} \ No newline at end of file diff --git a/tests/cli/importize-component.wit.stdout b/tests/cli/importize-component.wit.stdout deleted file mode 100644 index ef86527b93..0000000000 --- a/tests/cli/importize-component.wit.stdout +++ /dev/null @@ -1,60 +0,0 @@ -package root:root; - -world root { - import importize:importize/bar; - import importize:importize/t; - import foo: func(); - import bar: func() -> s; - import importize:importize/qux; - import something: interface { - foo: func(); - } - import something-else: interface { - use something-else-dep.{t}; - - bar: func() -> t; - } - - type s = u32; -} -package importize:importize { - interface t { - resource r; - } - interface bar { - use t.{r}; - - record foo { - x: string, - } - - importize: func(name: r); - } - interface something-else-dep { - type t = u32; - } - interface qux { - use bar.{foo}; - - blah: func(boo: foo); - } - world foo { - import t; - import bar; - import something-else-dep; - - type s = u32; - - export foo: func(); - export bar: func() -> s; - export qux; - export something: interface { - foo: func(); - } - export something-else: interface { - use something-else-dep.{t}; - - bar: func() -> t; - } - } -} diff --git a/tests/cli/importize.wit b/tests/cli/importize.wit new file mode 100644 index 0000000000..26a70649e8 --- /dev/null +++ b/tests/cli/importize.wit @@ -0,0 +1,80 @@ +// RUN[simple]: component wit --importize-world simple % +// RUN[simple-component]: component embed --dummy --world simple % | \ +// component wit --importize +// RUN[with-deps]: component wit --importize-world with-deps % +// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +// FAIL[fail1]: component wit --importize-world fail1 % +// RUN[trim-imports]: component wit --importize-world trim-imports % +// RUN[tricky-import]: component wit --importize-world tricky-import % + +package importize:importize; + +interface t { + resource r; +} +interface bar { + use t.{r}; + record foo { + x: string + } + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +world simple { + export t; +} + +world with-deps { + export qux; +} + +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} + +world toplevel-deps { + type s = u32; + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + bar: func() -> t; + } +} + +world fail1 { + type foo = u32; + export foo: func() -> foo; +} + +interface a {} +interface b {} + +world trim-imports { + import a; + import foo: func(); + import bar: interface {} + type t = u32; + export b; +} + +interface with-dep { + type t = u32; +} + +world tricky-import { + use with-dep.{t}; + export f: func() -> t; +} diff --git a/tests/cli/importize.wit.fail1.stderr b/tests/cli/importize.wit.fail1.stderr new file mode 100644 index 0000000000..8e750f5c4f --- /dev/null +++ b/tests/cli/importize.wit.fail1.stderr @@ -0,0 +1,4 @@ +error: failed to move world exports to imports + +Caused by: + 0: world export `foo` conflicts with import of same name diff --git a/tests/cli/importize.wit.simple-component.stdout b/tests/cli/importize.wit.simple-component.stdout new file mode 100644 index 0000000000..f9a7f62d68 --- /dev/null +++ b/tests/cli/importize.wit.simple-component.stdout @@ -0,0 +1,13 @@ +package root:root; + +world root-importized { + import importize:importize/t; +} +package importize:importize { + interface t { + resource r; + } + world simple { + export t; + } +} diff --git a/tests/cli/importize.wit.simple-toplevel.stdout b/tests/cli/importize.wit.simple-toplevel.stdout new file mode 100644 index 0000000000..fa9ccaa816 --- /dev/null +++ b/tests/cli/importize.wit.simple-toplevel.stdout @@ -0,0 +1,93 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world simple { + export t; +} +world with-deps { + import t; + import bar; + + export qux; +} +world toplevel-deps { + import something-else-dep; + + type s = u32; + + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world trim-imports { + import a; + import bar: interface { + } + import foo: func(); + + type t = u32; + + export b; +} +world tricky-import { + import with-dep; + use with-dep.{t}; + + export f: func() -> t; +} +world simple-toplevel-importized { + import foo: func(); + import something: interface { + foo: func(); + } +} diff --git a/tests/cli/importize.wit.simple.stdout b/tests/cli/importize.wit.simple.stdout new file mode 100644 index 0000000000..1202376615 --- /dev/null +++ b/tests/cli/importize.wit.simple.stdout @@ -0,0 +1,93 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world with-deps { + import t; + import bar; + + export qux; +} +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} +world toplevel-deps { + import something-else-dep; + + type s = u32; + + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world trim-imports { + import a; + import bar: interface { + } + import foo: func(); + + type t = u32; + + export b; +} +world tricky-import { + import with-dep; + use with-dep.{t}; + + export f: func() -> t; +} +world simple-importized { + import t; +} diff --git a/tests/cli/importize.wit.toplevel-deps.stdout b/tests/cli/importize.wit.toplevel-deps.stdout new file mode 100644 index 0000000000..1281c65c3b --- /dev/null +++ b/tests/cli/importize.wit.toplevel-deps.stdout @@ -0,0 +1,92 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world simple { + export t; +} +world with-deps { + import t; + import bar; + + export qux; +} +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world trim-imports { + import a; + import bar: interface { + } + import foo: func(); + + type t = u32; + + export b; +} +world tricky-import { + import with-dep; + use with-dep.{t}; + + export f: func() -> t; +} +world toplevel-deps-importized { + import something-else-dep; + import bar: func() -> s; + import something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } + + type s = u32; +} diff --git a/tests/cli/importize.wit.tricky-import.stdout b/tests/cli/importize.wit.tricky-import.stdout new file mode 100644 index 0000000000..af547e2645 --- /dev/null +++ b/tests/cli/importize.wit.tricky-import.stdout @@ -0,0 +1,92 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world simple { + export t; +} +world with-deps { + import t; + import bar; + + export qux; +} +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} +world toplevel-deps { + import something-else-dep; + + type s = u32; + + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world trim-imports { + import a; + import bar: interface { + } + import foo: func(); + + type t = u32; + + export b; +} +world tricky-import-importized { + import with-dep; + import f: func() -> t; + use with-dep.{t}; +} diff --git a/tests/cli/importize.wit.trim-imports.stdout b/tests/cli/importize.wit.trim-imports.stdout new file mode 100644 index 0000000000..933c035835 --- /dev/null +++ b/tests/cli/importize.wit.trim-imports.stdout @@ -0,0 +1,88 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world simple { + export t; +} +world with-deps { + import t; + import bar; + + export qux; +} +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} +world toplevel-deps { + import something-else-dep; + + type s = u32; + + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world tricky-import { + import with-dep; + use with-dep.{t}; + + export f: func() -> t; +} +world trim-imports-importized { + import b; + + type t = u32; +} diff --git a/tests/cli/importize.wit.with-deps.stdout b/tests/cli/importize.wit.with-deps.stdout new file mode 100644 index 0000000000..64c3f70bfb --- /dev/null +++ b/tests/cli/importize.wit.with-deps.stdout @@ -0,0 +1,92 @@ +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % +package importize:importize; + +interface t { + resource r; +} + +interface bar { + use t.{r}; + + record foo { + x: string, + } + + importize: func(name: r); +} + +interface qux { + use bar.{foo}; + + blah: func(boo: foo); +} + +interface something-else-dep { + type t = u32; +} + +interface a { +} + +interface b { +} + +interface with-dep { + type t = u32; +} + +world simple { + export t; +} +world simple-toplevel { + export foo: func(); + export something: interface { + foo: func(); + } +} +world toplevel-deps { + import something-else-dep; + + type s = u32; + + export bar: func() -> s; + export something-else: interface { + use something-else-dep.{t}; + + bar: func() -> t; + } +} +world fail1 { + type foo = u32; + + export foo: func() -> foo; +} +world trim-imports { + import a; + import bar: interface { + } + import foo: func(); + + type t = u32; + + export b; +} +world tricky-import { + import with-dep; + use with-dep.{t}; + + export f: func() -> t; +} +world with-deps-importized { + import t; + import bar; + import qux; +} From 01cc0b0b2a66eda575077334bcbd867facd0749d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Sep 2024 13:06:12 -0700 Subject: [PATCH 3/3] Enable fuzz-testing of `importize` --- fuzz/src/roundtrip_wit.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index c771bc6d88..b1b22fd3a0 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -34,15 +34,18 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { // to avoid timing out this fuzzer with asan enabled. let mut decoded_worlds = Vec::new(); for (id, world) in resolve.worlds.iter().take(20) { - log::debug!("testing world {}", world.name); + log::debug!("embedding world {} as in a dummy module", world.name); let mut dummy = wit_component::dummy_module(&resolve, id); wit_component::embed_component_metadata(&mut dummy, &resolve, id, StringEncoding::UTF8) .unwrap(); write_file("dummy.wasm", &dummy); + // Decode what was just created and record it later for testing merging + // worlds together. let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap(); decoded_worlds.push(decoded.resolve); + log::debug!("... componentizing the world into a binary component"); let wasm = wit_component::ComponentEncoder::default() .module(&dummy) .unwrap() @@ -55,7 +58,15 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { .validate_all(&wasm) .unwrap(); + log::debug!("... decoding the component itself"); wit_component::decode(&wasm).unwrap(); + + // Test out importizing the world and then assert the world is still + // valid. + log::debug!("... importizing this world"); + let mut resolve2 = resolve.clone(); + let _ = resolve2.importize(id); + resolve.assert_valid(); } if decoded_worlds.len() < 2 {