Repository: bytecodealliance/wasm-tools
Pull request #1469 (2024-03-25 18:48:59)

 Disallow returns-of-borrows in WIT 
Currently in the component model it's not valid to have a function that
returns a `borrow` resource. In WIT, however, there's no protection
currently against this until a component is actually made. Bindings
generators additionally might panic and/or assume that they won't need
to generate bindings for borrows.

This commit implements a `Resolve`-level validation that a function
never returns a `borrow<T>` transitively within it. This ensures that
the error for returning a borrowed resource shows up earlier in the
development cycle and additionally ensures that bindings generators
don't have to each specifically handle this case.

Closes https://github.com/bytecodealliance/wit-bindgen/issues/907


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 13ddc798..173a20e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1724,7 +1724,7 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "wasm-compose"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "glob",
@@ -1738,9 +1738,9 @@ dependencies = [
  "serde_derive",
  "serde_yaml",
  "smallvec",
- "wasm-encoder 0.201.0",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasm-encoder 0.202.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wat",
  "wit-component",
 ]
@@ -1756,17 +1756,17 @@ dependencies = [
 
 [[package]]
 name = "wasm-encoder"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "leb128",
  "tempfile",
- "wasmparser 0.201.0",
+ "wasmparser 0.202.0",
 ]
 
 [[package]]
 name = "wasm-metadata"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "clap 4.5.2",
@@ -1775,14 +1775,14 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder 0.201.0",
- "wasmparser 0.201.0",
+ "wasm-encoder 0.202.0",
+ "wasmparser 0.202.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "clap 4.5.2",
@@ -1791,9 +1791,9 @@ dependencies = [
  "log",
  "rand",
  "thiserror",
- "wasm-encoder 0.201.0",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasm-encoder 0.202.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wat",
 ]
 
@@ -1810,14 +1810,14 @@ dependencies = [
  "num_cpus",
  "rand",
  "wasm-mutate",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wasmtime",
 ]
 
 [[package]]
 name = "wasm-shrink"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "blake3",
