Repository: bytecodealliance/wasm-tools
Pull request #1767 (2024-09-06 21:32:05)

Start moving toward `wasm_encoder` in `wast`'s core wasm encoding
The `wast` crate is responsible for turning textual WebAssembly into binary WebAssembly. This crate predates the existence of `wasm-encoder` and as such had its own implementation of converting the textual AST to binary. This works by effectively having a method for most AST nodes which says "put yourself into this list of bytes" and then making sure to carefully call everything in the correct order to ensure that the final bytes are indeed valid.

Since `wast` was originally created, however, the `wasm-encoder` crate was created. This crate provides a static API and guarantee that the created module will be at least syntactically valid if not semantically valid. This makes emission less error prone and additionally deduplicates the encoding details between `wast` and the `wasm-encoder` crate.

For quite some time now the `component` support in the `wast` crate has already been using the `wasm-encoder` crate to encode components. This is done by implementing conversions of AST fragments in `wast` to equivalent fragments in `wasm-encoder`. These conversions are then used to feed data into the right sections to produce the final binary. The purpose of this commit is to start moving in the same direction for core wasm.

This commit itself doesn't update all of `binary.rs` but instead only replaces the top-level module with `wasm_encoder::Module`. This updates a few other minor locations too but the bulk of the work is left for a future commit. This is done to ensure there's support for this direction first before completing this work.

One downside to this approach is that it might mean that the `wast` crate's binary size grows a bit since the `wasm-encoder` crate isn't quite as "tight" as the encoding in `wast` today. That being said the binary size of `wast` itself has not so far been a concern for anyone and this concern is mostly theoretical and can hopefully be worked around with alternative API design in `wasm-encoder` if needed.

Another downside of this approach is that it's coupling `wast` to `wasm-encoder` more tightly, meaning change is harder. That is a good thing in that it reduces duplication but it's a bad thing where if you're only interested in text parsing then it means that adding new features would also require adding features to `wasm-encoder`. This downside is somewhat inescapable though and the only mitigating factor I'd have to offer is that so far most minor wasm extensions have already been coupled with sibling changes to the
parser/validator/printer/encoder/etc. This does mean that it's less likely that a new contributor could implement a major proposal like Wasm GC which required large refactorings in all crates (whereas GC was initially implemented in `wast` by an external contributor as it was relatively localized).

Overall I personally at least feel that the benefit of not having a duplicate encoder is the worth the tradeoffs here. All other crates in this repository are already deeply intertwined in terms of testing and feature support, and this is continuing that trend for the `wast` crate as well.


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3e5bebcd..df414bbc 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -92,6 +92,13 @@ jobs:
           # want this to break.
           - os: ubuntu-latest
             rust: nightly-2024-02-12
+          # test that if `RUST_BACKTRACE=1` is set in the environment that all
+          # tests with blessed error messages still pass.
+          - os: ubuntu-latest
+            rust: default
+            env:
+              RUST_BACKTRACE: 1
+    env: ${{ matrix.env || fromJSON('{}') }}
     steps:
     - uses: actions/checkout@v4
       with:
diff --git a/.github/workflows/release-process.yml b/.github/workflows/release-process.yml
index d6b9b060..a9f9f914 100644
--- a/.github/workflows/release-process.yml
+++ b/.github/workflows/release-process.yml
@@ -16,6 +16,10 @@ on:
         required: false
         default: 'bump'
 
+permissions:
+  contents: write
+  pull-requests: write
+
 jobs:
   release_process:
     name: Run the release process
