Skip to content
Open
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
102 changes: 102 additions & 0 deletions crates/wit-component/tests/shared_instance_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use anyhow::{Context, Result, bail};
use pretty_assertions::assert_eq;
use wasmparser::{Validator, WasmFeatures};
use wit_component::{DecodedWasm, WitPrinter, decode};
use wit_parser::{InterfaceId, Type, TypeDefKind, World, WorldItem, WorldKey};

#[test]
fn two_implements_imports_share_an_instance_type() -> Result<()> {

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.

Can this test be modeled as a test/cli/*.wat test instead? Or maybe crates/wit-component/tests/component/*?

let wasm = wat::parse_str(
r#"
(component
(type (instance
(type (variant (case "other" string)))
(export "error" (type (eq 0)))
(type (func (result 1)))
(export "open" (func (type 2)))
))
(import "primary" (implements "a:b/store@0.1.0") (instance (type 0)))
(import "backup" (implements "a:b/store@0.1.0") (instance (type 0)))
)
"#,
)?;
Validator::new_with_features(WasmFeatures::all())
.validate_all(&wasm)
.context("validation accepts this component")?;

let decoded = decode(&wasm).context("decoding the component to WIT")?;
let (resolve, world_id) = match decoded {
DecodedWasm::Component(resolve, world) => (resolve, world),
DecodedWasm::WitPackage(_, _) => bail!("expected a component, decoded a WIT package"),
};

let world = &resolve.worlds[world_id];
let primary = interface_import(world, "primary")?;
let backup = interface_import(world, "backup")?;
assert_eq!(primary, backup, "both imports are the same interface");
assert_eq!(world.imports.len(), 2);

let interface = &resolve.interfaces[primary];
assert_eq!(interface.name.as_deref(), Some("store"));
let package = &resolve.packages[interface.package.context("interface has a package")?];
assert_eq!(package.name.to_string(), "a:b@0.1.0");

let error_id = *interface
.types
.get("error")
.context("store exports the error type")?;
match &resolve.types[error_id].kind {
TypeDefKind::Variant(variant) => {
assert_eq!(variant.cases.len(), 1);
assert_eq!(variant.cases[0].name, "other");
assert_eq!(variant.cases[0].ty, Some(Type::String));
}
other => bail!("error has kind {other:?}, expected a variant"),
}
let open = interface
.functions
.get("open")
.context("store exports open")?;
assert!(open.params.is_empty());
assert_eq!(open.result, Some(Type::Id(error_id)));

let mut printer = WitPrinter::default();
let nested = resolve
.packages
.iter()
.map(|(id, _)| id)
.filter(|id| *id != resolve.worlds[world_id].package.unwrap())
.collect::<Vec<_>>();
printer
.print(&resolve, resolve.worlds[world_id].package.unwrap(), &nested)
.context("printing WIT")?;
assert_eq!(
printer.output.to_string(),
"\
package root:component;

world root {
import primary: a:b/store@0.1.0;
import backup: a:b/store@0.1.0;
}
package a:b@0.1.0 {
interface store {
variant error {
other(string),
}

open: func() -> error;
}
}
"
);
Ok(())
}

fn interface_import(world: &World, name: &str) -> Result<InterfaceId> {
match world.imports.get(&WorldKey::Name(name.to_string())) {
Some(WorldItem::Interface { id, .. }) => Ok(*id),
Some(_) => bail!("import `{name}` is not an interface"),
None => bail!("missing import `{name}`"),
}
}
12 changes: 7 additions & 5 deletions crates/wit-parser/src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use crate::*;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use anyhow::Result;
use anyhow::{Context, anyhow, bail};
use anyhow::{Context, Result, anyhow, bail, ensure};
use core::mem;
use std::io::Read;
use wasmparser::Chunk;
Expand Down Expand Up @@ -796,7 +795,10 @@ impl WitPackageDecoder<'_> {
_ => unreachable!(),
}
let prev = self.type_map.insert(created, id);
assert!(prev.is_none());
ensure!(
prev.is_none() || prev == Some(id),

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.

Can you elaborate more on why a new condition was added here?

"type export `{name}` was already mapped to a different type",
);
}

// If the name is not defined, however, then there's two
Expand All @@ -820,7 +822,7 @@ impl WitPackageDecoder<'_> {
let prev = self.resolve.interfaces[interface]
.types
.insert(name.to_string(), id);
assert!(prev.is_none());
ensure!(prev.is_none());
}
}
}
Expand All @@ -844,7 +846,7 @@ impl WitPackageDecoder<'_> {
let prev = self.resolve.interfaces[interface]
.functions
.insert(name.to_string(), func);
assert!(prev.is_none());
ensure!(prev.is_none());

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.

When changing assert! to ensure! can you be sure to list an appropriate error message? An error message of prev.is_none() here isn't too helpful for end users

}

_ => bail!("instance type export `{name}` is not a type"),
Expand Down
Loading