@@ -1826,14 +1826,14 @@ dependencies = [
  "log",
  "rand",
  "wasm-mutate",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "arbitrary",
@@ -1846,15 +1846,15 @@ dependencies = [
  "rand",
  "serde",
  "serde_derive",
- "wasm-encoder 0.201.0",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasm-encoder 0.202.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-tools"
-version = "1.201.0"
+version = "1.202.0"
 dependencies = [
  "addr2line 0.21.0",
  "anyhow",
@@ -1878,17 +1878,17 @@ dependencies = [
  "tempfile",
  "termcolor",
  "wasm-compose",
- "wasm-encoder 0.201.0",
+ "wasm-encoder 0.202.0",
  "wasm-metadata",
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.201.0",
+ "wit-parser 0.202.0",
  "wit-smith",
 ]
 
@@ -1900,8 +1900,8 @@ dependencies = [
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wast",
  "wat",
 ]
@@ -1916,16 +1916,16 @@ dependencies = [
  "libfuzzer-sys",
  "log",
  "tempfile",
- "wasm-encoder 0.201.0",
+ "wasm-encoder 0.202.0",
  "wasm-mutate",
  "wasm-smith",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wasmtime",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.201.0",
+ "wit-parser 0.202.0",
  "wit-smith",
 ]
 
@@ -1952,7 +1952,7 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "bitflags 2.4.2",
@@ -1963,7 +1963,7 @@ dependencies = [
  "once_cell",
  "rayon",
  "semver",
- "wasm-encoder 0.201.0",
+ "wasm-encoder 0.202.0",
  "wast",
  "wat",
 ]
@@ -1980,13 +1980,13 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "diff",
  "rayon",
  "tempfile",
- "wasmparser 0.201.0",
+ "wasmparser 0.202.0",
  "wast",
  "wat",
 ]
@@ -2244,7 +2244,7 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "201.0.0"
+version = "202.0.0"
 dependencies = [
  "anyhow",
  "bumpalo",
@@ -2252,14 +2252,14 @@ dependencies = [
  "libtest-mimic",
  "memchr",
  "unicode-width",
- "wasm-encoder 0.201.0",
- "wasmparser 0.201.0",
+ "wasm-encoder 0.202.0",
+ "wasmparser 0.202.0",
  "wat",
 ]
 
 [[package]]
 name = "wat"
-version = "1.201.0"
+version = "1.202.0"
 dependencies = [
  "wast",
 ]
@@ -2455,7 +2455,7 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
 
 [[package]]
 name = "wit-component"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "bitflags 2.4.2",
@@ -2467,14 +2467,14 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.201.0",
+ "wasm-encoder 0.202.0",
  "wasm-metadata",
- "wasmparser 0.201.0",
- "wasmprinter 0.201.0",
+ "wasmparser 0.202.0",
+ "wasmprinter 0.202.0",
  "wasmtime",
  "wast",
  "wat",
- "wit-parser 0.201.0",
+ "wit-parser 0.202.0",
 ]
 
 [[package]]
@@ -2495,7 +2495,7 @@ dependencies = [
 
 [[package]]
 name = "wit-parser"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "anyhow",
  "env_logger",
@@ -2509,9 +2509,9 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "unicode-xid",
- "wasmparser 0.201.0",
+ "wasmparser 0.202.0",
  "wat",
- "wit-parser 0.201.0",
+ "wit-parser 0.202.0",
 ]
 
 [[package]]
@@ -2522,13 +2522,13 @@ dependencies = [
  "env_logger",
  "libfuzzer-sys",
  "log",
- "wasmprinter 0.201.0",
- "wit-parser 0.201.0",
+ "wasmprinter 0.202.0",
+ "wit-parser 0.202.0",
 ]
 
 [[package]]
 name = "wit-smith"
-version = "0.201.0"
+version = "0.202.0"
 dependencies = [
  "arbitrary",
  "clap 4.5.2",
@@ -2536,7 +2536,7 @@ dependencies = [
  "log",
  "semver",
  "wit-component",
- "wit-parser 0.201.0",
+ "wit-parser 0.202.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 9f6bc484..565e131b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-tools"
-version = "1.201.0"
+version = "1.202.0"
 authors = ["The Wasmtime Project Developers"]
 edition.workspace = true
 description = "CLI tools for interoperating with WebAssembly files"
@@ -32,7 +32,7 @@ all = "allow"
 
 [workspace.package]
 edition = '2021'
-version = "0.201.0"
+version = "0.202.0"
 
 [workspace.dependencies]
 anyhow = "1.0.58"
@@ -58,19 +58,19 @@ semver = "1.0.0"
 smallvec = "1.11.1"
 libtest-mimic = "0.7.0"
 
-wasm-compose = { version = "0.201.0", path = "crates/wasm-compose" }
-wasm-encoder = { version = "0.201.0", path = "crates/wasm-encoder" }
-wasm-metadata = { version = "0.201.0", path = "crates/wasm-metadata" }
-wasm-mutate = { version = "0.201.0", path = "crates/wasm-mutate" }
-wasm-shrink = { version = "0.201.0", path = "crates/wasm-shrink" }
-wasm-smith = { version = "0.201.0", path = "crates/wasm-smith" }
-wasmparser = { version = "0.201.0", path = "crates/wasmparser" }
-wasmprinter = { version = "0.201.0", path = "crates/wasmprinter" }
-wast = { version = "201.0.0", path = "crates/wast" }
-wat = { version = "1.201.0", path = "crates/wat" }
-wit-component = { version = "0.201.0", path = "crates/wit-component" }
-wit-parser = { version = "0.201.0", path = "crates/wit-parser" }
-wit-smith = { version = "0.201.0", path = "crates/wit-smith" }
+wasm-compose = { version = "0.202.0", path = "crates/wasm-compose" }
+wasm-encoder = { version = "0.202.0", path = "crates/wasm-encoder" }
+wasm-metadata = { version = "0.202.0", path = "crates/wasm-metadata" }
+wasm-mutate = { version = "0.202.0", path = "crates/wasm-mutate" }
+wasm-shrink = { version = "0.202.0", path = "crates/wasm-shrink" }
+wasm-smith = { version = "0.202.0", path = "crates/wasm-smith" }
+wasmparser = { version = "0.202.0", path = "crates/wasmparser" }
+wasmprinter = { version = "0.202.0", path = "crates/wasmprinter" }
+wast = { version = "202.0.0", path = "crates/wast" }
+wat = { version = "1.202.0", path = "crates/wat" }
+wit-component = { version = "0.202.0", path = "crates/wit-component" }
+wit-parser = { version = "0.202.0", path = "crates/wit-parser" }
+wit-smith = { version = "0.202.0", path = "crates/wit-smith" }
 
 [dependencies]
 anyhow = { workspace = true }
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index 6b3aed5c..3ca342bc 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -43,7 +43,7 @@
 /// The list of specializable Wasm proposals is as follows:
 ///
 /// - `@mvp`: Denoting a Wasm operator from the initial Wasm MVP version.
-/// - `@exceptions`: [Wasm `expection-handling` proposal]
+/// - `@exceptions`: [Wasm `exception-handling` proposal]
 /// - `@tail_call`: [Wasm `tail-calls` proposal]
 /// - `@reference_types`: [Wasm `reference-types` proposal]
 /// - `@sign_extension`: [Wasm `sign-extension-ops` proposal]
@@ -54,7 +54,7 @@
 /// - `@relaxed_simd`: [Wasm `relaxed-simd` proposal]
 /// - `@gc`: [Wasm `gc` proposal]
 ///
-/// [Wasm `expection-handling` proposal]:
+/// [Wasm `exception-handling` proposal]:
 /// https://github.com/WebAssembly/exception-handling
 ///
 /// [Wasm `tail-calls` proposal]:
diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml
index b7db2768..bda3e204 100644
--- a/crates/wast/Cargo.toml
+++ b/crates/wast/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wast"
-version = "201.0.0"
+version = "202.0.0"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml
index 30ed1440..de80c139 100644
--- a/crates/wat/Cargo.toml
+++ b/crates/wat/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wat"
-version = "1.201.0"
+version = "1.202.0"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index b5bb6c63..fa959527 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -2078,7 +2078,7 @@ impl ComponentEncoder {
             bail!("a module is required when encoding a component");
         }
 
-        let world = ComponentWorld::new(self)?;
+        let world = ComponentWorld::new(self).context("failed to decode world from module")?;
         let mut state = EncodingState {
             component: ComponentBuilder::default(),
             module_index: None,
diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs
index 66a61a39..1545decd 100644
--- a/crates/wit-component/src/encoding/world.rs
+++ b/crates/wit-component/src/encoding/world.rs
@@ -76,7 +76,8 @@ impl<'a> ComponentWorld<'a> {
             &encoder.metadata,
             &encoder.main_module_exports,
             &adapters,
-        )?;
+        )
+        .context("module was not valid")?;
 
         let mut ret = ComponentWorld {
             encoder,
diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs
index 2409b02e..4c7fcf81 100644
--- a/crates/wit-component/src/metadata.rs
+++ b/crates/wit-component/src/metadata.rs
@@ -50,7 +50,7 @@ use wasm_encoder::{
     ComponentBuilder, ComponentExportKind, ComponentType, ComponentTypeRef, CustomSection,
 };
 use wasm_metadata::Producers;
-use wasmparser::{BinaryReader, Parser, Payload};
+use wasmparser::{BinaryReader, Encoding, Parser, Payload};
 use wit_parser::{Package, PackageName, Resolve, World, WorldId, WorldItem};
 
 const CURRENT_VERSION: u8 = 0x04;
@@ -127,6 +127,8 @@ pub struct ModuleMetadata {
 /// The extraction here provides the metadata necessary to continue the process
 /// later on.
 ///
+/// This will return an error if `wasm` is not a valid WebAssembly module.
+///
 /// Note that a "stripped" binary where `component-type` sections are removed
 /// is returned as well to embed within a component.
 pub fn decode(wasm: &[u8]) -> Result<(Vec<u8>, Bindgen)> {
@@ -142,6 +144,9 @@ pub fn decode(wasm: &[u8]) -> Result<(Vec<u8>, Bindgen)> {
                 ret.merge(data)
                     .with_context(|| format!("updating metadata for section {}", cs.name()))?;
             }
+            wasmparser::Payload::Version { encoding, .. } if encoding != Encoding::Module => {
+                bail!("decoding a component is not supported")
+            }
             _ => {
                 if let Some((id, range)) = payload.as_section() {
                     new_module.section(&wasm_encoder::RawSection {
diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs
index 444b9973..0962d05d 100644
--- a/crates/wit-parser/src/ast/resolve.rs
+++ b/crates/wit-parser/src/ast/resolve.rs
@@ -64,23 +64,17 @@ pub struct Resolver<'a> {
     /// pointing to it if the item isn't actually defined.
     unknown_type_spans: Vec<Span>,
 
-    /// Spans for each world in `self.world` to assign for each import/export
-    /// for later error reporting.
-    world_item_spans: Vec<(Vec<Span>, Vec<Span>)>,
-
     /// Spans for each world in `self.world`
-    world_spans: Vec<Span>,
+    world_spans: Vec<WorldSpan>,
 
     /// The span of each interface's definition which is used for error
     /// reporting during the final `Resolve` phase.
-    interface_spans: Vec<Span>,
+    interface_spans: Vec<InterfaceSpan>,
 
     /// Spans per entry in `self.foreign_deps` for where the dependency was
     /// introduced to print an error message if necessary.
     foreign_dep_spans: Vec<Span>,
 
-    include_world_spans: Vec<Span>,
-
     /// A list of `TypeDefKind::Unknown` types which are required to be
     /// resources when this package is resolved against its dependencies.
     required_resource_types: Vec<(TypeId, Span)>,
@@ -221,12 +215,10 @@ impl<'a> Resolver<'a> {
                 })
                 .collect(),
             unknown_type_spans: mem::take(&mut self.unknown_type_spans),
-            world_item_spans: mem::take(&mut self.world_item_spans),
             interface_spans: mem::take(&mut self.interface_spans),
             world_spans: mem::take(&mut self.world_spans),
             foreign_dep_spans: mem::take(&mut self.foreign_dep_spans),
             source_map: SourceMap::default(),
-            include_world_spans: mem::take(&mut self.include_world_spans),
             required_resource_types: mem::take(&mut self.required_resource_types),
         })
     }
@@ -258,7 +250,7 @@ impl<'a> Resolver<'a> {
                                 id.package_name(),
                                 name.name
                             );
-                            AstItem::World(self.alloc_world(name.span, true))
+                            AstItem::World(self.alloc_world(name.span))
                         }
                         WorldOrInterface::Interface | WorldOrInterface::Unknown => {
                             // Currently top-level `use` always assumes an interface, so the
@@ -289,7 +281,10 @@ impl<'a> Resolver<'a> {
 
     fn alloc_interface(&mut self, span: Span) -> InterfaceId {
         self.interface_types.push(IndexMap::new());
-        self.interface_spans.push(span);
+        self.interface_spans.push(InterfaceSpan {
+            span,
+            funcs: Vec::new(),
+        });
         self.interfaces.alloc(Interface {
             name: None,
             types: IndexMap::new(),
@@ -299,11 +294,13 @@ impl<'a> Resolver<'a> {
         })
     }
 
-    fn alloc_world(&mut self, span: Span, dummy_span: bool) -> WorldId {
-        self.world_spans.push(span);
-        if dummy_span {
-            self.world_item_spans.push((Vec::new(), Vec::new()));
-        }
+    fn alloc_world(&mut self, span: Span) -> WorldId {
+        self.world_spans.push(WorldSpan {
+            span,
+            imports: Vec::new(),
+            exports: Vec::new(),
+            includes: Vec::new(),
+        });
         self.worlds.alloc(World {
             name: String::new(),
             docs: Docs::default(),
@@ -461,8 +458,7 @@ impl<'a> Resolver<'a> {
                     iface_id_order.push(id);
                 }
                 ast::AstItem::World(_) => {
-                    // No dummy span needs to be created because they will be created at `resolve_world`
-                    let id = self.alloc_world(package_items[name], false);
+                    let id = self.alloc_world(package_items[name]);
                     self.worlds[id].name = name.to_string();
                     let prev = ids.insert(name, AstItem::World(id));
                     assert!(prev.is_none());
@@ -585,7 +581,7 @@ impl<'a> Resolver<'a> {
             _ => None,
         });
         for include in items {
-            self.resolve_include(TypeOwner::World(world_id), include)?;
+            self.resolve_include(world_id, include)?;
         }
 
         let mut export_spans = Vec::new();
@@ -698,7 +694,8 @@ impl<'a> Resolver<'a> {
             assert!(prev.is_none());
             spans.push(kind.span());
         }
-        self.world_item_spans.push((import_spans, export_spans));
+        self.world_spans[world_id.index()].imports = import_spans;
+        self.world_spans[world_id.index()].exports = export_spans;
         self.type_lookup.clear();
 
         Ok(world_id)
@@ -772,6 +769,9 @@ impl<'a> Resolver<'a> {
                         &f.func,
                         FunctionKind::Freestanding,
                     )?);
+                    self.interface_spans[interface_id.index()]
+                        .funcs
+                        .push(f.name.span);
                 }
                 ast::InterfaceItem::Use(_) => {}
                 ast::InterfaceItem::TypeDef(ast::TypeDef {
@@ -781,6 +781,9 @@ impl<'a> Resolver<'a> {
                 }) => {
                     for func in r.funcs.iter() {
                         funcs.push(self.resolve_resource_func(func, name)?);
+                        self.interface_spans[interface_id.index()]
+                            .funcs
+                            .push(func.named_func().name.span);
                     }
                 }
                 ast::InterfaceItem::TypeDef(_) => {}
@@ -900,14 +903,9 @@ impl<'a> Resolver<'a> {
     }
 
     /// For each name in the `include`, resolve the path of the include, add it to the self.includes
-    fn resolve_include(&mut self, owner: TypeOwner, i: &ast::Include<'a>) -> Result<()> {
+    fn resolve_include(&mut self, world_id: WorldId, i: &ast::Include<'a>) -> Result<()> {
         let (item, name, span) = self.resolve_ast_item_path(&i.from)?;
         let include_from = self.extract_world_from_item(&item, &name, span)?;
-        self.include_world_spans.push(span);
-        let world_id = match owner {
-            TypeOwner::World(id) => id,
-            _ => unreachable!(),
-        };
         self.worlds[world_id].includes.push(include_from);
         self.worlds[world_id].include_names.push(
             i.names
@@ -918,6 +916,7 @@ impl<'a> Resolver<'a> {
                 })
                 .collect(),
         );
+        self.world_spans[world_id.index()].includes.push(span);
         Ok(())
     }
 
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index 3b5f8dbe..25700fb3 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -109,15 +109,27 @@ pub struct UnresolvedPackage {
     pub docs: Docs,
 
     unknown_type_spans: Vec<Span>,
-    world_item_spans: Vec<(Vec<Span>, Vec<Span>)>,
-    interface_spans: Vec<Span>,
-    world_spans: Vec<Span>,
+    interface_spans: Vec<InterfaceSpan>,
+    world_spans: Vec<WorldSpan>,
     foreign_dep_spans: Vec<Span>,
     source_map: SourceMap,
-    include_world_spans: Vec<Span>,
     required_resource_types: Vec<(TypeId, Span)>,
 }
 
+#[derive(Clone)]
+struct WorldSpan {
+    span: Span,
+    imports: Vec<Span>,
+    exports: Vec<Span>,
+    includes: Vec<Span>,
+}
+
+#[derive(Clone)]
+struct InterfaceSpan {
+    span: Span,
+    funcs: Vec<Span>,
+}
+
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "serde", derive(Serialize))]
 #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index b7e1f95b..23fa136c 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -4,8 +4,8 @@ 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,
-    WorldId, WorldItem, WorldKey,
+    InterfaceSpan, PackageName, Results, Type, TypeDef, TypeDefKind, TypeId, TypeOwner,
+    UnresolvedPackage, World, WorldId, WorldItem, WorldKey, WorldSpan,
 };
 use anyhow::{anyhow, bail, Context, Result};
 use id_arena::{Arena, Id};
@@ -461,36 +461,43 @@ impl Resolve {
 
         let mut moved_interfaces = Vec::new();
         for (id, mut iface) in interfaces {
-            let new_id = interface_map.get(&id).copied().unwrap_or_else(|| {
-                log::debug!("moving interface {:?}", iface.name);
-                moved_interfaces.push(id);
-                remap.update_interface(self, &mut iface);
-                self.interfaces.alloc(iface)
-            });
+            let new_id = match interface_map.get(&id).copied() {
+                Some(id) => id,
+                None => {
+                    log::debug!("moving interface {:?}", iface.name);
+                    moved_interfaces.push(id);
+                    remap.update_interface(self, &mut iface, None)?;
+                    self.interfaces.alloc(iface)
+                }
+            };
             assert_eq!(remap.interfaces.len(), id.index());
             remap.interfaces.push(new_id);
         }
 
         let mut moved_worlds = Vec::new();
         for (id, mut world) in worlds {
-            let new_id = world_map.get(&id).copied().unwrap_or_else(|| {
-                log::debug!("moving world {}", world.name);
-                moved_worlds.push(id);
-                let mut update = |map: &mut IndexMap<WorldKey, WorldItem>| {
-                    for (mut name, mut item) in mem::take(map) {
-                        remap.update_world_key(&mut name);
-                        match &mut item {
-                            WorldItem::Function(f) => remap.update_function(self, f),
-                            WorldItem::Interface(i) => *i = remap.interfaces[i.index()],
-                            WorldItem::Type(i) => *i = remap.types[i.index()],
+            let new_id = match world_map.get(&id).copied() {
+                Some(id) => id,
+                None => {
+                    log::debug!("moving world {}", world.name);
+                    moved_worlds.push(id);
+                    let mut update = |map: &mut IndexMap<WorldKey, WorldItem>| -> Result<_> {
+                        for (mut name, mut item) in mem::take(map) {
+                            remap.update_world_key(&mut name);
+                            match &mut item {
+                                WorldItem::Function(f) => remap.update_function(self, f, None)?,
+                                WorldItem::Interface(i) => *i = remap.interfaces[i.index()],
+                                WorldItem::Type(i) => *i = remap.types[i.index()],
+                            }
+                            map.insert(name, item);
                         }
-                        map.insert(name, item);
-                    }
-                };
-                update(&mut world.imports);
-                update(&mut world.exports);
-                self.worlds.alloc(world)
-            });
+                        Ok(())
+                    };
+                    update(&mut world.imports)?;
+                    update(&mut world.exports)?;
+                    self.worlds.alloc(world)
+                }
+            };
             assert_eq!(remap.worlds.len(), id.index());
             remap.worlds.push(new_id);
         }
@@ -1062,6 +1069,8 @@ pub struct Remap {
     /// The key of this map is the resource id `T` in the new resolve, and
     /// the value is the `own<T>` type pointing to `T`.
     own_handles: HashMap<TypeId, TypeId>,
+
+    type_has_borrow: Vec<Option<bool>>,
 }
 
 impl Remap {
@@ -1107,8 +1116,13 @@ impl Remap {
 
         // Next transfer all interfaces into `Resolve`, updating type ids
         // referenced along the way.
-        for (id, mut iface) in unresolved.interfaces.into_iter().skip(foreign_interfaces) {
-            self.update_interface(resolve, &mut iface);
+        for ((id, mut iface), span) in unresolved
+            .interfaces
+            .into_iter()
+            .zip(&unresolved.interface_spans)
+            .skip(foreign_interfaces)
+        {
+            self.update_interface(resolve, &mut iface, Some(span))?;
             let new_id = resolve.interfaces.alloc(iface);
             assert_eq!(self.interfaces.len(), id.index());
             self.interfaces.push(new_id);
@@ -1130,21 +1144,14 @@ impl Remap {
         // This is done after types/interfaces are fully settled so the
         // transitive relation between interfaces, through types, is understood
         // here.
-        assert_eq!(unresolved.worlds.len(), unresolved.world_item_spans.len());
-        let include_world_spans = unresolved.include_world_spans;
-        for ((id, mut world), (import_spans, export_spans)) in unresolved
+        assert_eq!(unresolved.worlds.len(), unresolved.world_spans.len());
+        for ((id, mut world), span) in unresolved
             .worlds
             .into_iter()
-            .zip(unresolved.world_item_spans)
+            .zip(unresolved.world_spans)
             .skip(foreign_worlds)
         {
-            self.update_world(
-                &mut world,
-                resolve,
-                &import_spans,
-                &export_spans,
-                &include_world_spans,
-            )?;
+            self.update_world(&mut world, resolve, &span)?;
 
             let new_id = resolve.worlds.alloc(world);
             assert_eq!(self.worlds.len(), id.index());
@@ -1275,13 +1282,13 @@ impl Remap {
             assert!(unresolved_iface.functions.is_empty());
 
             let pkg = &resolve.packages[pkgid];
-            let span = unresolved.interface_spans[unresolved_iface_id.index()];
+            let span = &unresolved.interface_spans[unresolved_iface_id.index()];
             let iface_id = pkg
                 .interfaces
                 .get(interface)
                 .copied()
                 .ok_or_else(|| Error {
-                    span,
+                    span: span.span,
                     msg: format!("interface not found in package"),
                 })?;
             assert_eq!(self.interfaces.len(), unresolved_iface_id.index());
@@ -1319,9 +1326,9 @@ impl Remap {
                     msg: format!("package not found"),
                 })?;
             let pkg = &resolve.packages[pkgid];
-            let span = unresolved.world_spans[unresolved_world_id.index()];
+            let span = &unresolved.world_spans[unresolved_world_id.index()];
             let world_id = pkg.worlds.get(world).copied().ok_or_else(|| Error {
-                span,
+                span: span.span,
                 msg: format!("world not found in package"),
             })?;
             assert_eq!(self.worlds.len(), unresolved_world_id.index());
@@ -1470,18 +1477,33 @@ impl Remap {
         *id = self.types[id.index()];
     }
 
-    fn update_interface(&mut self, resolve: &mut Resolve, iface: &mut Interface) {
+    fn update_interface(
+        &mut self,
+        resolve: &mut Resolve,
+        iface: &mut Interface,
+        spans: Option<&InterfaceSpan>,
+    ) -> Result<()> {
         // NB: note that `iface.doc` is not updated here since interfaces
         // haven't been mapped yet and that's done in a separate step.
         for (_name, ty) in iface.types.iter_mut() {
             self.update_type_id(ty);
         }
-        for (_, func) in iface.functions.iter_mut() {
-            self.update_function(resolve, func);
+        if let Some(spans) = spans {
+            assert_eq!(iface.functions.len(), spans.funcs.len());
         }
+        for (i, (_, func)) in iface.functions.iter_mut().enumerate() {
+            let span = spans.map(|s| s.funcs[i]);
+            self.update_function(resolve, func, span)?;
+        }
+        Ok(())
     }
 
-    fn update_function(&mut self, resolve: &mut Resolve, func: &mut Function) {
+    fn update_function(
+        &mut self,
+        resolve: &mut Resolve,
+        func: &mut Function,
+        span: Option<Span>,
+    ) -> Result<()> {
         match &mut func.kind {
             FunctionKind::Freestanding => {}
             FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => {
@@ -1499,15 +1521,33 @@ impl Remap {
             }
             Results::Anon(ty) => self.update_ty(resolve, ty),
         }
+
+        for ty in func.results.iter_types() {
+            if !self.type_has_borrow(resolve, ty) {
+                continue;
+            }
+            match span {
+                Some(span) => {
+                    bail!(Error {
+                        span,
+                        msg: format!(
+                            "function returns a type which contains \
+                             a `borrow<T>` which is not supported"
+                        )
+                    })
+                }
+                None => unreachable!(),
+            }
+        }
+
+        Ok(())
     }
 
     fn update_world(
         &mut self,
         world: &mut World,
         resolve: &mut Resolve,
-        import_spans: &[Span],
-        export_spans: &[Span],
-        include_world_spans: &[Span],
+        spans: &WorldSpan,
     ) -> Result<()> {
         // NB: this function is more more complicated than the prior versions
         // of merging an item because this is the location that elaboration of
@@ -1515,8 +1555,8 @@ impl Remap {
         // knowledge of all interfaces a worlds imports, for example, are
         // expanded fully to ensure that all transitive items are necessarily
         // imported.
-        assert_eq!(world.imports.len(), import_spans.len());
-        assert_eq!(world.exports.len(), export_spans.len());
+        assert_eq!(world.imports.len(), spans.imports.len());
+        assert_eq!(world.exports.len(), spans.exports.len());
 
         // First up, process all the `imports` of the world. Note that this
         // starts by gutting the list of imports stored in `world` to get
@@ -1527,7 +1567,9 @@ impl Remap {
         // determining names later on.
         let mut import_funcs = Vec::new();
         let mut import_types = Vec::new();
-        for ((mut name, item), span) in mem::take(&mut world.imports).into_iter().zip(import_spans)
+        for ((mut name, item), span) in mem::take(&mut world.imports)
+            .into_iter()
+            .zip(&spans.imports)
         {
             self.update_world_key(&mut name);
             match item {
@@ -1536,7 +1578,7 @@ impl Remap {
                     self.add_world_import(resolve, world, name, id);
                 }
                 WorldItem::Function(mut f) => {
-                    self.update_function(resolve, &mut f);
+                    self.update_function(resolve, &mut f, Some(*span))?;
                     import_funcs.push((name.unwrap_name(), f, *span));
                 }
                 WorldItem::Type(id) => {
@@ -1557,7 +1599,9 @@ impl Remap {
 
         let mut export_funcs = Vec::new();
         let mut export_interfaces = IndexMap::new();
-        for ((mut name, item), span) in mem::take(&mut world.exports).into_iter().zip(export_spans)
+        for ((mut name, item), span) in mem::take(&mut world.exports)
+            .into_iter()
+            .zip(&spans.exports)
         {
             self.update_world_key(&mut name);
             match item {
@@ -1567,7 +1611,7 @@ impl Remap {
                     assert!(prev.is_none());
                 }
                 WorldItem::Function(mut f) => {
-                    self.update_function(resolve, &mut f);
+                    self.update_function(resolve, &mut f, Some(*span))?;
                     let name = match name {
                         WorldKey::Name(name) => name,
                         WorldKey::Interface(_) => unreachable!(),
@@ -1581,12 +1625,13 @@ impl Remap {
         self.add_world_exports(resolve, world, &export_interfaces)?;
 
         // Resolve all includes of the world
+        assert_eq!(world.includes.len(), spans.includes.len());
         let includes = mem::take(&mut world.includes);
         let include_names = mem::take(&mut world.include_names);
-        for (index, include_world) in includes.into_iter().enumerate() {
-            let span = include_world_spans[index];
+        for (index, (include_world, span)) in includes.into_iter().zip(&spans.includes).enumerate()
+        {
             let names = &include_names[index];
-            self.resolve_include(world, include_world, names, span, resolve)?;
+            self.resolve_include(world, include_world, names, *span, resolve)?;
         }
 
         for (name, id, span) in import_types {
@@ -1911,6 +1956,58 @@ impl Remap {
             _ => {}
         }
     }
+
+    fn type_has_borrow(&mut self, resolve: &Resolve, ty: &Type) -> bool {
+        let id = match ty {
+            Type::Id(id) => *id,
+            _ => return false,
+        };
+
+        if let Some(Some(has_borrow)) = self.type_has_borrow.get(id.index()) {
+            return *has_borrow;
+        }
+
+        let result = self.typedef_has_borrow(resolve, &resolve.types[id]);
+        if self.type_has_borrow.len() <= id.index() {
+            self.type_has_borrow.resize(id.index() + 1, None);
+        }
+        self.type_has_borrow[id.index()] = Some(result);
+        result
+    }
+
+    fn typedef_has_borrow(&mut self, resolve: &Resolve, ty: &TypeDef) -> bool {
+        match &ty.kind {
+            TypeDefKind::Type(t) => self.type_has_borrow(resolve, t),
+            TypeDefKind::Variant(v) => v
+                .cases
+                .iter()
+                .filter_map(|case| case.ty.as_ref())
+                .any(|ty| self.type_has_borrow(resolve, ty)),
+            TypeDefKind::Handle(Handle::Borrow(_)) => true,
+            TypeDefKind::Handle(Handle::Own(_)) => false,
+            TypeDefKind::Resource => false,
+            TypeDefKind::Record(r) => r
+                .fields
+                .iter()
+                .any(|case| self.type_has_borrow(resolve, &case.ty)),
+            TypeDefKind::Flags(_) => false,
+            TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.type_has_borrow(resolve, t)),
+            TypeDefKind::Enum(_) => false,
+            TypeDefKind::List(ty) | TypeDefKind::Future(Some(ty)) | TypeDefKind::Option(ty) => {
+                self.type_has_borrow(resolve, ty)
+            }
+            TypeDefKind::Result(r) => [&r.ok, &r.err]
+                .iter()
+                .filter_map(|t| t.as_ref())
+                .any(|t| self.type_has_borrow(resolve, t)),
+            TypeDefKind::Stream(r) => [&r.element, &r.end]
+                .iter()
+                .filter_map(|t| t.as_ref())
+                .any(|t| self.type_has_borrow(resolve, t)),
+            TypeDefKind::Future(None) => false,
+            TypeDefKind::Unknown => unreachable!(),
+        }
+    }
 }
 
 struct MergeMap<'a> {
diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs
index bbcbc302..298979af 100644
--- a/crates/wit-smith/src/generate.rs
+++ b/crates/wit-smith/src/generate.rs
@@ -318,16 +318,14 @@ impl Generator {
 
 impl<'a> InterfaceGenerator<'a> {
     fn new(gen: &'a Generator, file: &'a mut File) -> InterfaceGenerator<'a> {
-        // Claim the name `memory` to avoid conflicting with the canonical ABI
-        // always using a linear memory named `memory`.
-        let mut unique_names = HashSet::new();
-        unique_names.insert("memory".to_string());
         InterfaceGenerator {
             gen,
             file,
             config: &gen.config,
             types_in_interface: Vec::new(),
-            unique_names,
+            // Claim the name `memory` to avoid conflicting with the canonical
+            // ABI always using a linear memory named `memory`.
+            unique_names: HashSet::from_iter(["memory".to_string()]),
         }
     }
 
@@ -413,7 +411,10 @@ impl<'a> InterfaceGenerator<'a> {
         let mut parts = Vec::new();
         let mut imported_interfaces = HashSet::new();
         let mut exported_interfaces = HashSet::new();
-        let mut export_names = HashSet::new();
+
+        // Claim the name `memory` to avoid conflicting with the canonical
+        // ABI always using a linear memory named `memory`.
+        let mut export_names = HashSet::from_iter(["memory".to_string()]);
 
         while parts.len() < self.config.max_world_items && !u.is_empty() && u.arbitrary()? {
             let kind = u.arbitrary::<ItemKind>()?;


Test patch:
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 b7a8e003..904d1aef 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,1 +1,5 @@
-type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
+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
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 3fc2443a..75f7337b 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,1 +1,5 @@
-no top-level imported function `foo` specified
\ No newline at end of file
+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
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 d2226ac3..0588ebcf 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,4 +1,6 @@
-failed to validate exported interface `foo`
+failed to decode world from module
 
 Caused by:
-    type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []`
\ No newline at end of file
+    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
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 5a9a5e18..f9f291d9 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,1 +1,5 @@
-no top-level imported function `[resource-rep]a` specified
\ No newline at end of file
+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
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 205e848e..e76b70fe 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,1 +1,5 @@
-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
+
+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
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 7a6fd7d4..57dae399 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,4 +1,6 @@
-failed to validate import interface `foo`
+failed to decode world from module
 
 Caused by:
-    type mismatch for function `bar`: expected `[I32, I32] -> []` but found `[] -> []`
\ No newline at end of file
+    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
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 9d9f35c0..4a54c821 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,1 +1,5 @@
-module is only allowed to import functions
\ No newline at end of file
+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
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 8368ada3..1dc1b01f 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,1 +1,5 @@
-module does not export required function `a`
\ No newline at end of file
+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
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 afcfb4e2..6d4148bd 100644
--- a/crates/wit-component/tests/components/error-missing-export/error.txt
+++ b/crates/wit-component/tests/components/error-missing-export/error.txt
@@ -1,4 +1,6 @@
-failed to validate exported interface `foo`
+failed to decode world from module
 
 Caused by:
-    module does not export required function `foo#a`
\ No newline at end of file
+    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
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 b4d2327f..388a6087 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,1 +1,5 @@
-module requires an import interface named `foo`
\ No newline at end of file
+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
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 b4d2327f..388a6087 100644
--- a/crates/wit-component/tests/components/error-missing-import/error.txt
+++ b/crates/wit-component/tests/components/error-missing-import/error.txt
@@ -1,1 +1,5 @@
-module requires an import interface named `foo`
\ No newline at end of file
+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
diff --git a/crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit
similarity index 100%
rename from crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit
rename to crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit
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
new file mode 100644
index 00000000..62e78ca1
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result
@@ -0,0 +1,5 @@
+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/resources-return-borrow.wit b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit
similarity index 100%
rename from crates/wit-parser/tests/ui/resources-return-borrow.wit
rename to crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit
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
new file mode 100644
index 00000000..3919bf72
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result
@@ -0,0 +1,5 @@
+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 b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit
new file mode 100644
index 00000000..5c03e1e8
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+interface foo {
+  resource y;
+
+  x: func() -> borrow<y>;
+}
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
new file mode 100644
index 00000000..aa7dd507
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result
@@ -0,0 +1,5 @@
+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 b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit
new file mode 100644
index 00000000..2e5d4f9a
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit
@@ -0,0 +1,8 @@
+package foo:bar;
+
+interface foo {
+  resource y {
+    x: func() -> borrow<y>;
+  }
+}
+
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
new file mode 100644
index 00000000..d29f0ee7
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result
@@ -0,0 +1,5 @@
+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-borrow3.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow3.wit
new file mode 100644
index 00000000..4d1f84b8
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow3.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+world foo {
+  resource y {
+    x: func() -> borrow<y>;
+  }
+}
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow3.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow3.wit.result
new file mode 100644
index 00000000..429b804f
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow3.wit.result
@@ -0,0 +1,5 @@
+function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow3.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-borrow4.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow4.wit
new file mode 100644
index 00000000..51f836c5
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow4.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+world foo {
+  resource y;
+
+  import x: func() -> borrow<y>;
+}
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow4.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow4.wit.result
new file mode 100644
index 00000000..177b1ff9
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow4.wit.result
@@ -0,0 +1,5 @@
+function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow4.wit:6:10
+      |
+    6 |   import x: func() -> borrow<y>;
+      |          ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit
new file mode 100644
index 00000000..e823042e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+world foo {
+  resource y;
+
+  export x: func() -> borrow<y>;
+}
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit.result
new file mode 100644
index 00000000..1ca263de
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow5.wit.result
@@ -0,0 +1,5 @@
+function returns a type which contains a `borrow<T>` which is not supported
+     --> tests/ui/parse-fail/return-borrow5.wit:6:10
+      |
+    6 |   export x: func() -> borrow<y>;
+      |          ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit
new file mode 100644
index 00000000..b99ce63b
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+interface foo {
+  resource y;
+
+  x: func() -> tuple<borrow<y>>;
+}
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
new file mode 100644
index 00000000..4c0f86ab
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result
@@ -0,0 +1,5 @@
+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 b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit
new file mode 100644
index 00000000..72a36c15
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit
@@ -0,0 +1,11 @@
+package foo:bar;
+
+interface foo {
+  resource y;
+
+  record y2 {
+    a: borrow<y>,
+  }
+
+  x: func() -> y2;
+}
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
new file mode 100644
index 00000000..5d8532f9
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 00000000..6382feba
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result
@@ -0,0 +1,8 @@
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/return-borrow8]
+
+Caused by:
+    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/return-borrow8/deps/baz.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow8/deps/baz.wit
new file mode 100644
index 00000000..a1392e21
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow8/deps/baz.wit
@@ -0,0 +1,9 @@
+package foo:baz;
+
+interface i {
+  resource my-resource;
+
+  record r {
+    x: borrow<my-resource>,
+  }
+}
diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow8/foo.wit b/crates/wit-parser/tests/ui/parse-fail/return-borrow8/foo.wit
new file mode 100644
index 00000000..e5da4e2e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow8/foo.wit
@@ -0,0 +1,7 @@
+package foo:bar;
+
+interface foo {
+  use foo:baz/i.{r};
+
+  x: func() -> r;
+}
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
deleted file mode 100644
index d5fbf02a..00000000
--- a/crates/wit-parser/tests/ui/resources-multiple-returns-borrow.wit.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
-  "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": [
-    {
-      "name": "r1",
-      "kind": "resource",
-      "owner": {
-        "interface": 0
-      }
-    },
-    {
-      "name": null,
-      "kind": {
-        "handle": {
-          "borrow": 0
-        }
-      },
-      "owner": null
-    }
-  ],
-  "packages": [
-    {
-      "name": "foo:resources1",
-      "interfaces": {
-        "resources1": 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
deleted file mode 100644
index c48dd351..00000000
--- a/crates/wit-parser/tests/ui/resources-return-borrow.wit.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
-  "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": [
-    {
-      "name": "r1",
-      "kind": "resource",
-      "owner": {
-        "interface": 0
-      }
-    },
-    {
-      "name": null,
-      "kind": {
-        "handle": {
-          "borrow": 0
-        }
-      },
-      "owner": null
-    }
-  ],
-  "packages": [
-    {
-      "name": "foo:resources1",
-      "interfaces": {
-        "resources1": 0
-      },
-      "worlds": {}
-    }
-  ]
-}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/world-top-level-resources.wit b/crates/wit-parser/tests/ui/world-top-level-resources.wit
index c236fc7d..1a74e2a5 100644
--- a/crates/wit-parser/tests/ui/world-top-level-resources.wit
+++ b/crates/wit-parser/tests/ui/world-top-level-resources.wit
@@ -14,7 +14,7 @@ interface types {
 
 interface handler {
   use types.{request, response};
-  handle: func(some: borrow<request>) -> borrow<response>;
+  handle: func(some: borrow<request>) -> response;
   handle-owned: func(some: own<request>) -> own<response>;
 }
 
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
index ee1fd2c6..a4ef0416 100644
--- a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json
+++ b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json
@@ -122,7 +122,7 @@
           ],
           "results": [
             {
-              "type": 10
+              "type": 8
             }
           ]
         }
@@ -201,7 +201,7 @@
       "name": null,
       "kind": {
         "handle": {
-          "borrow": 6
+          "own": 6
         }
       },
       "owner": null
@@ -214,15 +214,6 @@
         }
       },
       "owner": null
-    },
-    {
-      "name": null,
-      "kind": {
-        "handle": {
-          "own": 6
-        }
-      },
-      "owner": null
     }
   ],
   "packages": [
diff --git a/tests/cli/component-new-existing-component.wat b/tests/cli/component-new-existing-component.wat
new file mode 100644
index 00000000..2251e2ef
--- /dev/null
+++ b/tests/cli/component-new-existing-component.wat
@@ -0,0 +1,2 @@
+;; FAIL: component new % -o component.wasm
+(component)
diff --git a/tests/cli/component-new-existing-component.wat.stderr b/tests/cli/component-new-existing-component.wat.stderr
new file mode 100644
index 00000000..bb6a3987
--- /dev/null
+++ b/tests/cli/component-new-existing-component.wat.stderr
@@ -0,0 +1,1 @@
+error: decoding a component is not supported