diff --git a/crates/wasm-encoder/src/component/exports.rs b/crates/wasm-encoder/src/component/exports.rs
index e4fcfee0..4d988e57 100644
--- a/crates/wasm-encoder/src/component/exports.rs
+++ b/crates/wasm-encoder/src/component/exports.rs
@@ -93,8 +93,7 @@ impl ComponentExportSection {
         index: u32,
         ty: Option<ComponentTypeRef>,
     ) -> &mut Self {
-        crate::component::imports::push_extern_name_byte(&mut self.bytes, name);
-        name.encode(&mut self.bytes);
+        crate::encode_component_export_name(&mut self.bytes, name);
         kind.encode(&mut self.bytes);
         index.encode(&mut self.bytes);
         match ty {
@@ -122,3 +121,9 @@ impl ComponentSection for ComponentExportSection {
         ComponentSectionId::Export.into()
     }
 }
+
+/// For more information on this see `encode_component_import_name`.
+pub(crate) fn encode_component_export_name(bytes: &mut Vec<u8>, name: &str) {
+    bytes.push(0x00);
+    name.encode(bytes);
+}
diff --git a/crates/wasm-encoder/src/component/imports.rs b/crates/wasm-encoder/src/component/imports.rs
index 6337a062..b818a178 100644
--- a/crates/wasm-encoder/src/component/imports.rs
+++ b/crates/wasm-encoder/src/component/imports.rs
@@ -131,8 +131,7 @@ impl ComponentImportSection {
 
     /// Define an import in the component import section.
     pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self {
-        push_extern_name_byte(&mut self.bytes, name);
-        name.encode(&mut self.bytes);
+        encode_component_import_name(&mut self.bytes, name);
         ty.encode(&mut self.bytes);
         self.num_added += 1;
         self
@@ -155,21 +154,16 @@ impl ComponentSection for ComponentImportSection {
 /// discriminated with a leading byte indicating what kind of import they are.
 /// After that PR though names are always prefixed with a 0x00 byte.
 ///
-/// This function is a compatibility shim for the time being while this change
-/// is being rolled out. That PR is technically a spec-breaking change relative
-/// to prior but we want old tooling to continue to work with new modules. To
-/// handle that names that look like IDs, `a:b/c`, get an 0x01 prefix instead of
-/// the spec-defined 0x00 prefix. That makes components produced by current
-/// versions of this crate compatible with older parsers.
+/// On 2023-10-28 in bytecodealliance/wasm-tools#1262 was landed to start
+/// transitioning to "always lead with 0x00". That updated the validator/parser
+/// to accept either 0x00 or 0x01 but the encoder wasn't updated at the time.
 ///
-/// Note that wasmparser has a similar case where it parses either 0x01 or 0x00.
-/// That means that the selection of a byte here doesn't actually matter for
-/// wasmparser's own validation. Eventually this will go away and an 0x00 byte
-/// will always be emitted to align with the spec.
-pub(crate) fn push_extern_name_byte(bytes: &mut Vec<u8>, name: &str) {
-    if name.contains(':') {
-        bytes.push(0x01);
-    } else {
-        bytes.push(0x00);
-    }
+/// On 2024-09-03 in bytecodealliance/wasm-tools#TODO this encoder was updated
+/// to always emit 0x00 as a leading byte.
+///
+/// This function corresponds with the `importname'` production in the
+/// specification.
+pub(crate) fn encode_component_import_name(bytes: &mut Vec<u8>, name: &str) {
+    bytes.push(0x00);
+    name.encode(bytes);
 }
diff --git a/crates/wasm-encoder/src/component/instances.rs b/crates/wasm-encoder/src/component/instances.rs
index e0877d16..fd3fc3c3 100644
--- a/crates/wasm-encoder/src/component/instances.rs
+++ b/crates/wasm-encoder/src/component/instances.rs
@@ -177,8 +177,7 @@ impl ComponentInstanceSection {
         self.bytes.push(0x01);
         exports.len().encode(&mut self.bytes);
         for (name, kind, index) in exports {
-            crate::push_extern_name_byte(&mut self.bytes, name);
-            name.encode(&mut self.bytes);
+            crate::encode_component_export_name(&mut self.bytes, name);
             kind.encode(&mut self.bytes);
             index.encode(&mut self.bytes);
         }
diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs
index 006b9a33..aa79d69c 100644
--- a/crates/wasm-encoder/src/component/types.rs
+++ b/crates/wasm-encoder/src/component/types.rs
@@ -20,7 +20,7 @@ impl ModuleType {
 
     /// Defines an import in this module type.
     pub fn import(&mut self, module: &str, name: &str, ty: EntityType) -> &mut Self {
-        crate::component::imports::push_extern_name_byte(&mut self.bytes, name);
+        self.bytes.push(0x00);
         module.encode(&mut self.bytes);
         name.encode(&mut self.bytes);
         ty.encode(&mut self.bytes);
@@ -252,8 +252,7 @@ impl ComponentType {
     /// Defines an import in this component type.
     pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self {
         self.bytes.push(0x03);
-        crate::component::imports::push_extern_name_byte(&mut self.bytes, name);
-        name.encode(&mut self.bytes);
+        crate::encode_component_import_name(&mut self.bytes, name);
         ty.encode(&mut self.bytes);
         self.num_added += 1;
         match ty {
@@ -267,8 +266,7 @@ impl ComponentType {
     /// Defines an export in this component type.
     pub fn export(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self {
         self.bytes.push(0x04);
-        crate::component::imports::push_extern_name_byte(&mut self.bytes, name);
-        name.encode(&mut self.bytes);
+        crate::encode_component_export_name(&mut self.bytes, name);
         ty.encode(&mut self.bytes);
         self.num_added += 1;
         match ty {
diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs
index 4290ff42..30c8a2f1 100644
--- a/crates/wasmparser/src/features.rs
+++ b/crates/wasmparser/src/features.rs
@@ -229,26 +229,39 @@ define_wasm_features! {
 }
 
 impl WasmFeatures {
-    /// Returns the feature set associated with the 1.0 version of the
+    /// The feature set associated with the 1.0 version of the
     /// WebAssembly specification or the "MVP" feature set.
     #[cfg(feature = "features")]
-    pub fn wasm1() -> WasmFeatures {
-        WasmFeatures::FLOATS | WasmFeatures::GC_TYPES
-    }
+    pub const WASM1: WasmFeatures = WasmFeatures::FLOATS.union(WasmFeatures::GC_TYPES);
 
-    /// Returns the feature set associated with the 2.0 version of the
+    /// The feature set associated with the 2.0 version of the
     /// WebAssembly specification.
     #[cfg(feature = "features")]
-    pub fn wasm2() -> WasmFeatures {
-        WasmFeatures::wasm1()
-            | WasmFeatures::BULK_MEMORY
-            | WasmFeatures::REFERENCE_TYPES
-            | WasmFeatures::SIGN_EXTENSION
-            | WasmFeatures::MUTABLE_GLOBAL
-            | WasmFeatures::SATURATING_FLOAT_TO_INT
-            | WasmFeatures::MULTI_VALUE
-            | WasmFeatures::SIMD
-    }
+    pub const WASM2: WasmFeatures = WasmFeatures::WASM1
+        .union(WasmFeatures::BULK_MEMORY)
+        .union(WasmFeatures::REFERENCE_TYPES)
+        .union(WasmFeatures::SIGN_EXTENSION)
+        .union(WasmFeatures::MUTABLE_GLOBAL)
+        .union(WasmFeatures::SATURATING_FLOAT_TO_INT)
+        .union(WasmFeatures::MULTI_VALUE)
+        .union(WasmFeatures::SIMD);
+
+    /// The feature set associated with the 3.0 version of the
+    /// WebAssembly specification.
+    ///
+    /// Note that as of the time of this writing the 3.0 version of the
+    /// specification is not yet published. The precise set of features set
+    /// here may change as that continues to evolve.
+    #[cfg(feature = "features")]
+    pub const WASM3: WasmFeatures = WasmFeatures::WASM2
+        .union(WasmFeatures::GC)
+        .union(WasmFeatures::TAIL_CALL)
+        .union(WasmFeatures::EXTENDED_CONST)
+        .union(WasmFeatures::FUNCTION_REFERENCES)
+        .union(WasmFeatures::MULTI_MEMORY)
+        .union(WasmFeatures::RELAXED_SIMD)
+        .union(WasmFeatures::THREADS)
+        .union(WasmFeatures::EXCEPTIONS);
 }
 
 #[cfg(feature = "features")]
diff --git a/crates/wasmparser/src/readers/component/imports.rs b/crates/wasmparser/src/readers/component/imports.rs
index f62c1093..bd14428d 100644
--- a/crates/wasmparser/src/readers/component/imports.rs
+++ b/crates/wasmparser/src/readers/component/imports.rs
@@ -117,12 +117,27 @@ pub struct ComponentImportName<'a>(pub &'a str);
 impl<'a> FromReader<'a> for ComponentImportName<'a> {
     fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
         match reader.read_u8()? {
+            // This is the spec-required byte as of this time.
             0x00 => {}
-            // Historically export names used a discriminator byte of 0x01 to
-            // indicate an "interface" of the form `a:b/c` but nowadays that's
-            // inferred from string syntax. Ignore 0-vs-1 to continue to parse
-            // older binaries. Eventually this will go away.
+
+            // Prior to WebAssembly/component-model#263 export names used a
+            // discriminator byte of 0x01 to indicate an "interface" of the
+            // form `a:b/c` but nowadays that's inferred from string syntax.
+            // Ignore 0-vs-1 to continue to parse older binaries. Eventually
+            // this will go away.
+            //
+            // This logic to ignore 0x01 was landed on 2023-10-28 in
+            // bytecodealliance/wasm-tools#1262 and the encoder at the time
+            // still emitted 0x01 to have better compatibility with prior
+            // validators.
+            //
+            // On 2024-09-03 in bytecodealliance/wasm-tools#TODO the encoder
+            // was updated to always emit 0x00 as a leading byte. After enough
+            // time has passed this case may be able to be removed. When
+            // removing this it's probably best to do it with a `WasmFeatures`
+            // flag first to ensure there's an opt-in way of fixing things.
             0x01 => {}
+
             x => return reader.invalid_leading_byte(x, "import name"),
         }
         Ok(ComponentImportName(reader.read_string()?))
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index b64e559a..87582709 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -6,6 +6,7 @@ use crate::Wat;
 use std::marker;
 #[cfg(feature = "dwarf")]
 use std::path::Path;
+use wasm_encoder::SectionId;
 
 /// Options that can be specified when encoding a component or a module to
 /// customize what the final binary looks like.
@@ -135,33 +136,35 @@ pub(crate) fn encode(
     }
 
     let mut e = Encoder {
-        wasm: Vec::new(),
+        wasm: wasm_encoder::Module::new(),
         tmp: Vec::new(),
         customs: &customs,
     };
-    e.wasm.extend(b"\0asm");
-    e.wasm.extend(b"\x01\0\0\0");
 
     e.custom_sections(BeforeFirst);
 
-    e.section_list(1, Type, &types);
-    e.section_list(2, Import, &imports);
+    e.section_list(SectionId::Type, Type, &types);
+    e.section_list(SectionId::Import, Import, &imports);
 
     let functys = funcs.iter().map(|f| &f.ty).collect::<Vec<_>>();
-    e.section_list(3, Func, &functys);
-    e.section_list(4, Table, &tables);
-    e.section_list(5, Memory, &memories);
-    e.section_list(13, Tag, &tags);
-    e.section_list(6, Global, &globals);
-    e.section_list(7, Export, &exports);
+    e.section_list(SectionId::Function, Func, &functys);
+    e.section_list(SectionId::Table, Table, &tables);
+    e.section_list(SectionId::Memory, Memory, &memories);
+    e.section_list(SectionId::Tag, Tag, &tags);
+    e.section_list(SectionId::Global, Global, &globals);
+    e.section_list(SectionId::Export, Export, &exports);
     e.custom_sections(Before(Start));
     if let Some(start) = start.get(0) {
-        e.section(8, start);
+        e.wasm.section(&wasm_encoder::StartSection {
+            function_index: start.unwrap_u32(),
+        });
     }
     e.custom_sections(After(Start));
-    e.section_list(9, Elem, &elem);
+    e.section_list(SectionId::Element, Elem, &elem);
     if needs_data_count(&funcs) {
-        e.section(12, &data.len());
+        e.wasm.section(&wasm_encoder::DataCountSection {
+            count: data.len().try_into().unwrap(),
+        });
     }
 
     // Prepare to and emit the code section. This is where DWARF may optionally
@@ -175,17 +178,17 @@ pub(crate) fn encode(
     let mut dwarf = dwarf::Dwarf::new(num_import_funcs, opts, &names, &types);
     e.code_section(&funcs, num_import_funcs, dwarf.as_mut());
 
-    e.section_list(11, Data, &data);
+    e.section_list(SectionId::Data, Data, &data);
 
     if !names.is_empty() {
-        e.section(0, &("name", names));
+        e.custom_section("name", &names);
     }
     e.custom_sections(AfterLast);
     if let Some(dwarf) = &mut dwarf {
         dwarf.emit(&mut e);
     }
 
-    return e.wasm;
+    return e.wasm.finish();
 
     fn needs_data_count(funcs: &[&crate::core::Func<'_>]) -> bool {
         funcs
@@ -200,38 +203,44 @@ pub(crate) fn encode(
 }
 
 struct Encoder<'a> {
-    wasm: Vec<u8>,
+    wasm: wasm_encoder::Module,
     tmp: Vec<u8>,
     customs: &'a [&'a Custom<'a>],
 }
 
 impl Encoder<'_> {
-    fn section(&mut self, id: u8, section: &dyn Encode) {
+    fn section(&mut self, id: SectionId, section: &dyn Encode) {
         self.tmp.truncate(0);
         section.encode(&mut self.tmp);
-        self.wasm.push(id);
-        self.tmp.encode(&mut self.wasm);
+        self.wasm.section(&wasm_encoder::RawSection {
+            id: id as u8,
+            data: &self.tmp,
+        });
     }
 
     fn custom_sections(&mut self, place: CustomPlace) {
         for entry in self.customs.iter() {
             if entry.place() == place {
-                let mut data = Vec::new();
-                entry.encode(&mut data);
-                self.custom_section(entry.name(), &data);
+                self.custom_section(entry.name(), entry);
             }
         }
     }
 
-    fn custom_section(&mut self, name: &str, data: &[u8]) {
+    fn custom_section(&mut self, name: &str, data: &dyn Encode) {
         self.tmp.truncate(0);
-        name.encode(&mut self.tmp);
-        self.tmp.extend_from_slice(data);
-        self.wasm.push(0);
-        self.tmp.encode(&mut self.wasm);
-    }
-
-    fn section_list(&mut self, id: u8, anchor: CustomPlaceAnchor, list: &[impl Encode]) {
+        data.encode(&mut self.tmp);
+        self.wasm.section(&wasm_encoder::CustomSection {
+            name: name.into(),
+            data: (&self.tmp).into(),
+        });
+    }
+
+    fn section_list(
+        &mut self,
+        id: wasm_encoder::SectionId,
+        anchor: CustomPlaceAnchor,
+        list: &[impl Encode],
+    ) {
         self.custom_sections(CustomPlace::Before(anchor));
         if !list.is_empty() {
             self.section(id, &list)
@@ -276,12 +285,14 @@ impl Encoder<'_> {
             // Branch hints section has to be inserted before the Code section
             // Insert the section only if we have some hints
             if !branch_hints.is_empty() {
-                self.section(0, &("metadata.code.branch_hint", branch_hints));
+                self.custom_section("metadata.code.branch_hint", &branch_hints);
             }
 
             // Finally, insert the Code section from the tmp buffer
-            self.wasm.push(10);
-            code_section.encode(&mut self.wasm);
+            self.wasm.section(&wasm_encoder::RawSection {
+                id: wasm_encoder::SectionId::Code as u8,
+                data: &code_section,
+            });
 
             if let Some(dwarf) = &mut dwarf {
                 dwarf.set_code_section_size(code_section.len());
@@ -542,8 +553,14 @@ impl<T> Encode for TypeUse<'_, T> {
 
 impl Encode for Index<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
+        self.unwrap_u32().encode(e)
+    }
+}
+
+impl Index<'_> {
+    fn unwrap_u32(&self) -> u32 {
         match self {
-            Index::Num(n, _) => n.encode(e),
+            Index::Num(n, _) => *n,
             Index::Id(n) => panic!("unresolved index in emission: {:?}", n),
         }
     }
diff --git a/crates/wast/src/core/binary/dwarf.rs b/crates/wast/src/core/binary/dwarf.rs
index b6aa086e..b080f255 100644
--- a/crates/wast/src/core/binary/dwarf.rs
+++ b/crates/wast/src/core/binary/dwarf.rs
@@ -432,7 +432,10 @@ impl<'a> Dwarf<'a> {
         sections
             .for_each(|id, writer| {
                 if !writer.bytes.is_empty() {
-                    dst.custom_section(id.name(), &writer.bytes);
+                    dst.wasm.section(&wasm_encoder::CustomSection {
+                        name: id.name().into(),
+                        data: (&writer.bytes).into(),
+                    });
                 }
                 Ok::<_, std::convert::Infallible>(())
             })
diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs
index 7283c4da..a065665d 100644
--- a/src/bin/wasm-tools/validate.rs
+++ b/src/bin/wasm-tools/validate.rs
@@ -1,5 +1,5 @@
 use addr2line::LookupResult;
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
 use bitflags::Flags;
 use rayon::prelude::*;
 use std::fmt::Write;
@@ -183,53 +183,76 @@ impl Opts {
 fn parse_features(arg: &str) -> Result<WasmFeatures> {
     let mut ret = WasmFeatures::default();
 
-    fn flag_name(flag: &bitflags::Flag<WasmFeatures>) -> String {
-        flag.name().to_lowercase().replace('_', "-")
+    const GROUPS: &[(&str, WasmFeatures)] = &[
+        ("mvp", WasmFeatures::WASM1),
+        ("wasm1", WasmFeatures::WASM1),
+        ("wasm2", WasmFeatures::WASM2),
+        ("wasm3", WasmFeatures::WASM3),
+    ];
+
+    enum Action {
+        ChangeAll,
+        Group(WasmFeatures),
+        Modify(WasmFeatures),
+    }
+
+    fn actions() -> impl Iterator<Item = (&'static str, Action)> {
+        WasmFeatures::FLAGS
+            .iter()
+            .map(|f| (f.name(), Action::Modify(*f.value())))
+            .chain(
+                GROUPS
+                    .iter()
+                    .map(|(name, features)| (*name, Action::Group(*features))),
+            )
+            .chain([("all", Action::ChangeAll)])
+    }
+
+    fn flag_name(name: &str) -> String {
+        name.to_lowercase().replace('_', "-")
     }
 
-    for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
+    'outer: for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
         let (enable, part) = if let Some(part) = part.strip_prefix("-") {
             (false, part)
         } else {
             (true, part)
         };
-        match part {
-            "all" => {
-                for flag in WasmFeatures::FLAGS.iter() {
-                    ret.set(*flag.value(), enable);
-                }
+        for (name, action) in actions() {
+            if part != flag_name(name) {
+                continue;
             }
-            "wasm1" | "mvp" => {
-                if !enable {
-                    bail!("cannot disable `{part}`, it can only be enabled");
+            match action {
+                Action::ChangeAll => {
+                    for flag in WasmFeatures::FLAGS.iter() {
+                        ret.set(*flag.value(), enable);
+                    }
                 }
-                ret = WasmFeatures::wasm1();
-            }
-            "wasm2" => {
-                if !enable {
-                    bail!("cannot disable `{part}`, it can only be enabled");
+                Action::Modify(feature) => {
+                    ret.set(feature, enable);
+                }
+                Action::Group(features) => {
+                    if !enable {
+                        bail!("cannot disable `{part}`, it can only be enabled");
+                    }
+                    ret = features;
                 }
-                ret = WasmFeatures::wasm2();
             }
+            continue 'outer;
+        }
 
-            name => {
-                let flag = WasmFeatures::FLAGS
-                    .iter()
-                    .find(|f| flag_name(f) == name)
-                    .ok_or_else(|| {
-                        anyhow!(
-                            "unknown feature `{}`\nValid features: {}",
-                            name,
-                            WasmFeatures::FLAGS
-                                .iter()
-                                .map(flag_name)
-                                .collect::<Vec<_>>()
-                                .join(", "),
-                        )
-                    })?;
-                ret.set(*flag.value(), enable);
+        let mut error = format!("unknown feature `{part}`\n");
+        error.push_str("Valid features: ");
+        let mut first = true;
+        for (name, _) in actions() {
+            if first {
+                first = false;
+            } else {
+                error.push_str(", ");
             }
+            error.push_str(&flag_name(name));
         }
+        bail!("{error}")
     }
 
     Ok(ret)


Test patch:
diff --git a/crates/wasm-smith/tests/common/mod.rs b/crates/wasm-smith/tests/common/mod.rs
index 332fd0ca..688bb87f 100644
--- a/crates/wasm-smith/tests/common/mod.rs
+++ b/crates/wasm-smith/tests/common/mod.rs
@@ -2,7 +2,7 @@ use wasm_smith::Config;
 use wasmparser::{types::Types, Validator, WasmFeatures};
 
 pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
-    let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::wasm1();
+    let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::WASM1;
     features.set(
         WasmFeatures::SATURATING_FLOAT_TO_INT,
         config.saturating_float_to_int_enabled,
diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs
index 8f2951f9..3684b8ec 100644
--- a/crates/wit-component/tests/components.rs
+++ b/crates/wit-component/tests/components.rs
@@ -147,7 +147,7 @@ fn run_test(path: &Path) -> Result<()> {
             if !test_case.starts_with("error-") {
                 return Err(err);
             }
-            assert_output(&format!("{err:?}"), &error_path)?;
+            assert_output(&format!("{err:#}"), &error_path)?;
             return Ok(());
         }
     };
diff --git a/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt
index 904d1aef..72264c4a 100644
--- a/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt
+++ b/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
+failed to decode world from module: module was not valid: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-empty-module-import/error.txt b/crates/wit-component/tests/components/error-empty-module-import/error.txt
index 75f7337b..f68bb1b6 100644
--- a/crates/wit-component/tests/components/error-empty-module-import/error.txt
+++ b/crates/wit-component/tests/components/error-empty-module-import/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: no top-level imported function `foo` specified
\ No newline at end of file
+failed to decode world from module: module was not valid: no top-level imported function `foo` specified
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt
index 0588ebcf..3b1c6afa 100644
--- a/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt
+++ b/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt
@@ -1,6 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: failed to validate exported interface `foo`
-    2: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
+failed to decode world from module: module was not valid: failed to validate exported interface `foo`: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-import-resource-rep/error.txt b/crates/wit-component/tests/components/error-import-resource-rep/error.txt
index f9f291d9..7dd176e6 100644
--- a/crates/wit-component/tests/components/error-import-resource-rep/error.txt
+++ b/crates/wit-component/tests/components/error-import-resource-rep/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: no top-level imported function `[resource-rep]a` specified
\ No newline at end of file
+failed to decode world from module: module was not valid: no top-level imported function `[resource-rep]a` specified
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt b/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt
index e76b70fe..d7eaac43 100644
--- a/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt
+++ b/crates/wit-component/tests/components/error-import-resource-wrong-signature/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: type mismatch for function `[resource-drop]a`: expected `[I32] -> []` but found `[I32] -> [I32]`
\ No newline at end of file
+failed to decode world from module: module was not valid: type mismatch for function `[resource-drop]a`: expected `[I32] -> []` but found `[I32] -> [I32]`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-import-sig-mismatch/error.txt b/crates/wit-component/tests/components/error-import-sig-mismatch/error.txt
index 57dae399..41ba5e77 100644
--- a/crates/wit-component/tests/components/error-import-sig-mismatch/error.txt
+++ b/crates/wit-component/tests/components/error-import-sig-mismatch/error.txt
@@ -1,6 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: failed to validate import interface `foo`
-    2: type mismatch for function `bar`: expected `[I32, I32] -> []` but found `[] -> []`
\ No newline at end of file
+failed to decode world from module: module was not valid: failed to validate import interface `foo`: type mismatch for function `bar`: expected `[I32, I32] -> []` but found `[] -> []`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-invalid-module-import/error.txt b/crates/wit-component/tests/components/error-invalid-module-import/error.txt
index 4a54c821..4c5195ec 100644
--- a/crates/wit-component/tests/components/error-invalid-module-import/error.txt
+++ b/crates/wit-component/tests/components/error-invalid-module-import/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: module is only allowed to import functions
\ No newline at end of file
+failed to decode world from module: module was not valid: module is only allowed to import functions
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-missing-default-export/error.txt b/crates/wit-component/tests/components/error-missing-default-export/error.txt
index 1dc1b01f..cd0b78a0 100644
--- a/crates/wit-component/tests/components/error-missing-default-export/error.txt
+++ b/crates/wit-component/tests/components/error-missing-default-export/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: module does not export required function `a`
\ No newline at end of file
+failed to decode world from module: module was not valid: module does not export required function `a`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-missing-export/error.txt b/crates/wit-component/tests/components/error-missing-export/error.txt
index 6d4148bd..0829150e 100644
--- a/crates/wit-component/tests/components/error-missing-export/error.txt
+++ b/crates/wit-component/tests/components/error-missing-export/error.txt
@@ -1,6 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: failed to validate exported interface `foo`
-    2: module does not export required function `foo#a`
\ No newline at end of file
+failed to decode world from module: module was not valid: failed to validate exported interface `foo`: module does not export required function `foo#a`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-missing-import-func/error.txt b/crates/wit-component/tests/components/error-missing-import-func/error.txt
index 388a6087..b6f4f0dd 100644
--- a/crates/wit-component/tests/components/error-missing-import-func/error.txt
+++ b/crates/wit-component/tests/components/error-missing-import-func/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: module requires an import interface named `foo`
\ No newline at end of file
+failed to decode world from module: module was not valid: module requires an import interface named `foo`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-missing-import/error.txt b/crates/wit-component/tests/components/error-missing-import/error.txt
index 388a6087..b6f4f0dd 100644
--- a/crates/wit-component/tests/components/error-missing-import/error.txt
+++ b/crates/wit-component/tests/components/error-missing-import/error.txt
@@ -1,5 +1,1 @@
-failed to decode world from module
-
-Caused by:
-    0: module was not valid
-    1: module requires an import interface named `foo`
\ No newline at end of file
+failed to decode world from module: module was not valid: module requires an import interface named `foo`
\ No newline at end of file
diff --git a/crates/wit-component/tests/components/error-missing-module-metadata/error.txt b/crates/wit-component/tests/components/error-missing-module-metadata/error.txt
index 16f77976..a256db38 100644
--- a/crates/wit-component/tests/components/error-missing-module-metadata/error.txt
+++ b/crates/wit-component/tests/components/error-missing-module-metadata/error.txt
@@ -1,4 +1,1 @@
-failed to register indirect shims for main module
-
-Caused by:
-    missing component metadata for import of `new::log`
\ No newline at end of file
+failed to register indirect shims for main module: missing component metadata for import of `new::log`
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge.rs b/crates/wit-component/tests/merge.rs
index f3b3918b..f7f72f2f 100644
--- a/crates/wit-component/tests/merge.rs
+++ b/crates/wit-component/tests/merge.rs
@@ -52,7 +52,7 @@ fn merging() -> Result<()> {
             }
             Err(e) => {
                 assert!(test_case.starts_with("bad-"), "failed to merge with {e:?}");
-                assert_output(&path.join("error.txt"), &format!("{e:?}"))?;
+                assert_output(&path.join("error.txt"), &format!("{e:#}"))?;
             }
         }
     }
diff --git a/crates/wit-component/tests/merge/bad-interface1/error.txt b/crates/wit-component/tests/merge/bad-interface1/error.txt
index 951bb365..0558df13 100644
--- a/crates/wit-component/tests/merge/bad-interface1/error.txt
+++ b/crates/wit-component/tests/merge/bad-interface1/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge interface `a`
-    1: expected type `a` to be present
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge interface `a`: expected type `a` to be present
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-interface2/error.txt b/crates/wit-component/tests/merge/bad-interface2/error.txt
index bbb5f232..0b3f674b 100644
--- a/crates/wit-component/tests/merge/bad-interface2/error.txt
+++ b/crates/wit-component/tests/merge/bad-interface2/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge interface `a`
-    1: expected function `a` to be present
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge interface `a`: expected function `a` to be present
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-world1/error.txt b/crates/wit-component/tests/merge/bad-world1/error.txt
index f04e2ed0..059b1e3d 100644
--- a/crates/wit-component/tests/merge/bad-world1/error.txt
+++ b/crates/wit-component/tests/merge/bad-world1/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge world `foo`
-    1: import `b` not found in target world
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge world `foo`: import `b` not found in target world
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-world2/error.txt b/crates/wit-component/tests/merge/bad-world2/error.txt
index bca74e7b..5f636c13 100644
--- a/crates/wit-component/tests/merge/bad-world2/error.txt
+++ b/crates/wit-component/tests/merge/bad-world2/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge world `foo`
-    1: world contains different number of imports than expected
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge world `foo`: world contains different number of imports than expected
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-world3/error.txt b/crates/wit-component/tests/merge/bad-world3/error.txt
index daf6860f..1311233f 100644
--- a/crates/wit-component/tests/merge/bad-world3/error.txt
+++ b/crates/wit-component/tests/merge/bad-world3/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge world `foo`
-    1: world contains different number of exports than expected
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge world `foo`: world contains different number of exports than expected
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-world4/error.txt b/crates/wit-component/tests/merge/bad-world4/error.txt
index 5cf21c92..9b950703 100644
--- a/crates/wit-component/tests/merge/bad-world4/error.txt
+++ b/crates/wit-component/tests/merge/bad-world4/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge world `foo`
-    1: export `b` not found in target world
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge world `foo`: export `b` not found in target world
\ No newline at end of file
diff --git a/crates/wit-component/tests/merge/bad-world5/error.txt b/crates/wit-component/tests/merge/bad-world5/error.txt
index 9eb40e75..38c83bb9 100644
--- a/crates/wit-component/tests/merge/bad-world5/error.txt
+++ b/crates/wit-component/tests/merge/bad-world5/error.txt
@@ -1,5 +1,1 @@
-failed to merge package `foo:foo` into existing copy
-
-Caused by:
-    0: failed to merge world `foo`
-    1: import `a` not found in target world
\ No newline at end of file
+failed to merge package `foo:foo` into existing copy: failed to merge world `foo`: import `a` not found in target world
\ No newline at end of file
diff --git a/crates/wit-component/tests/targets.rs b/crates/wit-component/tests/targets.rs
index 7bbd96ff..ff257285 100644
--- a/crates/wit-component/tests/targets.rs
+++ b/crates/wit-component/tests/targets.rs
@@ -50,7 +50,7 @@ fn targets() -> Result<()> {
             }
             Err(e) => {
                 assert!(test_case.starts_with("error-"), "{e:?}");
-                assert_output(&path.join("error.txt"), &format!("{e:?}"))?;
+                assert_output(&path.join("error.txt"), &format!("{e:#}"))?;
             }
         }
     }
diff --git a/crates/wit-component/tests/targets/error-missing-export/error.txt b/crates/wit-component/tests/targets/error-missing-export/error.txt
index ebfccba1..5751a106 100644
--- a/crates/wit-component/tests/targets/error-missing-export/error.txt
+++ b/crates/wit-component/tests/targets/error-missing-export/error.txt
@@ -1,5 +1,2 @@
-failed to validate encoded bytes
-
-Caused by:
-    type mismatch for import `foobar`
-    missing export named `test:foo/bar` (at offset 0x1d5)
\ No newline at end of file
+failed to validate encoded bytes: type mismatch for import `foobar`
+missing export named `test:foo/bar` (at offset 0x1d5)
\ No newline at end of file
diff --git a/crates/wit-component/tests/targets/error-missing-import/error.txt b/crates/wit-component/tests/targets/error-missing-import/error.txt
index 3a55fb02..d5d32ff9 100644
--- a/crates/wit-component/tests/targets/error-missing-import/error.txt
+++ b/crates/wit-component/tests/targets/error-missing-import/error.txt
@@ -1,5 +1,2 @@
-failed to validate encoded bytes
-
-Caused by:
-    type mismatch for import `foobar`
-    missing import named `test:foo/foo` (at offset 0x175)
\ No newline at end of file
+failed to validate encoded bytes: type mismatch for import `foobar`
+missing import named `test:foo/foo` (at offset 0x175)
\ No newline at end of file
diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs
index 6c7af1f7..ec9d4be9 100644
--- a/crates/wit-parser/tests/all.rs
+++ b/crates/wit-parser/tests/all.rs
@@ -87,7 +87,7 @@ impl Runner {
                             "some generic platform-agnostic error message",
                         );
                     }
-                    format!("{:?}", e)
+                    format!("{:#}", e)
                 }
             }
         } else {
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result
index e8e4df1d..181a55f6 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result
@@ -1,8 +1,5 @@
-this type is not gated by a feature but its interface is gated by a feature
-
-Caused by:
-    found a reference to a interface which is excluded due to its feature not being activated
-         --> tests/ui/parse-fail/bad-gate3.wit:5:8
-          |
-        5 |   type a = u32;
-          |        ^
\ No newline at end of file
+this type is not gated by a feature but its interface is gated by a feature: found a reference to a interface which is excluded due to its feature not being activated
+     --> tests/ui/parse-fail/bad-gate3.wit:5:8
+      |
+    5 |   type a = u32;
+      |        ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result
index 8b47145a..956ba063 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result
@@ -1,8 +1,5 @@
-failed to update function `[constructor]a`
-
-Caused by:
-    found a reference to a type which is excluded due to its feature not being activated
-         --> tests/ui/parse-fail/bad-gate4.wit:6:5
-          |
-        6 |     constructor();
-          |     ^----------
\ No newline at end of file
+failed to update function `[constructor]a`: found a reference to a type which is excluded due to its feature not being activated
+     --> tests/ui/parse-fail/bad-gate4.wit:6:5
+      |
+    6 |     constructor();
+      |     ^----------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result
index df063dd3..6c36fb3f 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result
@@ -1,8 +1,5 @@
-failed to update function `[static]a.x`
-
-Caused by:
-    found a reference to a type which is excluded due to its feature not being activated
-         --> tests/ui/parse-fail/bad-gate5.wit:9:5
-          |
-        9 |     x: static func();
-          |     ^
\ No newline at end of file
+failed to update function `[static]a.x`: found a reference to a type which is excluded due to its feature not being activated
+     --> tests/ui/parse-fail/bad-gate5.wit:9:5
+      |
+    9 |     x: static func();
+      |     ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result
index e366b729..ddc7c7c3 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg1]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/bad-pkg1
-    1: interface or world `nonexistent` not found in package
-            --> tests/ui/parse-fail/bad-pkg1/root.wit:4:7
-             |
-           4 |   use nonexistent.{};
-             |       ^----------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg1]: failed to parse package: tests/ui/parse-fail/bad-pkg1: interface or world `nonexistent` not found in package
+     --> tests/ui/parse-fail/bad-pkg1/root.wit:4:7
+      |
+    4 |   use nonexistent.{};
+      |       ^----------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result
index 3827d5db..235a806d 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg2.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg2]
-
-Caused by:
-    interface not found in package
-         --> tests/ui/parse-fail/bad-pkg2/root.wit:4:15
-          |
-        4 |   use foo:bar/nonexistent.{};
-          |               ^----------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg2]: interface not found in package
+     --> tests/ui/parse-fail/bad-pkg2/root.wit:4:15
+      |
+    4 |   use foo:bar/nonexistent.{};
+      |               ^----------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result
index f5d61a28..d3d2102d 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg3.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg3]
-
-Caused by:
-    interface not found in package
-         --> tests/ui/parse-fail/bad-pkg3/root.wit:4:15
-          |
-        4 |   use foo:bar/baz.{};
-          |               ^--
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg3]: interface not found in package
+     --> tests/ui/parse-fail/bad-pkg3/root.wit:4:15
+      |
+    4 |   use foo:bar/baz.{};
+      |               ^--
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result
index dac51efa..e4522856 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg4.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg4]
-
-Caused by:
-    type `a-name` not defined in interface
-         --> tests/ui/parse-fail/bad-pkg4/root.wit:3:20
-          |
-        3 |   use foo:bar/baz.{a-name};
-          |                    ^-----
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg4]: type `a-name` not defined in interface
+     --> tests/ui/parse-fail/bad-pkg4/root.wit:3:20
+      |
+    3 |   use foo:bar/baz.{a-name};
+      |                    ^-----
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result
index 2d37060f..0251000c 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg5.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg5]
-
-Caused by:
-    type `nonexistent` not defined in interface
-         --> tests/ui/parse-fail/bad-pkg5/root.wit:3:20
-          |
-        3 |   use foo:bar/baz.{nonexistent};
-          |                    ^----------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg5]: type `nonexistent` not defined in interface
+     --> tests/ui/parse-fail/bad-pkg5/root.wit:3:20
+      |
+    3 |   use foo:bar/baz.{nonexistent};
+      |                    ^----------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
index c0fdc9d9..56b5c85e 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg6]
-
-Caused by:
-    package not found
-         --> tests/ui/parse-fail/bad-pkg6/root.wit:3:7
-          |
-        3 |   use foo:bar/baz.{};
-          |       ^------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg6]: package not found
+     --> tests/ui/parse-fail/bad-pkg6/root.wit:3:7
+      |
+    3 |   use foo:bar/baz.{};
+      |       ^------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-resource15.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-resource15.wit.result
index c26467f8..1ed0a4f5 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-resource15.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-resource15.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-resource15]
-
-Caused by:
-    type used in a handle must be a resource
-         --> tests/ui/parse-fail/bad-resource15/foo.wit:6:16
-          |
-        6 |   type t = own<r>;
-          |                ^
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-resource15]: type used in a handle must be a resource
+     --> tests/ui/parse-fail/bad-resource15/foo.wit:6:16
+      |
+    6 |   type t = own<r>;
+      |                ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result b/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result
index bffa1fc4..dc9dcee4 100644
--- a/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result
@@ -1,10 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/conflicting-package]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/conflicting-package
-    1: failed to start resolving path: tests/ui/parse-fail/conflicting-package/b.wit
-    2: package identifier `foo:b` does not match previous package name of `foo:a`
-            --> tests/ui/parse-fail/conflicting-package/b.wit:1:9
-             |
-           1 | package foo:b;
-             |         ^----
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/conflicting-package]: failed to parse package: tests/ui/parse-fail/conflicting-package: failed to start resolving path: tests/ui/parse-fail/conflicting-package/b.wit: package identifier `foo:b` does not match previous package name of `foo:a`
+     --> tests/ui/parse-fail/conflicting-package/b.wit:1:9
+      |
+    1 | package foo:b;
+      |         ^----
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface2.wit.result b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface2.wit.result
index 81735c76..8eb038bf 100644
--- a/crates/wit-parser/tests/ui/parse-fail/duplicate-interface2.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/duplicate-interface2.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/duplicate-interface2]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/duplicate-interface2
-    1: duplicate item named `foo`
-            --> tests/ui/parse-fail/duplicate-interface2/foo2.wit:3:11
-             |
-           3 | interface foo {}
-             |           ^--
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/duplicate-interface2]: failed to parse package: tests/ui/parse-fail/duplicate-interface2: duplicate item named `foo`
+     --> tests/ui/parse-fail/duplicate-interface2/foo2.wit:3:11
+      |
+    3 | interface foo {}
+      |           ^--
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result b/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result
index 00257283..495d3310 100644
--- a/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/include-foreign]
-
-Caused by:
-    world not found in package
-         --> tests/ui/parse-fail/include-foreign/root.wit:4:19
-          |
-        4 |   include foo:bar/bar;
-          |                   ^--
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/include-foreign]: world not found in package
+     --> tests/ui/parse-fail/include-foreign/root.wit:4:19
+      |
+    4 |   include foo:bar/bar;
+      |                   ^--
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result
index 45400e63..8182a1a4 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multi-file-missing-delimiter]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/multi-file-missing-delimiter
-    1: expected `type`, `resource` or `func`, found eof
-            --> tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit:6:1
-             |
-           6 | 
-             | ^
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multi-file-missing-delimiter]: failed to parse package: tests/ui/parse-fail/multi-file-missing-delimiter: expected `type`, `resource` or `func`, found eof
+     --> tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit:6:1
+      |
+    6 | 
+      | ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest.wit.result b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest.wit.result
index 31c994e7..053a98b1 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest.wit.result
@@ -1,6 +1,3 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multi-package-deps-share-nest]
-
-Caused by:
-    package foo:shared is defined in two different locations:
-    * tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep2/types.wit:3:9
-    * tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep1/types.wit:3:9
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multi-package-deps-share-nest]: package foo:shared is defined in two different locations:
+* tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep2/types.wit:3:9
+* tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep1/types.wit:3:9
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result b/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result
index 4ce44238..fd70371f 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result
@@ -1,10 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multiple-package-docs]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/multiple-package-docs
-    1: failed to start resolving path: tests/ui/parse-fail/multiple-package-docs/b.wit
-    2: found doc comments on multiple 'package' items
-            --> tests/ui/parse-fail/multiple-package-docs/b.wit:1:1
-             |
-           1 | /// Multiple package docs, B
-             | ^---------------------------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multiple-package-docs]: failed to parse package: tests/ui/parse-fail/multiple-package-docs: failed to start resolving path: tests/ui/parse-fail/multiple-package-docs/b.wit: found doc comments on multiple 'package' items
+     --> tests/ui/parse-fail/multiple-package-docs/b.wit:1:1
+      |
+    1 | /// Multiple package docs, B
+      | ^---------------------------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result
index 72830f2b..91c39804 100644
--- a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/no-access-to-sibling-use]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/no-access-to-sibling-use
-    1: interface or world `bar-renamed` does not exist
-            --> tests/ui/parse-fail/no-access-to-sibling-use/foo.wit:3:5
-             |
-           3 | use bar-renamed;
-             |     ^----------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/no-access-to-sibling-use]: failed to parse package: tests/ui/parse-fail/no-access-to-sibling-use: interface or world `bar-renamed` does not exist
+     --> tests/ui/parse-fail/no-access-to-sibling-use/foo.wit:3:5
+      |
+    3 | use bar-renamed;
+      |     ^----------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result
index 13a11227..f7c853c8 100644
--- a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/non-existance-world-include]
-
-Caused by:
-    world not found in package
-         --> tests/ui/parse-fail/non-existance-world-include/root.wit:4:19
-          |
-        4 |   include foo:baz/non-existance;
-          |                   ^------------
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/non-existance-world-include]: world not found in package
+     --> tests/ui/parse-fail/non-existance-world-include/root.wit:4:19
+      |
+    4 |   include foo:baz/non-existance;
+      |                   ^------------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result
index f6497093..250e1581 100644
--- a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/pkg-cycle]
-
-Caused by:
-    package depends on itself
-         --> tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit:3:7
-          |
-        3 |   use foo:a1/foo.{};
-          |       ^-----
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/pkg-cycle]: package depends on itself
+     --> tests/ui/parse-fail/pkg-cycle/deps/a1/root.wit:3:7
+      |
+    3 |   use foo:a1/foo.{};
+      |       ^-----
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result
index 53054871..ec10e18c 100644
--- a/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/pkg-cycle2.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/pkg-cycle2]
-
-Caused by:
-    package depends on itself
-         --> tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit:3:7
-          |
-        3 |   use foo:a2/foo.{};
-          |       ^-----
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/pkg-cycle2]: package depends on itself
+     --> tests/ui/parse-fail/pkg-cycle2/deps/a1/root.wit:3:7
+      |
+    3 |   use foo:a2/foo.{};
+      |       ^-----
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result
index 2094237f..ba5d4f7f 100644
--- a/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result
@@ -1,8 +1,5 @@
-failed to update function `[method]r1.f1`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/resources-multiple-returns-borrow.wit:7:5
-          |
-        7 |     f1: func() -> (a: s32, handle: borrow<r1>);
-          |     ^-
\ No newline at end of file
+failed to update function `[method]r1.f1`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/resources-multiple-returns-borrow.wit:7:5
+      |
+    7 |     f1: func() -> (a: s32, handle: borrow<r1>);
+      |     ^-
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result
index 79de3292..b6a7361b 100644
--- a/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result
@@ -1,8 +1,5 @@
-failed to update function `[method]r1.f1`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/resources-return-borrow.wit:7:5
-          |
-        7 |     f1: func() -> borrow<r1>;
-          |     ^-
\ No newline at end of file
+failed to update function `[method]r1.f1`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/resources-return-borrow.wit:7:5
+      |
+    7 |     f1: func() -> borrow<r1>;
+      |     ^-
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result
index 0a0468dd..a4ddb59f 100644
--- a/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result
@@ -1,8 +1,5 @@
-failed to update function `x`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/return-borrow1.wit:6:3
-          |
-        6 |   x: func() -> borrow<y>;
-          |   ^
\ No newline at end of file
+failed to update function `x`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow1.wit:6:3
+      |
+    6 |   x: func() -> borrow<y>;
+      |   ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result
index b46631bb..01c7f6da 100644
--- a/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result
@@ -1,8 +1,5 @@
-failed to update function `[method]y.x`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/return-borrow2.wit:5:5
-          |
-        5 |     x: func() -> borrow<y>;
-          |     ^
\ No newline at end of file
+failed to update function `[method]y.x`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow2.wit:5:5
+      |
+    5 |     x: func() -> borrow<y>;
+      |     ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result
index 13d61fba..e1264b57 100644
--- a/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result
@@ -1,8 +1,5 @@
-failed to update function `x`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/return-borrow6.wit:6:3
-          |
-        6 |   x: func() -> tuple<borrow<y>>;
-          |   ^
\ No newline at end of file
+failed to update function `x`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow6.wit:6:3
+      |
+    6 |   x: func() -> tuple<borrow<y>>;
+      |   ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result
index 3caaf3fe..f2175ae6 100644
--- a/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result
@@ -1,8 +1,5 @@
-failed to update function `x`
-
-Caused by:
-    function returns a type which contains a `borrow<T>` which is not supported
-         --> tests/ui/parse-fail/return-borrow7.wit:10:3
-          |
-       10 |   x: func() -> y2;
-          |   ^
\ No newline at end of file
+failed to update function `x`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow7.wit:10:3
+      |
+   10 |   x: func() -> y2;
+      |   ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result
index 9fd1f027..3ace7b8b 100644
--- a/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/return-borrow8]
-
-Caused by:
-    0: failed to update function `x`
-    1: function returns a type which contains a `borrow<T>` which is not supported
-            --> tests/ui/parse-fail/return-borrow8/foo.wit:6:3
-             |
-           6 |   x: func() -> r;
-             |   ^
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/return-borrow8]: failed to update function `x`: function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow8/foo.wit:6:3
+      |
+    6 |   x: func() -> r;
+      |   ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result b/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result
index d86ebfb3..782921c6 100644
--- a/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/type-and-resource-same-name.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/type-and-resource-same-name]
-
-Caused by:
-    type used in a handle must be a resource
-         --> tests/ui/parse-fail/type-and-resource-same-name/foo.wit:7:20
-          |
-        7 |   type t2 = borrow<a>;
-          |                    ^
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/type-and-resource-same-name]: type used in a handle must be a resource
+     --> tests/ui/parse-fail/type-and-resource-same-name/foo.wit:7:20
+      |
+    7 |   type t2 = borrow<a>;
+      |                    ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result
index 75343de6..8d36b21d 100644
--- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/unresolved-use10]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/unresolved-use10
-    1: name `thing` is not defined
-            --> tests/ui/parse-fail/unresolved-use10/bar.wit:4:12
-             |
-           4 |   use foo.{thing};
-             |            ^----
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/unresolved-use10]: failed to parse package: tests/ui/parse-fail/unresolved-use10: name `thing` is not defined
+     --> tests/ui/parse-fail/unresolved-use10/bar.wit:4:12
+      |
+    4 |   use foo.{thing};
+      |            ^----
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result
index 0d4fcd91..e61a285a 100644
--- a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result
@@ -1,9 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-and-include-world]
-
-Caused by:
-    0: failed to parse package: tests/ui/parse-fail/use-and-include-world
-    1: name `bar` is defined as an interface, not a world
-            --> tests/ui/parse-fail/use-and-include-world/root.wit:6:21
-             |
-           6 |     include foo:baz/bar;
-             |                     ^--
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-and-include-world]: failed to parse package: tests/ui/parse-fail/use-and-include-world: name `bar` is defined as an interface, not a world
+     --> tests/ui/parse-fail/use-and-include-world/root.wit:6:21
+      |
+    6 |     include foo:baz/bar;
+      |                     ^--
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result
index df56488b..a8597257 100644
--- a/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result
@@ -1,8 +1,5 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-world]
-
-Caused by:
-    interface not found in package
-         --> tests/ui/parse-fail/use-world/root.wit:3:13
-          |
-        3 | use foo:baz/bar;
-          |             ^--
\ No newline at end of file
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/use-world]: interface not found in package
+     --> tests/ui/parse-fail/use-world/root.wit:3:13
+      |
+    3 | use foo:baz/bar;
+      |             ^--
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result
index eaf0e670..3109de95 100644
--- a/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result
@@ -1,8 +1,5 @@
-failed to handle nested package in: tests/ui/parse-fail/very-nested-packages.wit
-
-Caused by:
-    nested packages must be placed at the top-level
-         --> tests/ui/parse-fail/very-nested-packages.wit:4:11
-          |
-        4 |   package a:c2 {
-          |           ^---
\ No newline at end of file
+failed to handle nested package in: tests/ui/parse-fail/very-nested-packages.wit: nested packages must be placed at the top-level
+     --> tests/ui/parse-fail/very-nested-packages.wit:4:11
+      |
+    4 |   package a:c2 {
+      |           ^---
\ No newline at end of file
diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr
index 3e0ccb84..95e79d76 100644
--- a/tests/cli/validate-unknown-features.wat.stderr
+++ b/tests/cli/validate-unknown-features.wat.stderr
@@ -1,4 +1,4 @@
 error: invalid value 'unknown' for '--features <FEATURES>': unknown feature `unknown`
-Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types
+Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types, mvp, wasm1, wasm2, wasm3, all
 
 For more information, try '--help'.
diff --git a/tests/local/component-model/import.wast b/tests/local/component-model/import.wast
index a04823cf..b12eca5e 100644
--- a/tests/local/component-model/import.wast
+++ b/tests/local/component-model/import.wast
@@ -341,3 +341,24 @@
 (assert_invalid
   (component (import "integrity=<md5-ABC>" (func)))
   "unrecognized hash algorithm")
+
+;; Prior to WebAssembly/component-model#263 this was a valid component.
+;; Specifically the 0x01 prefix byte on the import was valid. Nowadays that's
+;; not valid in the spec but it's accepted for backwards compatibility. This
+;; tests is here to ensure such compatibility. In the future this test should
+;; be changed to `(assert_invalid ...)`
+(component binary
+  "\00asm" "\0d\00\01\00"   ;; component header
+
+  "\07\05"          ;; type section, 5 bytes large
+  "\01"             ;; 1 count
+  "\40"             ;; function
+  "\00"             ;; parameters, 0 count
+  "\01\00"          ;; results, named, 0 count
+
+  "\0a\06"          ;; import section, 6 bytes large
+  "\01"             ;; 1 count
+  "\01"             ;; prefix byte of 0x01 (invalid by the spec nowadays)
+  "\01a"            ;; name = "a"
+  "\01\00"          ;; type = func ($type 0)
+)
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index a5b15c4c..5861c6b4 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -32,6 +32,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
 use std::sync::Arc;
 use wasm_encoder::reencode::{Reencode, ReencodeComponent, RoundtripReencoder};
 use wasmparser::*;
+use wast::component::{Component, ComponentKind};
 use wast::core::{Module, ModuleKind};
 use wast::lexer::Lexer;
 use wast::parser::ParseBuffer;
@@ -301,8 +302,11 @@ impl TestState {
                     QuoteWat::Wat(Wat::Module(Module {
                         kind: ModuleKind::Binary(_),
                         ..
+                    }))
+                    | QuoteWat::Wat(Wat::Component(Component {
+                        kind: ComponentKind::Binary(_),
+                        ..
                     })) => false,
-
                     _ => true,
                 };
 
@@ -598,16 +602,9 @@ impl TestState {
         for part in test.iter().filter_map(|t| t.to_str()) {
             match part {
                 "testsuite" => {
-                    features = WasmFeatures::wasm2();
-                    features |= WasmFeatures::TAIL_CALL;
-                    features |= WasmFeatures::EXTENDED_CONST;
-
-                    // NB: when these proposals are merged upstream in the spec
-                    // repo then this should be removed. Currently this hasn't
-                    // happened so this is required to get tests passing for
-                    // when these proposals are enabled by default.
-                    features.remove(WasmFeatures::MULTI_MEMORY);
-                    features.remove(WasmFeatures::THREADS);
+                    features = WasmFeatures::WASM2
+                        | WasmFeatures::TAIL_CALL
+                        | WasmFeatures::EXTENDED_CONST;
                 }
                 "missing-features" => {
                     features =
@@ -624,14 +621,7 @@ impl TestState {
                 "exception-handling" => features.insert(WasmFeatures::EXCEPTIONS),
                 "legacy-exceptions" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS),
                 "tail-call" => features.insert(WasmFeatures::TAIL_CALL),
-                "memory64" => features.insert(
-                    WasmFeatures::MEMORY64
-                        | WasmFeatures::GC
-                        | WasmFeatures::REFERENCE_TYPES
-                        | WasmFeatures::MULTI_MEMORY
-                        | WasmFeatures::FUNCTION_REFERENCES
-                        | WasmFeatures::EXCEPTIONS,
-                ),
+                "memory64" => features.insert(WasmFeatures::MEMORY64 | WasmFeatures::WASM3),
                 "component-model" => features.insert(WasmFeatures::COMPONENT_MODEL),
                 "shared-everything-threads" => {
                     features.insert(WasmFeatures::COMPONENT_MODEL);
diff --git a/tests/snapshots/local/component-model/import.wast.json b/tests/snapshots/local/component-model/import.wast.json
index ad846432..4b291d20 100644
--- a/tests/snapshots/local/component-model/import.wast.json
+++ b/tests/snapshots/local/component-model/import.wast.json
@@ -509,6 +509,11 @@
       "filename": "import.74.wasm",
       "text": "unrecognized hash algorithm",
       "module_type": "binary"
+    },
+    {
+      "type": "module",
+      "line": 350,
+      "filename": "import.75.wasm"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/local/component-model/import.wast/75.print b/tests/snapshots/local/component-model/import.wast/75.print
new file mode 100644
index 00000000..e6015551
--- /dev/null
+++ b/tests/snapshots/local/component-model/import.wast/75.print
@@ -0,0 +1,4 @@
+(component
+  (type (;0;) (func))
+  (import "a" (func (;0;) (type 0)))
+)