Repository: bytecodealliance/wasm-tools
Pull request #1201 (2023-09-14 08:48:39)

Added the ability to add features when decoding Wasm with wit-component
Added the ability to add features when decoding Wasm with wit-component
This is something I needed to get the WASM Analyzer working with arbitrary Wasm files as some Wasm files had features enabled that wasn't supported with the default features used in wit-component.

silesmo: @peterhuene 
alexcrichton: Thanks! For this use case though I think it'd be ok to unconditionally enable all the various features. This isn't a first-class validation step for the core wasm itself and ideally this wouldn't validate core wasm at all. Given how internal it is though I think it's ok to avoid the need for configuration here.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9047ce01..e7c6686a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2397,6 +2397,8 @@ dependencies = [
  "pulldown-cmark",
  "rayon",
  "semver",
+ "serde",
+ "serde_json",
  "unicode-xid",
  "url",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 90738fed..3a243ede 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -170,6 +170,7 @@ component = [
   'wast',
   'wasm-encoder',
   'dep:wasmparser',
+  'serde_json',
 ]
 metadata = ['dep:wasmparser', 'wasm-metadata', 'serde_json']
 wit-smith = ['dep:wit-smith', 'arbitrary']
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index 391c7b3f..0d79b2e6 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -523,7 +523,7 @@ pub enum Instruction<'a> {
     RefAsNonNull,
 
     // GC types instructions.
-    I31New,
+    RefI31,
     I31GetS,
     I31GetU,
 
@@ -917,7 +917,7 @@ impl Encode for Instruction<'_> {
                 l.encode(sink);
             }
             Instruction::BrOnNull(l) => {
-                sink.push(0xD4);
+                sink.push(0xD5);
                 l.encode(sink);
             }
             Instruction::BrOnNonNull(l) => {
@@ -1318,20 +1318,20 @@ impl Encode for Instruction<'_> {
                 sink.push(0xd2);
                 f.encode(sink);
             }
-            Instruction::RefAsNonNull => sink.push(0xD3),
+            Instruction::RefAsNonNull => sink.push(0xd4),
 
             // GC instructions.
