Repository: bytecodealliance/wasm-tools (rust)

Issue:
`merge_worlds` on `wit_parser::resolve::Resolve` fails when worlds contain the same export 
While trying to merge multiple worlds, I noticed worlds with the same exports could not be merged (i.e.) if 2 worlds export wasi:http/incoming-handler@0.2.0, the worlds cannot be merged. 
```
export `wasi:http/incoming-handler@0.2.0` conflicts with the previous name of `wasi:http/incoming-handler@0.2.0`
```

The expected behavior would be for the worlds to merge successfully as long as the types of exports are the same.

Here is a [repo](https://github.com/karthik2804/wit-parser-merge-issue) with an example of it

Pull request description:
Rewrite `Resolve::merge_worlds`
This commit updates and rewrites the `Resolve::merge_worlds` helper which is one of the workhorses of `wit-bindgen` and `wit-component`. The previous implementation is largely thrown out and replaced with:

* Any new import can always be added.
* Any preexisting import must "match" the one being added. This is done by updating `MergeMap` with some more functionality (but not everything) and handling all kinds of imports.
* Any preexisting export must "match" in the same way as imports.
* New exports can only be added if the meaning of previous exports don't change. This means that if a new export is a transitive dependency of an existing export then it can't be added.

This fixes a number of issues with the previous implementation which was written during a different time with a different structure of WIT.

Closes #1685

Patch:
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index f8e09f62..f8945d5b 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -919,7 +919,7 @@ impl Printer<'_, '_> {
         // we need to be careful to terminate previous param blocks and open
         // a new one if that's the case with a named parameter.
         for (i, param) in ty.params().iter().enumerate() {
-            params.start_local(names_for.unwrap_or(u32::MAX), i as u32, self, state)?;
+            params.start_local(names_for, i as u32, self, state)?;
             self.print_valtype(state, *param)?;
             params.end_local(self)?;
         }
@@ -1335,7 +1335,7 @@ impl Printer<'_, '_> {
                     self.newline(offset)?;
                     first = false;
                 }
-                locals.start_local(func_idx, params + local_idx, self, state)?;
+                locals.start_local(Some(func_idx), params + local_idx, self, state)?;
                 self.print_valtype(state, ty)?;
                 locals.end_local(self)?;
                 local_idx += 1;
@@ -2914,12 +2914,16 @@ impl NamedLocalPrinter {
 
     fn start_local(
         &mut self,
-        func: u32,
+        func: Option<u32>,
         local: u32,
         dst: &mut Printer,
         state: &State,
     ) -> Result<()> {
-        let name = state.core.local_names.index_to_name.get(&(func, local));
+        let name = state
+            .core
+            .local_names
+            .index_to_name
+            .get(&(func.unwrap_or(u32::MAX), local));
 
         // Named locals must be in their own group, so if we have a name we need
         // to terminate the previous group.
@@ -2949,7 +2953,7 @@ impl NamedLocalPrinter {
                 dst.result.write_str(" ")?;
                 self.end_group_after_local = true;
             }
-            None if dst.config.name_unnamed => {
+            None if dst.config.name_unnamed && func.is_some() => {
                 write!(dst.result, "$#local{local} ")?;
                 self.end_group_after_local = true;
             }
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index 91c25b47..0baf28f1 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -531,7 +531,7 @@ impl Resolve {
             self.packages.len()
         );
 
-        let mut map = MergeMap::new(&resolve, &self)?;
+        let mut map = MergeMap::new(&resolve, &self);
         map.build()?;
         let MergeMap {
             package_map,
@@ -729,84 +729,44 @@ impl Resolve {
         let from_world = &self.worlds[from];
         let into_world = &self.worlds[into];
 
-        // Build a map of the imports/exports in `into` going the reverse
-        // direction from what's listed. This is then consulted below to ensure
-        // that the same item isn't exported or imported under two different
-        // names which isn't allowed in the component model.
-        let mut into_imports_by_id = HashMap::new();
-        let mut into_exports_by_id = HashMap::new();
-        for (name, import) in into_world.imports.iter() {
-            if let WorldItem::Interface { id, .. } = *import {
-                let prev = into_imports_by_id.insert(id, name);
-                assert!(prev.is_none());
-            }
-        }
-        for (name, export) in into_world.exports.iter() {
-            if let WorldItem::Interface { id, .. } = *export {
-                let prev = into_exports_by_id.insert(id, name);
-                assert!(prev.is_none());
-            }
-        }
-        for (name, import) in from_world.imports.iter() {
-            // If the "from" world imports an interface which is already
-            // imported by the "into" world then this is allowed if the names
-            // are the same. Importing the same interface under different names
-            // isn't allowed, but otherwise merging imports of
-            // same-named-interfaces is allowed to merge them together.
-            if let WorldItem::Interface { id, .. } = import {
-                if let Some(prev) = into_imports_by_id.get(id) {
-                    if *prev != name {
-                        let name = self.name_world_key(name);
-                        let prev = self.name_world_key(prev);
-                        bail!("import `{name}` conflicts with previous name of `{prev}`");
-                    }
-                }
-            }
-        }
-        for (name, export) in from_world.exports.iter() {
-            // Note that unlike imports same-named exports are not handled here
-            // since if something is exported twice there's no way to "unify" it
-            // so it's left as an error.
-            if let WorldItem::Interface { id, .. } = export {
-                if let Some(prev) = into_exports_by_id.get(id) {
-                    let name = self.name_world_key(name);
-                    let prev = self.name_world_key(prev);
-                    bail!("export `{name}` conflicts with previous name of `{prev}`");
-                }
-            }
-        }
-
-        // Next walk over the interfaces imported into `from_world` and queue up
-        // imports to get inserted into `into_world`.
+        // First walk over all the imports of `from` world and figure out what
+        // to do with them.
+        //
+        // If the same item exists in `from` and `into` then merge it together
+        // below with `merge_world_item` which basically asserts they're the
+        // same. Otherwise queue up a new import since if `from` has more
+        // imports than `into` then it's fine to add new imports.
         for (name, from_import) in from_world.imports.iter() {
             match into_world.imports.get(name) {
-                Some(into_import) => match (from_import, into_import) {
-                    // If these imports, which have the same name, are of the
-                    // same interface then union them together at this point.
-                    (
-                        WorldItem::Interface { id: from, .. },
-                        WorldItem::Interface { id: into, .. },
-                    ) if from == into => continue,
-                    _ => {
-                        let name = self.name_world_key(name);
-                        bail!("duplicate import found for interface `{name}`");
-                    }
-                },
-                None => new_imports.push((name.clone(), from_import.clone())),
+                Some(into_import) => {
+                    let name = self.name_world_key(name);
+                    self.merge_world_item(from_import, into_import)
+                        .with_context(|| format!("failed to merge world import {name}"))?;
+                }
+                None => {
+                    new_imports.push((name.clone(), from_import.clone()));
+                }
             }
         }
 
-        // All exports at this time must be unique. For example the same
-        // interface exported from two locations can't really be resolved to one
-        // canonical definition, so make sure that merging worlds only succeeds
-        // if the worlds have disjoint sets of exports.
-        for (name, export) in from_world.exports.iter() {
+        // Next walk over exports of `from` and process these similarly to
+        // imports.
+        for (name, from_export) in from_world.exports.iter() {
             match into_world.exports.get(name) {
-                Some(_) => {
+                Some(into_export) => {
                     let name = self.name_world_key(name);
-                    bail!("duplicate export found for interface `{name}`");
+                    self.merge_world_item(from_export, into_export)
+                        .with_context(|| format!("failed to merge world export {name}"))?;
+                }
+                None => {
+                    // See comments in `ensure_can_add_world_export` for why
+                    // this is slightly different than imports.
+                    self.ensure_can_add_world_export(into_world, name, from_export)
+                        .with_context(|| {
+                            format!("failed to add export `{}`", self.name_world_key(name))
+                        })?;
+                    new_exports.push((name.clone(), from_export.clone()));
                 }
-                None => new_exports.push((name.clone(), export.clone())),
             }
         }
 
@@ -824,6 +784,125 @@ impl Resolve {
         Ok(())
     }
 
+    fn merge_world_item(&self, from: &WorldItem, into: &WorldItem) -> Result<()> {
+        let mut map = MergeMap::new(self, self);
+        match (from, into) {
+            (WorldItem::Interface { id: from, .. }, WorldItem::Interface { id: into, .. }) => {
+                // If these imports are the same that can happen, for
+                // example, when both worlds to `import foo:bar/baz;`. That
+                // foreign interface will point to the same interface within
+                // `Resolve`.
+                if from == into {
+                    return Ok(());
+                }
+
+                // .. otherwise this MUST be a case of
+                // `import foo: interface { ... }`. If `from != into` but
+                // both `from` and `into` have the same name then the
+                // `WorldKey::Interface` case is ruled out as otherwise
+                // they'd have different names.
+                //
+                // In the case of an anonymous interface all we can do is
+                // ensure that the interfaces both match, so use `MergeMap`
+                // for that.
+                map.build_interface(*from, *into)
+                    .context("failed to merge interfaces")?;
+            }
+
+            // Like `WorldKey::Name` interfaces for functions and types the
+            // structure is asserted to be the same.
+            (WorldItem::Function(from), WorldItem::Function(into)) => {
+                map.build_function(from, into)
+                    .context("failed to merge functions")?;
+            }
+            (WorldItem::Type(from), WorldItem::Type(into)) => {
+                map.build_type_id(*from, *into)
+                    .context("failed to merge types")?;
+            }
+
+            // Kind-level mismatches are caught here.
+            (WorldItem::Interface { .. }, _)
+            | (WorldItem::Function { .. }, _)
+            | (WorldItem::Type { .. }, _) => {
+                bail!("different kinds of items");
+            }
+        }
+        assert!(map.interfaces_to_add.is_empty());
+        assert!(map.worlds_to_add.is_empty());
+        Ok(())
+    }
+
+    /// This method ensures that the world export of `name` and `item` can be
+    /// added to the world `into` without changing the meaning of `into`.
+    ///
+    /// This is somewhat tricky due to how exports/imports are elaborated today
+    /// but the basic idea is that the transitive dependencies of an `export`
+    /// will be implicitly `import`ed if they're not otherwise listed as
+    /// exports. That means that if a transitive dependency of a preexisting
+    /// export is added as a new export it might change the meaning of an
+    /// existing import if it was otherwise already hooked up to an import.
+    ///
+    /// This method rules out this situation.
+    fn ensure_can_add_world_export(
+        &self,
+        into: &World,
+        name: &WorldKey,
+        item: &WorldItem,
+    ) -> Result<()> {
+        assert!(!into.exports.contains_key(name));
+        let interface = match name {
+            // Top-level exports always depend on imports, so these are always
+            // allowed to be added.
+            WorldKey::Name(_) => return Ok(()),
+
+            // This is the case we're worried about. Here if the key is an
+            // interface then the item must also be an interface.
+            WorldKey::Interface(key) => {
+                match item {
+                    WorldItem::Interface { id, .. } => assert_eq!(id, key),
+                    _ => unreachable!(),
+                }
+                *key
+            }
+        };
+
+        // For `interface` to be added as a new export of `into` then it must be
+        // the case that no previous export of `into` depends on `interface`.
+        // Test that by walking all interface exports and seeing if any types
+        // refer to this interface.
+        for (export_name, export) in into.exports.iter() {
+            let export_interface = match export_name {
+                WorldKey::Name(_) => continue,
+                WorldKey::Interface(key) => {
+                    match export {
+                        WorldItem::Interface { id, .. } => assert_eq!(id, key),
+                        _ => unreachable!(),
+                    }
+                    *key
+                }
+            };
+            assert!(export_interface != interface);
+            let iface = &self.interfaces[export_interface];
+            for (name, ty) in iface.types.iter() {
+                let other_ty = match self.types[*ty].kind {
+                    TypeDefKind::Type(Type::Id(ty)) => ty,
+                    _ => continue,
+                };
+                if self.types[other_ty].owner != TypeOwner::Interface(interface) {
+                    continue;
+                }
+
+                let export_name = self.name_world_key(export_name);
+                bail!(
+                    "export `{export_name}` has a type `{name}` which could \
+                     change meaning if this world export were added"
+                )
+            }
+        }
+
+        Ok(())
+    }
+
     /// Returns the ID of the specified `interface`.
     ///
     /// Returns `None` for unnamed interfaces.
@@ -2483,8 +2562,8 @@ struct MergeMap<'a> {
 }
 
 impl<'a> MergeMap<'a> {
-    fn new(from: &'a Resolve, into: &'a Resolve) -> Result<MergeMap<'a>> {
-        Ok(MergeMap {
+    fn new(from: &'a Resolve, into: &'a Resolve) -> MergeMap<'a> {
+        MergeMap {
             package_map: Default::default(),
             interface_map: Default::default(),
             type_map: Default::default(),
@@ -2493,7 +2572,7 @@ impl<'a> MergeMap<'a> {
             worlds_to_add: Default::default(),
             from,
             into,
-        })
+        }
     }
 
     fn build(&mut self) -> Result<()> {
@@ -2592,21 +2671,90 @@ impl<'a> MergeMap<'a> {
             let prev = self.type_map.insert(*from_type_id, into_type_id);
             assert!(prev.is_none());
 
-            // FIXME: ideally the types should be "structurally
-            // equal" but that's not trivial to do in the face of
-            // resources.
+            self.build_type_id(*from_type_id, into_type_id)
+                .with_context(|| format!("mismatch in type `{name}`"))?;
         }
 
-        for (name, _) in from_interface.functions.iter() {
-            if !into_interface.functions.contains_key(name) {
-                bail!("expected function `{name}` to be present");
+        for (name, from_func) in from_interface.functions.iter() {
+            let into_func = match into_interface.functions.get(name) {
+                Some(func) => func,
+                None => bail!("expected function `{name}` to be present"),
+            };
+            self.build_function(from_func, into_func)
+                .with_context(|| format!("mismatch in function `{name}`"))?;
+        }
+
+        Ok(())
+    }
+
+    fn build_type_id(&mut self, from_id: TypeId, into_id: TypeId) -> Result<()> {
+        // FIXME: ideally the types should be "structurally
+        // equal" but that's not trivial to do in the face of
+        // resources.
+        let _ = from_id;
+        let _ = into_id;
+        Ok(())
+    }
+
+    fn build_type(&mut self, from_ty: &Type, into_ty: &Type) -> Result<()> {
+        match (from_ty, into_ty) {
+            (Type::Id(from), Type::Id(into)) => {
+                self.build_type_id(*from, *into)?;
             }
+            (from, into) if from != into => bail!("different kinds of types"),
+            _ => {}
+        }
+        Ok(())
+    }
 
-            // FIXME: ideally the functions should be "structurally
-            // equal" but that's not trivial to do in the face of
-            // resources.
+    fn build_function(&mut self, from_func: &Function, into_func: &Function) -> Result<()> {
+        if from_func.name != into_func.name {
+            bail!(
+                "different function names `{}` and `{}`",
+                from_func.name,
+                into_func.name
+            );
         }
+        match (&from_func.kind, &into_func.kind) {
+            (FunctionKind::Freestanding, FunctionKind::Freestanding) => {}
 
+            (FunctionKind::Method(from), FunctionKind::Method(into))
+            | (FunctionKind::Constructor(from), FunctionKind::Constructor(into))
+            | (FunctionKind::Static(from), FunctionKind::Static(into)) => self
+                .build_type_id(*from, *into)
+                .context("different function kind types")?,
+
+            (FunctionKind::Method(_), _)
+            | (FunctionKind::Constructor(_), _)
+            | (FunctionKind::Static(_), _)
+            | (FunctionKind::Freestanding, _) => {
+                bail!("different function kind types")
+            }
+        }
+
+        if from_func.params.len() != into_func.params.len() {
+            bail!("different number of function parameters");
+        }
+        for ((from_name, from_ty), (into_name, into_ty)) in
+            from_func.params.iter().zip(&into_func.params)
+        {
+            if from_name != into_name {
+                bail!("different function parameter names: {from_name} != {into_name}");
+            }
+            self.build_type(from_ty, into_ty)
+                .with_context(|| format!("different function parameter types for `{from_name}`"))?;
+        }
+        if from_func.results.len() != into_func.results.len() {
+            bail!("different number of function results");
+        }
+        for (from_ty, into_ty) in from_func
+            .results
+            .iter_types()
+            .zip(into_func.results.iter_types())
+        {
+            self.build_type(from_ty, into_ty)
+                .context("different function result types")?;
+        }
         Ok(())
     }
 


Test patch:
diff --git a/tests/cli/print-unnamed.wat b/tests/cli/print-unnamed.wat
index 33de1cbc..5a8819a3 100644
--- a/tests/cli/print-unnamed.wat
+++ b/tests/cli/print-unnamed.wat
@@ -31,4 +31,11 @@
       )
     )
   )
+
+  (func $foo
+    unreachable
+    block (param i32 f32) (result i32 f32)
+      unreachable
+    end
+  )
 )
diff --git a/tests/cli/print-unnamed.wat.stdout b/tests/cli/print-unnamed.wat.stdout
index ef3483ad..16f92910 100644
--- a/tests/cli/print-unnamed.wat.stdout
+++ b/tests/cli/print-unnamed.wat.stdout
@@ -1,5 +1,6 @@
 (module
   (type $#type0 (;0;) (func))
+  (type $#type1 (;1;) (func (param i32 f32) (result i32 f32)))
   (func $#func0 (;0;) (type $#type0)
     block $a
       block $#label1
@@ -26,4 +27,10 @@
       end
     end
   )
+  (func $foo (;1;) (type $#type0)
+    unreachable
+    block $#label0 (type $#type1) (param i32 f32) (result i32 f32)
+      unreachable
+    end
+  )
 )
diff --git a/tests/cli/world-merging-add-imports.wit b/tests/cli/world-merging-add-imports.wit
new file mode 100644
index 00000000..43331ec5
--- /dev/null
+++ b/tests/cli/world-merging-add-imports.wit
@@ -0,0 +1,17 @@
+// RUN: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+world into {
+}
+
+interface b {
+  type t = u32;
+}
+
+world %from {
+  import a: interface {}
+  import b;
+  use b.{t};
+  import c: func() -> t;
+}
diff --git a/tests/cli/world-merging-add-imports.wit.stdout b/tests/cli/world-merging-add-imports.wit.stdout
new file mode 100644
index 00000000..b0e52a26
--- /dev/null
+++ b/tests/cli/world-merging-add-imports.wit.stdout
@@ -0,0 +1,26 @@
+package root:root {
+  world root {
+    import a: interface {
+    }
+    import a:b/b;
+    import c: func() -> t;
+    use a:b/b.{t};
+  }
+}
+
+
+package a:b {
+  interface b {
+    type t = u32;
+  }
+
+  world into {
+  }
+  world %from {
+    import a: interface {
+    }
+    import b;
+    import c: func() -> t;
+    use b.{t};
+  }
+}
diff --git a/tests/cli/world-merging-export-conflict.wit b/tests/cli/world-merging-export-conflict.wit
new file mode 100644
index 00000000..87910b34
--- /dev/null
+++ b/tests/cli/world-merging-export-conflict.wit
@@ -0,0 +1,11 @@
+// FAIL: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+world into {
+  export a: interface {}
+}
+
+world %from {
+  export a: func();
+}
diff --git a/tests/cli/world-merging-export-conflict.wit.stderr b/tests/cli/world-merging-export-conflict.wit.stderr
new file mode 100644
index 00000000..fde21b39
--- /dev/null
+++ b/tests/cli/world-merging-export-conflict.wit.stderr
@@ -0,0 +1,6 @@
+error: updating metadata for section component-type
+
+Caused by:
+    0: failed to merge worlds from two documents
+    1: failed to merge world export a
+    2: different kinds of items
diff --git a/tests/cli/world-merging-export-semantic-change.wit b/tests/cli/world-merging-export-semantic-change.wit
new file mode 100644
index 00000000..ec10ad06
--- /dev/null
+++ b/tests/cli/world-merging-export-semantic-change.wit
@@ -0,0 +1,24 @@
+// FAIL: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+interface i1 {
+  type t = u32;
+}
+interface i2 {
+  use i1.{t};
+}
+
+world into {
+  export i2;
+}
+
+// This world cannot be merged into `into` because it would change the meaning
+// of `into`. Previously in `into` i2's export of `i2` would refer to the
+// implicit import of `i1`, but here the export of `i2` refers to the export of
+// `i1`.
+world %from {
+  export i1;
+  export i2;
+}
+
diff --git a/tests/cli/world-merging-export-semantic-change.wit.stderr b/tests/cli/world-merging-export-semantic-change.wit.stderr
new file mode 100644
index 00000000..8d0bf87d
--- /dev/null
+++ b/tests/cli/world-merging-export-semantic-change.wit.stderr
@@ -0,0 +1,6 @@
+error: updating metadata for section component-type
+
+Caused by:
+    0: failed to merge worlds from two documents
+    1: failed to add export `a:b/i1`
+    2: export `a:b/i2` has a type `t` which could change meaning if this world export were added
diff --git a/tests/cli/world-merging-import-conflict.wit b/tests/cli/world-merging-import-conflict.wit
new file mode 100644
index 00000000..410e7621
--- /dev/null
+++ b/tests/cli/world-merging-import-conflict.wit
@@ -0,0 +1,11 @@
+// FAIL: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+world into {
+  import a: interface {}
+}
+
+world %from {
+  import a: func();
+}
diff --git a/tests/cli/world-merging-import-conflict.wit.stderr b/tests/cli/world-merging-import-conflict.wit.stderr
new file mode 100644
index 00000000..73a1ab86
--- /dev/null
+++ b/tests/cli/world-merging-import-conflict.wit.stderr
@@ -0,0 +1,6 @@
+error: updating metadata for section component-type
+
+Caused by:
+    0: failed to merge worlds from two documents
+    1: failed to merge world import a
+    2: different kinds of items
diff --git a/tests/cli/world-merging-same-imports.wit b/tests/cli/world-merging-same-imports.wit
new file mode 100644
index 00000000..8b477f1c
--- /dev/null
+++ b/tests/cli/world-merging-same-imports.wit
@@ -0,0 +1,21 @@
+// RUN: component embed --dummy --world into % | component embed --world %from % | component wit
+
+package a:b;
+
+interface b {
+  type t = u32;
+}
+
+world into {
+  import a: interface {}
+  import b;
+  import c: func();
+  use b.{t};
+}
+
+world %from {
+  import a: interface {}
+  import b;
+  import c: func();
+  use b.{t};
+}
diff --git a/tests/cli/world-merging-same-imports.wit.stdout b/tests/cli/world-merging-same-imports.wit.stdout
new file mode 100644
index 00000000..2b10e904
--- /dev/null
+++ b/tests/cli/world-merging-same-imports.wit.stdout
@@ -0,0 +1,31 @@
+package root:root {
+  world root {
+    import a: interface {
+    }
+    import a:b/b;
+    import c: func();
+    use a:b/b.{t};
+  }
+}
+
+
+package a:b {
+  interface b {
+    type t = u32;
+  }
+
+  world into {
+    import a: interface {
+    }
+    import b;
+    import c: func();
+    use b.{t};
+  }
+  world %from {
+    import a: interface {
+    }
+    import b;
+    import c: func();
+    use b.{t};
+  }
+}
diff --git a/tests/cli/world-merging.wit b/tests/cli/world-merging.wit
new file mode 100644
index 00000000..99abe407
--- /dev/null
+++ b/tests/cli/world-merging.wit
@@ -0,0 +1,22 @@
+// RUN: component embed --dummy --world foo % | component embed --world foo % | component wit
+
+// This test embeds this world twice and then ensures that merging the two
+// worlds together works (it's the same definition anyway).
+
+package a:b;
+
+interface x {
+  type t = u32;
+}
+
+world foo {
+  import x;
+  export x;
+
+  use x.{t};
+
+  import a: func() -> t;
+
+  export x: interface {}
+  export y: func();
+}
diff --git a/tests/cli/world-merging.wit.stdout b/tests/cli/world-merging.wit.stdout
new file mode 100644
index 00000000..dd928bfc
--- /dev/null
+++ b/tests/cli/world-merging.wit.stdout
@@ -0,0 +1,30 @@
+package root:root {
+  world root {
+    import a:b/x;
+    import a: func() -> t;
+    use a:b/x.{t};
+
+    export y: func();
+    export a:b/x;
+    export x: interface {
+    }
+  }
+}
+
+
+package a:b {
+  interface x {
+    type t = u32;
+  }
+
+  world foo {
+    import x;
+    import a: func() -> t;
+    use x.{t};
+
+    export y: func();
+    export x;
+    export x: interface {
+    }
+  }
+}