From c2a10b6781c85c29fedab1ea1c45752008318588 Mon Sep 17 00:00:00 2001 From: Ivan Mikushin Date: Fri, 6 Nov 2026 01:35:05 +0000 Subject: [PATCH 1/3] test(wit-component): cover two implements imports of one instance type Validation accepts a component that imports one interface twice, under two plain names, through `(implements ...)` on a shared instance type. Decoding that component panics in `register_import`. --- .../tests/shared_instance_type.rs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 crates/wit-component/tests/shared_instance_type.rs diff --git a/crates/wit-component/tests/shared_instance_type.rs b/crates/wit-component/tests/shared_instance_type.rs new file mode 100644 index 0000000000..602662a3ee --- /dev/null +++ b/crates/wit-component/tests/shared_instance_type.rs @@ -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<()> { + 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::>(); + 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 { + 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}`"), + } +} From a7a9f44947e15218bd11c489dad08d5a05b90042 Mon Sep 17 00:00:00 2001 From: Ivan Mikushin Date: Fri, 6 Nov 2026 01:35:26 +0000 Subject: [PATCH 2/3] fix(wit-parser): accept a repeated export alias in register_import Two imports can share one instance type. The `(type (eq ...))` alias on that type is allocated once. The first import maps the alias to the WIT type. The second import maps it to the same type. Keep that mapping. The assert still rejects a mapping to another WIT type. --- crates/wit-parser/src/decoding.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index aeddc85eb9..8dec03ace0 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -796,7 +796,10 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), } let prev = self.type_map.insert(created, id); - assert!(prev.is_none()); + assert!( + prev.is_none() || prev == Some(id), + "type export `{name}` was already mapped to a different type", + ); } // If the name is not defined, however, then there's two From 905f6687ac76031f21ac17f40c7ac3d325dcf58b Mon Sep 17 00:00:00 2001 From: Ivan Mikushin Date: Thu, 5 Nov 2026 22:16:21 -0800 Subject: [PATCH 3/3] fix(wit-parser): return errors instead of panicking in register_import Conflicting type and function exports while decoding an imported interface now fail the decode instead of asserting. --- crates/wit-parser/src/decoding.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 8dec03ace0..c3d659be98 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -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; @@ -796,7 +795,7 @@ impl WitPackageDecoder<'_> { _ => unreachable!(), } let prev = self.type_map.insert(created, id); - assert!( + ensure!( prev.is_none() || prev == Some(id), "type export `{name}` was already mapped to a different type", ); @@ -823,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()); } } } @@ -847,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()); } _ => bail!("instance type export `{name}` is not a type"),