-            Instruction::I31New => {
+            Instruction::RefI31 => {
                 sink.push(0xfb);
-                sink.push(0x20)
+                sink.push(0x1c)
             }
             Instruction::I31GetS => {
                 sink.push(0xfb);
-                sink.push(0x21)
+                sink.push(0x1d)
             }
             Instruction::I31GetU => {
                 sink.push(0xfb);
-                sink.push(0x22)
+                sink.push(0x1e)
             }
 
             // Bulk memory instructions.
diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs
index 5c931f6c..acc11b1e 100644
--- a/crates/wasm-encoder/src/core/types.rs
+++ b/crates/wasm-encoder/src/core/types.rs
@@ -124,8 +124,8 @@ impl ValType {
 impl Encode for StorageType {
     fn encode(&self, sink: &mut Vec<u8>) {
         match self {
-            StorageType::I8 => sink.push(0x7A),
-            StorageType::I16 => sink.push(0x79),
+            StorageType::I8 => sink.push(0x78),
+            StorageType::I16 => sink.push(0x77),
             StorageType::Val(vt) => vt.encode(sink),
         }
     }
@@ -183,9 +183,9 @@ impl Encode for RefType {
         }
 
         if self.nullable {
-            sink.push(0x6C);
+            sink.push(0x63);
         } else {
-            sink.push(0x6B);
+            sink.push(0x64);
         }
         self.heap_type.encode(sink);
     }
@@ -231,13 +231,13 @@ impl Encode for HeapType {
             HeapType::Func => sink.push(0x70),
             HeapType::Extern => sink.push(0x6F),
             HeapType::Any => sink.push(0x6E),
-            HeapType::None => sink.push(0x65),
-            HeapType::NoExtern => sink.push(0x69),
-            HeapType::NoFunc => sink.push(0x68),
+            HeapType::None => sink.push(0x71),
+            HeapType::NoExtern => sink.push(0x72),
+            HeapType::NoFunc => sink.push(0x73),
             HeapType::Eq => sink.push(0x6D),
-            HeapType::Struct => sink.push(0x67),
-            HeapType::Array => sink.push(0x66),
-            HeapType::I31 => sink.push(0x6A),
+            HeapType::Struct => sink.push(0x6B),
+            HeapType::Array => sink.push(0x6A),
+            HeapType::I31 => sink.push(0x6C),
             // Note that this is encoded as a signed type rather than unsigned
             // as it's decoded as an s33
             HeapType::Indexed(i) => i64::from(*i).encode(sink),
diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs
index 9fcfca89..6e535491 100644
--- a/crates/wasmparser/src/binary_reader.rs
+++ b/crates/wasmparser/src/binary_reader.rs
@@ -978,8 +978,8 @@ impl<'a> BinaryReader<'a> {
             0xd0 => visitor.visit_ref_null(self.read()?),
             0xd1 => visitor.visit_ref_is_null(),
             0xd2 => visitor.visit_ref_func(self.read_var_u32()?),
-            0xd3 => visitor.visit_ref_as_non_null(),
-            0xd4 => visitor.visit_br_on_null(self.read_var_u32()?),
+            0xd4 => visitor.visit_ref_as_non_null(),
+            0xd5 => visitor.visit_br_on_null(self.read_var_u32()?),
             0xd6 => visitor.visit_br_on_non_null(self.read_var_u32()?),
 
             0xfb => self.visit_0xfb_operator(pos, visitor)?,
@@ -1001,9 +1001,9 @@ impl<'a> BinaryReader<'a> {
     {
         let code = self.read_var_u32()?;
         Ok(match code {
-            0x20 => visitor.visit_i31_new(),
-            0x21 => visitor.visit_i31_get_s(),
-            0x22 => visitor.visit_i31_get_u(),
+            0x1c => visitor.visit_ref_i31(),
+            0x1d => visitor.visit_i31_get_s(),
+            0x1e => visitor.visit_i31_get_u(),
 
             _ => bail!(pos, "unknown 0xfb subopcode: 0x{code:x}"),
         })
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index bc1516c0..19416723 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -316,7 +316,7 @@ macro_rules! for_each_operator {
             // 0xFB prefixed operators
             // Garbage Collection
             // http://github.com/WebAssembly/gc
-            @gc I31New => visit_i31_new
+            @gc RefI31 => visit_ref_i31
             @gc I31GetS => visit_i31_get_s
             @gc I31GetU => visit_i31_get_u
 
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index fad9d426..bf3aa4a9 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -99,8 +99,8 @@ impl ValType {
 
     pub(crate) fn is_valtype_byte(byte: u8) -> bool {
         match byte {
-            0x7F | 0x7E | 0x7D | 0x7C | 0x7B | 0x70 | 0x6F | 0x6B | 0x6C | 0x6E | 0x65 | 0x69
-            | 0x68 | 0x6D | 0x67 | 0x66 | 0x6A => true,
+            0x7F | 0x7E | 0x7D | 0x7C | 0x7B | 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72
+            | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C => true,
             _ => false,
         }
     }
@@ -132,11 +132,11 @@ impl Inherits for ValType {
 impl<'a> FromReader<'a> for StorageType {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
         match reader.peek()? {
-            0x7A => {
+            0x78 => {
                 reader.position += 1;
                 Ok(StorageType::I8)
             }
-            0x79 => {
+            0x77 => {
                 reader.position += 1;
                 Ok(StorageType::I16)
             }
@@ -168,7 +168,7 @@ impl<'a> FromReader<'a> for ValType {
                 reader.position += 1;
                 Ok(ValType::V128)
             }
-            0x70 | 0x6F | 0x6B | 0x6C | 0x6E | 0x65 | 0x69 | 0x68 | 0x6D | 0x67 | 0x66 | 0x6A => {
+            0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C => {
                 Ok(ValType::Ref(reader.read()?))
             }
             _ => bail!(reader.original_position(), "invalid value type"),
@@ -578,15 +578,15 @@ impl<'a> FromReader<'a> for RefType {
             0x70 => Ok(RefType::FUNC.nullable()),
             0x6F => Ok(RefType::EXTERN.nullable()),
             0x6E => Ok(RefType::ANY.nullable()),
-            0x65 => Ok(RefType::NONE.nullable()),
-            0x69 => Ok(RefType::NOEXTERN.nullable()),
-            0x68 => Ok(RefType::NOFUNC.nullable()),
+            0x71 => Ok(RefType::NONE.nullable()),
+            0x72 => Ok(RefType::NOEXTERN.nullable()),
+            0x73 => Ok(RefType::NOFUNC.nullable()),
             0x6D => Ok(RefType::EQ.nullable()),
-            0x67 => Ok(RefType::STRUCT.nullable()),
-            0x66 => Ok(RefType::ARRAY.nullable()),
-            0x6A => Ok(RefType::I31.nullable()),
-            byte @ (0x6B | 0x6C) => {
-                let nullable = byte == 0x6C;
+            0x6B => Ok(RefType::STRUCT.nullable()),
+            0x6A => Ok(RefType::ARRAY.nullable()),
+            0x6C => Ok(RefType::I31.nullable()),
+            byte @ (0x63 | 0x64) => {
+                let nullable = byte == 0x63;
                 let pos = reader.original_position();
                 RefType::new(nullable, reader.read()?)
                     .ok_or_else(|| crate::BinaryReaderError::new("type index too large", pos))
@@ -754,15 +754,15 @@ impl<'a> FromReader<'a> for HeapType {
                 reader.position += 1;
                 Ok(HeapType::Any)
             }
-            0x65 => {
+            0x71 => {
                 reader.position += 1;
                 Ok(HeapType::None)
             }
-            0x69 => {
+            0x72 => {
                 reader.position += 1;
                 Ok(HeapType::NoExtern)
             }
-            0x68 => {
+            0x73 => {
                 reader.position += 1;
                 Ok(HeapType::NoFunc)
             }
@@ -770,15 +770,15 @@ impl<'a> FromReader<'a> for HeapType {
                 reader.position += 1;
                 Ok(HeapType::Eq)
             }
-            0x67 => {
+            0x6B => {
                 reader.position += 1;
                 Ok(HeapType::Struct)
             }
-            0x66 => {
+            0x6A => {
                 reader.position += 1;
                 Ok(HeapType::Array)
             }
-            0x6A => {
+            0x6C => {
                 reader.position += 1;
                 Ok(HeapType::I31)
             }
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 59df3136..f6869c77 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -252,6 +252,32 @@ pub struct WasmFeatures {
 }
 
 impl WasmFeatures {
+    /// Returns [`WasmFeatures`] with all features enabled.
+    pub fn all() -> Self {
+        WasmFeatures {
+            mutable_global: true,
+            saturating_float_to_int: true,
+            sign_extension: true,
+            reference_types: true,
+            multi_value: true,
+            bulk_memory: true,
+            simd: true,
+            relaxed_simd: true,
+            threads: true,
+            tail_call: true,
+            floats: true,
+            multi_memory: true,
+            exceptions: true,
+            memory64: true,
+            extended_const: true,
+            component_model: true,
+            function_references: true,
+            memory_control: true,
+            gc: true,
+            component_model_values: true,
+        }
+    }
+
     /// NOTE: This only checks that the value type corresponds to the feature set!!
     ///
     /// To check that reference types are valid, we need access to the module
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 4d6bcf72..1dc1b9b6 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -3338,7 +3338,7 @@ where
         self.pop_operand(Some(ValType::I32))?;
         Ok(())
     }
-    fn visit_i31_new(&mut self) -> Self::Output {
+    fn visit_ref_i31(&mut self) -> Self::Output {
         self.pop_operand(Some(ValType::I32))?;
         self.push_operand(ValType::Ref(RefType::I31))
     }
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index 08b42529..df4c46d8 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -861,7 +861,7 @@ macro_rules! define_visit {
     (name I16x8RelaxedQ15mulrS) => ("i16x8.relaxed_q15mulr_s");
     (name I16x8RelaxedDotI8x16I7x16S) => ("i16x8.relaxed_dot_i8x16_i7x16_s");
     (name I32x4RelaxedDotI8x16I7x16AddS) => ("i32x4.relaxed_dot_i8x16_i7x16_add_s");
-    (name I31New) => ("i31.new");
+    (name RefI31) => ("ref.i31");
     (name I31GetS) => ("i31.get_s");
     (name I31GetU) => ("i31.get_u");
 }
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 734e7b47..5508da6b 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -248,12 +248,12 @@ impl<'a> Encode for HeapType<'a> {
             HeapType::Extern => e.push(0x6f),
             HeapType::Any => e.push(0x6e),
             HeapType::Eq => e.push(0x6d),
-            HeapType::Struct => e.push(0x67),
-            HeapType::Array => e.push(0x66),
-            HeapType::I31 => e.push(0x6a),
-            HeapType::NoFunc => e.push(0x68),
-            HeapType::NoExtern => e.push(0x69),
-            HeapType::None => e.push(0x65),
+            HeapType::Struct => e.push(0x6b),
+            HeapType::Array => e.push(0x6a),
+            HeapType::I31 => e.push(0x6c),
+            HeapType::NoFunc => e.push(0x73),
+            HeapType::NoExtern => e.push(0x72),
+            HeapType::None => e.push(0x71),
             // Note that this is encoded as a signed leb128 so be sure to cast
             // to an i64 first
             HeapType::Index(Index::Num(n, _)) => i64::from(*n).encode(e),
@@ -286,34 +286,34 @@ impl<'a> Encode for RefType<'a> {
             RefType {
                 nullable: true,
                 heap: HeapType::Struct,
-            } => e.push(0x67),
+            } => e.push(0x6b),
             // The 'i31ref' binary abbreviation
             RefType {
                 nullable: true,
                 heap: HeapType::I31,
-            } => e.push(0x6a),
+            } => e.push(0x6c),
             // The 'nullfuncref' binary abbreviation
             RefType {
                 nullable: true,
                 heap: HeapType::NoFunc,
-            } => e.push(0x68),
+            } => e.push(0x73),
             // The 'nullexternref' binary abbreviation
             RefType {
                 nullable: true,
                 heap: HeapType::NoExtern,
-            } => e.push(0x69),
+            } => e.push(0x72),
             // The 'nullref' binary abbreviation
             RefType {
                 nullable: true,
                 heap: HeapType::None,
-            } => e.push(0x65),
+            } => e.push(0x71),
 
             // Generic 'ref null <heaptype>' encoding
             RefType {
                 nullable: true,
                 heap,
             } => {
-                e.push(0x6c);
+                e.push(0x63);
                 heap.encode(e);
             }
             // Generic 'ref <heaptype>' encoding
@@ -321,7 +321,7 @@ impl<'a> Encode for RefType<'a> {
                 nullable: false,
                 heap,
             } => {
-                e.push(0x6b);
+                e.push(0x64);
                 heap.encode(e);
             }
         }
@@ -331,8 +331,8 @@ impl<'a> Encode for RefType<'a> {
 impl<'a> Encode for StorageType<'a> {
     fn encode(&self, e: &mut Vec<u8>) {
         match self {
-            StorageType::I8 => e.push(0x7a),
-            StorageType::I16 => e.push(0x79),
+            StorageType::I8 => e.push(0x78),
+            StorageType::I16 => e.push(0x77),
             StorageType::Val(ty) => {
                 ty.encode(e);
             }
@@ -1181,9 +1181,9 @@ impl Encode for RefTest<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
         e.push(0xfb);
         if self.r#type.nullable {
-            e.push(0x48);
+            e.push(0x15);
         } else {
-            e.push(0x40);
+            e.push(0x14);
         }
         self.r#type.heap.encode(e);
     }
@@ -1193,9 +1193,9 @@ impl Encode for RefCast<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
         e.push(0xfb);
         if self.r#type.nullable {
-            e.push(0x49);
+            e.push(0x17);
         } else {
-            e.push(0x41);
+            e.push(0x16);
         }
         self.r#type.heap.encode(e);
     }
@@ -1215,7 +1215,7 @@ fn br_on_cast_flags(from_nullable: bool, to_nullable: bool) -> u8 {
 impl Encode for BrOnCast<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
         e.push(0xfb);
-        e.push(0x4e);
+        e.push(0x18);
         e.push(br_on_cast_flags(
             self.from_type.nullable,
             self.to_type.nullable,
@@ -1229,7 +1229,7 @@ impl Encode for BrOnCast<'_> {
 impl Encode for BrOnCastFail<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
         e.push(0xfb);
-        e.push(0x4f);
+        e.push(0x19);
         e.push(br_on_cast_flags(
             self.from_type.nullable,
             self.to_type.nullable,
diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs
index dc5e8bb5..951c5ff7 100644
--- a/crates/wast/src/core/expr.rs
+++ b/crates/wast/src/core/expr.rs
@@ -582,41 +582,41 @@ instructions! {
         RefFunc(Index<'a>) : [0xd2] : "ref.func",
 
         // function-references proposal
-        RefAsNonNull : [0xd3] : "ref.as_non_null",
-        BrOnNull(Index<'a>) : [0xd4] : "br_on_null",
+        RefAsNonNull : [0xd4] : "ref.as_non_null",
+        BrOnNull(Index<'a>) : [0xd5] : "br_on_null",
         BrOnNonNull(Index<'a>) : [0xd6] : "br_on_non_null",
 
         // gc proposal: eqref
-        RefEq : [0xd5] : "ref.eq",
+        RefEq : [0xd3] : "ref.eq",
 
         // gc proposal: struct
-        StructNew(Index<'a>) : [0xfb, 0x07] : "struct.new",
-        StructNewDefault(Index<'a>) : [0xfb, 0x08] : "struct.new_default",
-        StructGet(StructAccess<'a>) : [0xfb, 0x03] : "struct.get",
-        StructGetS(StructAccess<'a>) : [0xfb, 0x04] : "struct.get_s",
-        StructGetU(StructAccess<'a>) : [0xfb, 0x05] : "struct.get_u",
-        StructSet(StructAccess<'a>) : [0xfb, 0x06] : "struct.set",
+        StructNew(Index<'a>) : [0xfb, 0x00] : "struct.new",
+        StructNewDefault(Index<'a>) : [0xfb, 0x01] : "struct.new_default",
+        StructGet(StructAccess<'a>) : [0xfb, 0x02] : "struct.get",
+        StructGetS(StructAccess<'a>) : [0xfb, 0x03] : "struct.get_s",
+        StructGetU(StructAccess<'a>) : [0xfb, 0x04] : "struct.get_u",
+        StructSet(StructAccess<'a>) : [0xfb, 0x05] : "struct.set",
 
         // gc proposal: array
-        ArrayNew(Index<'a>) : [0xfb, 0x1b] : "array.new",
-        ArrayNewDefault(Index<'a>) : [0xfb, 0x1c] : "array.new_default",
-        ArrayNewFixed(ArrayNewFixed<'a>) : [0xfb, 0x1a] : "array.new_fixed",
-        ArrayNewData(ArrayNewData<'a>) : [0xfb, 0x1d] : "array.new_data",
-        ArrayNewElem(ArrayNewElem<'a>) : [0xfb, 0x1f] : "array.new_elem",
-        ArrayGet(Index<'a>) : [0xfb, 0x13] : "array.get",
-        ArrayGetS(Index<'a>) : [0xfb, 0x14] : "array.get_s",
-        ArrayGetU(Index<'a>) : [0xfb, 0x15] : "array.get_u",
-        ArraySet(Index<'a>) : [0xfb, 0x16] : "array.set",
-        ArrayLen : [0xfb, 0x19] : "array.len",
-        ArrayFill(ArrayFill<'a>) : [0xfb, 0x0f] : "array.fill",
-        ArrayCopy(ArrayCopy<'a>) : [0xfb, 0x18] : "array.copy",
-        ArrayInitData(ArrayInit<'a>) : [0xfb, 0x54] : "array.init_data",
-        ArrayInitElem(ArrayInit<'a>) : [0xfb, 0x55] : "array.init_elem",
+        ArrayNew(Index<'a>) : [0xfb, 0x06] : "array.new",
+        ArrayNewDefault(Index<'a>) : [0xfb, 0x07] : "array.new_default",
+        ArrayNewFixed(ArrayNewFixed<'a>) : [0xfb, 0x08] : "array.new_fixed",
+        ArrayNewData(ArrayNewData<'a>) : [0xfb, 0x09] : "array.new_data",
+        ArrayNewElem(ArrayNewElem<'a>) : [0xfb, 0x0a] : "array.new_elem",
+        ArrayGet(Index<'a>) : [0xfb, 0x0b] : "array.get",
+        ArrayGetS(Index<'a>) : [0xfb, 0x0c] : "array.get_s",
+        ArrayGetU(Index<'a>) : [0xfb, 0x0d] : "array.get_u",
+        ArraySet(Index<'a>) : [0xfb, 0x0e] : "array.set",
+        ArrayLen : [0xfb, 0x0f] : "array.len",
+        ArrayFill(ArrayFill<'a>) : [0xfb, 0x10] : "array.fill",
+        ArrayCopy(ArrayCopy<'a>) : [0xfb, 0x11] : "array.copy",
+        ArrayInitData(ArrayInit<'a>) : [0xfb, 0x12] : "array.init_data",
+        ArrayInitElem(ArrayInit<'a>) : [0xfb, 0x13] : "array.init_elem",
 
         // gc proposal, i31
-        I31New : [0xfb, 0x20] : "i31.new",
-        I31GetS : [0xfb, 0x21] : "i31.get_s",
-        I31GetU : [0xfb, 0x22] : "i31.get_u",
+        RefI31 : [0xfb, 0x1c] : "ref.i31",
+        I31GetS : [0xfb, 0x1d] : "i31.get_s",
+        I31GetU : [0xfb, 0x1e] : "i31.get_u",
 
         // gc proposal, concrete casting
         RefTest(RefTest<'a>) : [] : "ref.test",
@@ -625,8 +625,8 @@ instructions! {
         BrOnCastFail(Box<BrOnCastFail<'a>>) : [] : "br_on_cast_fail",
 
         // gc proposal extern/any coercion operations
-        ExternInternalize : [0xfb, 0x70] : "extern.internalize",
-        ExternExternalize : [0xfb, 0x71] : "extern.externalize",
+        ExternInternalize : [0xfb, 0x1a] : "extern.internalize",
+        ExternExternalize : [0xfb, 0x1b] : "extern.externalize",
 
         I32Const(i32) : [0x41] : "i32.const",
         I64Const(i64) : [0x42] : "i64.const",
diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs
index cee84d0b..8fc5ca21 100644
--- a/crates/wit-component/src/decoding.rs
+++ b/crates/wit-component/src/decoding.rs
@@ -30,10 +30,7 @@ enum Extern<'a> {
 impl<'a> ComponentInfo<'a> {
     /// Creates a new component info by parsing the given WebAssembly component bytes.
     fn new(bytes: &'a [u8]) -> Result<Self> {
-        let mut validator = Validator::new_with_features(WasmFeatures {
-            component_model: true,
-            ..Default::default()
-        });
+        let mut validator = Validator::new_with_features(WasmFeatures::all());
         let mut externs = Vec::new();
         let mut depth = 1;
         let mut types = None;
diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml
index f426267f..5b3b5a95 100644
--- a/crates/wit-parser/Cargo.toml
+++ b/crates/wit-parser/Cargo.toml
@@ -15,12 +15,14 @@ Tooling for parsing `*.wit` files and working with their contents.
 [dependencies]
 id-arena = "2"
 anyhow = { workspace = true }
-indexmap = { workspace = true }
+indexmap = { workspace = true, features = ["serde"] }
 pulldown-cmark = { version = "0.9.3", default-features = false }
 unicode-xid = "0.2.2"
 log = { workspace = true }
 url = { workspace = true }
 semver = { workspace = true }
+serde.workspace = true
+serde_json = "1.0.105"
 
 [dev-dependencies]
 rayon = "1"
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index 57a1455e..b1bb6370 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
 use id_arena::{Arena, Id};
 use indexmap::IndexMap;
 use semver::Version;
+use serde::Serialize;
 use std::borrow::Cow;
 use std::fmt;
 use std::path::Path;
@@ -16,6 +17,10 @@ mod resolve;
 pub use resolve::{Package, PackageId, Remap, Resolve};
 mod live;
 pub use live::LiveTypes;
+mod serde_;
+use serde_::{
+    serialize_anon_result, serialize_id, serialize_id_map, serialize_optional_id, serialize_params,
+};
 
 /// Checks if the given string is a legal identifier in wit.
 pub fn validate_id(s: &str) -> Result<()> {
@@ -101,9 +106,12 @@ pub struct UnresolvedPackage {
     required_resource_types: Vec<(TypeId, Span)>,
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum AstItem {
+    #[serde(serialize_with = "serialize_id")]
     Interface(InterfaceId),
+    #[serde(serialize_with = "serialize_id")]
     World(WorldId),
 }
 
@@ -112,7 +120,8 @@ pub enum AstItem {
 ///
 /// This is directly encoded as an "ID" in the binary component representation
 /// with an interfaced tacked on as well.
-#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize)]
+#[serde(into = "String")]
 pub struct PackageName {
     /// A namespace such as `wasi` in `wasi:foo/bar`
     pub namespace: String,
@@ -122,6 +131,12 @@ pub struct PackageName {
     pub version: Option<Version>,
 }
 
+impl From<PackageName> for String {
+    fn from(name: PackageName) -> String {
+        name.to_string()
+    }
+}
+
 impl PackageName {
     /// Returns the ID that this package name would assign the `interface` name
     /// specified.
@@ -231,12 +246,13 @@ impl UnresolvedPackage {
     }
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
 pub struct World {
     /// The WIT identifier name of this world.
     pub name: String,
 
     /// Documentation associated with this world declaration.
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
 
     /// All imported items into this interface, both worlds and functions.
@@ -246,12 +262,15 @@ pub struct World {
     pub exports: IndexMap<WorldKey, WorldItem>,
 
     /// The package that owns this world.
+    #[serde(serialize_with = "serialize_optional_id")]
     pub package: Option<PackageId>,
 
     /// All the included worlds from this world. Empty if this is fully resolved
+    #[serde(skip)]
     pub includes: Vec<WorldId>,
 
     /// All the included worlds names. Empty if this is fully resolved
+    #[serde(skip)]
     pub include_names: Vec<Vec<IncludeName>>,
 }
 
@@ -266,7 +285,8 @@ pub struct IncludeName {
 
 /// The key to the import/export maps of a world. Either a kebab-name or a
 /// unique interface.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
+#[serde(into = "String")]
 pub enum WorldKey {
     /// A kebab-name.
     Name(String),
@@ -274,6 +294,15 @@ pub enum WorldKey {
     Interface(InterfaceId),
 }
 
+impl From<WorldKey> for String {
+    fn from(key: WorldKey) -> String {
+        match key {
+            WorldKey::Name(name) => name,
+            WorldKey::Interface(id) => format!("interface-{}", id.index()),
+        }
+    }
+}
+
 impl WorldKey {
     /// Asserts that this is `WorldKey::Name` and returns the name.
     #[track_caller]
@@ -285,10 +314,12 @@ impl WorldKey {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum WorldItem {
     /// An interface is being imported or exported from a world, indicating that
     /// it's a namespace of functions.
+    #[serde(serialize_with = "serialize_id")]
     Interface(InterfaceId),
 
     /// A function is being directly imported or exported from this world.
@@ -297,10 +328,11 @@ pub enum WorldItem {
     /// A type is being exported from this world.
     ///
     /// Note that types are never imported into worlds at this time.
+    #[serde(serialize_with = "serialize_id")]
     Type(TypeId),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
 pub struct Interface {
     /// Optionally listed name of this interface.
     ///
@@ -308,30 +340,35 @@ pub struct Interface {
     pub name: Option<String>,
 
     /// Documentation associated with this interface.
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
 
     /// Exported types from this interface.
     ///
     /// Export names are listed within the types themselves. Note that the
     /// export name here matches the name listed in the `TypeDef`.
+    #[serde(serialize_with = "serialize_id_map")]
     pub types: IndexMap<String, TypeId>,
 
     /// Exported functions from this interface.
     pub functions: IndexMap<String, Function>,
 
     /// The package that owns this interface.
+    #[serde(serialize_with = "serialize_optional_id")]
     pub package: Option<PackageId>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct TypeDef {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub kind: TypeDefKind,
     pub name: Option<String>,
     pub owner: TypeOwner,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum TypeDefKind {
     Record(Record),
     Resource,
@@ -379,20 +416,26 @@ impl TypeDefKind {
     }
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum TypeOwner {
     /// This type was defined within a `world` block.
+    #[serde(serialize_with = "serialize_id")]
     World(WorldId),
     /// This type was defined within an `interface` block.
+    #[serde(serialize_with = "serialize_id")]
     Interface(InterfaceId),
     /// This type wasn't inherently defined anywhere, such as a `list<T>`, which
     /// doesn't need an owner.
     None,
 }
 
-#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum Handle {
+    #[serde(serialize_with = "serialize_id")]
     Own(TypeId),
+    #[serde(serialize_with = "serialize_id")]
     Borrow(TypeId),
 }
 
@@ -414,7 +457,7 @@ pub enum Type {
     Id(TypeId),
 }
 
-#[derive(PartialEq, Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum Int {
     U8,
     U16,
@@ -422,30 +465,33 @@ pub enum Int {
     U64,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Record {
     pub fields: Vec<Field>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Field {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub name: String,
+    #[serde(rename = "type")]
     pub ty: Type,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Flags {
     pub flags: Vec<Flag>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Flag {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub name: String,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq)]
 pub enum FlagsRepr {
     U8,
     U16,
@@ -473,20 +519,22 @@ impl FlagsRepr {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Tuple {
     pub types: Vec<Type>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Variant {
     pub cases: Vec<Case>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Case {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub name: String,
+    #[serde(rename = "type")]
     pub ty: Option<Type>,
 }
 
@@ -501,13 +549,14 @@ impl Variant {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Enum {
     pub cases: Vec<EnumCase>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct EnumCase {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub name: String,
 }
@@ -523,28 +572,37 @@ impl Enum {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Result_ {
     pub ok: Option<Type>,
     pub err: Option<Type>,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct Stream {
     pub element: Option<Type>,
     pub end: Option<Type>,
 }
 
-#[derive(Clone, Default, Debug, PartialEq, Eq)]
+#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize)]
 pub struct Docs {
     pub contents: Option<String>,
 }
 
+impl Docs {
+    pub fn is_empty(&self) -> bool {
+        self.contents.is_none()
+    }
+}
+
 pub type Params = Vec<(String, Type)>;
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
+#[serde(untagged)]
 pub enum Results {
+    #[serde(serialize_with = "serialize_params")]
     Named(Params),
+    #[serde(serialize_with = "serialize_anon_result")]
     Anon(Type),
 }
 
@@ -607,20 +665,26 @@ impl Results {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
 pub struct Function {
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
     pub name: String,
     pub kind: FunctionKind,
+    #[serde(serialize_with = "serialize_params")]
     pub params: Params,
     pub results: Results,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "lowercase")]
 pub enum FunctionKind {
     Freestanding,
+    #[serde(serialize_with = "serialize_id")]
     Method(TypeId),
+    #[serde(serialize_with = "serialize_id")]
     Static(TypeId),
+    #[serde(serialize_with = "serialize_id")]
     Constructor(TypeId),
 }
 
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index 397b26ae..2bd16e58 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -1,5 +1,6 @@
 use crate::ast::lex::Span;
 use crate::ast::{parse_use_path, AstUsePath};
+use crate::serde_::{serialize_arena, serialize_id_map};
 use crate::{
     AstItem, Docs, Error, Function, FunctionKind, Handle, IncludeName, Interface, InterfaceId,
     PackageName, Results, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World,
@@ -8,6 +9,7 @@ use crate::{
 use anyhow::{anyhow, bail, Context, Result};
 use id_arena::{Arena, Id};
 use indexmap::{IndexMap, IndexSet};
+use serde::Serialize;
 use std::collections::{BTreeMap, HashMap, HashSet};
 use std::mem;
 use std::path::{Path, PathBuf};
@@ -25,12 +27,17 @@ use std::path::{Path, PathBuf};
 ///
 /// Each item in a `Resolve` has a parent link to trace it back to the original
 /// package as necessary.
-#[derive(Default, Clone)]
+#[derive(Default, Clone, Serialize)]
 pub struct Resolve {
+    #[serde(serialize_with = "serialize_arena")]
     pub worlds: Arena<World>,
+    #[serde(serialize_with = "serialize_arena")]
     pub interfaces: Arena<Interface>,
+    #[serde(serialize_with = "serialize_arena")]
     pub types: Arena<TypeDef>,
+    #[serde(serialize_with = "serialize_arena")]
     pub packages: Arena<Package>,
+    #[serde(skip)]
     pub package_names: IndexMap<PackageName, PackageId>,
 }
 
@@ -39,19 +46,22 @@ pub struct Resolve {
 /// A package is a collection of interfaces and worlds. Packages additionally
 /// have a unique identifier that affects generated components and uniquely
 /// identifiers this particular package.
-#[derive(Clone)]
+#[derive(Clone, Serialize)]
 pub struct Package {
     /// A unique name corresponding to this package.
     pub name: PackageName,
 
     /// Documentation associated with this package.
+    #[serde(skip_serializing_if = "Docs::is_empty")]
     pub docs: Docs,
 
     /// All interfaces contained in this packaged, keyed by the interface's
     /// name.
+    #[serde(serialize_with = "serialize_id_map")]
     pub interfaces: IndexMap<String, InterfaceId>,
 
     /// All worlds contained in this package, keyed by the world's name.
+    #[serde(serialize_with = "serialize_id_map")]
     pub worlds: IndexMap<String, WorldId>,
 }
 
@@ -824,7 +834,7 @@ impl Remap {
     fn process_foreign_interfaces(
         &mut self,
         unresolved: &UnresolvedPackage,
-        interface_to_package: &HashMap<Id<Interface>, (&PackageName, &String, Span)>,
+        interface_to_package: &HashMap<InterfaceId, (&PackageName, &String, Span)>,
         resolve: &mut Resolve,
     ) -> Result<(), anyhow::Error> {
         for (unresolved_iface_id, unresolved_iface) in unresolved.interfaces.iter() {
@@ -872,7 +882,7 @@ impl Remap {
     fn process_foreign_worlds(
         &mut self,
         unresolved: &UnresolvedPackage,
-        world_to_package: &HashMap<Id<World>, (&PackageName, &String, Span)>,
+        world_to_package: &HashMap<WorldId, (&PackageName, &String, Span)>,
         resolve: &mut Resolve,
     ) -> Result<(), anyhow::Error> {
         for (unresolved_world_id, _) in unresolved.worlds.iter() {
diff --git a/crates/wit-parser/src/serde_.rs b/crates/wit-parser/src/serde_.rs
new file mode 100644
index 00000000..64497b3c
--- /dev/null
+++ b/crates/wit-parser/src/serde_.rs
@@ -0,0 +1,101 @@
+use crate::{Params, Type};
+use id_arena::{Arena, Id};
+use indexmap::IndexMap;
+use serde::ser::{SerializeMap, SerializeSeq, Serializer};
+use serde::Serialize;
+
+pub fn serialize_arena<T, S>(arena: &Arena<T>, serializer: S) -> Result<S::Ok, S::Error>
+where
+    T: Serialize,
+    S: Serializer,
+{
+    let mut seq = serializer.serialize_seq(Some(arena.len()))?;
+    for (_, item) in arena.iter() {
+        seq.serialize_element(&item)?;
+    }
+    seq.end()
+}
+
+pub fn serialize_id<T, S>(id: &Id<T>, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    serializer.serialize_u64(id.index() as u64)
+}
+
+pub fn serialize_optional_id<T, S>(id: &Option<Id<T>>, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    match id {
+        Some(id) => serialize_id(&id, serializer),
+        None => serializer.serialize_none(),
+    }
+}
+
+pub fn serialize_id_map<K, T, S>(map: &IndexMap<K, Id<T>>, serializer: S) -> Result<S::Ok, S::Error>
+where
+    K: Serialize,
+    S: Serializer,
+{
+    let mut s = serializer.serialize_map(Some(map.len()))?;
+    for (key, id) in map.iter() {
+        s.serialize_entry(key, &(id.index() as u64))?;
+    }
+    s.end()
+}
+
+impl Serialize for Type {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            Type::Bool => serializer.serialize_str("bool"),
+            Type::U8 => serializer.serialize_str("u8"),
+            Type::U16 => serializer.serialize_str("u16"),
+            Type::U32 => serializer.serialize_str("u32"),
+            Type::U64 => serializer.serialize_str("u64"),
+            Type::S8 => serializer.serialize_str("s8"),
+            Type::S16 => serializer.serialize_str("s16"),
+            Type::S32 => serializer.serialize_str("s32"),
+            Type::S64 => serializer.serialize_str("s64"),
+            Type::Float32 => serializer.serialize_str("float32"),
+            Type::Float64 => serializer.serialize_str("float64"),
+            Type::Char => serializer.serialize_str("char"),
+            Type::String => serializer.serialize_str("string"),
+            Type::Id(type_id) => serializer.serialize_u64(type_id.index() as u64),
+        }
+    }
+}
+
+pub fn serialize_anon_result<S>(typ: &Type, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let params: Params = vec![(String::default(), *typ)];
+    serialize_params(&params, serializer)
+}
+
+pub fn serialize_params<S>(params: &Params, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let mut seq = serializer.serialize_seq(Some(params.len()))?;
+    for (name, typ) in params.iter() {
+        let param = Param {
+            name: name.to_string(),
+            typ: *typ,
+        };
+        seq.serialize_element(&param)?;
+    }
+    seq.end()
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize)]
+struct Param {
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub name: String,
+    #[serde(rename = "type")]
+    pub typ: Type,
+}
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index 2757e5d4..edbdd097 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -416,6 +416,16 @@ pub struct WitOpts {
     /// options.
     #[clap(long)]
     skip_validation: bool,
+
+    /// Emit the WIT document as JSON instead of text.
+    #[clap(
+        short,
+        long,
+        conflicts_with = "wasm",
+        conflicts_with = "out_dir",
+        conflicts_with = "wat"
+    )]
+    json: bool,
 }
 
 impl WitOpts {
@@ -478,6 +488,10 @@ impl WitOpts {
 
         // 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.
+        if self.json {
+            self.emit_json(&decoded)?;
+            return Ok(());
+        }
         if self.wasm || self.wat {
             self.emit_wasm(&decoded)?;
         } else {
@@ -567,6 +581,16 @@ impl WitOpts {
 
         Ok(())
     }
+
+    fn emit_json(&self, decoded: &DecodedWasm) -> Result<()> {
+        assert!(!self.wasm && !self.wat);
+
+        let resolve = decoded.resolve();
+        let output = serde_json::to_string_pretty(&resolve)?;
+        self.output.output(Output::Json(&output))?;
+
+        Ok(())
+    }
 }
 
 /// Tool for verifying whether a component conforms to a world.
diff --git a/src/lib.rs b/src/lib.rs
index d0b74cab..0a558d28 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -94,6 +94,7 @@ pub struct OutputArg {
 pub enum Output<'a> {
     Wat(&'a str),
     Wasm { bytes: &'a [u8], wat: bool },
+    Json(&'a str),
 }
 
 impl InputOutput {
@@ -147,6 +148,7 @@ impl OutputArg {
                 }
                 Ok(())
             }
+            Output::Json(s) => self.output_str(s),
         }
     }
 


Test patch:
diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs
index c1c1132c..c8830391 100644
--- a/crates/wit-parser/tests/all.rs
+++ b/crates/wit-parser/tests/all.rs
@@ -124,20 +124,34 @@ impl Runner<'_> {
             }
         } else {
             result?;
+            // format json string to human readable
+            let json_result = serde_json::to_string_pretty(&resolve)?;
+            // "foo.wit" => "foo.wit.json"
+            self.read_or_write_to_file(test, &json_result, "json")?;
             return Ok(());
         };
 
         // "foo.wit" => "foo.wit.result"
         // "foo.wit.md" => "foo.wit.md.result"
+        self.read_or_write_to_file(test, &result, "result")?;
+        return Ok(());
+    }
+
+    fn read_or_write_to_file(
+        &mut self,
+        test: &Path,
+        result: &str,
+        extension: &str,
+    ) -> Result<(), anyhow::Error> {
         let result_file = if test.extension() == Some(OsStr::new("md"))
             && test
                 .file_stem()
                 .and_then(|path| Path::new(path).extension())
                 == Some(OsStr::new("wit"))
         {
-            test.with_extension("md.result")
+            test.with_extension(format!("md.{extension}"))
         } else {
-            test.with_extension("wit.result")
+            test.with_extension(format!("wit.{extension}"))
         };
         if env::var_os("BLESS").is_some() {
             fs::write(&result_file, result)?;
@@ -155,14 +169,14 @@ impl Runner<'_> {
             }
         }
         self.bump_ntests();
-        return Ok(());
-
-        fn normalize(s: &str) -> String {
-            s.replace('\\', "/").replace("\r\n", "\n")
-        }
+        Ok(())
     }
 
     fn bump_ntests(&self) {
         self.ntests.fetch_add(1, SeqCst);
     }
 }
+
+fn normalize(s: &str) -> String {
+    s.replace('\\', "/").replace("\r\n", "\n")
+}
diff --git a/crates/wit-parser/tests/ui/comments.wit.json b/crates/wit-parser/tests/ui/comments.wit.json
new file mode 100644
index 00000000..bd49b943
--- /dev/null
+++ b/crates/wit-parser/tests/ui/comments.wit.json
@@ -0,0 +1,46 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "x": 0,
+        "bar": 1
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "x",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "stream": {
+          "element": 0,
+          "end": null
+        }
+      },
+      "name": "bar",
+      "owner": {
+        "interface": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:comments",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/complex-include.wit.json b/crates/wit-parser/tests/ui/complex-include.wit.json
new file mode 100644
index 00000000..2c657bea
--- /dev/null
+++ b/crates/wit-parser/tests/ui/complex-include.wit.json
@@ -0,0 +1,168 @@
+{
+  "worlds": [
+    {
+      "name": "bar-a",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "baz-a",
+      "imports": {
+        "interface-2": {
+          "interface": 2
+        },
+        "interface-3": {
+          "interface": 3
+        }
+      },
+      "exports": {},
+      "package": 1
+    },
+    {
+      "name": "a",
+      "imports": {
+        "interface-4": {
+          "interface": 4
+        },
+        "interface-5": {
+          "interface": 5
+        }
+      },
+      "exports": {},
+      "package": 2
+    },
+    {
+      "name": "b",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "exports": {},
+      "package": 2
+    },
+    {
+      "name": "c",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "exports": {},
+      "package": 2
+    },
+    {
+      "name": "union-world",
+      "imports": {
+        "interface-4": {
+          "interface": 4
+        },
+        "interface-5": {
+          "interface": 5
+        },
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        },
+        "interface-2": {
+          "interface": 2
+        },
+        "interface-3": {
+          "interface": 3
+        }
+      },
+      "exports": {},
+      "package": 2
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "a",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "b",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "a",
+      "types": {},
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "b",
+      "types": {},
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "ai",
+      "types": {},
+      "functions": {},
+      "package": 2
+    },
+    {
+      "name": "bi",
+      "types": {},
+      "functions": {},
+      "package": 2
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "a": 0,
+        "b": 1
+      },
+      "worlds": {
+        "bar-a": 0
+      }
+    },
+    {
+      "name": "foo:baz",
+      "interfaces": {
+        "a": 2,
+        "b": 3
+      },
+      "worlds": {
+        "baz-a": 1
+      }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "ai": 4,
+        "bi": 5
+      },
+      "worlds": {
+        "a": 2,
+        "b": 3,
+        "c": 4,
+        "union-world": 5
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/cross-package-resource.wit.json b/crates/wit-parser/tests/ui/cross-package-resource.wit.json
new file mode 100644
index 00000000..437bc014
--- /dev/null
+++ b/crates/wit-parser/tests/ui/cross-package-resource.wit.json
@@ -0,0 +1,67 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "r": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "types": {
+        "r": 1,
+        "t": 2
+      },
+      "functions": {},
+      "package": 1
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "r",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 1
+        }
+      },
+      "name": "t",
+      "owner": {
+        "interface": 1
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "some:dep",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "foo": 1
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/diamond1.wit.json b/crates/wit-parser/tests/ui/diamond1.wit.json
new file mode 100644
index 00000000..8d7ce082
--- /dev/null
+++ b/crates/wit-parser/tests/ui/diamond1.wit.json
@@ -0,0 +1,55 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "exports": {},
+      "package": 2
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "types",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "types",
+      "types": {},
+      "functions": {},
+      "package": 1
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:dep1",
+      "interfaces": {
+        "types": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:dep2",
+      "interfaces": {
+        "types": 1
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:foo",
+      "interfaces": {},
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json
new file mode 100644
index 00000000..c3d2a8e1
--- /dev/null
+++ b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json
@@ -0,0 +1,107 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "foo": {
+          "interface": 2
+        },
+        "interface-1": {
+          "interface": 1
+        },
+        "bar": {
+          "interface": 3
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "shared1",
+      "types": {
+        "the-type": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "shared2",
+      "types": {
+        "the-type": 1
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {
+        "the-type": 2
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {
+        "the-type": 3
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 3
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:diamond",
+      "interfaces": {
+        "shared1": 0,
+        "shared2": 1
+      },
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/embedded.wit.md.json b/crates/wit-parser/tests/ui/embedded.wit.md.json
new file mode 100644
index 00000000..83a2a44a
--- /dev/null
+++ b/crates/wit-parser/tests/ui/embedded.wit.md.json
@@ -0,0 +1,40 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {},
+      "functions": {
+        "x": {
+          "name": "x",
+          "kind": "freestanding",
+          "params": [],
+          "results": []
+        },
+        "y": {
+          "name": "y",
+          "kind": "freestanding",
+          "params": [],
+          "results": []
+        },
+        "z": {
+          "name": "z",
+          "kind": "freestanding",
+          "params": [],
+          "results": []
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/empty.wit.json b/crates/wit-parser/tests/ui/empty.wit.json
new file mode 100644
index 00000000..092abe5e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/empty.wit.json
@@ -0,0 +1,12 @@
+{
+  "worlds": [],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:empty",
+      "interfaces": {},
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json
new file mode 100644
index 00000000..6a7cd7f1
--- /dev/null
+++ b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json
@@ -0,0 +1,380 @@
+{
+  "worlds": [
+    {
+      "name": "wasi",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {},
+      "package": 5
+    },
+    {
+      "name": "my-world",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 6
+    },
+    {
+      "name": "my-world2",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {
+        "interface-9": {
+          "interface": 9
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 6
+    },
+    {
+      "name": "bars-world",
+      "imports": {
+        "interface-4": {
+          "interface": 4
+        },
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {},
+      "package": 6
+    },
+    {
+      "name": "unionw-world",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        },
+        "interface-9": {
+          "interface": 9
+        }
+      },
+      "package": 6
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "other-interface",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "saas",
+      "types": {},
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "i",
+      "types": {},
+      "functions": {},
+      "package": 2
+    },
+    {
+      "name": "the-default",
+      "types": {
+        "some-type": 0
+      },
+      "functions": {},
+      "package": 3
+    },
+    {
+      "name": "the-default",
+      "types": {
+        "from-default": 1
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "some-interface",
+      "types": {
+        "another-type": 2
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "another-interface",
+      "types": {
+        "yet-another-type": 3
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "clocks",
+      "types": {
+        "timestamp": 4
+      },
+      "functions": {},
+      "package": 5
+    },
+    {
+      "name": "filesystem",
+      "types": {
+        "stat": 5
+      },
+      "functions": {},
+      "package": 5
+    },
+    {
+      "name": "foo",
+      "types": {
+        "timestamp": 6,
+        "stat": 7
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "bar",
+      "types": {
+        "from-default": 8,
+        "another-type": 9,
+        "yet-another-type": 10
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "use1",
+      "types": {
+        "some-type": 11
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "use2",
+      "types": {
+        "some-type": 12
+      },
+      "functions": {},
+      "package": 6
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": "string"
+      },
+      "name": "from-default",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "another-type",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": "u8"
+      },
+      "name": "yet-another-type",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": "u64"
+      },
+      "name": "timestamp",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "ino",
+              "type": "u64"
+            }
+          ]
+        }
+      },
+      "name": "stat",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 4
+      },
+      "name": "timestamp",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 5
+      },
+      "name": "stat",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "from-default",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 2
+      },
+      "name": "another-type",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 3
+      },
+      "name": "yet-another-type",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 11
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 12
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:another-pkg",
+      "interfaces": {
+        "other-interface": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:corp",
+      "interfaces": {
+        "saas": 1
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:different-pkg",
+      "interfaces": {
+        "i": 2
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:foreign-pkg",
+      "interfaces": {
+        "the-default": 3
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:some-pkg",
+      "interfaces": {
+        "the-default": 4,
+        "some-interface": 5,
+        "another-interface": 6
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:wasi",
+      "interfaces": {
+        "clocks": 7,
+        "filesystem": 8
+      },
+      "worlds": {
+        "wasi": 0
+      }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "foo": 9,
+        "bar": 10,
+        "use1": 11,
+        "use2": 12
+      },
+      "worlds": {
+        "my-world": 1,
+        "my-world2": 2,
+        "bars-world": 3,
+        "unionw-world": 4
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/foreign-deps.wit.json b/crates/wit-parser/tests/ui/foreign-deps.wit.json
new file mode 100644
index 00000000..d75bd5ad
--- /dev/null
+++ b/crates/wit-parser/tests/ui/foreign-deps.wit.json
@@ -0,0 +1,344 @@
+{
+  "worlds": [
+    {
+      "name": "my-world",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 6
+    },
+    {
+      "name": "my-world2",
+      "imports": {
+        "interface-8": {
+          "interface": 8
+        },
+        "interface-7": {
+          "interface": 7
+        }
+      },
+      "exports": {
+        "interface-9": {
+          "interface": 9
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 6
+    },
+    {
+      "name": "bars-world",
+      "imports": {
+        "interface-4": {
+          "interface": 4
+        },
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {},
+      "package": 6
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "other-interface",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "saas",
+      "types": {},
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "i",
+      "types": {},
+      "functions": {},
+      "package": 2
+    },
+    {
+      "name": "the-default",
+      "types": {
+        "some-type": 0
+      },
+      "functions": {},
+      "package": 3
+    },
+    {
+      "name": "the-default",
+      "types": {
+        "from-default": 1
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "some-interface",
+      "types": {
+        "another-type": 2
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "another-interface",
+      "types": {
+        "yet-another-type": 3
+      },
+      "functions": {},
+      "package": 4
+    },
+    {
+      "name": "clocks",
+      "types": {
+        "timestamp": 4
+      },
+      "functions": {},
+      "package": 5
+    },
+    {
+      "name": "filesystem",
+      "types": {
+        "stat": 5
+      },
+      "functions": {},
+      "package": 5
+    },
+    {
+      "name": "foo",
+      "types": {
+        "timestamp": 6,
+        "stat": 7
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "bar",
+      "types": {
+        "from-default": 8,
+        "another-type": 9,
+        "yet-another-type": 10
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "use1",
+      "types": {
+        "some-type": 11
+      },
+      "functions": {},
+      "package": 6
+    },
+    {
+      "name": "use2",
+      "types": {
+        "some-type": 12
+      },
+      "functions": {},
+      "package": 6
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": "string"
+      },
+      "name": "from-default",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "another-type",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": "u8"
+      },
+      "name": "yet-another-type",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": "u64"
+      },
+      "name": "timestamp",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "ino",
+              "type": "u64"
+            }
+          ]
+        }
+      },
+      "name": "stat",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 4
+      },
+      "name": "timestamp",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 5
+      },
+      "name": "stat",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "from-default",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 2
+      },
+      "name": "another-type",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 3
+      },
+      "name": "yet-another-type",
+      "owner": {
+        "interface": 10
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 11
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "some-type",
+      "owner": {
+        "interface": 12
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:another-pkg",
+      "interfaces": {
+        "other-interface": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:corp",
+      "interfaces": {
+        "saas": 1
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:different-pkg",
+      "interfaces": {
+        "i": 2
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:foreign-pkg",
+      "interfaces": {
+        "the-default": 3
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:some-pkg",
+      "interfaces": {
+        "the-default": 4,
+        "some-interface": 5,
+        "another-interface": 6
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:wasi",
+      "interfaces": {
+        "clocks": 7,
+        "filesystem": 8
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "foo": 9,
+        "bar": 10,
+        "use1": 11,
+        "use2": 12
+      },
+      "worlds": {
+        "my-world": 0,
+        "my-world2": 1,
+        "bars-world": 2
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/functions.wit.json b/crates/wit-parser/tests/ui/functions.wit.json
new file mode 100644
index 00000000..0fae5b8b
--- /dev/null
+++ b/crates/wit-parser/tests/ui/functions.wit.json
@@ -0,0 +1,166 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "functions",
+      "types": {},
+      "functions": {
+        "f1": {
+          "name": "f1",
+          "kind": "freestanding",
+          "params": [],
+          "results": []
+        },
+        "f2": {
+          "name": "f2",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ],
+          "results": []
+        },
+        "f3": {
+          "name": "f3",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ],
+          "results": []
+        },
+        "f4": {
+          "name": "f4",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": "u32"
+            }
+          ]
+        },
+        "f6": {
+          "name": "f6",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": 0
+            }
+          ]
+        },
+        "f7": {
+          "name": "f7",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": "float32"
+            },
+            {
+              "name": "b",
+              "type": "float32"
+            }
+          ],
+          "results": [
+            {
+              "type": 0
+            }
+          ]
+        },
+        "f8": {
+          "name": "f8",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "type": 2
+            }
+          ]
+        },
+        "f9": {
+          "name": "f9",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "name": "u",
+              "type": "u32"
+            },
+            {
+              "name": "f",
+              "type": "float32"
+            }
+          ]
+        },
+        "f10": {
+          "name": "f10",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "name": "u",
+              "type": "u32"
+            }
+          ]
+        },
+        "f11": {
+          "name": "f11",
+          "kind": "freestanding",
+          "params": [],
+          "results": []
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            "u32",
+            "u32"
+          ]
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "option": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": "u32",
+          "err": "float32"
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:functions",
+      "interfaces": {
+        "functions": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/ignore-files-deps.wit.json b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json
new file mode 100644
index 00000000..1b9d00c3
--- /dev/null
+++ b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json
@@ -0,0 +1,39 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {},
+      "package": 1
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "types",
+      "types": {},
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "types": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:foo",
+      "interfaces": {},
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/include-reps.wit.json b/crates/wit-parser/tests/ui/include-reps.wit.json
new file mode 100644
index 00000000..8217e2ee
--- /dev/null
+++ b/crates/wit-parser/tests/ui/include-reps.wit.json
@@ -0,0 +1,60 @@
+{
+  "worlds": [
+    {
+      "name": "bar",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "a",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "b",
+      "types": {},
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "a": 0,
+        "b": 1
+      },
+      "worlds": {
+        "bar": 0,
+        "foo": 1
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json b/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json
new file mode 100644
index 00000000..14c6d0d0
--- /dev/null
+++ b/crates/wit-parser/tests/ui/kebab-name-include-with.wit.json
@@ -0,0 +1,70 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "a": {
+          "function": {
+            "name": "a",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "bar",
+      "imports": {
+        "a": {
+          "function": {
+            "name": "a",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "baz",
+      "imports": {
+        "b": {
+          "function": {
+            "name": "a",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        },
+        "a": {
+          "function": {
+            "name": "a",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {},
+      "worlds": {
+        "foo": 0,
+        "bar": 1,
+        "baz": 2
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/many-names.wit.json b/crates/wit-parser/tests/ui/many-names.wit.json
new file mode 100644
index 00000000..87d111bd
--- /dev/null
+++ b/crates/wit-parser/tests/ui/many-names.wit.json
@@ -0,0 +1,40 @@
+{
+  "worlds": [
+    {
+      "name": "name",
+      "imports": {
+        "name": {
+          "interface": 1
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "x",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {},
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:name",
+      "interfaces": {
+        "x": 0
+      },
+      "worlds": {
+        "name": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-file.wit.json b/crates/wit-parser/tests/ui/multi-file.wit.json
new file mode 100644
index 00000000..c7055676
--- /dev/null
+++ b/crates/wit-parser/tests/ui/multi-file.wit.json
@@ -0,0 +1,298 @@
+{
+  "worlds": [
+    {
+      "name": "more-depends-on-later-things",
+      "imports": {
+        "interface-3": {
+          "interface": 3
+        }
+      },
+      "exports": {
+        "interface-3": {
+          "interface": 3
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "the-world",
+      "imports": {
+        "interface-1": {
+          "interface": 1
+        },
+        "x": {
+          "type": 15
+        },
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": [
+              {
+                "type": 15
+              }
+            ]
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "irrelevant-name",
+      "types": {
+        "a-name": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "depend-on-me",
+      "types": {
+        "x": 1
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "depends-on-later-item",
+      "types": {
+        "x": 2
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "later-interface",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "cycle1",
+      "types": {
+        "t": 3
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "cycle2",
+      "types": {
+        "t": 4
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "cycle3",
+      "types": {
+        "t": 5
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "types": {
+        "x": 6
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "something-else",
+      "types": {
+        "y": 7
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "bar",
+      "types": {
+        "x": 8,
+        "x2": 9,
+        "x3": 10,
+        "x4": 11,
+        "y": 12,
+        "y2": 13,
+        "a-name": 14
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "record": {
+          "fields": []
+        }
+      },
+      "name": "a-name",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "x",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "x",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 3
+      },
+      "name": "t",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 4
+      },
+      "name": "t",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "x",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": "u64"
+      },
+      "name": "y",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 6
+      },
+      "name": "x",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 6
+      },
+      "name": "x2",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 6
+      },
+      "name": "x3",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "x4",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 7
+      },
+      "name": "y",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 7
+      },
+      "name": "y2",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "a-name",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "x",
+      "owner": {
+        "world": 1
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:multi-file",
+      "interfaces": {
+        "irrelevant-name": 0,
+        "depend-on-me": 1,
+        "depends-on-later-item": 2,
+        "later-interface": 3,
+        "cycle1": 4,
+        "cycle2": 5,
+        "cycle3": 6,
+        "foo": 7,
+        "something-else": 8,
+        "bar": 9
+      },
+      "worlds": {
+        "more-depends-on-later-things": 0,
+        "the-world": 1
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json b/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json
new file mode 100644
index 00000000..d0bcb7d4
--- /dev/null
+++ b/crates/wit-parser/tests/ui/name-both-resource-and-type.wit.json
@@ -0,0 +1,89 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "a": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "types": {
+        "a": 1,
+        "t1": 2,
+        "t2": 3,
+        "t3": 4
+      },
+      "functions": {},
+      "package": 1
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "a",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "a",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 1
+        }
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 2
+        }
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 1
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "some:dep",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "foo": 1
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/package-syntax1.wit.json b/crates/wit-parser/tests/ui/package-syntax1.wit.json
new file mode 100644
index 00000000..80af0cbb
--- /dev/null
+++ b/crates/wit-parser/tests/ui/package-syntax1.wit.json
@@ -0,0 +1,12 @@
+{
+  "worlds": [],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {},
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/package-syntax3.wit.json b/crates/wit-parser/tests/ui/package-syntax3.wit.json
new file mode 100644
index 00000000..e0323c51
--- /dev/null
+++ b/crates/wit-parser/tests/ui/package-syntax3.wit.json
@@ -0,0 +1,12 @@
+{
+  "worlds": [],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {},
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/package-syntax4.wit.json b/crates/wit-parser/tests/ui/package-syntax4.wit.json
new file mode 100644
index 00000000..7bacc86f
--- /dev/null
+++ b/crates/wit-parser/tests/ui/package-syntax4.wit.json
@@ -0,0 +1,12 @@
+{
+  "worlds": [],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:bar@2.0.0",
+      "interfaces": {},
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-empty.wit.json b/crates/wit-parser/tests/ui/resources-empty.wit.json
new file mode 100644
index 00000000..ddb0ce92
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-empty.wit.json
@@ -0,0 +1,72 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources-empty",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "t2": {
+          "name": "t2",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": []
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources-empty",
+      "interfaces": {
+        "resources-empty": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit.json b/crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit.json
new file mode 100644
index 00000000..3109f6f3
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit.json
@@ -0,0 +1,74 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources1",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "name": "a",
+              "type": "s32"
+            },
+            {
+              "name": "handle",
+              "type": 1
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources1",
+      "interfaces": {
+        "resources1": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json b/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json
new file mode 100644
index 00000000..91d49631
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-multiple-returns-own.wit.json
@@ -0,0 +1,83 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources1",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "name": "a",
+              "type": "s32"
+            },
+            {
+              "name": "handle",
+              "type": 2
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources1",
+      "interfaces": {
+        "resources1": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-multiple.wit.json b/crates/wit-parser/tests/ui/resources-multiple.wit.json
new file mode 100644
index 00000000..79674fb6
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-multiple.wit.json
@@ -0,0 +1,281 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources-multiple",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": []
+        },
+        "t2": {
+          "name": "t2",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 5
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f2": {
+          "name": "[method]r1.f2",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            },
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f3": {
+          "name": "[method]r1.f3",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            },
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f4": {
+          "name": "[method]r1.f4",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "type": "u32"
+            }
+          ]
+        },
+        "[method]r1.f6": {
+          "name": "[method]r1.f6",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "type": 2
+            }
+          ]
+        },
+        "[method]r1.f7": {
+          "name": "[method]r1.f7",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            },
+            {
+              "name": "a",
+              "type": "float32"
+            },
+            {
+              "name": "b",
+              "type": "float32"
+            }
+          ],
+          "results": [
+            {
+              "type": 2
+            }
+          ]
+        },
+        "[method]r1.f8": {
+          "name": "[method]r1.f8",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            },
+            {
+              "name": "a",
+              "type": 3
+            }
+          ],
+          "results": [
+            {
+              "type": 4
+            }
+          ]
+        },
+        "[method]r1.f9": {
+          "name": "[method]r1.f9",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "name": "u",
+              "type": "u32"
+            },
+            {
+              "name": "f",
+              "type": "float32"
+            }
+          ]
+        },
+        "[method]r1.f10": {
+          "name": "[method]r1.f10",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "name": "u",
+              "type": "u32"
+            }
+          ]
+        },
+        "[method]r1.f11": {
+          "name": "[method]r1.f11",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": []
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            "u32",
+            "u32"
+          ]
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "option": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": "u32",
+          "err": "float32"
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources-multiple",
+      "interfaces": {
+        "resources-multiple": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-return-borrow.wit.json b/crates/wit-parser/tests/ui/resources-return-borrow.wit.json
new file mode 100644
index 00000000..7e52922f
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-return-borrow.wit.json
@@ -0,0 +1,69 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources1",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "type": 1
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources1",
+      "interfaces": {
+        "resources1": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources-return-own.wit.json b/crates/wit-parser/tests/ui/resources-return-own.wit.json
new file mode 100644
index 00000000..c7e2936c
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources-return-own.wit.json
@@ -0,0 +1,78 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources1",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": [
+            {
+              "type": 2
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources1",
+      "interfaces": {
+        "resources1": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources.wit.json b/crates/wit-parser/tests/ui/resources.wit.json
new file mode 100644
index 00000000..8df576af
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources.wit.json
@@ -0,0 +1,340 @@
+{
+  "worlds": [
+    {
+      "name": "w",
+      "imports": {
+        "a": {
+          "type": 11
+        },
+        "b": {
+          "type": 12
+        },
+        "c": {
+          "type": 13
+        },
+        "[constructor]c": {
+          "function": {
+            "name": "[constructor]c",
+            "kind": {
+              "constructor": 13
+            },
+            "params": [],
+            "results": [
+              {
+                "type": 18
+              }
+            ]
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "a": 0,
+        "b": 1,
+        "c": 2,
+        "d": 3,
+        "e": 4
+      },
+      "functions": {
+        "[constructor]b": {
+          "name": "[constructor]b",
+          "kind": {
+            "constructor": 1
+          },
+          "params": [],
+          "results": [
+            {
+              "type": 14
+            }
+          ]
+        },
+        "[constructor]c": {
+          "name": "[constructor]c",
+          "kind": {
+            "constructor": 2
+          },
+          "params": [
+            {
+              "name": "x",
+              "type": "u32"
+            }
+          ],
+          "results": [
+            {
+              "type": 15
+            }
+          ]
+        },
+        "[constructor]d": {
+          "name": "[constructor]d",
+          "kind": {
+            "constructor": 3
+          },
+          "params": [
+            {
+              "name": "x",
+              "type": "u32"
+            }
+          ],
+          "results": [
+            {
+              "type": 16
+            }
+          ]
+        },
+        "[method]d.a": {
+          "name": "[method]d.a",
+          "kind": {
+            "method": 3
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 5
+            }
+          ],
+          "results": []
+        },
+        "[static]d.b": {
+          "name": "[static]d.b",
+          "kind": {
+            "static": 3
+          },
+          "params": [],
+          "results": []
+        },
+        "[constructor]e": {
+          "name": "[constructor]e",
+          "kind": {
+            "constructor": 4
+          },
+          "params": [
+            {
+              "name": "other",
+              "type": 17
+            },
+            {
+              "name": "other2",
+              "type": 6
+            }
+          ],
+          "results": [
+            {
+              "type": 17
+            }
+          ]
+        },
+        "[method]e.method": {
+          "name": "[method]e.method",
+          "kind": {
+            "method": 4
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 6
+            },
+            {
+              "name": "thing",
+              "type": 17
+            },
+            {
+              "name": "thing2",
+              "type": 6
+            }
+          ],
+          "results": []
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "i",
+      "types": {
+        "a": 7,
+        "t1": 8,
+        "t2": 9,
+        "t3": 10
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "a",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "b",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "c",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "d",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "e",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 3
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 4
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": "resource",
+      "name": "a",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 7
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 7
+        }
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 8
+        }
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "a",
+      "owner": {
+        "world": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "b",
+      "owner": {
+        "world": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "c",
+      "owner": {
+        "world": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 1
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 2
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 3
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 4
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 13
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "foo": 0,
+        "i": 1
+      },
+      "worlds": {
+        "w": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/resources1.wit.json b/crates/wit-parser/tests/ui/resources1.wit.json
new file mode 100644
index 00000000..0945e573
--- /dev/null
+++ b/crates/wit-parser/tests/ui/resources1.wit.json
@@ -0,0 +1,96 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "resources1",
+      "types": {
+        "r1": 0
+      },
+      "functions": {
+        "t1": {
+          "name": "t1",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 1
+            }
+          ],
+          "results": []
+        },
+        "t2": {
+          "name": "t2",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "t3": {
+          "name": "t3",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "a",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "[method]r1.f1": {
+          "name": "[method]r1.f1",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 1
+            }
+          ],
+          "results": []
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "r1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:resources1",
+      "interfaces": {
+        "resources1": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/shared-types.wit.json b/crates/wit-parser/tests/ui/shared-types.wit.json
new file mode 100644
index 00000000..76729dd3
--- /dev/null
+++ b/crates/wit-parser/tests/ui/shared-types.wit.json
@@ -0,0 +1,83 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "foo": {
+          "interface": 0
+        }
+      },
+      "exports": {
+        "bar": {
+          "interface": 1
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": null,
+      "types": {},
+      "functions": {
+        "a": {
+          "name": "a",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": 0
+            }
+          ]
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {},
+      "functions": {
+        "a": {
+          "name": "a",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": 1
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "list": "u8"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            0
+          ]
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:shared-items",
+      "interfaces": {},
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json
new file mode 100644
index 00000000..e7081d15
--- /dev/null
+++ b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json
@@ -0,0 +1,1136 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        },
+        "interface-2": {
+          "interface": 2
+        },
+        "interface-3": {
+          "interface": 3
+        },
+        "interface-4": {
+          "interface": 4
+        },
+        "interface-5": {
+          "interface": 5
+        },
+        "interface-6": {
+          "interface": 6
+        },
+        "interface-7": {
+          "interface": 7
+        },
+        "interface-8": {
+          "interface": 8
+        }
+      },
+      "exports": {
+        "interface-9": {
+          "interface": 9
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "i1",
+      "types": {
+        "t1": 0,
+        "t2": 1,
+        "t3": 2,
+        "t4": 3,
+        "t5": 4,
+        "t6": 5,
+        "t7": 6,
+        "t8": 7,
+        "t9": 8,
+        "t10": 9
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i2",
+      "types": {
+        "t1": 10,
+        "t2": 11,
+        "t3": 12,
+        "t4": 13,
+        "t5": 14,
+        "t6": 15,
+        "t7": 16,
+        "t8": 17,
+        "t9": 18,
+        "t10": 19
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i3",
+      "types": {
+        "t1": 20,
+        "t2": 21,
+        "t3": 22,
+        "t4": 23,
+        "t5": 24,
+        "t6": 25,
+        "t7": 26,
+        "t8": 27,
+        "t9": 28,
+        "t10": 29
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i4",
+      "types": {
+        "t1": 30,
+        "t2": 31,
+        "t3": 32,
+        "t4": 33,
+        "t5": 34,
+        "t6": 35,
+        "t7": 36,
+        "t8": 37,
+        "t9": 38,
+        "t10": 39
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i5",
+      "types": {
+        "t1": 40,
+        "t2": 41,
+        "t3": 42,
+        "t4": 43,
+        "t5": 44,
+        "t6": 45,
+        "t7": 46,
+        "t8": 47,
+        "t9": 48,
+        "t10": 49
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i6",
+      "types": {
+        "t1": 50,
+        "t2": 51,
+        "t3": 52,
+        "t4": 53,
+        "t5": 54,
+        "t6": 55,
+        "t7": 56,
+        "t8": 57,
+        "t9": 58,
+        "t10": 59
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i7",
+      "types": {
+        "t1": 60,
+        "t2": 61,
+        "t3": 62,
+        "t4": 63,
+        "t5": 64,
+        "t6": 65,
+        "t7": 66,
+        "t8": 67,
+        "t9": 68,
+        "t10": 69
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i8",
+      "types": {
+        "t1": 70,
+        "t2": 71,
+        "t3": 72,
+        "t4": 73,
+        "t5": 74,
+        "t6": 75,
+        "t7": 76,
+        "t8": 77,
+        "t9": 78,
+        "t10": 79
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i9",
+      "types": {
+        "t1": 80,
+        "t2": 81,
+        "t3": 82,
+        "t4": 83,
+        "t5": 84,
+        "t6": 85,
+        "t7": 86,
+        "t8": 87,
+        "t9": 88,
+        "t10": 89
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i10",
+      "types": {
+        "t1": 90,
+        "t2": 91,
+        "t3": 92,
+        "t4": 93,
+        "t5": 94,
+        "t6": 95,
+        "t7": 96,
+        "t8": 97,
+        "t9": 98,
+        "t10": 99
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 2
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 3
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 4
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 5
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 6
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 7
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 8
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 9
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 10
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 11
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 12
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 13
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 14
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 15
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 16
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 17
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 18
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 19
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 20
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 21
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 22
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 23
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 24
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 25
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 26
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 27
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 28
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 29
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "kind": {
+        "type": 30
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 31
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 32
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 33
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 34
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 35
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 36
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 37
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 38
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 39
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "kind": {
+        "type": 40
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 41
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 42
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 43
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 44
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 45
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 46
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 47
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 48
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 49
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 50
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 51
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 52
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 53
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 54
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 55
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 56
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 57
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 58
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 59
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "type": 60
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 61
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 62
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 63
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 64
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 65
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 66
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 67
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 68
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 69
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 7
+      }
+    },
+    {
+      "kind": {
+        "type": 70
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 71
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 72
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 73
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 74
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 75
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 76
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 77
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 78
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 79
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 8
+      }
+    },
+    {
+      "kind": {
+        "type": 80
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 81
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 82
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 83
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 84
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 85
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 86
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 87
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 88
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 9
+      }
+    },
+    {
+      "kind": {
+        "type": 89
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 9
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {
+        "i1": 0,
+        "i2": 1,
+        "i3": 2,
+        "i4": 3,
+        "i5": 4,
+        "i6": 5,
+        "i7": 6,
+        "i8": 7,
+        "i9": 8,
+        "i10": 9
+      },
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/type-then-eof.wit.json b/crates/wit-parser/tests/ui/type-then-eof.wit.json
new file mode 100644
index 00000000..beb0a7ad
--- /dev/null
+++ b/crates/wit-parser/tests/ui/type-then-eof.wit.json
@@ -0,0 +1,32 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {},
+      "functions": {
+        "foo": {
+          "name": "foo",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": "string"
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/types.wit.json b/crates/wit-parser/tests/ui/types.wit.json
new file mode 100644
index 00000000..46c3452e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/types.wit.json
@@ -0,0 +1,751 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "types",
+      "types": {
+        "t1": 0,
+        "t2": 1,
+        "t3": 2,
+        "t4": 3,
+        "t5": 4,
+        "t6": 5,
+        "t7": 6,
+        "t8": 7,
+        "t9": 8,
+        "t10": 9,
+        "t11": 10,
+        "t12": 11,
+        "t13": 12,
+        "t14": 13,
+        "t15": 14,
+        "t16": 15,
+        "t17": 16,
+        "t18": 17,
+        "t20": 18,
+        "t21": 19,
+        "t22": 20,
+        "t23": 21,
+        "t24": 22,
+        "t25": 23,
+        "record": 24,
+        "t26": 25,
+        "t27": 26,
+        "t28": 27,
+        "t29": 28,
+        "t30": 29,
+        "t31": 30,
+        "t32": 31,
+        "t33": 32,
+        "t34": 33,
+        "t35": 34,
+        "t36": 35,
+        "t37": 37,
+        "t41": 38,
+        "t42": 39,
+        "t43": 40,
+        "t44": 41,
+        "t45": 44,
+        "t46": 45,
+        "t47": 46,
+        "t48": 47,
+        "t49": 48,
+        "t50": 49,
+        "t51": 50,
+        "t52": 51,
+        "t53": 52,
+        "bar": 53,
+        "foo": 54
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u8"
+      },
+      "name": "t1",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u16"
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t3",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u64"
+      },
+      "name": "t4",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "s8"
+      },
+      "name": "t5",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "s16"
+      },
+      "name": "t6",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "s32"
+      },
+      "name": "t7",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "s64"
+      },
+      "name": "t8",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "float32"
+      },
+      "name": "t9",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "float64"
+      },
+      "name": "t10",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "char"
+      },
+      "name": "t11",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "list": "char"
+      },
+      "name": "t12",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "string"
+      },
+      "name": "t13",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "option": "u32"
+      },
+      "name": "t14",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": "u32",
+          "err": "u32"
+        }
+      },
+      "name": "t15",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": null,
+          "err": "u32"
+        }
+      },
+      "name": "t16",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": "u32",
+          "err": null
+        }
+      },
+      "name": "t17",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "result": {
+          "ok": null,
+          "err": null
+        }
+      },
+      "name": "t18",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": []
+        }
+      },
+      "name": "t20",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ]
+        }
+      },
+      "name": "t21",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "a",
+              "type": "u32"
+            }
+          ]
+        }
+      },
+      "name": "t22",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "a",
+              "type": "u32"
+            },
+            {
+              "name": "b",
+              "type": "u64"
+            }
+          ]
+        }
+      },
+      "name": "t23",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "a",
+              "type": "u32"
+            },
+            {
+              "name": "b",
+              "type": "u64"
+            }
+          ]
+        }
+      },
+      "name": "t24",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "x",
+              "type": "u32"
+            }
+          ]
+        }
+      },
+      "name": "t25",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": []
+        }
+      },
+      "name": "record",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": []
+        }
+      },
+      "name": "t26",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            "u32"
+          ]
+        }
+      },
+      "name": "t27",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            "u32"
+          ]
+        }
+      },
+      "name": "t28",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "tuple": {
+          "types": [
+            "u32",
+            "u64"
+          ]
+        }
+      },
+      "name": "t29",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "flags": {
+          "flags": []
+        }
+      },
+      "name": "t30",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "flags": {
+          "flags": [
+            {
+              "name": "a"
+            },
+            {
+              "name": "b"
+            },
+            {
+              "name": "c"
+            }
+          ]
+        }
+      },
+      "name": "t31",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "flags": {
+          "flags": [
+            {
+              "name": "a"
+            },
+            {
+              "name": "b"
+            },
+            {
+              "name": "c"
+            }
+          ]
+        }
+      },
+      "name": "t32",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "a",
+              "type": null
+            }
+          ]
+        }
+      },
+      "name": "t33",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "a",
+              "type": null
+            },
+            {
+              "name": "b",
+              "type": null
+            }
+          ]
+        }
+      },
+      "name": "t34",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "a",
+              "type": null
+            },
+            {
+              "name": "b",
+              "type": null
+            }
+          ]
+        }
+      },
+      "name": "t35",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "a",
+              "type": null
+            },
+            {
+              "name": "b",
+              "type": "u32"
+            }
+          ]
+        }
+      },
+      "name": "t36",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "option": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "a",
+              "type": null
+            },
+            {
+              "name": "b",
+              "type": 36
+            }
+          ]
+        }
+      },
+      "name": "t37",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "enum": {
+          "cases": [
+            {
+              "name": "a"
+            },
+            {
+              "name": "b"
+            },
+            {
+              "name": "c"
+            }
+          ]
+        }
+      },
+      "name": "t41",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "enum": {
+          "cases": [
+            {
+              "name": "a"
+            },
+            {
+              "name": "b"
+            },
+            {
+              "name": "c"
+            }
+          ]
+        }
+      },
+      "name": "t42",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "bool"
+      },
+      "name": "t43",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "string"
+      },
+      "name": "t44",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "list": 31
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "list": 42
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "list": 43
+      },
+      "name": "t45",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 41
+      },
+      "name": "t46",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 41
+      },
+      "name": "t47",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "stream": {
+          "element": "u32",
+          "end": "u32"
+        }
+      },
+      "name": "t48",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "stream": {
+          "element": null,
+          "end": "u32"
+        }
+      },
+      "name": "t49",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "stream": {
+          "element": "u32",
+          "end": null
+        }
+      },
+      "name": "t50",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "stream": {
+          "element": null,
+          "end": null
+        }
+      },
+      "name": "t51",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "future": "u32"
+      },
+      "name": "t52",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "future": null
+      },
+      "name": "t53",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "bar",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 53
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:types",
+      "interfaces": {
+        "types": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/union-fuzz-1.wit.json b/crates/wit-parser/tests/ui/union-fuzz-1.wit.json
new file mode 100644
index 00000000..31c7c826
--- /dev/null
+++ b/crates/wit-parser/tests/ui/union-fuzz-1.wit.json
@@ -0,0 +1,35 @@
+{
+  "worlds": [
+    {
+      "name": "xo",
+      "imports": {},
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "name",
+      "imports": {},
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "x",
+      "imports": {},
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:bar",
+      "interfaces": {},
+      "worlds": {
+        "xo": 0,
+        "name": 1,
+        "x": 2
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/use-chain.wit.json b/crates/wit-parser/tests/ui/use-chain.wit.json
new file mode 100644
index 00000000..f8598bcb
--- /dev/null
+++ b/crates/wit-parser/tests/ui/use-chain.wit.json
@@ -0,0 +1,53 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "foo": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "name",
+      "types": {
+        "foo": 1
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "record": {
+          "fields": []
+        }
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 1
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:name",
+      "interfaces": {
+        "foo": 0,
+        "name": 1
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/use.wit.json b/crates/wit-parser/tests/ui/use.wit.json
new file mode 100644
index 00000000..e37de2c1
--- /dev/null
+++ b/crates/wit-parser/tests/ui/use.wit.json
@@ -0,0 +1,172 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "bar",
+      "types": {
+        "the-type": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "types": {
+        "the-type": 1
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "baz",
+      "types": {
+        "the-type": 2,
+        "test": 3
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "empty",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "use-from-empty",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "use-multiple",
+      "types": {
+        "the-type": 4,
+        "test": 5
+      },
+      "functions": {
+        "some-function": {
+          "name": "some-function",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "x",
+              "type": 4
+            }
+          ],
+          "results": [
+            {
+              "type": 5
+            }
+          ]
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "trailing-comma",
+      "types": {
+        "the-type": 6,
+        "the-foo": 7
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "test",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 2
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 3
+      },
+      "name": "test",
+      "owner": {
+        "interface": 5
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "the-type",
+      "owner": {
+        "interface": 6
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "a",
+              "type": 6
+            }
+          ]
+        }
+      },
+      "name": "the-foo",
+      "owner": {
+        "interface": 6
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "bar": 0,
+        "foo": 1,
+        "baz": 2,
+        "empty": 3,
+        "use-from-empty": 4,
+        "use-multiple": 5,
+        "trailing-comma": 6
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/versions.wit.json b/crates/wit-parser/tests/ui/versions.wit.json
new file mode 100644
index 00000000..aabcc1b6
--- /dev/null
+++ b/crates/wit-parser/tests/ui/versions.wit.json
@@ -0,0 +1,91 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "t": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "foo",
+      "types": {
+        "t": 1
+      },
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "foo",
+      "types": {
+        "t": 2,
+        "t2": 3
+      },
+      "functions": {},
+      "package": 2
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "t",
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "t2",
+      "owner": {
+        "interface": 2
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "a:a@1.0.0",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "a:a@2.0.0",
+      "interfaces": {
+        "foo": 1
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:versions",
+      "interfaces": {
+        "foo": 2
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/wasi.wit.json b/crates/wit-parser/tests/ui/wasi.wit.json
new file mode 100644
index 00000000..43e6102d
--- /dev/null
+++ b/crates/wit-parser/tests/ui/wasi.wit.json
@@ -0,0 +1,296 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "wasi",
+      "types": {
+        "clockid": 0,
+        "timestamp": 1,
+        "errno": 2
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "enum": {
+          "cases": [
+            {
+              "name": "realtime"
+            },
+            {
+              "name": "monotonic"
+            }
+          ]
+        }
+      },
+      "name": "clockid",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u64"
+      },
+      "name": "timestamp",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "enum": {
+          "cases": [
+            {
+              "name": "success"
+            },
+            {
+              "name": "toobig"
+            },
+            {
+              "name": "access"
+            },
+            {
+              "name": "addrinuse"
+            },
+            {
+              "name": "addrnotavail"
+            },
+            {
+              "name": "afnosupport"
+            },
+            {
+              "name": "again"
+            },
+            {
+              "name": "already"
+            },
+            {
+              "name": "badf"
+            },
+            {
+              "name": "badmsg"
+            },
+            {
+              "name": "busy"
+            },
+            {
+              "name": "canceled"
+            },
+            {
+              "name": "child"
+            },
+            {
+              "name": "connaborted"
+            },
+            {
+              "name": "connrefused"
+            },
+            {
+              "name": "connreset"
+            },
+            {
+              "name": "deadlk"
+            },
+            {
+              "name": "destaddrreq"
+            },
+            {
+              "name": "dom"
+            },
+            {
+              "name": "dquot"
+            },
+            {
+              "name": "exist"
+            },
+            {
+              "name": "fault"
+            },
+            {
+              "name": "fbig"
+            },
+            {
+              "name": "hostunreach"
+            },
+            {
+              "name": "idrm"
+            },
+            {
+              "name": "ilseq"
+            },
+            {
+              "name": "inprogress"
+            },
+            {
+              "name": "intr"
+            },
+            {
+              "name": "inval"
+            },
+            {
+              "name": "io"
+            },
+            {
+              "name": "isconn"
+            },
+            {
+              "name": "isdir"
+            },
+            {
+              "name": "loop"
+            },
+            {
+              "name": "mfile"
+            },
+            {
+              "name": "mlink"
+            },
+            {
+              "name": "msgsize"
+            },
+            {
+              "name": "multihop"
+            },
+            {
+              "name": "nametoolong"
+            },
+            {
+              "name": "netdown"
+            },
+            {
+              "name": "netreset"
+            },
+            {
+              "name": "netunreach"
+            },
+            {
+              "name": "nfile"
+            },
+            {
+              "name": "nobufs"
+            },
+            {
+              "name": "nodev"
+            },
+            {
+              "name": "noent"
+            },
+            {
+              "name": "noexec"
+            },
+            {
+              "name": "nolck"
+            },
+            {
+              "name": "nolink"
+            },
+            {
+              "name": "nomem"
+            },
+            {
+              "name": "nomsg"
+            },
+            {
+              "name": "noprotoopt"
+            },
+            {
+              "name": "nospc"
+            },
+            {
+              "name": "nosys"
+            },
+            {
+              "name": "notconn"
+            },
+            {
+              "name": "notdir"
+            },
+            {
+              "name": "notempty"
+            },
+            {
+              "name": "notrecoverable"
+            },
+            {
+              "name": "notsock"
+            },
+            {
+              "name": "notsup"
+            },
+            {
+              "name": "notty"
+            },
+            {
+              "name": "nxio"
+            },
+            {
+              "name": "overflow"
+            },
+            {
+              "name": "ownerdead"
+            },
+            {
+              "name": "perm"
+            },
+            {
+              "name": "pipe"
+            },
+            {
+              "name": "proto"
+            },
+            {
+              "name": "protonosupport"
+            },
+            {
+              "name": "prototype"
+            },
+            {
+              "name": "range"
+            },
+            {
+              "name": "rofs"
+            },
+            {
+              "name": "spipe"
+            },
+            {
+              "name": "srch"
+            },
+            {
+              "name": "stale"
+            },
+            {
+              "name": "timedout"
+            },
+            {
+              "name": "txtbsy"
+            },
+            {
+              "name": "xdev"
+            },
+            {
+              "name": "notcapable"
+            }
+          ]
+        }
+      },
+      "name": "errno",
+      "owner": {
+        "interface": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "wasi:filesystem",
+      "interfaces": {
+        "wasi": 0
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-diamond.wit b/crates/wit-parser/tests/ui/world-diamond.wit
index 6dd4ce88..c858ed36 100644
--- a/crates/wit-parser/tests/ui/world-diamond.wit
+++ b/crates/wit-parser/tests/ui/world-diamond.wit
@@ -19,4 +19,5 @@ interface i2 {
 world the-world {
   import i1
   import i2
+  import a: func()
 }
diff --git a/crates/wit-parser/tests/ui/world-diamond.wit.json b/crates/wit-parser/tests/ui/world-diamond.wit.json
new file mode 100644
index 00000000..75c8900e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-diamond.wit.json
@@ -0,0 +1,118 @@
+{
+  "worlds": [
+    {
+      "name": "the-world",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        },
+        "interface-2": {
+          "interface": 2
+        },
+        "a": {
+          "function": {
+            "name": "a",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "shared-items",
+      "types": {
+        "foo": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i1",
+      "types": {
+        "foo": 1
+      },
+      "functions": {
+        "a": {
+          "name": "a",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": 1
+            }
+          ]
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "i2",
+      "types": {
+        "foo": 2
+      },
+      "functions": {
+        "a": {
+          "name": "a",
+          "kind": "freestanding",
+          "params": [],
+          "results": [
+            {
+              "type": 2
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "foo",
+      "owner": {
+        "interface": 2
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "shared-items": 0,
+        "i1": 1,
+        "i2": 2
+      },
+      "worlds": {
+        "the-world": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json
new file mode 100644
index 00000000..9d705dee
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json
@@ -0,0 +1,66 @@
+{
+  "worlds": [
+    {
+      "name": "bar",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "t": {
+          "type": 1
+        },
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "t": 0
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "t",
+      "owner": {
+        "world": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {
+        "bar": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-implicit-import1.wit.json b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json
new file mode 100644
index 00000000..03bfab32
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json
@@ -0,0 +1,75 @@
+{
+  "worlds": [
+    {
+      "name": "the-world",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "bar": {
+          "interface": 1
+        },
+        "foo": {
+          "interface": 2
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "a": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {
+        "a": 1
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {},
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "a",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "a",
+      "owner": {
+        "interface": 1
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {
+        "the-world": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-implicit-import2.wit.json b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json
new file mode 100644
index 00000000..7bd1a871
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json
@@ -0,0 +1,70 @@
+{
+  "worlds": [
+    {
+      "name": "w",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "g": {
+          "type": 1
+        },
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": [
+              {
+                "type": 1
+              }
+            ]
+          }
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "g": 0
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "char"
+      },
+      "name": "g",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "g",
+      "owner": {
+        "world": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {
+        "w": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-implicit-import3.wit.json b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json
new file mode 100644
index 00000000..3dcbde53
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json
@@ -0,0 +1,71 @@
+{
+  "worlds": [
+    {
+      "name": "w",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "g": {
+          "type": 1
+        }
+      },
+      "exports": {
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": [
+              {
+                "type": 1
+              }
+            ]
+          }
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "foo",
+      "types": {
+        "g": 0
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "char"
+      },
+      "name": "g",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "g",
+      "owner": {
+        "world": 0
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "foo": 0
+      },
+      "worlds": {
+        "w": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-same-fields4.wit.json b/crates/wit-parser/tests/ui/world-same-fields4.wit.json
new file mode 100644
index 00000000..83bb910d
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-same-fields4.wit.json
@@ -0,0 +1,76 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "shared-items": {
+          "interface": 1
+        },
+        "interface-0": {
+          "interface": 0
+        }
+      },
+      "exports": {
+        "a-name": {
+          "interface": 2
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "shared-items",
+      "types": {
+        "a": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": null,
+      "types": {
+        "a": 1
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "a",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "a",
+      "owner": {
+        "interface": 2
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "shared-items": 0
+      },
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json b/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json
new file mode 100644
index 00000000..43401ee3
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-top-level-funcs.wit.json
@@ -0,0 +1,86 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        },
+        "bar": {
+          "function": {
+            "name": "bar",
+            "kind": "freestanding",
+            "params": [
+              {
+                "name": "arg",
+                "type": 0
+              }
+            ],
+            "results": []
+          }
+        }
+      },
+      "exports": {
+        "foo2": {
+          "function": {
+            "name": "foo2",
+            "kind": "freestanding",
+            "params": [],
+            "results": []
+          }
+        },
+        "some-name": {
+          "function": {
+            "name": "some-name",
+            "kind": "freestanding",
+            "params": [],
+            "results": [
+              {
+                "type": 2
+              }
+            ]
+          }
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [],
+  "types": [
+    {
+      "kind": {
+        "list": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "option": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "list": 1
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {},
+      "worlds": {
+        "foo": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json
new file mode 100644
index 00000000..f869fb1a
--- /dev/null
+++ b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json
@@ -0,0 +1,240 @@
+{
+  "worlds": [
+    {
+      "name": "proxy",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "exports": {
+        "interface-1": {
+          "interface": 1
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "types",
+      "types": {
+        "request": 0,
+        "response": 1
+      },
+      "functions": {
+        "[method]request.foo": {
+          "name": "[method]request.foo",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 2
+            }
+          ],
+          "results": []
+        },
+        "[method]request.bar": {
+          "name": "[method]request.bar",
+          "kind": {
+            "method": 0
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 2
+            },
+            {
+              "name": "arg",
+              "type": 3
+            }
+          ],
+          "results": []
+        },
+        "[method]response.foo": {
+          "name": "[method]response.foo",
+          "kind": {
+            "method": 1
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 4
+            }
+          ],
+          "results": []
+        },
+        "[method]response.bar": {
+          "name": "[method]response.bar",
+          "kind": {
+            "method": 1
+          },
+          "params": [
+            {
+              "name": "self",
+              "type": 4
+            },
+            {
+              "name": "arg",
+              "type": 3
+            }
+          ],
+          "results": []
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "handler",
+      "types": {
+        "request": 5,
+        "response": 6
+      },
+      "functions": {
+        "handle": {
+          "name": "handle",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "some",
+              "type": 7
+            }
+          ],
+          "results": [
+            {
+              "type": 8
+            }
+          ]
+        },
+        "handle-owned": {
+          "name": "handle-owned",
+          "kind": "freestanding",
+          "params": [
+            {
+              "name": "some",
+              "type": 9
+            }
+          ],
+          "results": [
+            {
+              "type": 10
+            }
+          ]
+        }
+      },
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": "resource",
+      "name": "request",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": "resource",
+      "name": "response",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 0
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "list": "u32"
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 1
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "request",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "response",
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 5
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "borrow": 6
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 5
+        }
+      },
+      "name": null,
+      "owner": "none"
+    },
+    {
+      "kind": {
+        "handle": {
+          "own": 6
+        }
+      },
+      "name": null,
+      "owner": "none"
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "types": 0,
+        "handler": 1
+      },
+      "worlds": {
+        "proxy": 0
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json
new file mode 100644
index 00000000..7fb8ff0a
--- /dev/null
+++ b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json
@@ -0,0 +1,100 @@
+{
+  "worlds": [
+    {
+      "name": "my-world-a",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-2": {
+          "interface": 2
+        }
+      },
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "my-world-b",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-2": {
+          "interface": 2
+        }
+      },
+      "exports": {},
+      "package": 0
+    },
+    {
+      "name": "union-my-world",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "interface-2": {
+          "interface": 2
+        }
+      },
+      "exports": {},
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "a1",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "a2",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "b1",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "b2",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "c",
+      "types": {},
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "d",
+      "types": {},
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "a1": 0,
+        "a2": 1,
+        "b1": 2,
+        "b2": 3,
+        "c": 4,
+        "d": 5
+      },
+      "worlds": {
+        "my-world-a": 0,
+        "my-world-b": 1,
+        "union-my-world": 2
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/worlds-with-types.wit.json b/crates/wit-parser/tests/ui/worlds-with-types.wit.json
new file mode 100644
index 00000000..d4f85282
--- /dev/null
+++ b/crates/wit-parser/tests/ui/worlds-with-types.wit.json
@@ -0,0 +1,202 @@
+{
+  "worlds": [
+    {
+      "name": "foo",
+      "imports": {
+        "a": {
+          "type": 1
+        },
+        "b": {
+          "type": 2
+        }
+      },
+      "exports": {
+        "c": {
+          "function": {
+            "name": "c",
+            "kind": "freestanding",
+            "params": [
+              {
+                "name": "a",
+                "type": 1
+              }
+            ],
+            "results": [
+              {
+                "type": 2
+              }
+            ]
+          }
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "bar",
+      "imports": {
+        "interface-0": {
+          "interface": 0
+        },
+        "t": {
+          "type": 3
+        }
+      },
+      "exports": {
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [],
+            "results": [
+              {
+                "type": 3
+              }
+            ]
+          }
+        }
+      },
+      "package": 0
+    },
+    {
+      "name": "the-test",
+      "imports": {
+        "a": {
+          "type": 4
+        },
+        "b": {
+          "type": 5
+        },
+        "foo": {
+          "function": {
+            "name": "foo",
+            "kind": "freestanding",
+            "params": [
+              {
+                "name": "a",
+                "type": 4
+              }
+            ],
+            "results": [
+              {
+                "type": 5
+              }
+            ]
+          }
+        }
+      },
+      "exports": {
+        "bar": {
+          "function": {
+            "name": "bar",
+            "kind": "freestanding",
+            "params": [
+              {
+                "name": "a",
+                "type": 4
+              }
+            ],
+            "results": [
+              {
+                "type": 5
+              }
+            ]
+          }
+        }
+      },
+      "package": 0
+    }
+  ],
+  "interfaces": [
+    {
+      "name": "disambiguate",
+      "types": {
+        "t": 0
+      },
+      "functions": {},
+      "package": 0
+    }
+  ],
+  "types": [
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "t",
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "kind": {
+        "type": "u32"
+      },
+      "name": "a",
+      "owner": {
+        "world": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 1
+      },
+      "name": "b",
+      "owner": {
+        "world": 0
+      }
+    },
+    {
+      "kind": {
+        "type": 0
+      },
+      "name": "t",
+      "owner": {
+        "world": 1
+      }
+    },
+    {
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "x",
+              "type": "u32"
+            }
+          ]
+        }
+      },
+      "name": "a",
+      "owner": {
+        "world": 2
+      }
+    },
+    {
+      "kind": {
+        "variant": {
+          "cases": [
+            {
+              "name": "c",
+              "type": 4
+            }
+          ]
+        }
+      },
+      "name": "b",
+      "owner": {
+        "world": 2
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:foo",
+      "interfaces": {
+        "disambiguate": 0
+      },
+      "worlds": {
+        "foo": 0,
+        "bar": 1,
+        "the-test": 2
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/local/gc/gc-i31.wat b/tests/local/gc/gc-i31.wat
index 90352174..f723e211 100644
--- a/tests/local/gc/gc-i31.wat
+++ b/tests/local/gc/gc-i31.wat
@@ -6,8 +6,8 @@
     (local $b (ref null i31))
     (local $c i31ref)
 
-    (local.set $a (i31.new (i32.const 42)))
-    (local.set $b (i31.new (i32.const 0)))
+    (local.set $a (ref.i31 (i32.const 42)))
+    (local.set $b (ref.i31 (i32.const 0)))
     (i31.get_u (local.get $a))
     (i31.get_s (local.get $b))
   )
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index 41e01585..ed223d19 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -741,7 +741,7 @@ fn error_matches(error: &str, message: &str) -> bool {
 
     if message == "illegal opcode" {
         // The test suite includes "bad opcodes" that later became valid opcodes
-        // (0xd3, function references proposal). However, they are still not
+        // (0xd4, function references proposal). However, they are still not
         // constant expressions, so we can sidestep by checking for that error
         // instead
         return error.contains("constant expression required")
diff --git a/tests/snapshots/local/gc/gc-i31.wat.print b/tests/snapshots/local/gc/gc-i31.wat.print
index 983d103f..7f89e0c9 100644
--- a/tests/snapshots/local/gc/gc-i31.wat.print
+++ b/tests/snapshots/local/gc/gc-i31.wat.print
@@ -3,10 +3,10 @@
   (func $f (;0;) (type 0) (result i32 i32)
     (local $a (ref i31)) (local $b i31ref) (local $c i31ref)
     i32.const 42
-    i31.new
+    ref.i31
     local.set $a
     i32.const 0
-    i31.new
+    ref.i31
     local.set $b
     local.get $a
     i31.get_u