Repository: bytecodealliance/wasm-tools
Pull request #1700 (2024-07-25 16:13:50)

Require root packages when defining nested packages in wit
This PR is an initial implementation of the wit functionality described [here](https://github.com/WebAssembly/component-model/pull/380)

Now, whenever defining packages as `package {...}`, it will be required that some file has `package namespace:name;` as a header somewhere in the project.  Thus there is a single root package, and the other package declarations are nested.  It also supports intermixing interface and world definitions between nested package definitions across files, and the packages in a deps folder can also nest packages, which are usable by the root package definition and its nested packages



alexcrichton: I'll do a final review of this tomorrow and afterwards start to turn the cranks again.

Patch:
diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml
index 5b9e2d00..afff1563 100644
--- a/crates/c-api/Cargo.toml
+++ b/crates/c-api/Cargo.toml
@@ -4,9 +4,7 @@ authors = ["Addison Hart <tgr@tgrcode.com>"]
 categories = ["development-tools", "development-tools::testing", "wasm"]
 description = "C API to expose wasm-tools"
 edition.workspace = true
-# Note that this is intentionally not the workspace's license. For more
-# information see #1637.
-license = "Apache-2.0 WITH LLVM-exception"
+license.workspace = true
 readme = "./README.md"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/c-api"
 publish = false
diff --git a/crates/c-api/LICENSE b/crates/c-api/LICENSE
deleted file mode 100644
index f9d81955..00000000
--- a/crates/c-api/LICENSE
+++ /dev/null
@@ -1,220 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
---- LLVM Exceptions to the Apache 2.0 License ----
-
-As an exception, if, as a result of your compiling your source code, portions
-of this Software are embedded into an Object form of such source code, you
-may redistribute such embedded portions in such Object form without complying
-with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
-
-In addition, if you combine or link compiled forms of this Software with
-software that is licensed under the GPLv2 ("Combined Software") and if a
-court of competent jurisdiction determines that the patent provision (Section
-3), the indemnity provision (Section 9) or other Section of the License
-conflicts with the conditions of the GPLv2, you may retroactively and
-prospectively choose to deem waived or otherwise exclude such Section(s) of
-the License, but only in their entirety and only with respect to the Combined
-Software.
-
diff --git a/crates/wasm-wave/src/value/wit.rs b/crates/wasm-wave/src/value/wit.rs
index 73ab19d4..8d8231a7 100644
--- a/crates/wasm-wave/src/value/wit.rs
+++ b/crates/wasm-wave/src/value/wit.rs
@@ -149,27 +149,23 @@ impl<'a> TypeResolver<'a> {
 
 #[cfg(test)]
 mod tests {
-    use wit_parser::UnresolvedPackageGroup;
 
     use super::*;
 
     #[test]
     fn resolve_wit_type_smoke_test() {
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = UnresolvedPackageGroup::parse(
-            "test.wit",
-            r#"
-            package test:types;
-            interface types {
-                type uint8 = u8;
-            }
-        "#,
-        )
-        .unwrap();
         let mut resolve = Resolve::new();
-        resolve.push(packages.remove(0), &source_map).unwrap();
+        resolve
+            .push_str(
+                "test.wit",
+                "
+package test:types;
+interface types {
+    type uint8 = u8;
+}
+                ",
+            )
+            .unwrap();
 
         let (type_id, _) = resolve.types.iter().next().unwrap();
         let ty = resolve_wit_type(&resolve, type_id).unwrap();
@@ -178,24 +174,21 @@ mod tests {
 
     #[test]
     fn resolve_wit_func_type_smoke_test() {
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = UnresolvedPackageGroup::parse(
-            "test.wit",
-            r#"
-            package test:types;
-            interface types {
-                type uint8 = u8;
-                no-results: func(a: uint8, b: string);
-                one-result: func(c: uint8, d: string) -> uint8;
-                named-results: func(e: uint8, f: string) -> (x: u8, y: string);
-            }
-        "#,
-        )
-        .unwrap();
         let mut resolve = Resolve::new();
-        resolve.push(packages.remove(0), &source_map).unwrap();
+        resolve
+            .push_str(
+                "test.wit",
+                r#"
+package test:types;
+interface types {
+    type uint8 = u8;
+    no-results: func(a: uint8, b: string);
+    one-result: func(c: uint8, d: string) -> uint8;
+    named-results: func(e: uint8, f: string) -> (x: u8, y: string);
+}
+                "#,
+            )
+            .unwrap();
 
         for (func_name, expected_display) in [
             ("no-results", "func(a: u8, b: string)"),
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index 35599208..40c50786 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -2136,7 +2136,7 @@ mod test {
     #[test]
     fn it_renames_imports() {
         let mut resolve = Resolve::new();
-        let pkgs = resolve
+        let pkg = resolve
             .push_str(
                 "test.wit",
                 r#"
@@ -2155,7 +2155,7 @@ world test {
 "#,
             )
             .unwrap();
-        let world = resolve.select_world(&pkgs, None).unwrap();
+        let world = resolve.select_world(pkg, None).unwrap();
 
         let mut module = dummy_module(&resolve, world);
 
diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs
index ba175aa8..867e1b82 100644
--- a/crates/wit-component/src/lib.rs
+++ b/crates/wit-component/src/lib.rs
@@ -145,8 +145,8 @@ world test-world {}
 
         // Parse pre-canned WIT to build resolver
         let mut resolver = Resolve::default();
-        let pkgs = resolver.push_str("in-code.wit", COMPONENT_WIT)?;
-        let world = resolver.select_world(&pkgs, Some("test-world"))?;
+        let pkg = resolver.push_str("in-code.wit", COMPONENT_WIT)?;
+        let world = resolver.select_world(pkg, Some("test-world"))?;
 
         // Embed component metadata
         embed_component_metadata(&mut bytes, &resolver, world, StringEncoding::UTF8)?;
diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs
index f3e43614..982b11f7 100644
--- a/crates/wit-component/src/metadata.rs
+++ b/crates/wit-component/src/metadata.rs
@@ -259,12 +259,12 @@ impl Bindgen {
                 let world_name = reader.read_string()?;
                 wasm = &data[reader.original_position()..];
 
-                let (r, pkgs) = match crate::decode(wasm)? {
-                    DecodedWasm::WitPackages(resolve, pkgs) => (resolve, pkgs),
+                let (r, pkg) = match crate::decode(wasm)? {
+                    DecodedWasm::WitPackage(resolve, pkgs) => (resolve, pkgs),
                     DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"),
                 };
                 resolve = r;
-                world = resolve.select_world(&pkgs, Some(world_name.into()))?;
+                world = resolve.select_world(pkg, Some(world_name.into()))?;
             }
 
             // Current format where `data` is a wasm component itself.
diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs
index 0bd41c8a..15855645 100644
--- a/crates/wit-component/src/printing.rs
+++ b/crates/wit-component/src/printing.rs
@@ -50,62 +50,72 @@ impl WitPrinter {
         self
     }
 
-    /// Print a set of one or more WIT packages into a string.
+    /// Prints the specified `pkg` which is located in `resolve` to a string.
+    ///
+    /// The `nested` list of packages are other packages to include at the end
+    /// of the output in `package ... { ... }` syntax.
     pub fn print(
         &mut self,
         resolve: &Resolve,
-        pkg_ids: &[PackageId],
-        force_print_package_in_curlies: bool,
+        pkg: PackageId,
+        nested: &[PackageId],
     ) -> Result<String> {
-        let print_package_in_curlies = force_print_package_in_curlies || pkg_ids.len() > 1;
-        for (i, pkg_id) in pkg_ids.into_iter().enumerate() {
+        self.print_package(resolve, pkg, true)?;
+        for (i, pkg_id) in nested.iter().enumerate() {
             if i > 0 {
                 self.output.push_str("\n\n");
             }
+            self.print_package(resolve, *pkg_id, false)?;
+        }
 
-            let pkg = &resolve.packages[*pkg_id];
-            self.print_docs(&pkg.docs);
-            self.output.push_str("package ");
-            self.print_name(&pkg.name.namespace);
-            self.output.push_str(":");
-            self.print_name(&pkg.name.name);
-            if let Some(version) = &pkg.name.version {
-                self.output.push_str(&format!("@{version}"));
-            }
+        Ok(std::mem::take(&mut self.output).into())
+    }
 
-            if print_package_in_curlies {
-                self.output.push_str(" {\n");
-            } else {
-                self.print_semicolon();
-                self.output.push_str("\n\n");
-            }
+    fn print_package(&mut self, resolve: &Resolve, pkg: PackageId, is_main: bool) -> Result<()> {
+        let pkg = &resolve.packages[pkg];
+        self.print_docs(&pkg.docs);
+        self.output.push_str("package ");
+        self.print_name(&pkg.name.namespace);
+        self.output.push_str(":");
+        self.print_name(&pkg.name.name);
+        if let Some(version) = &pkg.name.version {
+            self.output.push_str(&format!("@{version}"));
+        }
 
-            for (name, id) in pkg.interfaces.iter() {
-                self.print_docs(&resolve.interfaces[*id].docs);
-                self.print_stability(&resolve.interfaces[*id].stability);
-                self.output.push_str("interface ");
-                self.print_name(name);
-                self.output.push_str(" {\n");
-                self.print_interface(resolve, *id)?;
-                writeln!(&mut self.output, "}}\n")?;
-            }
+        if is_main {
+            self.print_semicolon();
+            self.output.push_str("\n\n");
+        } else {
+            self.output.push_str(" {\n");
+        }
 
-            for (name, id) in pkg.worlds.iter() {
-                self.print_docs(&resolve.worlds[*id].docs);
-                self.print_stability(&resolve.worlds[*id].stability);
-                self.output.push_str("world ");
-                self.print_name(name);
-                self.output.push_str(" {\n");
-                self.print_world(resolve, *id)?;
+        for (name, id) in pkg.interfaces.iter() {
+            self.print_docs(&resolve.interfaces[*id].docs);
+            self.print_stability(&resolve.interfaces[*id].stability);
+            self.output.push_str("interface ");
+            self.print_name(name);
+            self.output.push_str(" {\n");
+            self.print_interface(resolve, *id)?;
+            if is_main {
+                writeln!(&mut self.output, "}}\n")?;
+            } else {
                 writeln!(&mut self.output, "}}")?;
             }
-
-            if print_package_in_curlies {
-                self.output.push_str("}\n");
-            }
         }
 
-        Ok(std::mem::take(&mut self.output).into())
+        for (name, id) in pkg.worlds.iter() {
+            self.print_docs(&resolve.worlds[*id].docs);
+            self.print_stability(&resolve.worlds[*id].stability);
+            self.output.push_str("world ");
+            self.print_name(name);
+            self.output.push_str(" {\n");
+            self.print_world(resolve, *id)?;
+            writeln!(&mut self.output, "}}")?;
+        }
+        if !is_main {
+            writeln!(&mut self.output, "}}")?;
+        }
+        Ok(())
     }
 
     fn print_semicolon(&mut self) {
diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs
index 8979e2bb..9412b6ac 100644
--- a/crates/wit-parser/src/ast.rs
+++ b/crates/wit-parser/src/ast.rs
@@ -1,10 +1,10 @@
-use crate::{Error, UnresolvedPackage, UnresolvedPackageGroup};
+use crate::{Error, UnresolvedPackageGroup};
 use anyhow::{bail, Context, Result};
 use lex::{Span, Token, Tokenizer};
 use semver::Version;
 use std::borrow::Cow;
-use std::collections::HashSet;
 use std::fmt;
+use std::mem;
 use std::path::{Path, PathBuf};
 
 pub mod lex;
@@ -15,19 +15,64 @@ pub mod toposort;
 
 pub use lex::validate_id;
 
-enum Ast<'a> {
-    ExplicitPackages(Vec<ExplicitPackage<'a>>),
-    PartialImplicitPackage(PartialImplicitPackage<'a>),
-}
-
-pub struct ExplicitPackage<'a> {
-    package_id: PackageName<'a>,
+/// Representation of a single WIT `*.wit` file and nested packages.
+struct PackageFile<'a> {
+    /// Optional `package foo:bar;` header
+    package_id: Option<PackageName<'a>>,
+    /// Other AST items.
     decl_list: DeclList<'a>,
 }
 
-pub struct PartialImplicitPackage<'a> {
-    package_id: Option<PackageName<'a>>,
-    decl_list: DeclList<'a>,
+impl<'a> PackageFile<'a> {
+    /// Parse a standalone file represented by `tokens`.
+    ///
+    /// This will optionally start with `package foo:bar;` and then will have a
+    /// list of ast items after it.
+    fn parse(tokens: &mut Tokenizer<'a>) -> Result<Self> {
+        let mut package_name_tokens_peek = tokens.clone();
+        let docs = parse_docs(&mut package_name_tokens_peek)?;
+
+        // Parse `package foo:bar;` but throw it out if it's actually
+        // `package foo:bar { ... }` since that's an ast item instead.
+        let package_id = if package_name_tokens_peek.eat(Token::Package)? {
+            let name = PackageName::parse(&mut package_name_tokens_peek, docs)?;
+            if package_name_tokens_peek.eat(Token::Semicolon)? {
+                *tokens = package_name_tokens_peek;
+                Some(name)
+            } else {
+                None
+            }
+        } else {
+            None
+        };
+        let decl_list = DeclList::parse_until(tokens, None)?;
+        Ok(PackageFile {
+            package_id,
+            decl_list,
+        })
+    }
+
+    /// Parse a nested package of the form `package foo:bar { ... }`
+    fn parse_nested(
+        tokens: &mut Tokenizer<'a>,
+        docs: Docs<'a>,
+        attributes: Vec<Attribute<'a>>,
+    ) -> Result<Self> {
+        let span = tokens.expect(Token::Package)?;
+        if !attributes.is_empty() {
+            bail!(Error::new(
+                span,
+                format!("cannot place attributes on nested packages"),
+            ));
+        }
+        let package_id = PackageName::parse(tokens, docs)?;
+        tokens.expect(Token::LeftBrace)?;
+        let decl_list = DeclList::parse_until(tokens, Some(Token::RightBrace))?;
+        Ok(PackageFile {
+            package_id: Some(package_id),
+            decl_list,
+        })
+    }
 }
 
 /// Stores all of the declarations in a package's scope. In AST terms, this
@@ -45,7 +90,7 @@ pub struct PartialImplicitPackage<'a> {
 /// /* END DECL LIST */
 /// ```
 ///
-/// In the explicit package style, a [`DeclList`] is everything inside of each
+/// In the nested package style, a [`DeclList`] is everything inside of each
 /// `package` element's brackets:
 ///
 /// ```wit
@@ -65,84 +110,37 @@ pub struct PartialImplicitPackage<'a> {
 ///   /* END SECOND DECL LIST */
 /// }
 /// ```
+#[derive(Default)]
 pub struct DeclList<'a> {
     items: Vec<AstItem<'a>>,
 }
 
 impl<'a> DeclList<'a> {
-    fn parse_explicit_package_items(tokens: &mut Tokenizer<'a>) -> Result<DeclList<'a>> {
+    fn parse_until(tokens: &mut Tokenizer<'a>, end: Option<Token>) -> Result<DeclList<'a>> {
         let mut items = Vec::new();
         let mut docs = parse_docs(tokens)?;
         loop {
-            if tokens.eat(Token::RightBrace)? {
-                return Ok(DeclList { items });
+            match end {
+                Some(end) => {
+                    if tokens.eat(end)? {
+                        break;
+                    }
+                }
+                None => {
+                    if tokens.clone().next()?.is_none() {
+                        break;
+                    }
+                }
             }
             items.push(AstItem::parse(tokens, docs)?);
             docs = parse_docs(tokens)?;
         }
-    }
-
-    fn parse_implicit_package_items(
-        tokens: &mut Tokenizer<'a>,
-        mut docs: Docs<'a>,
-    ) -> Result<DeclList<'a>> {
-        let mut items = Vec::new();
-        while tokens.clone().next()?.is_some() {
-            items.push(AstItem::parse(tokens, docs)?);
-            docs = parse_docs(tokens)?;
-        }
         Ok(DeclList { items })
     }
-}
 
-impl<'a> Ast<'a> {
-    fn parse(tokens: &mut Tokenizer<'a>) -> Result<Self> {
-        let mut maybe_package_id = None;
-        let mut packages = Vec::new();
-        let mut docs = parse_docs(tokens)?;
-        loop {
-            if tokens.clone().next()?.is_none() {
-                break;
-            }
-            if !tokens.eat(Token::Package)? {
-                if !packages.is_empty() {
-                    bail!("WIT files cannot mix top-level explicit `package` declarations with other declaration kinds");
-                }
-                break;
-            }
-
-            let package_id = PackageName::parse(tokens, std::mem::take(&mut docs))?;
-            if tokens.eat(Token::LeftBrace)? {
-                packages.push(ExplicitPackage {
-                    package_id: package_id,
-                    decl_list: DeclList::parse_explicit_package_items(tokens)?,
-                });
-                docs = parse_docs(tokens)?;
-            } else {
-                maybe_package_id = Some(package_id);
-                tokens.expect_semicolon()?;
-                if !packages.is_empty() {
-                    bail!("WIT files cannot mix top-level explicit `package` declarations with other declaration kinds");
-                }
-                docs = parse_docs(tokens)?;
-                break;
-            }
-        }
-
-        if packages.is_empty() {
-            return Ok(Ast::PartialImplicitPackage(PartialImplicitPackage {
-                package_id: maybe_package_id,
-                decl_list: DeclList::parse_implicit_package_items(tokens, docs)?,
-            }));
-        }
-        Ok(Ast::ExplicitPackages(packages))
-    }
-}
-
-impl<'a> DeclList<'a> {
     fn for_each_path<'b>(
         &'b self,
-        mut f: impl FnMut(
+        f: &mut dyn FnMut(
             Option<&'b Id<'a>>,
             &'b UsePath<'a>,
             Option<&'b [UseName<'a>]>,
@@ -216,6 +214,8 @@ impl<'a> DeclList<'a> {
                     // It is up to the resolver to decides how to handle this ambiguity.
                     f(None, &u.item, None, WorldOrInterface::Unknown)?;
                 }
+
+                AstItem::Package(pkg) => pkg.decl_list.for_each_path(f)?,
             }
         }
         Ok(())
@@ -226,6 +226,7 @@ enum AstItem<'a> {
     Interface(Interface<'a>),
     World(World<'a>),
     Use(ToplevelUse<'a>),
+    Package(PackageFile<'a>),
 }
 
 impl<'a> AstItem<'a> {
@@ -237,6 +238,9 @@ impl<'a> AstItem<'a> {
             }
             Some((_span, Token::World)) => World::parse(tokens, docs, attributes).map(Self::World),
             Some((_span, Token::Use)) => ToplevelUse::parse(tokens, attributes).map(Self::Use),
+            Some((_span, Token::Package)) => {
+                PackageFile::parse_nested(tokens, docs, attributes).map(Self::Package)
+            }
             other => Err(err_expected(tokens, "`world`, `interface` or `use`", other).into()),
         }
     }
@@ -1649,40 +1653,6 @@ struct Source {
     contents: String,
 }
 
-enum ResolverKind<'a> {
-    Unknown,
-    Explicit(Vec<UnresolvedPackage>),
-    PartialImplicit(Resolver<'a>),
-}
-
-fn parse_package(
-    unparsed_pkgs: Vec<ExplicitPackage>,
-    src: &Source,
-    explicit_pkg_names: &mut HashSet<crate::PackageName>,
-    parsed_pkgs: &mut Vec<UnresolvedPackage>,
-) -> Result<()> {
-    for pkg in unparsed_pkgs {
-        let mut resolver = Resolver::default();
-        let pkg_name = pkg.package_id.package_name();
-        let ingested = resolver
-            .push_then_resolve(pkg)
-            .with_context(|| format!("failed to start resolving path: {}", src.path.display()))?;
-
-        match explicit_pkg_names.get(&pkg_name) {
-            Some(_) => bail!(
-                "colliding explicit package names, multiple packages named `{}`",
-                pkg_name
-            ),
-            None => explicit_pkg_names.insert(pkg_name),
-        };
-
-        if let Some(unresolved) = ingested {
-            parsed_pkgs.push(unresolved);
-        }
-    }
-    Ok(())
-}
-
 impl SourceMap {
     /// Creates a new empty source map.
     pub fn new() -> SourceMap {
@@ -1732,13 +1702,18 @@ impl SourceMap {
         self.offset = new_offset;
     }
 
-    /// Parses the files added to this source map into one or more [`UnresolvedPackage`]s.
+    /// Parses the files added to this source map into a
+    /// [`UnresolvedPackageGroup`].
     pub fn parse(self) -> Result<UnresolvedPackageGroup> {
-        let mut resolver_kind = ResolverKind::Unknown;
-        let parsed_pkgs = self.rewrite_error(|| {
-            let mut explicit_pkg_names: HashSet<crate::PackageName> = HashSet::new();
+        let mut nested = Vec::new();
+        let main = self.rewrite_error(|| {
+            let mut resolver = Resolver::default();
             let mut srcs = self.sources.iter().collect::<Vec<_>>();
             srcs.sort_by_key(|src| &src.path);
+
+            // Parse each source file individually. A tokenizer is created here
+            // form settings and then `PackageFile` is used to parse the whole
+            // stream of tokens.
             for src in srcs {
                 let mut tokens = Tokenizer::new(
                     // chop off the forcibly appended `\n` character when
@@ -1749,53 +1724,44 @@ impl SourceMap {
                     self.require_f32_f64,
                 )
                 .with_context(|| format!("failed to tokenize path: {}", src.path.display()))?;
-
-                match Ast::parse(&mut tokens)? {
-                    Ast::ExplicitPackages(pkgs) => {
-                        match &mut resolver_kind {
-                            ResolverKind::Unknown => {
-                                let mut parsed_pkgs = Vec::new();
-                                parse_package(pkgs, src, &mut explicit_pkg_names, &mut parsed_pkgs)?;
-                                resolver_kind = ResolverKind::Explicit(parsed_pkgs);
-                            },
-                            ResolverKind::Explicit(parsed_pkgs) => {
-                                parse_package(pkgs, src, &mut explicit_pkg_names, parsed_pkgs)?;
-                            },
-                            ResolverKind::PartialImplicit(_) => bail!("WIT files cannot mix top-level explicit `package` declarations with other declaration kinds"),
-                        }
-                    }
-                    Ast::PartialImplicitPackage(partial) => {
-                        match &mut resolver_kind {
-                            ResolverKind::Unknown => {
-                                let mut resolver = Resolver::default();
-                                resolver.push_partial(partial).with_context(|| {
-                                    format!("failed to start resolving path: {}", src.path.display())
-                                })?;
-                                resolver_kind = ResolverKind::PartialImplicit(resolver);
-                            },
-                            ResolverKind::Explicit(_) => bail!("WIT files cannot mix top-level explicit `package` declarations with other declaration kinds"),
-                            ResolverKind::PartialImplicit(resolver) => {
-                                resolver.push_partial(partial).with_context(|| {
-                                    format!("failed to start resolving path: {}", src.path.display())
-                                })?;
-                            }
+                let mut file = PackageFile::parse(&mut tokens)?;
+
+                // Filter out any nested packages and resolve them separately.
+                // Nested packages have only a single "file" so only one item
+                // is pushed into a `Resolver`. Note that a nested `Resolver`
+                // is used here, not the outer one.
+                //
+                // Note that filtering out `Package` items is required due to
+                // how the implementation of disallowing nested packages in
+                // nested packages currently works.
+                for item in mem::take(&mut file.decl_list.items) {
+                    match item {
+                        AstItem::Package(nested_pkg) => {
+                            let mut resolve = Resolver::default();
+                            resolve.push(nested_pkg).with_context(|| {
+                                format!(
+                                    "failed to handle nested package in: {}",
+                                    src.path.display()
+                                )
+                            })?;
+
+                            nested.push(resolve.resolve()?);
                         }
+                        other => file.decl_list.items.push(other),
                     }
                 }
-            }
 
-            match resolver_kind {
-                ResolverKind::Unknown => bail!("No WIT packages found in the supplied source"),
-                ResolverKind::Explicit(pkgs) => Ok(pkgs),
-                ResolverKind::PartialImplicit(mut resolver) => match resolver.resolve()? {
-                    Some(pkg) => Ok(vec![pkg]),
-                    None => bail!("No WIT packages found in the supplied source"),
-                },
+                // With nested packages handled push this file into the
+                // resolver.
+                resolver.push(file).with_context(|| {
+                    format!("failed to start resolving path: {}", src.path.display())
+                })?;
             }
+            Ok(resolver.resolve()?)
         })?;
-
         Ok(UnresolvedPackageGroup {
-            packages: parsed_pkgs,
+            main,
+            nested,
             source_map: self,
         })
     }
@@ -1813,6 +1779,8 @@ impl SourceMap {
                 let msg = self.highlight_err(parse.span.start, Some(parse.span.end), &parse.msg);
                 parse.highlighted = Some(msg);
             }
+        }
+        if let Some(_) = err.downcast_mut::<Error>() {
             return Err(err);
         }
 
@@ -1842,14 +1810,10 @@ impl SourceMap {
     }
 
     fn highlight_err(&self, start: u32, end: Option<u32>, err: impl fmt::Display) -> String {
-        let i = match self.sources.binary_search_by_key(&start, |src| src.offset) {
-            Ok(i) => i,
-            Err(i) => i - 1,
-        };
-        let src = &self.sources[i];
-        let start = usize::try_from(start - src.offset).unwrap();
-        let end = end.map(|end| usize::try_from(end - src.offset).unwrap());
-        let (line, col) = linecol_in(start, &src.contents);
+        let src = self.source_for_offset(start);
+        let start = src.to_relative_offset(start);
+        let end = end.map(|end| src.to_relative_offset(end));
+        let (line, col) = src.linecol(start);
         let snippet = src.contents.lines().nth(line).unwrap_or("");
         let mut msg = format!(
             "\
@@ -1872,20 +1836,26 @@ impl SourceMap {
             }
         }
         return msg;
+    }
 
-        fn linecol_in(pos: usize, text: &str) -> (usize, usize) {
-            let mut cur = 0;
-            // Use split_terminator instead of lines so that if there is a `\r`,
-            // it is included in the offset calculation. The `+1` values below
-            // account for the `\n`.
-            for (i, line) in text.split_terminator('\n').enumerate() {
-                if cur + line.len() + 1 > pos {
-                    return (i, pos - cur);
-                }
-                cur += line.len() + 1;
-            }
-            (text.lines().count(), 0)
-        }
+    pub(crate) fn render_location(&self, span: Span) -> String {
+        let src = self.source_for_offset(span.start);
+        let start = src.to_relative_offset(span.start);
+        let (line, col) = src.linecol(start);
+        format!(
+            "{file}:{line}:{col}",
+            file = src.path.display(),
+            line = line + 1,
+            col = col + 1,
+        )
+    }
+
+    fn source_for_offset(&self, start: u32) -> &Source {
+        let i = match self.sources.binary_search_by_key(&start, |src| src.offset) {
+            Ok(i) => i,
+            Err(i) => i - 1,
+        };
+        &self.sources[i]
     }
 
     /// Returns an iterator over all filenames added to this source map.
@@ -1894,6 +1864,26 @@ impl SourceMap {
     }
 }
 
+impl Source {
+    fn to_relative_offset(&self, offset: u32) -> usize {
+        usize::try_from(offset - self.offset).unwrap()
+    }
+
+    fn linecol(&self, relative_offset: usize) -> (usize, usize) {
+        let mut cur = 0;
+        // Use split_terminator instead of lines so that if there is a `\r`,
+        // it is included in the offset calculation. The `+1` values below
+        // account for the `\n`.
+        for (i, line) in self.contents.split_terminator('\n').enumerate() {
+            if cur + line.len() + 1 > relative_offset {
+                return (i, relative_offset - cur);
+            }
+            cur += line.len() + 1;
+        }
+        (self.contents.lines().count(), 0)
+    }
+}
+
 pub enum ParsedUsePath {
     Name(String),
     Package(crate::PackageName, String),
diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs
index cddbde46..bab7568c 100644
--- a/crates/wit-parser/src/ast/resolve.rs
+++ b/crates/wit-parser/src/ast/resolve.rs
@@ -8,7 +8,7 @@ use std::mem;
 #[derive(Default)]
 pub struct Resolver<'a> {
     /// Current package name learned through the ASTs pushed onto this resolver.
-    package_name: Option<PackageName>,
+    package_name: Option<(PackageName, Span)>,
 
     /// Package docs.
     package_docs: Docs,
@@ -109,13 +109,13 @@ enum TypeOrItem {
 }
 
 impl<'a> Resolver<'a> {
-    pub(crate) fn push_partial(&mut self, partial: ast::PartialImplicitPackage<'a>) -> Result<()> {
+    pub(super) fn push(&mut self, file: ast::PackageFile<'a>) -> Result<()> {
         // As each WIT file is pushed into this resolver keep track of the
         // current package name assigned. Only one file needs to mention it, but
         // if multiple mention it then they must all match.
-        if let Some(cur) = &partial.package_id {
+        if let Some(cur) = &file.package_id {
             let cur_name = cur.package_name();
-            if let Some(prev) = &self.package_name {
+            if let Some((prev, _)) = &self.package_name {
                 if cur_name != *prev {
                     bail!(Error::new(
                         cur.span,
@@ -126,7 +126,7 @@ impl<'a> Resolver<'a> {
                     ))
                 }
             }
-            self.package_name = Some(cur_name);
+            self.package_name = Some((cur_name, cur.span));
 
             // At most one 'package' item can have doc comments.
             let docs = self.docs(&cur.docs);
@@ -140,18 +140,30 @@ impl<'a> Resolver<'a> {
                 self.package_docs = docs;
             }
         }
-        self.decl_lists.push(partial.decl_list);
+
+        // Ensure that there are no nested packages in `file`. Note that for
+        // top level files nested packages are handled separately in `ast.rs`
+        // with their own resolver.
+        for item in file.decl_list.items.iter() {
+            let span = match item {
+                ast::AstItem::Package(pkg) => pkg.package_id.as_ref().unwrap().span,
+                _ => continue,
+            };
+            bail!(Error::new(
+                span,
+                "nested packages must be placed at the top-level"
+            ))
+        }
+
+        self.decl_lists.push(file.decl_list);
         Ok(())
     }
 
-    pub(crate) fn resolve(&mut self) -> Result<Option<UnresolvedPackage>> {
+    pub(crate) fn resolve(&mut self) -> Result<UnresolvedPackage> {
         // At least one of the WIT files must have a `package` annotation.
-        let name = match &self.package_name {
+        let (name, package_name_span) = match &self.package_name {
             Some(name) => name.clone(),
             None => {
-                if self.decl_lists.is_empty() {
-                    return Ok(None);
-                }
                 bail!("no `package` header was found in any WIT file for this package")
             }
         };
@@ -188,6 +200,7 @@ impl<'a> Resolver<'a> {
                         world_id_to_ast.insert(id, (world, i));
                     }
                     ast::AstItem::Use(_) => {}
+                    ast::AstItem::Package(_) => unreachable!(),
                 }
             }
         }
@@ -204,7 +217,9 @@ impl<'a> Resolver<'a> {
             self.resolve_world(id, world)?;
         }
 
-        Ok(Some(UnresolvedPackage {
+        self.decl_lists = decl_lists;
+        Ok(UnresolvedPackage {
+            package_name_span,
             name,
             docs: mem::take(&mut self.package_docs),
             worlds: mem::take(&mut self.worlds),
@@ -228,17 +243,7 @@ impl<'a> Resolver<'a> {
             type_spans: mem::take(&mut self.type_spans),
             foreign_dep_spans: mem::take(&mut self.foreign_dep_spans),
             required_resource_types: mem::take(&mut self.required_resource_types),
-        }))
-    }
-
-    pub(crate) fn push_then_resolve(
-        &mut self,
-        package: ast::ExplicitPackage<'a>,
-    ) -> Result<Option<UnresolvedPackage>> {
-        self.package_name = Some(package.package_id.package_name());
-        self.docs(&package.package_id.docs);
-        self.decl_lists = vec![package.decl_list];
-        self.resolve()
+        })
     }
 
     /// Registers all foreign dependencies made within the ASTs provided.
@@ -251,7 +256,7 @@ impl<'a> Resolver<'a> {
         let mut foreign_worlds = mem::take(&mut self.foreign_worlds);
         for decl_list in decl_lists {
             decl_list
-                .for_each_path(|_, path, _names, world_or_iface| {
+                .for_each_path(&mut |_, path, _names, world_or_iface| {
                     let (id, name) = match path {
                         ast::UsePath::Package { id, name } => (id, name),
                         _ => return Ok(()),
@@ -381,6 +386,8 @@ impl<'a> Resolver<'a> {
                     }
                     // These are processed down below.
                     ast::AstItem::Use(_) => {}
+
+                    ast::AstItem::Package(_) => unreachable!(),
                 }
             }
             decl_list_namespaces.push(decl_list_ns);
@@ -413,6 +420,7 @@ impl<'a> Resolver<'a> {
                     }
                     ast::AstItem::Interface(i) => (&i.name, ItemSource::Local(i.name.clone())),
                     ast::AstItem::World(w) => (&w.name, ItemSource::Local(w.name.clone())),
+                    ast::AstItem::Package(_) => unreachable!(),
                 };
                 if decl_list_ns.insert(name.name, (name.span, src)).is_some() {
                     bail!(Error::new(
@@ -424,7 +432,7 @@ impl<'a> Resolver<'a> {
 
             // With this file's namespace information look at all `use` paths
             // and record dependencies between interfaces.
-            decl_list.for_each_path(|iface, path, _names, _| {
+            decl_list.for_each_path(&mut |iface, path, _names, _| {
                 // If this import isn't contained within an interface then it's
                 // in a world and it doesn't need to participate in our
                 // topo-sort.
@@ -485,7 +493,7 @@ impl<'a> Resolver<'a> {
                     assert!(prev.is_none());
                     world_id_order.push(id);
                 }
-                ast::AstItem::Use(_) => unreachable!(),
+                ast::AstItem::Use(_) | ast::AstItem::Package(_) => unreachable!(),
             };
         }
         for decl_list in decl_lists {
@@ -526,6 +534,7 @@ impl<'a> Resolver<'a> {
                         assert!(matches!(world_item, AstItem::World(_)));
                         (w.name.name, world_item)
                     }
+                    ast::AstItem::Package(_) => unreachable!(),
                 };
                 let prev = items.insert(name, ast_item);
                 assert!(prev.is_none());
@@ -551,7 +560,7 @@ impl<'a> Resolver<'a> {
     fn populate_foreign_types(&mut self, decl_lists: &[ast::DeclList<'a>]) -> Result<()> {
         for (i, decl_list) in decl_lists.iter().enumerate() {
             self.cur_ast_index = i;
-            decl_list.for_each_path(|_, path, names, _| {
+            decl_list.for_each_path(&mut |_, path, names, _| {
                 let names = match names {
                     Some(names) => names,
                     None => return Ok(()),
diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs
index 9574cdc2..dc042f15 100644
--- a/crates/wit-parser/src/decoding.rs
+++ b/crates/wit-parser/src/decoding.rs
@@ -2,7 +2,6 @@ use crate::*;
 use anyhow::{anyhow, bail};
 use indexmap::IndexSet;
 use std::mem;
-use std::slice;
 use std::{collections::HashMap, io::Read};
 use wasmparser::Chunk;
 use wasmparser::{
@@ -350,7 +349,7 @@ pub enum DecodedWasm {
     /// The full resolve graph is here plus the identifier of the packages that
     /// were encoded. Note that other packages may be within the resolve if any
     /// of the main packages refer to other, foreign packages.
-    WitPackages(Resolve, Vec<PackageId>),
+    WitPackage(Resolve, PackageId),
 
     /// The input to [`decode`] was a component and its interface is specified
     /// by the world here.
@@ -361,18 +360,16 @@ impl DecodedWasm {
     /// Returns the [`Resolve`] for WIT types contained.
     pub fn resolve(&self) -> &Resolve {
         match self {
-            DecodedWasm::WitPackages(resolve, _) => resolve,
+            DecodedWasm::WitPackage(resolve, _) => resolve,
             DecodedWasm::Component(resolve, _) => resolve,
         }
     }
 
     /// Returns the main packages of what was decoded.
-    pub fn packages(&self) -> &[PackageId] {
+    pub fn package(&self) -> PackageId {
         match self {
-            DecodedWasm::WitPackages(_, ids) => ids,
-            DecodedWasm::Component(resolve, world) => {
-                slice::from_ref(&resolve.worlds[*world].package.as_ref().unwrap())
-            }
+            DecodedWasm::WitPackage(_, id) => *id,
+            DecodedWasm::Component(resolve, world) => resolve.worlds[*world].package.unwrap(),
         }
     }
 }
@@ -386,12 +383,12 @@ pub fn decode_reader(reader: impl Read) -> Result<DecodedWasm> {
             WitEncodingVersion::V1 => {
                 log::debug!("decoding a v1 WIT package encoded as wasm");
                 let (resolve, pkg) = info.decode_wit_v1_package()?;
-                Ok(DecodedWasm::WitPackages(resolve, vec![pkg]))
+                Ok(DecodedWasm::WitPackage(resolve, pkg))
             }
             WitEncodingVersion::V2 => {
                 log::debug!("decoding a v2 WIT package encoded as wasm");
                 let (resolve, pkg) = info.decode_wit_v2_package()?;
-                Ok(DecodedWasm::WitPackages(resolve, vec![pkg]))
+                Ok(DecodedWasm::WitPackage(resolve, pkg))
             }
         }
     } else {
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index d711ef07..66d538e0 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -106,6 +106,7 @@ pub struct UnresolvedPackage {
     /// Doc comments for this package.
     pub docs: Docs,
 
+    package_name_span: Span,
     unknown_type_spans: Vec<Span>,
     interface_spans: Vec<InterfaceSpan>,
     world_spans: Vec<WorldSpan>,
@@ -115,10 +116,16 @@ pub struct UnresolvedPackage {
 }
 
 /// Tracks a set of packages, all pulled from the same group of WIT source files.
-#[derive(Default)]
+#[derive(Clone)]
 pub struct UnresolvedPackageGroup {
-    /// A set of packages that share source file(s).
-    pub packages: Vec<UnresolvedPackage>,
+    /// The "main" package in this package group which was found at the root of
+    /// the WIT files.
+    ///
+    /// Note that this is required to be present in all WIT files.
+    pub main: UnresolvedPackage,
+
+    /// Nested packages found while parsing `main`, if any.
+    pub nested: Vec<UnresolvedPackage>,
 
     /// A set of processed source files from which these packages have been parsed.
     pub source_map: SourceMap,
@@ -220,11 +227,6 @@ impl fmt::Display for Error {
 impl std::error::Error for Error {}
 
 impl UnresolvedPackageGroup {
-    /// Creates an empty set of packages.
-    pub fn new() -> UnresolvedPackageGroup {
-        UnresolvedPackageGroup::default()
-    }
-
     /// Parses the given string as a wit document.
     ///
     /// The `path` argument is used for error reporting. The `contents` provided
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index 5471a54c..08042d5a 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -185,11 +185,11 @@ impl Resolve {
     ///
     /// More information can also be found at [`Resolve::push_dir`] and
     /// [`Resolve::push_file`].
-    pub fn push_path(&mut self, path: impl AsRef<Path>) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
+    pub fn push_path(&mut self, path: impl AsRef<Path>) -> Result<(PackageId, Vec<PathBuf>)> {
         self._push_path(path.as_ref())
     }
 
-    fn _push_path(&mut self, path: &Path) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
+    fn _push_path(&mut self, path: &Path) -> Result<(PackageId, Vec<PathBuf>)> {
         if path.is_dir() {
             self.push_dir(path).with_context(|| {
                 format!(
@@ -198,29 +198,52 @@ impl Resolve {
                 )
             })
         } else {
-            let ids = self.push_file(path)?;
-            Ok((ids, vec![path.to_path_buf()]))
+            let id = self.push_file(path)?;
+            Ok((id, vec![path.to_path_buf()]))
         }
     }
 
     fn sort_unresolved_packages(
         &mut self,
-        unresolved_groups: Vec<UnresolvedPackageGroup>,
-    ) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
-        let mut pkg_ids = Vec::new();
-        let mut path_bufs = Vec::new();
+        main: UnresolvedPackageGroup,
+        deps: Vec<UnresolvedPackageGroup>,
+    ) -> Result<(PackageId, Vec<PathBuf>)> {
         let mut pkg_details_map = BTreeMap::new();
         let mut source_maps = Vec::new();
-        for (i, unresolved_group) in unresolved_groups.into_iter().enumerate() {
+
+        let mut insert = |group: UnresolvedPackageGroup| {
             let UnresolvedPackageGroup {
-                packages,
+                main,
+                nested,
                 source_map,
-            } = unresolved_group;
-
+            } = group;
+            let i = source_maps.len();
             source_maps.push(source_map);
-            for pkg in packages {
-                pkg_details_map.insert(pkg.name.clone(), (pkg, i));
+
+            for pkg in nested.into_iter().chain([main]) {
+                let name = pkg.name.clone();
+                let my_span = pkg.package_name_span;
+                let (prev_pkg, prev_i) = match pkg_details_map.insert(name.clone(), (pkg, i)) {
+                    Some(pair) => pair,
+                    None => continue,
+                };
+                let loc1 = source_maps[i].render_location(my_span);
+                let loc2 = source_maps[prev_i].render_location(prev_pkg.package_name_span);
+                bail!(
+                    "\
+package {name} is defined in two different locations:\n\
+  * {loc1}\n\
+  * {loc2}\n\
+                     "
+                )
             }
+            Ok(())
+        };
+
+        let main_name = main.main.name.clone();
+        insert(main)?;
+        for dep in deps {
+            insert(dep)?;
         }
 
         // Perform a simple topological sort which will bail out on cycles
@@ -242,22 +265,25 @@ impl Resolve {
         // Ensure that the final output is topologically sorted. Use a set to ensure that we render
         // the buffers for each `SourceMap` only once, even though multiple packages may references
         // the same `SourceMap`.
-        let mut seen_source_maps = HashSet::new();
+        let mut main_pkg_id = None;
         for name in order {
-            match pkg_details_map.remove(&name) {
-                Some((pkg, source_map_index)) => {
-                    let source_map = &source_maps[source_map_index];
-                    if !seen_source_maps.contains(&source_map_index) {
-                        seen_source_maps.insert(source_map_index);
-                        path_bufs.extend(source_map.source_files().map(|p| p.to_path_buf()));
-                    }
-                    pkg_ids.push(self.push(pkg, source_map)?);
-                }
-                None => panic!("package in topologically ordered set, but not in package map"),
+            let (pkg, source_map_index) = pkg_details_map.remove(&name).unwrap();
+            let source_map = &source_maps[source_map_index];
+            let is_main = pkg.name == main_name;
+            let id = self.push(pkg, source_map)?;
+            if is_main {
+                assert!(main_pkg_id.is_none());
+                main_pkg_id = Some(id);
             }
         }
 
-        Ok((pkg_ids, path_bufs))
+        let path_bufs = source_maps
+            .iter()
+            .flat_map(|s| s.source_files())
+            .map(|p| p.to_path_buf())
+            .collect();
+
+        Ok((main_pkg_id.unwrap(), path_bufs))
     }
 
     /// Parses the filesystem directory at `path` as a WIT package and returns
@@ -295,32 +321,25 @@ impl Resolve {
     /// The second value returned here is the list of paths that were parsed
     /// when generating the return value. This can be useful for build systems
     /// that want to rebuild bindings whenever one of the files change.
-    pub fn push_dir(&mut self, path: impl AsRef<Path>) -> Result<(Vec<PackageId>, Vec<PathBuf>)> {
-        let path = path.as_ref();
-        let deps_path = path.join("deps");
-        let unresolved_deps = self.parse_deps_dir(&deps_path).with_context(|| {
-            format!(
-                "failed to parse dependency directory: {}",
-                deps_path.display()
-            )
-        })?;
-        let (_, mut path_bufs) = self.sort_unresolved_packages(unresolved_deps)?;
+    pub fn push_dir(&mut self, path: impl AsRef<Path>) -> Result<(PackageId, Vec<PathBuf>)> {
+        self._push_dir(path.as_ref())
+    }
 
-        let unresolved_top_level = UnresolvedPackageGroup::parse_dir(path)
+    fn _push_dir(&mut self, path: &Path) -> Result<(PackageId, Vec<PathBuf>)> {
+        let top_pkg = UnresolvedPackageGroup::parse_dir(path)
             .with_context(|| format!("failed to parse package: {}", path.display()))?;
-        let (pkgs_ids, mut top_level_path_bufs) =
-            self.sort_unresolved_packages(vec![unresolved_top_level])?;
+        let deps = path.join("deps");
+        let deps = self
+            .parse_deps_dir(&deps)
+            .with_context(|| format!("failed to parse dependency directory: {}", deps.display()))?;
 
-        path_bufs.append(&mut top_level_path_bufs);
-        Ok((pkgs_ids, path_bufs))
+        self.sort_unresolved_packages(top_pkg, deps)
     }
 
     fn parse_deps_dir(&mut self, path: &Path) -> Result<Vec<UnresolvedPackageGroup>> {
-        let mut unresolved_deps = Vec::new();
-        // If there's no `deps` dir, then there's no deps, so return the
-        // empty set.
+        let mut ret = Vec::new();
         if !path.exists() {
-            return Ok(unresolved_deps);
+            return Ok(ret);
         }
         let mut entries = path
             .read_dir()
@@ -329,37 +348,34 @@ impl Resolve {
         entries.sort_by_key(|e| e.file_name());
         for dep in entries {
             let path = dep.path();
-            if dep.file_type()?.is_dir() || path.metadata()?.is_dir() {
+            let pkg = if dep.file_type()?.is_dir() || path.metadata()?.is_dir() {
                 // If this entry is a directory or a symlink point to a
                 // directory then always parse it as an `UnresolvedPackage`
                 // since it's intentional to not support recursive `deps`
                 // directories.
-                unresolved_deps.push(
-                    UnresolvedPackageGroup::parse_dir(&path)
-                        .with_context(|| format!("failed to parse package: {}", path.display()))?,
-                )
+                UnresolvedPackageGroup::parse_dir(&path)
+                    .with_context(|| format!("failed to parse package: {}", path.display()))?
             } else {
                 // If this entry is a file then we may want to ignore it but
                 // this may also be a standalone WIT file or a `*.wasm` or
                 // `*.wat` encoded package.
                 let filename = dep.file_name();
-                unresolved_deps.push(
-                    match Path::new(&filename).extension().and_then(|s| s.to_str()) {
-                        Some("wit") | Some("wat") | Some("wasm") => match self._push_file(&path)? {
-                            #[cfg(feature = "decoding")]
-                            ParsedFile::Package(_) => continue,
-                            ParsedFile::Unresolved(pkgs) => pkgs,
-                        },
-
-                        // Other files in deps dir are ignored for now to avoid
-                        // accidentally including things like `.DS_Store` files in
-                        // the call below to `parse_dir`.
-                        _ => continue,
+                match Path::new(&filename).extension().and_then(|s| s.to_str()) {
+                    Some("wit") | Some("wat") | Some("wasm") => match self._push_file(&path)? {
+                        #[cfg(feature = "decoding")]
+                        ParsedFile::Package(_) => continue,
+                        ParsedFile::Unresolved(pkg) => pkg,
                     },
-                )
+
+                    // Other files in deps dir are ignored for now to avoid
+                    // accidentally including things like `.DS_Store` files in
+                    // the call below to `parse_dir`.
+                    _ => continue,
+                }
             };
+            ret.push(pkg);
         }
-        Ok(unresolved_deps)
+        Ok(ret)
     }
 
     /// Parses the contents of `path` from the filesystem and pushes the result
@@ -376,11 +392,11 @@ impl Resolve {
     /// In both situations the `PackageId`s of the resulting resolved packages
     /// are returned from this method. The return value is mostly useful in
     /// conjunction with [`Resolve::select_world`].
-    pub fn push_file(&mut self, path: impl AsRef<Path>) -> Result<Vec<PackageId>> {
+    pub fn push_file(&mut self, path: impl AsRef<Path>) -> Result<PackageId> {
         match self._push_file(path.as_ref())? {
             #[cfg(feature = "decoding")]
-            ParsedFile::Package(id) => Ok(vec![id]),
-            ParsedFile::Unresolved(pkgs) => self.push_group(pkgs),
+            ParsedFile::Package(id) => Ok(id),
+            ParsedFile::Unresolved(pkg) => self.push_group(pkg),
         }
     }
 
@@ -410,9 +426,9 @@ impl Resolve {
                     DecodedWasm::Component(..) => {
                         bail!("found an actual component instead of an encoded WIT package in wasm")
                     }
-                    DecodedWasm::WitPackages(resolve, pkgs) => {
+                    DecodedWasm::WitPackage(resolve, pkg) => {
                         let remap = self.merge(resolve)?;
-                        return Ok(ParsedFile::Package(remap.packages[pkgs[0].index()]));
+                        return Ok(ParsedFile::Package(remap.packages[pkg.index()]));
                     }
                 }
             }
@@ -453,12 +469,9 @@ impl Resolve {
     /// which corresponds to the package that was just inserted.
     ///
     /// The returned [`PackageId`]s are listed in topologically sorted order.
-    pub fn push_group(
-        &mut self,
-        unresolved_groups: UnresolvedPackageGroup,
-    ) -> Result<Vec<PackageId>> {
-        let (pkg_ids, _) = self.sort_unresolved_packages(vec![unresolved_groups])?;
-        Ok(pkg_ids)
+    pub fn push_group(&mut self, unresolved_group: UnresolvedPackageGroup) -> Result<PackageId> {
+        let (pkg_id, _) = self.sort_unresolved_packages(unresolved_group, Vec::new())?;
+        Ok(pkg_id)
     }
 
     /// Convenience method for combining [`UnresolvedPackageGroup::parse`] and
@@ -467,7 +480,7 @@ impl Resolve {
     /// The `path` provided is used for error messages but otherwise is not
     /// read. This method does not touch the filesystem. The `contents` provided
     /// are the contents of a WIT package.
-    pub fn push_str(&mut self, path: impl AsRef<Path>, contents: &str) -> Result<Vec<PackageId>> {
+    pub fn push_str(&mut self, path: impl AsRef<Path>, contents: &str) -> Result<PackageId> {
         self.push_group(UnresolvedPackageGroup::parse(path.as_ref(), contents)?)
     }
 
@@ -972,7 +985,7 @@ impl Resolve {
     ///
     ///     // For inputs which have a single package and only one world `None`
     ///     // can be specified.
-    ///     let ids = resolve.push_str(
+    ///     let id = resolve.push_str(
     ///         "./my-test.wit",
     ///         r#"
     ///             package example:wit1;
@@ -982,11 +995,11 @@ impl Resolve {
     ///             }
     ///         "#,
     ///     )?;
-    ///     assert!(resolve.select_world(&ids, None).is_ok());
+    ///     assert!(resolve.select_world(id, None).is_ok());
     ///
     ///     // For inputs which have a single package and multiple worlds then
     ///     // a world must be specified.
-    ///     let ids = resolve.push_str(
+    ///     let id = resolve.push_str(
     ///         "./my-test.wit",
     ///         r#"
     ///             package example:wit2;
@@ -996,34 +1009,18 @@ impl Resolve {
     ///             world bar { /* ... */ }
     ///         "#,
     ///     )?;
-    ///     assert!(resolve.select_world(&ids, None).is_err());
-    ///     assert!(resolve.select_world(&ids, Some("foo")).is_ok());
-    ///     assert!(resolve.select_world(&ids, Some("bar")).is_ok());
+    ///     assert!(resolve.select_world(id, None).is_err());
+    ///     assert!(resolve.select_world(id, Some("foo")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("bar")).is_ok());
     ///
     ///     // For inputs which have more than one package then a fully
     ///     // qualified name must be specified.
-    ///     let ids = resolve.push_str(
-    ///         "./my-test.wit",
-    ///         r#"
-    ///             package example:wit3 {
-    ///                 world foo { /* ... */ }
-    ///             }
-    ///
-    ///             package example:wit4 {
-    ///                 world foo { /* ... */ }
-    ///             }
-    ///         "#,
-    ///     )?;
-    ///     assert!(resolve.select_world(&ids, None).is_err());
-    ///     assert!(resolve.select_world(&ids, Some("foo")).is_err());
-    ///     assert!(resolve.select_world(&ids, Some("example:wit3/foo")).is_ok());
-    ///     assert!(resolve.select_world(&ids, Some("example:wit4/foo")).is_ok());
     ///
     ///     // Note that the `ids` or `packages` argument is ignored if a fully
     ///     // qualified world specified is provided meaning previous worlds
     ///     // can be selected.
-    ///     assert!(resolve.select_world(&[], Some("example:wit1/foo")).is_ok());
-    ///     assert!(resolve.select_world(&[], Some("example:wit2/foo")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("example:wit1/foo")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("example:wit2/foo")).is_ok());
     ///
     ///     // When selecting with a version it's ok to drop the version when
     ///     // there's only a single copy of that package in `Resolve`.
@@ -1035,7 +1032,7 @@ impl Resolve {
     ///             world foo { /* ... */ }
     ///         "#,
     ///     )?;
-    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("example:wit5/foo")).is_ok());
     ///
     ///     // However when a single package has multiple versions in a resolve
     ///     // it's required to specify the version to select which one.
@@ -1047,14 +1044,14 @@ impl Resolve {
     ///             world foo { /* ... */ }
     ///         "#,
     ///     )?;
-    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_err());
-    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo@1.0.0")).is_ok());
-    ///     assert!(resolve.select_world(&[], Some("example:wit5/foo@2.0.0")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("example:wit5/foo")).is_err());
+    ///     assert!(resolve.select_world(id, Some("example:wit5/foo@1.0.0")).is_ok());
+    ///     assert!(resolve.select_world(id, Some("example:wit5/foo@2.0.0")).is_ok());
     ///
     ///     Ok(())
     /// }
     /// ```
-    pub fn select_world(&self, packages: &[PackageId], world: Option<&str>) -> Result<WorldId> {
+    pub fn select_world(&self, package: PackageId, world: Option<&str>) -> Result<WorldId> {
         let world_path = match world {
             Some(world) => Some(
                 parse_use_path(world)
@@ -1064,17 +1061,7 @@ impl Resolve {
         };
 
         let (pkg, world_name) = match world_path {
-            Some(ParsedUsePath::Name(name)) => match packages {
-                [] => bail!("no packages were found to locate the world `{name}` within"),
-                [one] => (*one, name),
-                [..] => {
-                    bail!(
-                        "the supplied WIT source files describe multiple packages; \
-                         please provide a fully-qualified world-specifier select \
-                         a world amongst these packages"
-                    )
-                }
-            },
+            Some(ParsedUsePath::Name(name)) => (package, name),
             Some(ParsedUsePath::Package(pkg, interface)) => {
                 let pkg = match self.package_names.get(&pkg) {
                     Some(pkg) => *pkg,
@@ -1103,35 +1090,29 @@ impl Resolve {
                 };
                 (pkg, interface.to_string())
             }
-            None => match packages {
-                [] => bail!("no packages were specified nor is a world specified"),
-                [..] => {
-                    let worlds = packages
-                        .iter()
-                        .flat_map(|pkg| {
-                            self.packages[*pkg]
-                                .worlds
-                                .values()
-                                .map(|world| (*pkg, *world))
-                        })
-                        .collect::<Vec<_>>();
-
-                    match &worlds[..] {
-                        [] => bail!("none of the specified packages contain a world"),
-                        [(_, world)] => return Ok(*world),
-                        _ => bail!(
-                            "multiple worlds found; one must be explicitly chosen:{}",
-                            worlds
-                                .iter()
-                                .map(|(pkg, world)| format!(
-                                    "\n  {}/{}",
-                                    self.packages[*pkg].name, self.worlds[*world].name
-                                ))
-                                .collect::<String>()
-                        ),
-                    }
+            None => {
+                let pkg = &self.packages[package];
+                let worlds = pkg
+                    .worlds
+                    .values()
+                    .map(|world| (package, *world))
+                    .collect::<Vec<_>>();
+
+                match &worlds[..] {
+                    [] => bail!("The main package `{}` contains no worlds", pkg.name),
+                    [(_, world)] => return Ok(*world),
+                    _ => bail!(
+                        "multiple worlds found; one must be explicitly chosen:{}",
+                        worlds
+                            .iter()
+                            .map(|(pkg, world)| format!(
+                                "\n  {}/{}",
+                                self.packages[*pkg].name, self.worlds[*world].name
+                            ))
+                            .collect::<String>()
+                    ),
                 }
-            },
+            }
         };
         let pkg = &self.packages[pkg];
         pkg.worlds
@@ -1634,7 +1615,7 @@ impl Remap {
         for ((id, mut world), span) in unresolved
             .worlds
             .into_iter()
-            .zip(unresolved.world_spans)
+            .zip(&unresolved.world_spans)
             .skip(foreign_worlds)
         {
             if !resolve
@@ -3034,19 +3015,19 @@ mod tests {
             "#,
         )?;
 
-        assert!(resolve.select_world(&dummy, None).is_ok());
-        assert!(resolve.select_world(&dummy, Some("xx")).is_err());
-        assert!(resolve.select_world(&dummy, Some("")).is_err());
-        assert!(resolve.select_world(&dummy, Some("foo:bar/foo")).is_ok());
+        assert!(resolve.select_world(dummy, None).is_ok());
+        assert!(resolve.select_world(dummy, Some("xx")).is_err());
+        assert!(resolve.select_world(dummy, Some("")).is_err());
+        assert!(resolve.select_world(dummy, Some("foo:bar/foo")).is_ok());
         assert!(resolve
-            .select_world(&dummy, Some("foo:bar/foo@0.1.0"))
+            .select_world(dummy, Some("foo:bar/foo@0.1.0"))
             .is_ok());
-        assert!(resolve.select_world(&dummy, Some("foo:baz/foo")).is_err());
+        assert!(resolve.select_world(dummy, Some("foo:baz/foo")).is_err());
         assert!(resolve
-            .select_world(&dummy, Some("foo:baz/foo@0.1.0"))
+            .select_world(dummy, Some("foo:baz/foo@0.1.0"))
             .is_ok());
         assert!(resolve
-            .select_world(&dummy, Some("foo:baz/foo@0.2.0"))
+            .select_world(dummy, Some("foo:baz/foo@0.2.0"))
             .is_ok());
         Ok(())
     }
diff --git a/crates/wit-smith/src/lib.rs b/crates/wit-smith/src/lib.rs
index 3a70a00f..0ec425cd 100644
--- a/crates/wit-smith/src/lib.rs
+++ b/crates/wit-smith/src/lib.rs
@@ -6,7 +6,7 @@
 //! type structures.
 
 use arbitrary::{Result, Unstructured};
-use wit_parser::{Resolve, UnresolvedPackageGroup};
+use wit_parser::Resolve;
 
 mod config;
 pub use self::config::Config;
@@ -21,11 +21,8 @@ pub fn smith(config: &Config, u: &mut Unstructured<'_>) -> Result<Vec<u8>> {
     let mut resolve = Resolve::default();
     let mut last = None;
     for pkg in pkgs {
-        let UnresolvedPackageGroup {
-            mut packages,
-            source_map,
-        } = pkg.sources.parse().unwrap();
-        let id = match resolve.push(packages.remove(0), &source_map) {
+        let group = pkg.sources.parse().unwrap();
+        let id = match resolve.push_group(group) {
             Ok(id) => id,
             Err(e) => {
                 if e.to_string().contains(
diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs
index 5cc66782..ee315980 100644
--- a/fuzz/src/roundtrip_wit.rs
+++ b/fuzz/src/roundtrip_wit.rs
@@ -2,7 +2,7 @@ use arbitrary::{Result, Unstructured};
 use std::path::Path;
 use wasmparser::WasmFeatures;
 use wit_component::*;
-use wit_parser::{Resolve, SourceMap};
+use wit_parser::{PackageId, Resolve};
 
 pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     let wasm = u.arbitrary().and_then(|config| {
@@ -10,28 +10,22 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
         wit_smith::smith(&config, u)
     })?;
     write_file("doc1.wasm", &wasm);
-    let (resolve, _pkg) = match wit_component::decode(&wasm).unwrap() {
-        DecodedWasm::WitPackages(resolve, pkg) => (resolve, pkg),
+    let (resolve, pkg) = match wit_component::decode(&wasm).unwrap() {
+        DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
         DecodedWasm::Component(..) => unreachable!(),
     };
 
-    roundtrip_through_printing("doc1", &resolve, &wasm);
+    roundtrip_through_printing("doc1", &resolve, pkg, &wasm);
 
-    let (resolve2, pkgs2) = match wit_component::decode(&wasm).unwrap() {
-        DecodedWasm::WitPackages(resolve, pkgs) => (resolve, pkgs),
+    let (resolve2, pkg2) = match wit_component::decode(&wasm).unwrap() {
+        DecodedWasm::WitPackage(resolve, pkgs) => (resolve, pkgs),
         DecodedWasm::Component(..) => unreachable!(),
     };
 
-    // wit_smith returns WIT source with only a single package.
-    if pkgs2.len() != 1 {
-        panic!("rountrip WIT test smithed file with multiple packages")
-    }
-
-    let pkg2 = pkgs2[0];
     let wasm2 =
         wit_component::encode(Some(true), &resolve2, pkg2).expect("failed to encode WIT document");
     write_file("doc2.wasm", &wasm2);
-    roundtrip_through_printing("doc2", &resolve2, &wasm2);
+    roundtrip_through_printing("doc2", &resolve2, pkg2, &wasm2);
 
     if wasm != wasm2 {
         panic!("roundtrip wasm didn't match");
@@ -78,25 +72,26 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     Ok(())
 }
 
-fn roundtrip_through_printing(file: &str, resolve: &Resolve, wasm: &[u8]) {
-    // For all packages in `resolve` print them all to a string, then re-parse
-    // them and insert them into a `new_resolve`.
+fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId, wasm: &[u8]) {
+    // Print to a single string, using nested `package ... { .. }` statements,
+    // and then parse that in a new `Resolve`.
     let mut new_resolve = Resolve::default();
-    let mut last = None;
-    for (id, pkg) in resolve.packages.iter() {
-        let mut map = SourceMap::new();
-        let pkg_name = &pkg.name;
-        let doc = WitPrinter::default().print(resolve, &[id], false).unwrap();
-        write_file(&format!("{file}-{pkg_name}.wit"), &doc);
-        map.push(format!("{pkg_name}.wit").as_ref(), doc);
-        let unresolved = map.parse().unwrap();
-        let id = new_resolve.push_group(unresolved).unwrap();
-        last = Some(id.last().unwrap().to_owned());
-    }
+    let package_deps = resolve
+        .packages
+        .iter()
+        .map(|p| p.0)
+        .filter(|k| *k != pkg)
+        .collect::<Vec<_>>();
+    let doc = WitPrinter::default()
+        .print(resolve, pkg, &package_deps)
+        .unwrap();
+    let new_pkg = new_resolve
+        .push_str(&format!("printed-{file}.wit"), &doc)
+        .unwrap();
 
     // Finally encode the `new_resolve` which should be the exact same as
     // before.
-    let wasm2 = wit_component::encode(Some(true), &new_resolve, last.unwrap()).unwrap();
+    let wasm2 = wit_component::encode(Some(true), &new_resolve, new_pkg).unwrap();
     write_file(&format!("{file}-reencoded.wasm"), &wasm2);
     if wasm != wasm2 {
         panic!("failed to roundtrip through text printing");
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index adc24f30..a5c01c6c 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -204,10 +204,10 @@ impl WitResolve {
         return resolve;
     }
 
-    fn load(&self) -> Result<(Resolve, Vec<PackageId>)> {
+    fn load(&self) -> Result<(Resolve, PackageId)> {
         let mut resolve = Self::resolve_with_features(&self.features, self.all_features);
-        let (pkg_ids, _) = resolve.push_path(&self.wit)?;
-        Ok((resolve, pkg_ids))
+        let (pkg_id, _) = resolve.push_path(&self.wit)?;
+        Ok((resolve, pkg_id))
     }
 }
 
@@ -283,8 +283,8 @@ impl EmbedOpts {
         } else {
             Some(self.io.parse_input_wasm()?)
         };
-        let (resolve, pkg_ids) = self.resolve.load()?;
-        let world = resolve.select_world(&pkg_ids, self.world.as_deref())?;
+        let (resolve, pkg_id) = self.resolve.load()?;
+        let world = resolve.select_world(pkg_id, self.world.as_deref())?;
         let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world));
 
         embed_component_metadata(
@@ -538,8 +538,8 @@ impl WitOpts {
             if input.is_dir() {
                 let mut resolve =
                     WitResolve::resolve_with_features(&self.features, self.all_features);
-                let (pkg_ids, _) = resolve.push_dir(&input)?;
-                return Ok(DecodedWasm::WitPackages(resolve, pkg_ids));
+                let (pkg_id, _) = resolve.push_dir(&input)?;
+                return Ok(DecodedWasm::WitPackage(resolve, pkg_id));
             }
         }
 
@@ -587,8 +587,8 @@ impl WitOpts {
                 };
                 let mut resolve =
                     WitResolve::resolve_with_features(&self.features, self.all_features);
-                let ids = resolve.push_str(path, input)?;
-                Ok(DecodedWasm::WitPackages(resolve, ids))
+                let id = resolve.push_str(path, input)?;
+                Ok(DecodedWasm::WitPackage(resolve, id))
             }
         }
     }
@@ -596,11 +596,8 @@ impl WitOpts {
     fn emit_wasm(&self, decoded: &DecodedWasm) -> Result<()> {
         assert!(self.wasm || self.wat);
         assert!(self.out_dir.is_none());
-        if decoded.packages().len() != 1 {
-            bail!("emitting WASM for multi-package WIT files is not yet supported")
-        }
 
-        let decoded_package = decoded.packages()[0];
+        let decoded_package = decoded.package();
         let bytes = wit_component::encode(None, decoded.resolve(), decoded_package)?;
         if !self.skip_validation {
             wasmparser::Validator::new_with_features(
@@ -638,10 +635,10 @@ impl WitOpts {
                     *cnt += 1;
                 }
 
-                let main = decoded.packages();
+                let main = decoded.package();
                 for (id, pkg) in resolve.packages.iter() {
-                    let is_main = main.contains(&id);
-                    let output = printer.print(resolve, &[id], is_main)?;
+                    let is_main = id == main;
+                    let output = printer.print(resolve, id, &[])?;
                     let out_dir = if is_main {
                         dir.clone()
                     } else {
@@ -726,8 +723,8 @@ impl TargetsOpts {
 
     /// Executes the application.
     fn run(self) -> Result<()> {
-        let (resolve, pkg_ids) = self.resolve.load()?;
-        let world = resolve.select_world(&pkg_ids, self.world.as_deref())?;
+        let (resolve, pkg_id) = self.resolve.load()?;
+        let world = resolve.select_world(pkg_id, self.world.as_deref())?;
         let component_to_test = self.input.parse_wasm()?;
 
         wit_component::targets(&resolve, world, &component_to_test)?;
@@ -766,9 +763,9 @@ impl SemverCheckOpts {
     }
 
     fn run(self) -> Result<()> {
-        let (resolve, pkg_ids) = self.resolve.load()?;
-        let prev = resolve.select_world(&pkg_ids, Some(self.prev.as_str()))?;
-        let new = resolve.select_world(&pkg_ids, Some(self.new.as_str()))?;
+        let (resolve, pkg_id) = self.resolve.load()?;
+        let prev = resolve.select_world(pkg_id, Some(self.prev.as_str()))?;
+        let new = resolve.select_world(pkg_id, Some(self.new.as_str()))?;
         wit_component::semver_check(resolve, prev, new)?;
         Ok(())
     }
diff --git a/src/lib.rs b/src/lib.rs
index 70210b83..1f05e19a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -238,8 +238,9 @@ impl OutputArg {
                     .packages
                     .iter()
                     .map(|(id, _)| id)
+                    .filter(|id| *id != wit.package())
                     .collect::<Vec<_>>();
-                let output = printer.print(resolve, &ids, false)?;
+                let output = printer.print(resolve, wit.package(), &ids)?;
                 self.output_str(&output)
             }
         }


Test patch:
diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs
index 9d00e2f7..8f2951f9 100644
--- a/crates/wit-component/tests/components.rs
+++ b/crates/wit-component/tests/components.rs
@@ -76,140 +76,127 @@ fn main() -> Result<()> {
 fn run_test(path: &Path) -> Result<()> {
     let test_case = path.file_stem().unwrap().to_str().unwrap();
     let mut resolve = Resolve::default();
-    let (pkg_ids, _) = resolve.push_dir(&path)?;
-    let pkg_count = pkg_ids.len();
+    let (pkg_id, _) = resolve.push_dir(&path)?;
 
-    for pkg_id in pkg_ids {
-        // If this test case contained multiple packages, create separate sub-directories for
-        // each.
-        let mut path = path.to_path_buf();
-        if pkg_count > 1 {
-            let pkg_name = &resolve.packages[pkg_id].name;
-            path.push(pkg_name.namespace.clone());
-            path.push(pkg_name.name.clone());
-            fs::create_dir_all(path.clone())?;
-        }
+    // If this test case contained multiple packages, create separate sub-directories for
+    // each.
+    let path = path.to_path_buf();
 
-        let module_path = path.join("module.wat");
-        let mut adapters = glob::glob(path.join("adapt-*.wat").to_str().unwrap())?;
-        let result = if module_path.is_file() {
-            let module = read_core_module(&module_path, &resolve, pkg_id)
-                .with_context(|| format!("failed to read core module at {module_path:?}"))?;
-            adapters
-                .try_fold(
-                    ComponentEncoder::default().module(&module)?.validate(true),
-                    |encoder, path| {
-                        let (name, wasm) =
-                            read_name_and_module("adapt-", &path?, &resolve, pkg_id)?;
-                        Ok::<_, Error>(encoder.adapter(&name, &wasm)?)
-                    },
-                )?
-                .encode()
-        } else {
-            let mut libs = glob::glob(path.join("lib-*.wat").to_str().unwrap())?
-                .map(|path| Ok(("lib-", path?, false)))
-                .chain(
-                    glob::glob(path.join("dlopen-lib-*.wat").to_str().unwrap())?
-                        .map(|path| Ok(("dlopen-lib-", path?, true))),
-                )
-                .collect::<Result<Vec<_>>>()?;
+    let module_path = path.join("module.wat");
+    let mut adapters = glob::glob(path.join("adapt-*.wat").to_str().unwrap())?;
+    let result = if module_path.is_file() {
+        let module = read_core_module(&module_path, &resolve, pkg_id)
+            .with_context(|| format!("failed to read core module at {module_path:?}"))?;
+        adapters
+            .try_fold(
+                ComponentEncoder::default().module(&module)?.validate(true),
+                |encoder, path| {
+                    let (name, wasm) = read_name_and_module("adapt-", &path?, &resolve, pkg_id)?;
+                    Ok::<_, Error>(encoder.adapter(&name, &wasm)?)
+                },
+            )?
+            .encode()
+    } else {
+        let mut libs = glob::glob(path.join("lib-*.wat").to_str().unwrap())?
+            .map(|path| Ok(("lib-", path?, false)))
+            .chain(
+                glob::glob(path.join("dlopen-lib-*.wat").to_str().unwrap())?
+                    .map(|path| Ok(("dlopen-lib-", path?, true))),
+            )
+            .collect::<Result<Vec<_>>>()?;
 
-            // Sort list to ensure deterministic order, which determines priority in cases of duplicate symbols:
-            libs.sort_by(|(_, a, _), (_, b, _)| a.cmp(b));
+        // Sort list to ensure deterministic order, which determines priority in cases of duplicate symbols:
+        libs.sort_by(|(_, a, _), (_, b, _)| a.cmp(b));
 
-            let mut linker = Linker::default().validate(true);
+        let mut linker = Linker::default().validate(true);
 
-            if path.join("stub-missing-functions").is_file() {
-                linker = linker.stub_missing_functions(true);
-            }
+        if path.join("stub-missing-functions").is_file() {
+            linker = linker.stub_missing_functions(true);
+        }
 
-            if path.join("use-built-in-libdl").is_file() {
-                linker = linker.use_built_in_libdl(true);
-            }
+        if path.join("use-built-in-libdl").is_file() {
+            linker = linker.use_built_in_libdl(true);
+        }
 
-            let linker =
-                libs.into_iter()
-                    .try_fold(linker, |linker, (prefix, path, dl_openable)| {
-                        let (name, wasm) = read_name_and_module(prefix, &path, &resolve, pkg_id)?;
-                        Ok::<_, Error>(linker.library(&name, &wasm, dl_openable)?)
-                    })?;
+        let linker = libs
+            .into_iter()
+            .try_fold(linker, |linker, (prefix, path, dl_openable)| {
+                let (name, wasm) = read_name_and_module(prefix, &path, &resolve, pkg_id)?;
+                Ok::<_, Error>(linker.library(&name, &wasm, dl_openable)?)
+            })?;
 
-            adapters
-                .try_fold(linker, |linker, path| {
-                    let (name, wasm) = read_name_and_module("adapt-", &path?, &resolve, pkg_id)?;
-                    Ok::<_, Error>(linker.adapter(&name, &wasm)?)
-                })?
-                .encode()
-        };
-        let component_path = path.join("component.wat");
-        let component_wit_path = path.join("component.wit.print");
-        let error_path = path.join("error.txt");
+        adapters
+            .try_fold(linker, |linker, path| {
+                let (name, wasm) = read_name_and_module("adapt-", &path?, &resolve, pkg_id)?;
+                Ok::<_, Error>(linker.adapter(&name, &wasm)?)
+            })?
+            .encode()
+    };
+    let component_path = path.join("component.wat");
+    let component_wit_path = path.join("component.wit.print");
+    let error_path = path.join("error.txt");
 
-        let bytes = match result {
-            Ok(bytes) => {
-                if test_case.starts_with("error-") {
-                    bail!("expected an error but got success");
-                }
-                bytes
+    let bytes = match result {
+        Ok(bytes) => {
+            if test_case.starts_with("error-") {
+                bail!("expected an error but got success");
             }
-            Err(err) => {
-                if !test_case.starts_with("error-") {
-                    return Err(err);
-                }
-                assert_output(&format!("{err:?}"), &error_path)?;
-                return Ok(());
+            bytes
+        }
+        Err(err) => {
+            if !test_case.starts_with("error-") {
+                return Err(err);
             }
-        };
+            assert_output(&format!("{err:?}"), &error_path)?;
+            return Ok(());
+        }
+    };
 
-        let wat = wasmprinter::print_bytes(&bytes).context("failed to print bytes")?;
-        assert_output(&wat, &component_path)?;
-        let (pkg, resolve) =
-            match wit_component::decode(&bytes).context("failed to decode resolve")? {
-                DecodedWasm::WitPackages(..) => unreachable!(),
-                DecodedWasm::Component(resolve, world) => {
-                    (resolve.worlds[world].package.unwrap(), resolve)
-                }
-            };
-        let wit = WitPrinter::default()
-            .print(&resolve, &[pkg], false)
-            .context("failed to print WIT")?;
-        assert_output(&wit, &component_wit_path)?;
+    let wat = wasmprinter::print_bytes(&bytes).context("failed to print bytes")?;
+    assert_output(&wat, &component_path)?;
+    let (pkg, resolve) = match wit_component::decode(&bytes).context("failed to decode resolve")? {
+        DecodedWasm::WitPackage(..) => unreachable!(),
+        DecodedWasm::Component(resolve, world) => (resolve.worlds[world].package.unwrap(), resolve),
+    };
+    let wit = WitPrinter::default()
+        .print(&resolve, pkg, &[])
+        .context("failed to print WIT")?;
+    assert_output(&wit, &component_wit_path)?;
 
-        UnresolvedPackageGroup::parse(&component_wit_path, &wit)
-            .context("failed to parse printed WIT")?;
+    UnresolvedPackageGroup::parse(&component_wit_path, &wit)
+        .context("failed to parse printed WIT")?;
 
-        // Check that the producer data got piped through properly
-        let metadata = wasm_metadata::Metadata::from_binary(&bytes)?;
-        match metadata {
-            // Depends on the ComponentEncoder always putting the first module as the 0th child:
-            wasm_metadata::Metadata::Component { children, .. } => match children[0].as_ref() {
-                wasm_metadata::Metadata::Module { producers, .. } => {
-                    let producers = producers.as_ref().expect("child module has producers");
-                    let processed_by = producers
-                        .get("processed-by")
-                        .expect("child has processed-by section");
+    // Check that the producer data got piped through properly
+    let metadata = wasm_metadata::Metadata::from_binary(&bytes)?;
+    match metadata {
+        // Depends on the ComponentEncoder always putting the first module as the 0th child:
+        wasm_metadata::Metadata::Component { children, .. } => match children[0].as_ref() {
+            wasm_metadata::Metadata::Module { producers, .. } => {
+                let producers = producers.as_ref().expect("child module has producers");
+                let processed_by = producers
+                    .get("processed-by")
+                    .expect("child has processed-by section");
+                assert_eq!(
+                    processed_by
+                        .get("wit-component")
+                        .expect("wit-component producer present"),
+                    env!("CARGO_PKG_VERSION")
+                );
+                if module_path.is_file() {
                     assert_eq!(
                         processed_by
-                            .get("wit-component")
-                            .expect("wit-component producer present"),
-                        env!("CARGO_PKG_VERSION")
+                            .get("my-fake-bindgen")
+                            .expect("added bindgen field present"),
+                        "123.45"
                     );
-                    if module_path.is_file() {
-                        assert_eq!(
-                            processed_by
-                                .get("my-fake-bindgen")
-                                .expect("added bindgen field present"),
-                            "123.45"
-                        );
-                    } else {
-                        // Otherwise, we used `Linker`, which synthesizes the
-                        // "main" module and thus won't have `my-fake-bindgen`
-                    }
+                } else {
+                    // Otherwise, we used `Linker`, which synthesizes the
+                    // "main" module and thus won't have `my-fake-bindgen`
                 }
-                _ => panic!("expected child to be a module"),
-            },
-            _ => panic!("expected top level metadata of component"),
-        }
+            }
+            _ => panic!("expected child to be a module"),
+        },
+        _ => panic!("expected top level metadata of component"),
     }
 
     Ok(())
@@ -245,7 +232,7 @@ fn read_core_module(path: &Path, resolve: &Resolve, pkg: PackageId) -> Result<Ve
     let mut wasm = wat::parse_file(path)?;
     let name = path.file_stem().and_then(|s| s.to_str()).unwrap();
     let world = resolve
-        .select_world(&[pkg], Some(name))
+        .select_world(pkg, Some(name))
         .context("failed to select a world")?;
 
     // Add this producer data to the wit-component metadata so we can make sure it gets through the
diff --git a/crates/wit-component/tests/components/multi-package/component.wat b/crates/wit-component/tests/components/multi-package/component.wat
new file mode 100644
index 00000000..a6d81094
--- /dev/null
+++ b/crates/wit-component/tests/components/multi-package/component.wat
@@ -0,0 +1,40 @@
+(component
+  (core module (;0;)
+    (table (;0;) 1 funcref)
+    (memory (;0;) 17)
+    (global (;0;) (mut i32) i32.const 1048576)
+    (global (;1;) (mut i32) i32.const 1048592)
+    (global (;2;) (mut i32) i32.const 1114112)
+    (export "__stack_pointer" (global 0))
+    (export "__heap_base" (global 1))
+    (export "__heap_end" (global 2))
+    (export "__indirect_function_table" (table 0))
+    (export "memory" (memory 0))
+    (@producers
+      (processed-by "wit-component" "$CARGO_PKG_VERSION")
+    )
+  )
+  (core module (;1;)
+    (type (;0;) (func))
+    (type (;1;) (func (param i32)))
+    (import "env" "memory" (memory (;0;) 0))
+    (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
+    (func (;0;) (type 0))
+    (start 0)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
+    (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
+    (@producers
+      (processed-by "wit-component" "$CARGO_PKG_VERSION")
+    )
+  )
+  (core instance (;0;) (instantiate 0))
+  (alias core export 0 "memory" (core memory (;0;)))
+  (core instance (;1;) (instantiate 1
+      (with "env" (instance 0))
+    )
+  )
+  (@producers
+    (processed-by "wit-component" "$CARGO_PKG_VERSION")
+  )
+)
diff --git a/crates/wit-component/tests/components/multi-package/component.wit.print b/crates/wit-component/tests/components/multi-package/component.wit.print
new file mode 100644
index 00000000..b4548abf
--- /dev/null
+++ b/crates/wit-component/tests/components/multi-package/component.wit.print
@@ -0,0 +1,4 @@
+package root:component;
+
+world root {
+}
diff --git a/crates/wit-component/tests/components/multi-package/module.wit b/crates/wit-component/tests/components/multi-package/module.wit
index 838ad730..4334fd65 100644
--- a/crates/wit-component/tests/components/multi-package/module.wit
+++ b/crates/wit-component/tests/components/multi-package/module.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:bar {
   world module {
     import bar: interface {
diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs
index 55a4cadd..47034951 100644
--- a/crates/wit-component/tests/interfaces.rs
+++ b/crates/wit-component/tests/interfaces.rs
@@ -48,64 +48,55 @@ fn main() -> Result<()> {
 
 fn run_test(path: &Path, is_dir: bool) -> Result<()> {
     let mut resolve = Resolve::new();
-    let packages = if is_dir {
+    let package = if is_dir {
         resolve.push_dir(path)?.0
     } else {
         resolve.push_file(path)?
     };
 
-    for package in packages {
-        assert_print(&resolve, &[package], path, is_dir)?;
+    assert_print(&resolve, package, path, is_dir)?;
 
-        let features = WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL;
+    let features = WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL;
 
-        // First convert the WIT package to a binary WebAssembly output, then
-        // convert that binary wasm to textual wasm, then assert it matches the
-        // expectation.
-        let wasm = wit_component::encode(Some(true), &resolve, package)?;
-        let wat = wasmprinter::print_bytes(&wasm)?;
-        assert_output(&path.with_extension("wat"), &wat)?;
-        wasmparser::Validator::new_with_features(features)
-            .validate_all(&wasm)
-            .context("failed to validate wasm output")?;
+    // First convert the WIT package to a binary WebAssembly output, then
+    // convert that binary wasm to textual wasm, then assert it matches the
+    // expectation.
+    let wasm = wit_component::encode(Some(true), &resolve, package)?;
+    let wat = wasmprinter::print_bytes(&wasm)?;
+    assert_output(&path.with_extension("wat"), &wat)?;
+    wasmparser::Validator::new_with_features(features)
+        .validate_all(&wasm)
+        .context("failed to validate wasm output")?;
 
-        // Next decode a fresh WIT package from the WebAssembly generated. Print
-        // this package's documents and assert they all match the expectations.
-        let decoded = wit_component::decode(&wasm)?;
-        assert_eq!(
-            1,
-            decoded.packages().len(),
-            "Each input WIT package should produce WASM that contains only one package"
-        );
+    // Next decode a fresh WIT package from the WebAssembly generated. Print
+    // this package's documents and assert they all match the expectations.
+    let decoded = wit_component::decode(&wasm)?;
 
-        let decoded_package = decoded.packages()[0];
-        let resolve = decoded.resolve();
+    let decoded_package = decoded.package();
+    let resolve = decoded.resolve();
 
-        assert_print(resolve, decoded.packages(), path, is_dir)?;
+    assert_print(resolve, decoded.package(), path, is_dir)?;
 
-        // Finally convert the decoded package to wasm again and make sure it
-        // matches the prior wasm.
-        let wasm2 = wit_component::encode(Some(true), resolve, decoded_package)?;
-        if wasm != wasm2 {
-            let wat2 = wasmprinter::print_bytes(&wasm)?;
-            assert_eq!(wat, wat2, "document did not roundtrip correctly");
-        }
+    // Finally convert the decoded package to wasm again and make sure it
+    // matches the prior wasm.
+    let wasm2 = wit_component::encode(Some(true), resolve, decoded_package)?;
+    if wasm != wasm2 {
+        let wat2 = wasmprinter::print_bytes(&wasm)?;
+        assert_eq!(wat, wat2, "document did not roundtrip correctly");
     }
 
     Ok(())
 }
 
-fn assert_print(resolve: &Resolve, pkg_ids: &[PackageId], path: &Path, is_dir: bool) -> Result<()> {
-    let output = WitPrinter::default().print(resolve, &pkg_ids, false)?;
-    for pkg_id in pkg_ids {
-        let pkg = &resolve.packages[*pkg_id];
-        let expected = if is_dir {
-            path.join(format!("{}.wit.print", &pkg.name.name))
-        } else {
-            path.with_extension("wit.print")
-        };
-        assert_output(&expected, &output)?;
-    }
+fn assert_print(resolve: &Resolve, pkg_id: PackageId, path: &Path, is_dir: bool) -> Result<()> {
+    let output = WitPrinter::default().print(resolve, pkg_id, &[])?;
+    let pkg = &resolve.packages[pkg_id];
+    let expected = if is_dir {
+        path.join(format!("{}.wit.print", &pkg.name.name))
+    } else {
+        path.with_extension("wit.print")
+    };
+    assert_output(&expected, &output)?;
 
     UnresolvedPackageGroup::parse("foo.wit", &output).context("failed to parse printed output")?;
     Ok(())
diff --git a/crates/wit-component/tests/linking.rs b/crates/wit-component/tests/linking.rs
index f1d9ac35..d610ec4e 100644
--- a/crates/wit-component/tests/linking.rs
+++ b/crates/wit-component/tests/linking.rs
@@ -140,8 +140,8 @@ fn encode(wat: &str, wit: Option<&str>) -> Result<Vec<u8>> {
 
     if let Some(wit) = wit {
         let mut resolve = Resolve::default();
-        let pkgs = resolve.push_str("test.wit", wit)?;
-        let world = resolve.select_world(&pkgs, None)?;
+        let pkg = resolve.push_str("test.wit", wit)?;
+        let world = resolve.select_world(pkg, None)?;
 
         wit_component::embed_component_metadata(
             &mut module,
diff --git a/crates/wit-component/tests/merge.rs b/crates/wit-component/tests/merge.rs
index 3dd25da9..f3b3918b 100644
--- a/crates/wit-component/tests/merge.rs
+++ b/crates/wit-component/tests/merge.rs
@@ -46,7 +46,7 @@ fn merging() -> Result<()> {
                         .join("merge")
                         .join(&pkg.name.name)
                         .with_extension("wit");
-                    let output = WitPrinter::default().print(&into, &[id], false)?;
+                    let output = WitPrinter::default().print(&into, id, &[])?;
                     assert_output(&expected, &output)?;
                 }
             }
diff --git a/crates/wit-component/tests/targets.rs b/crates/wit-component/tests/targets.rs
index 7c9581a5..7bbd96ff 100644
--- a/crates/wit-component/tests/targets.rs
+++ b/crates/wit-component/tests/targets.rs
@@ -80,9 +80,9 @@ fn load_test_wit(path: &Path) -> Result<(Resolve, WorldId)> {
 
     let test_wit_path = path.join("test.wit");
     let mut resolve = Resolve::default();
-    let pkgs = resolve.push_file(&test_wit_path)?;
+    let pkg = resolve.push_file(&test_wit_path)?;
     let world_id = resolve
-        .select_world(&pkgs, Some(TEST_TARGET_WORLD_ID))
+        .select_world(pkg, Some(TEST_TARGET_WORLD_ID))
         .with_context(|| "failed to select world from package".to_string())?;
 
     Ok((resolve, world_id))
diff --git a/crates/wit-component/tests/wit.rs b/crates/wit-component/tests/wit.rs
index 1930eabf..20dddf28 100644
--- a/crates/wit-component/tests/wit.rs
+++ b/crates/wit-component/tests/wit.rs
@@ -9,9 +9,9 @@ fn parse_wit_dir() -> Result<()> {
     drop(env_logger::try_init());
 
     let mut resolver = Resolve::default();
-    let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit")?;
+    let (package_id, _) = resolver.push_path("tests/wit/parse-dir/wit")?;
     assert!(resolver
-        .select_world(&package_ids, "foo-world".into())
+        .select_world(package_id, "foo-world".into())
         .is_ok());
 
     Ok(())
@@ -23,8 +23,8 @@ fn parse_wit_file() -> Result<()> {
     drop(env_logger::try_init());
 
     let mut resolver = Resolve::default();
-    let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?;
-    resolver.select_world(&package_ids, "bar-world".into())?;
+    let (package_id, _) = resolver.push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?;
+    resolver.select_world(package_id, "bar-world".into())?;
     assert!(resolver
         .interfaces
         .iter()
diff --git a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json
index 419d62e7..a6b2d44a 100644
--- a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json
+++ b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json
@@ -3,31 +3,31 @@
     {
       "name": "wasi",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
       "exports": {},
-      "package": 5
+      "package": 4
     },
     {
       "name": "my-world",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
@@ -43,14 +43,14 @@
     {
       "name": "my-world2",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
@@ -71,9 +71,9 @@
     {
       "name": "bars-world",
       "imports": {
-        "interface-4": {
+        "interface-6": {
           "interface": {
-            "id": 4
+            "id": 6
           }
         },
         "interface-0": {
@@ -88,14 +88,14 @@
     {
       "name": "unionw-world",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
@@ -142,41 +142,41 @@
       "package": 3
     },
     {
-      "name": "the-default",
+      "name": "clocks",
       "types": {
-        "from-default": 1
+        "timestamp": 1
       },
       "functions": {},
       "package": 4
     },
     {
-      "name": "some-interface",
+      "name": "filesystem",
       "types": {
-        "another-type": 2
+        "stat": 2
       },
       "functions": {},
       "package": 4
     },
     {
-      "name": "another-interface",
+      "name": "the-default",
       "types": {
-        "yet-another-type": 3
+        "from-default": 3
       },
       "functions": {},
-      "package": 4
+      "package": 5
     },
     {
-      "name": "clocks",
+      "name": "some-interface",
       "types": {
-        "timestamp": 4
+        "another-type": 4
       },
       "functions": {},
       "package": 5
     },
     {
-      "name": "filesystem",
+      "name": "another-interface",
       "types": {
-        "stat": 5
+        "yet-another-type": 5
       },
       "functions": {},
       "package": 5
@@ -228,52 +228,52 @@
       }
     },
     {
-      "name": "from-default",
+      "name": "timestamp",
       "kind": {
-        "type": "string"
+        "type": "u64"
       },
       "owner": {
         "interface": 4
       }
     },
     {
-      "name": "another-type",
+      "name": "stat",
       "kind": {
-        "type": "u32"
+        "record": {
+          "fields": [
+            {
+              "name": "ino",
+              "type": "u64"
+            }
+          ]
+        }
       },
       "owner": {
         "interface": 5
       }
     },
     {
-      "name": "yet-another-type",
+      "name": "from-default",
       "kind": {
-        "type": "u8"
+        "type": "string"
       },
       "owner": {
         "interface": 6
       }
     },
     {
-      "name": "timestamp",
+      "name": "another-type",
       "kind": {
-        "type": "u64"
+        "type": "u32"
       },
       "owner": {
         "interface": 7
       }
     },
     {
-      "name": "stat",
+      "name": "yet-another-type",
       "kind": {
-        "record": {
-          "fields": [
-            {
-              "name": "ino",
-              "type": "u64"
-            }
-          ]
-        }
+        "type": "u8"
       },
       "owner": {
         "interface": 8
@@ -282,7 +282,7 @@
     {
       "name": "timestamp",
       "kind": {
-        "type": 4
+        "type": 1
       },
       "owner": {
         "interface": 9
@@ -291,7 +291,7 @@
     {
       "name": "stat",
       "kind": {
-        "type": 5
+        "type": 2
       },
       "owner": {
         "interface": 9
@@ -300,7 +300,7 @@
     {
       "name": "from-default",
       "kind": {
-        "type": 1
+        "type": 3
       },
       "owner": {
         "interface": 10
@@ -309,7 +309,7 @@
     {
       "name": "another-type",
       "kind": {
-        "type": 2
+        "type": 4
       },
       "owner": {
         "interface": 10
@@ -318,7 +318,7 @@
     {
       "name": "yet-another-type",
       "kind": {
-        "type": 3
+        "type": 5
       },
       "owner": {
         "interface": 10
@@ -372,25 +372,25 @@
       },
       "worlds": {}
     },
-    {
-      "name": "foo:some-pkg",
-      "interfaces": {
-        "the-default": 4,
-        "some-interface": 5,
-        "another-interface": 6
-      },
-      "worlds": {}
-    },
     {
       "name": "foo:wasi",
       "interfaces": {
-        "clocks": 7,
-        "filesystem": 8
+        "clocks": 4,
+        "filesystem": 5
       },
       "worlds": {
         "wasi": 0
       }
     },
+    {
+      "name": "foo:some-pkg",
+      "interfaces": {
+        "the-default": 6,
+        "some-interface": 7,
+        "another-interface": 8
+      },
+      "worlds": {}
+    },
     {
       "name": "foo:root",
       "interfaces": {
diff --git a/crates/wit-parser/tests/ui/foreign-deps.wit.json b/crates/wit-parser/tests/ui/foreign-deps.wit.json
index 503c1b32..f24404d8 100644
--- a/crates/wit-parser/tests/ui/foreign-deps.wit.json
+++ b/crates/wit-parser/tests/ui/foreign-deps.wit.json
@@ -3,14 +3,14 @@
     {
       "name": "my-world",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
@@ -26,14 +26,14 @@
     {
       "name": "my-world2",
       "imports": {
-        "interface-8": {
+        "interface-5": {
           "interface": {
-            "id": 8
+            "id": 5
           }
         },
-        "interface-7": {
+        "interface-4": {
           "interface": {
-            "id": 7
+            "id": 4
           }
         }
       },
@@ -54,9 +54,9 @@
     {
       "name": "bars-world",
       "imports": {
-        "interface-4": {
+        "interface-6": {
           "interface": {
-            "id": 4
+            "id": 6
           }
         },
         "interface-0": {
@@ -97,41 +97,41 @@
       "package": 3
     },
     {
-      "name": "the-default",
+      "name": "clocks",
       "types": {
-        "from-default": 1
+        "timestamp": 1
       },
       "functions": {},
       "package": 4
     },
     {
-      "name": "some-interface",
+      "name": "filesystem",
       "types": {
-        "another-type": 2
+        "stat": 2
       },
       "functions": {},
       "package": 4
     },
     {
-      "name": "another-interface",
+      "name": "the-default",
       "types": {
-        "yet-another-type": 3
+        "from-default": 3
       },
       "functions": {},
-      "package": 4
+      "package": 5
     },
     {
-      "name": "clocks",
+      "name": "some-interface",
       "types": {
-        "timestamp": 4
+        "another-type": 4
       },
       "functions": {},
       "package": 5
     },
     {
-      "name": "filesystem",
+      "name": "another-interface",
       "types": {
-        "stat": 5
+        "yet-another-type": 5
       },
       "functions": {},
       "package": 5
@@ -183,52 +183,52 @@
       }
     },
     {
-      "name": "from-default",
+      "name": "timestamp",
       "kind": {
-        "type": "string"
+        "type": "u64"
       },
       "owner": {
         "interface": 4
       }
     },
     {
-      "name": "another-type",
+      "name": "stat",
       "kind": {
-        "type": "u32"
+        "record": {
+          "fields": [
+            {
+              "name": "ino",
+              "type": "u64"
+            }
+          ]
+        }
       },
       "owner": {
         "interface": 5
       }
     },
     {
-      "name": "yet-another-type",
+      "name": "from-default",
       "kind": {
-        "type": "u8"
+        "type": "string"
       },
       "owner": {
         "interface": 6
       }
     },
     {
-      "name": "timestamp",
+      "name": "another-type",
       "kind": {
-        "type": "u64"
+        "type": "u32"
       },
       "owner": {
         "interface": 7
       }
     },
     {
-      "name": "stat",
+      "name": "yet-another-type",
       "kind": {
-        "record": {
-          "fields": [
-            {
-              "name": "ino",
-              "type": "u64"
-            }
-          ]
-        }
+        "type": "u8"
       },
       "owner": {
         "interface": 8
@@ -237,7 +237,7 @@
     {
       "name": "timestamp",
       "kind": {
-        "type": 4
+        "type": 1
       },
       "owner": {
         "interface": 9
@@ -246,7 +246,7 @@
     {
       "name": "stat",
       "kind": {
-        "type": 5
+        "type": 2
       },
       "owner": {
         "interface": 9
@@ -255,7 +255,7 @@
     {
       "name": "from-default",
       "kind": {
-        "type": 1
+        "type": 3
       },
       "owner": {
         "interface": 10
@@ -264,7 +264,7 @@
     {
       "name": "another-type",
       "kind": {
-        "type": 2
+        "type": 4
       },
       "owner": {
         "interface": 10
@@ -273,7 +273,7 @@
     {
       "name": "yet-another-type",
       "kind": {
-        "type": 3
+        "type": 5
       },
       "owner": {
         "interface": 10
@@ -328,19 +328,19 @@
       "worlds": {}
     },
     {
-      "name": "foo:some-pkg",
+      "name": "foo:wasi",
       "interfaces": {
-        "the-default": 4,
-        "some-interface": 5,
-        "another-interface": 6
+        "clocks": 4,
+        "filesystem": 5
       },
       "worlds": {}
     },
     {
-      "name": "foo:wasi",
+      "name": "foo:some-pkg",
       "interfaces": {
-        "clocks": 7,
-        "filesystem": 8
+        "the-default": 6,
+        "some-interface": 7,
+        "another-interface": 8
       },
       "worlds": {}
     },
diff --git a/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json b/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json
index a2dea317..2d0132c8 100644
--- a/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json
+++ b/crates/wit-parser/tests/ui/multi-file-multi-package.wit.json
@@ -49,7 +49,7 @@
         }
       },
       "exports": {},
-      "package": 2
+      "package": 3
     },
     {
       "name": "w4",
@@ -66,7 +66,7 @@
         }
       },
       "exports": {},
-      "package": 3
+      "package": 4
     }
   ],
   "interfaces": [
@@ -108,7 +108,7 @@
         "a": 4
       },
       "functions": {},
-      "package": 2
+      "package": 3
     },
     {
       "name": null,
@@ -116,7 +116,7 @@
         "a": 5
       },
       "functions": {},
-      "package": 2
+      "package": 3
     },
     {
       "name": "i4",
@@ -124,7 +124,7 @@
         "b": 6
       },
       "functions": {},
-      "package": 3
+      "package": 4
     },
     {
       "name": null,
@@ -132,7 +132,7 @@
         "b": 7
       },
       "functions": {},
-      "package": 3
+      "package": 4
     }
   ],
   "types": [
@@ -228,6 +228,11 @@
         "w3": 1
       }
     },
+    {
+      "name": "foo:main",
+      "interfaces": {},
+      "worlds": {}
+    },
     {
       "name": "foo:name",
       "interfaces": {
diff --git a/crates/wit-parser/tests/ui/multi-file-multi-package/a.wit b/crates/wit-parser/tests/ui/multi-file-multi-package/a.wit
index 79e98376..723b5b7d 100644
--- a/crates/wit-parser/tests/ui/multi-file-multi-package/a.wit
+++ b/crates/wit-parser/tests/ui/multi-file-multi-package/a.wit
@@ -1,3 +1,4 @@
+package foo:main;
 package foo:name {
   interface i1 {
     type a = u32;
diff --git a/crates/wit-parser/tests/ui/multi-package-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-deps.wit.json
new file mode 100644
index 00000000..427c1bdf
--- /dev/null
+++ b/crates/wit-parser/tests/ui/multi-package-deps.wit.json
@@ -0,0 +1,161 @@
+{
+  "worlds": [],
+  "interfaces": [
+    {
+      "name": "i2",
+      "types": {
+        "t": 0
+      },
+      "functions": {},
+      "package": 0
+    },
+    {
+      "name": "i1",
+      "types": {
+        "t": 1,
+        "o": 2
+      },
+      "functions": {},
+      "package": 1
+    },
+    {
+      "name": "nesty",
+      "types": {
+        "o": 3,
+        "l": 4
+      },
+      "functions": {},
+      "package": 2
+    },
+    {
+      "name": "i0",
+      "types": {
+        "o": 5,
+        "l": 6,
+        "r": 7,
+        "r1": 8
+      },
+      "functions": {},
+      "package": 3
+    }
+  ],
+  "types": [
+    {
+      "name": "t",
+      "kind": {
+        "type": "string"
+      },
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "name": "t",
+      "kind": {
+        "type": 0
+      },
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "name": "o",
+      "kind": {
+        "option": 1
+      },
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "name": "o",
+      "kind": {
+        "type": 2
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "l",
+      "kind": {
+        "list": 3
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "o",
+      "kind": {
+        "type": 2
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "l",
+      "kind": {
+        "type": 4
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "r",
+      "kind": {
+        "result": {
+          "ok": 5,
+          "err": null
+        }
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "r1",
+      "kind": {
+        "result": {
+          "ok": 6,
+          "err": null
+        }
+      },
+      "owner": {
+        "interface": 3
+      }
+    }
+  ],
+  "packages": [
+    {
+      "name": "foo:dep2",
+      "interfaces": {
+        "i2": 0
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:dep1",
+      "interfaces": {
+        "i1": 1
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:nest",
+      "interfaces": {
+        "nesty": 2
+      },
+      "worlds": {}
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "i0": 3
+      },
+      "worlds": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-deps/deps/dep.wit b/crates/wit-parser/tests/ui/multi-package-deps/deps/dep.wit
new file mode 100644
index 00000000..50f12006
--- /dev/null
+++ b/crates/wit-parser/tests/ui/multi-package-deps/deps/dep.wit
@@ -0,0 +1,13 @@
+package foo:dep1;
+
+package foo:dep2 {
+  interface i2 {
+    type t = string;
+  }
+}
+
+interface i1 {
+  use foo:dep2/i2.{t};
+
+  type o = option<t>;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-deps/root.wit b/crates/wit-parser/tests/ui/multi-package-deps/root.wit
new file mode 100644
index 00000000..66e89030
--- /dev/null
+++ b/crates/wit-parser/tests/ui/multi-package-deps/root.wit
@@ -0,0 +1,16 @@
+package foo:root;
+
+package foo:nest {
+  interface nesty {
+    use foo:dep1/i1.{o};
+    type l = list<o>;
+  }
+}
+
+interface i0 {
+  use foo:dep1/i1.{o};
+  use foo:nest/nesty.{l};
+
+  type r = result<o>;
+  type r1 = result<l>;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json
index d3434da0..8076d195 100644
--- a/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json
+++ b/crates/wit-parser/tests/ui/multi-package-shared-deps.wit.json
@@ -38,18 +38,185 @@
   "interfaces": [
     {
       "name": "types",
-      "types": {},
+      "types": {
+        "type1": 0
+      },
       "functions": {},
       "package": 0
     },
     {
       "name": "types",
-      "types": {},
+      "types": {
+        "type2": 1
+      },
       "functions": {},
       "package": 1
+    },
+    {
+      "name": "i0",
+      "types": {
+        "type1": 2,
+        "type2": 3,
+        "rec0": 4
+      },
+      "functions": {},
+      "package": 2
+    },
+    {
+      "name": "i1",
+      "types": {
+        "type1": 5,
+        "type2": 6,
+        "rec1": 7
+      },
+      "functions": {},
+      "package": 3
+    },
+    {
+      "name": "root",
+      "types": {
+        "rec0": 8,
+        "rec1": 9,
+        "rec": 10
+      },
+      "functions": {},
+      "package": 4
+    }
+  ],
+  "types": [
+    {
+      "name": "type1",
+      "kind": {
+        "type": "string"
+      },
+      "owner": {
+        "interface": 0
+      }
+    },
+    {
+      "name": "type2",
+      "kind": {
+        "type": "string"
+      },
+      "owner": {
+        "interface": 1
+      }
+    },
+    {
+      "name": "type1",
+      "kind": {
+        "type": 0
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "type2",
+      "kind": {
+        "type": 1
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "rec0",
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "foo",
+              "type": 2
+            },
+            {
+              "name": "bar",
+              "type": 3
+            }
+          ]
+        }
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "type1",
+      "kind": {
+        "type": 0
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "type2",
+      "kind": {
+        "type": 1
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "rec1",
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "foo",
+              "type": 5
+            },
+            {
+              "name": "bar",
+              "type": 6
+            }
+          ]
+        }
+      },
+      "owner": {
+        "interface": 3
+      }
+    },
+    {
+      "name": "rec0",
+      "kind": {
+        "type": 4
+      },
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "name": "rec1",
+      "kind": {
+        "type": 7
+      },
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "name": "rec",
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "first",
+              "type": 8
+            },
+            {
+              "name": "second",
+              "type": 9
+            }
+          ]
+        }
+      },
+      "owner": {
+        "interface": 4
+      }
     }
   ],
-  "types": [],
   "packages": [
     {
       "name": "foo:dep1",
@@ -67,17 +234,28 @@
     },
     {
       "name": "foo:bar",
-      "interfaces": {},
+      "interfaces": {
+        "i0": 2
+      },
       "worlds": {
         "w-bar": 0
       }
     },
     {
       "name": "foo:qux",
-      "interfaces": {},
+      "interfaces": {
+        "i1": 3
+      },
       "worlds": {
         "w-qux": 1
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "root": 4
+      },
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep1/types.wit b/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep1/types.wit
index 4c84420d..ebdbb641 100644
--- a/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep1/types.wit
+++ b/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep1/types.wit
@@ -1,2 +1,4 @@
 package foo:dep1;
-interface types {}
+interface types {
+  type type1 = string;
+}
diff --git a/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep2/types.wit b/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep2/types.wit
index b7cf9466..e25d50f7 100644
--- a/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep2/types.wit
+++ b/crates/wit-parser/tests/ui/multi-package-shared-deps/deps/dep2/types.wit
@@ -1,2 +1,4 @@
 package foo:dep2;
-interface types {}
+interface types {
+  type type2 = string;
+}
diff --git a/crates/wit-parser/tests/ui/multi-package-shared-deps/packages.wit b/crates/wit-parser/tests/ui/multi-package-shared-deps/packages.wit
index 22d82aa9..c15789d8 100644
--- a/crates/wit-parser/tests/ui/multi-package-shared-deps/packages.wit
+++ b/crates/wit-parser/tests/ui/multi-package-shared-deps/packages.wit
@@ -1,4 +1,13 @@
+package foo:root;
 package foo:bar {
+  interface i0 {
+    use foo:dep1/types.{type1};
+    use foo:dep2/types.{type2};
+    record rec0 {
+      foo: type1,
+      bar: type2
+    }
+  }
   world w-bar {
     import foo:dep1/types;
     import foo:dep2/types;
@@ -6,8 +15,27 @@ package foo:bar {
 }
 
 package foo:qux {
+  interface i1 {
+    use foo:dep1/types.{type1};
+    use foo:dep2/types.{type2};
+    record rec1 {
+      foo: type1,
+      bar: type2
+    }
+  }
   world w-qux {
     import foo:dep1/types;
     import foo:dep2/types;
   }
 }
+
+interface root {
+  use foo:bar/i0.{rec0};
+  use foo:qux/i1.{rec1};
+  record rec {
+    first: rec0,
+    second: rec1
+  }
+}
+
+
diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json b/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json
index ece4cdad..ed79fcc0 100644
--- a/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json
+++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps.wit.json
@@ -12,10 +12,15 @@
           "interface": {
             "id": 1
           }
+        },
+        "interface-2": {
+          "interface": {
+            "id": 2
+          }
         }
       },
       "exports": {},
-      "package": 2
+      "package": 3
     },
     {
       "name": "w-qux",
@@ -24,17 +29,22 @@
           "interface": {
             "id": 0
           }
+        },
+        "interface-1": {
+          "interface": {
+            "id": 1
+          }
         }
       },
       "exports": {},
-      "package": 3
+      "package": 4
     }
   ],
   "interfaces": [
     {
       "name": "types",
       "types": {
-        "a": 0
+        "t2": 0
       },
       "functions": {},
       "package": 0
@@ -42,23 +52,34 @@
     {
       "name": "types",
       "types": {
-        "a": 1,
-        "r": 2
+        "t2": 1,
+        "a": 2
       },
       "functions": {},
       "package": 1
+    },
+    {
+      "name": "types",
+      "types": {
+        "a": 3,
+        "b": 4
+      },
+      "functions": {},
+      "package": 2
     }
   ],
   "types": [
     {
-      "name": "a",
-      "kind": "resource",
+      "name": "t2",
+      "kind": {
+        "type": "string"
+      },
       "owner": {
         "interface": 0
       }
     },
     {
-      "name": "a",
+      "name": "t2",
       "kind": {
         "type": 0
       },
@@ -67,13 +88,13 @@
       }
     },
     {
-      "name": "r",
+      "name": "a",
       "kind": {
         "record": {
           "fields": [
             {
-              "name": "f",
-              "type": "u8"
+              "name": "foo",
+              "type": 1
             }
           ]
         }
@@ -81,23 +102,55 @@
       "owner": {
         "interface": 1
       }
+    },
+    {
+      "name": "a",
+      "kind": {
+        "type": 2
+      },
+      "owner": {
+        "interface": 2
+      }
+    },
+    {
+      "name": "b",
+      "kind": {
+        "record": {
+          "fields": [
+            {
+              "name": "r",
+              "type": 3
+            }
+          ]
+        }
+      },
+      "owner": {
+        "interface": 2
+      }
     }
   ],
   "packages": [
     {
-      "name": "foo:dep2",
+      "name": "foo:nest",
       "interfaces": {
         "types": 0
       },
       "worlds": {}
     },
     {
-      "name": "foo:dep1",
+      "name": "foo:dep2",
       "interfaces": {
         "types": 1
       },
       "worlds": {}
     },
+    {
+      "name": "foo:dep1",
+      "interfaces": {
+        "types": 2
+      },
+      "worlds": {}
+    },
     {
       "name": "foo:bar",
       "interfaces": {},
@@ -111,6 +164,11 @@
       "worlds": {
         "w-qux": 1
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep1/types.wit b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep1/types.wit
index 825a3964..d56f5cd4 100644
--- a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep1/types.wit
+++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep1/types.wit
@@ -2,8 +2,7 @@ package foo:dep1;
 
 interface types {
   use foo:dep2/types.{a};
-
-  record r {
-    f: u8,
+  record b {
+    r: a,
   }
 }
diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit
index 603efb70..d47d01f6 100644
--- a/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit
+++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps/deps/dep2/types.wit
@@ -1,5 +1,14 @@
 package foo:dep2;
 
 interface types {
-  resource a;
+  use foo:nest/types.{t2};
+  record a {
+    foo: t2
+  }
 }
+
+package foo:nest {
+  interface types {
+    type t2 = string;
+  }
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/multi-package-transitive-deps/packages.wit b/crates/wit-parser/tests/ui/multi-package-transitive-deps/packages.wit
index 04e04f0a..ee7f1e89 100644
--- a/crates/wit-parser/tests/ui/multi-package-transitive-deps/packages.wit
+++ b/crates/wit-parser/tests/ui/multi-package-transitive-deps/packages.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:bar {
   world w-bar {
     import foo:dep1/types;
diff --git a/crates/wit-parser/tests/ui/packages-multiple-explicit.wit b/crates/wit-parser/tests/ui/packages-multiple-nested.wit
similarity index 67%
rename from crates/wit-parser/tests/ui/packages-multiple-explicit.wit
rename to crates/wit-parser/tests/ui/packages-multiple-nested.wit
index 79e98376..acd56c6b 100644
--- a/crates/wit-parser/tests/ui/packages-multiple-explicit.wit
+++ b/crates/wit-parser/tests/ui/packages-multiple-nested.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name {
   interface i1 {
     type a = u32;
@@ -21,3 +22,14 @@ package bar:name {
     }
   }
 }
+
+interface i0 {
+  use foo:name/i1.{a};
+
+  type c = option<a>;
+}
+
+world w0 {
+  export i0;
+  import foo:name/i1;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/packages-multiple-explicit.wit.json b/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json
similarity index 69%
rename from crates/wit-parser/tests/ui/packages-multiple-explicit.wit.json
rename to crates/wit-parser/tests/ui/packages-multiple-nested.wit.json
index de73c51d..f2c8d6b0 100644
--- a/crates/wit-parser/tests/ui/packages-multiple-explicit.wit.json
+++ b/crates/wit-parser/tests/ui/packages-multiple-nested.wit.json
@@ -33,6 +33,24 @@
       },
       "exports": {},
       "package": 1
+    },
+    {
+      "name": "w0",
+      "imports": {
+        "interface-2": {
+          "interface": {
+            "id": 2
+          }
+        }
+      },
+      "exports": {
+        "interface-4": {
+          "interface": {
+            "id": 4
+          }
+        }
+      },
+      "package": 2
     }
   ],
   "interfaces": [
@@ -67,6 +85,15 @@
       },
       "functions": {},
       "package": 1
+    },
+    {
+      "name": "i0",
+      "types": {
+        "a": 4,
+        "c": 5
+      },
+      "functions": {},
+      "package": 2
     }
   ],
   "types": [
@@ -105,6 +132,24 @@
       "owner": {
         "interface": 3
       }
+    },
+    {
+      "name": "a",
+      "kind": {
+        "type": 2
+      },
+      "owner": {
+        "interface": 4
+      }
+    },
+    {
+      "name": "c",
+      "kind": {
+        "option": 4
+      },
+      "owner": {
+        "interface": 4
+      }
     }
   ],
   "packages": [
@@ -125,6 +170,15 @@
       "worlds": {
         "w1": 1
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {
+        "i0": 4
+      },
+      "worlds": {
+        "w0": 2
+      }
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit
similarity index 93%
rename from crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit
rename to crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit
index 876012cf..b448a5c3 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit
+++ b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name {
   interface i {
     type a = u32;
diff --git a/crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit.json b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json
similarity index 95%
rename from crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit.json
rename to crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json
index f4c9e705..3a9dc3b0 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-colliding-decl-names.wit.json
+++ b/crates/wit-parser/tests/ui/packages-nested-colliding-decl-names.wit.json
@@ -125,6 +125,11 @@
       "worlds": {
         "w": 1
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/packages-explicit-internal-references.wit b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit
similarity index 91%
rename from crates/wit-parser/tests/ui/packages-explicit-internal-references.wit
rename to crates/wit-parser/tests/ui/packages-nested-internal-references.wit
index 52d1c682..8710567e 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-internal-references.wit
+++ b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name {
   interface i1 {
     type a = u32;
diff --git a/crates/wit-parser/tests/ui/packages-explicit-internal-references.wit.json b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json
similarity index 93%
rename from crates/wit-parser/tests/ui/packages-explicit-internal-references.wit.json
rename to crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json
index 1d59a07e..0c26221a 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-internal-references.wit.json
+++ b/crates/wit-parser/tests/ui/packages-nested-internal-references.wit.json
@@ -82,6 +82,11 @@
       "worlds": {
         "w1": 0
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/packages-explicit-with-semver.wit b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit
similarity index 93%
rename from crates/wit-parser/tests/ui/packages-explicit-with-semver.wit
rename to crates/wit-parser/tests/ui/packages-nested-with-semver.wit
index 583b5559..1f438b71 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-with-semver.wit
+++ b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name@1.0.0 {
   interface i1 {
     type a = u32;
diff --git a/crates/wit-parser/tests/ui/packages-explicit-with-semver.wit.json b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json
similarity index 95%
rename from crates/wit-parser/tests/ui/packages-explicit-with-semver.wit.json
rename to crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json
index 23a10462..ecd0fe37 100644
--- a/crates/wit-parser/tests/ui/packages-explicit-with-semver.wit.json
+++ b/crates/wit-parser/tests/ui/packages-nested-with-semver.wit.json
@@ -125,6 +125,11 @@
       "worlds": {
         "w1": 1
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/packages-single-explicit.wit b/crates/wit-parser/tests/ui/packages-single-nested.wit
similarity index 88%
rename from crates/wit-parser/tests/ui/packages-single-explicit.wit
rename to crates/wit-parser/tests/ui/packages-single-nested.wit
index 695155a9..ded8d48d 100644
--- a/crates/wit-parser/tests/ui/packages-single-explicit.wit
+++ b/crates/wit-parser/tests/ui/packages-single-nested.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name {
   interface i1 {
     type a = u32;
diff --git a/crates/wit-parser/tests/ui/packages-single-explicit.wit.json b/crates/wit-parser/tests/ui/packages-single-nested.wit.json
similarity index 92%
rename from crates/wit-parser/tests/ui/packages-single-explicit.wit.json
rename to crates/wit-parser/tests/ui/packages-single-nested.wit.json
index 3eefde28..c67c2a78 100644
--- a/crates/wit-parser/tests/ui/packages-single-explicit.wit.json
+++ b/crates/wit-parser/tests/ui/packages-single-nested.wit.json
@@ -65,6 +65,11 @@
       "worlds": {
         "w1": 0
       }
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit b/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit
deleted file mode 100644
index 1e9e0a5c..00000000
--- a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit
+++ /dev/null
@@ -1,3 +0,0 @@
-package foo:name {}
-
-package foo:name {}
diff --git a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit.result b/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit.result
deleted file mode 100644
index fcc55c6e..00000000
--- a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-colliding-names.wit.result
+++ /dev/null
@@ -1,1 +0,0 @@
-colliding explicit package names, multiple packages named `foo:name`
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit.result b/crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit.result
deleted file mode 100644
index 2c0161f9..00000000
--- a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit.result
+++ /dev/null
@@ -1,8 +0,0 @@
-failed to start resolving path: tests/ui/parse-fail/explicit-packages-with-error.wit
-
-Caused by:
-    interface or world `missing` does not exist
-         --> tests/ui/parse-fail/explicit-packages-with-error.wit:10:13
-          |
-       10 |         use missing.{a};
-          |             ^------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/mix-implicit-then-explicit-package.wit b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-main.wit
similarity index 100%
rename from crates/wit-parser/tests/ui/parse-fail/mix-implicit-then-explicit-package.wit
rename to crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-main.wit
diff --git a/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-main.wit.result b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-main.wit.result
new file mode 100644
index 00000000..d25b316c
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-main.wit.result
@@ -0,0 +1,1 @@
+no `package` header was found in any WIT file for this package
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/mix-explicit-then-implicit-package.wit b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-nested.wit
similarity index 100%
rename from crates/wit-parser/tests/ui/parse-fail/mix-explicit-then-implicit-package.wit
rename to crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-nested.wit
diff --git a/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-nested.wit.result b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-nested.wit.result
new file mode 100644
index 00000000..d25b316c
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/missing-main-declaration-initial-nested.wit.result
@@ -0,0 +1,1 @@
+no `package` header was found in any WIT file for this package
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/mix-explicit-then-implicit-package.wit.result b/crates/wit-parser/tests/ui/parse-fail/mix-explicit-then-implicit-package.wit.result
deleted file mode 100644
index 8a6fa246..00000000
--- a/crates/wit-parser/tests/ui/parse-fail/mix-explicit-then-implicit-package.wit.result
+++ /dev/null
@@ -1,1 +0,0 @@
-WIT files cannot mix top-level explicit `package` declarations with other declaration kinds
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/mix-implicit-then-explicit-package.wit.result b/crates/wit-parser/tests/ui/parse-fail/mix-implicit-then-explicit-package.wit.result
deleted file mode 100644
index 6f71c3a3..00000000
--- a/crates/wit-parser/tests/ui/parse-fail/mix-implicit-then-explicit-package.wit.result
+++ /dev/null
@@ -1,5 +0,0 @@
-expected `world`, `interface` or `use`, found keyword `package`
-     --> tests/ui/parse-fail/mix-implicit-then-explicit-package.wit:12:1
-      |
-   12 | package foo:name {
-      | ^------
\ 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
new file mode 100644
index 00000000..31c994e7
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest.wit.result
@@ -0,0 +1,6 @@
+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
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep1/types.wit b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep1/types.wit
new file mode 100644
index 00000000..2232618e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep1/types.wit
@@ -0,0 +1,12 @@
+package foo:dep1;
+
+package foo:shared {
+  interface shared1 {
+    type t1 = string;
+  }
+}
+
+interface i1 {
+  use foo:shared/shared1.{t1};
+  type o1 = option<t1>;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep2/types.wit b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep2/types.wit
new file mode 100644
index 00000000..7efa503f
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/deps/dep2/types.wit
@@ -0,0 +1,12 @@
+package foo:dep2;
+
+package foo:shared {
+  interface shared2 {
+    type t2 = string;
+  }
+}
+
+interface i2 {
+  use foo:shared/shared2.{t2};
+  type o2 = option<t2>;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/root.wit b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/root.wit
new file mode 100644
index 00000000..42ba1277
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-package-deps-share-nest/root.wit
@@ -0,0 +1,8 @@
+package foo:root;
+
+interface i0 {
+  use foo:dep1/i1.{o1};
+  use foo:dep2/i2.{o2};
+
+  type v = tuple<o1, o2>;
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit b/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit
index fb0e456f..01ee4780 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit
+++ b/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:bar {
   interface i {
     use foo:qux/i.{};
@@ -8,4 +9,4 @@ package foo:qux {
   interface i {
     use foo:bar/i.{};
   }
-}
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit.result
index b596cf2d..420ca84d 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/multiple-package-inline-cycle.wit.result
@@ -1,5 +1,5 @@
 package depends on itself
-     --> tests/ui/parse-fail/multiple-package-inline-cycle.wit:3:9
+     --> tests/ui/parse-fail/multiple-package-inline-cycle.wit:4:9
       |
-    3 |     use foo:qux/i.{};
+    4 |     use foo:qux/i.{};
       |         ^------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit.result b/crates/wit-parser/tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit.result
index 2135ac63..53c3636c 100644
--- a/crates/wit-parser/tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit.result
@@ -1,5 +1,5 @@
-expected `world`, `interface` or `use`, found keyword `package`
-     --> tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit:7:1
+expected '{', found ';'
+     --> tests/ui/parse-fail/multiple-packages-no-scope-blocks.wit:7:17
       |
     7 | package bar:name;
-      | ^------
\ No newline at end of file
+      |                 ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit b/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit
new file mode 100644
index 00000000..0e2373ac
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit
@@ -0,0 +1,5 @@
+package foo:root;
+
+package foo:name {}
+
+package foo:name {}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit.result b/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit.result
new file mode 100644
index 00000000..1376351d
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/nested-packages-colliding-names.wit.result
@@ -0,0 +1,3 @@
+package foo:name is defined in two different locations:
+* tests/ui/parse-fail/nested-packages-colliding-names.wit:5:9
+* tests/ui/parse-fail/nested-packages-colliding-names.wit:3:9
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit b/crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit
similarity index 89%
rename from crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit
rename to crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit
index 4fcb27cb..2b5c9408 100644
--- a/crates/wit-parser/tests/ui/parse-fail/explicit-packages-with-error.wit
+++ b/crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit
@@ -1,3 +1,4 @@
+package foo:root;
 package foo:name {}
 
 package bar:name {
@@ -10,4 +11,4 @@ package bar:name {
         use missing.{a};
     }
   }
-}
+}
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit.result b/crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit.result
new file mode 100644
index 00000000..98229820
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/nested-packages-with-error.wit.result
@@ -0,0 +1,5 @@
+interface or world `missing` does not exist
+     --> tests/ui/parse-fail/nested-packages-with-error.wit:11:13
+      |
+   11 |         use missing.{a};
+      |             ^------
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit
new file mode 100644
index 00000000..630cfe8e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit
@@ -0,0 +1,8 @@
+package a:b;
+
+package a:c {
+  package a:c2 {
+    package a:c3 {
+    }
+  }
+}
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
new file mode 100644
index 00000000..eaf0e670
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result
@@ -0,0 +1,8 @@
+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
diff --git a/crates/wit-parser/tests/ui/version-syntax.wit b/crates/wit-parser/tests/ui/version-syntax.wit
index 33ae783b..14d08afe 100644
--- a/crates/wit-parser/tests/ui/version-syntax.wit
+++ b/crates/wit-parser/tests/ui/version-syntax.wit
@@ -1,4 +1,4 @@
-
+package foo:root;
 package a:b@1.0.0 {}
 package a:b@1.0.1 {}
 package a:b@1.0.1-- {}
diff --git a/crates/wit-parser/tests/ui/version-syntax.wit.json b/crates/wit-parser/tests/ui/version-syntax.wit.json
index 6ae121f5..a31d7153 100644
--- a/crates/wit-parser/tests/ui/version-syntax.wit.json
+++ b/crates/wit-parser/tests/ui/version-syntax.wit.json
@@ -47,6 +47,11 @@
       "name": "a:b@1.0.1",
       "interfaces": {},
       "worlds": {}
+    },
+    {
+      "name": "foo:root",
+      "interfaces": {},
+      "worlds": {}
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/cli.rs b/tests/cli.rs
index 2e9d1664..598595a6 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -131,9 +131,9 @@ fn execute(cmd: &mut Command, stdin: Option<&[u8]>, should_fail: bool) -> Result
         if !should_fail {
             bail!(
                 "{cmd:?} failed:
-            status: {}
-            stdout: {}
-            stderr: {}",
+                status: {}
+                stdout: {}
+                stderr: {}",
                 output.status,
                 String::from_utf8_lossy(&output.stdout),
                 String::from_utf8_lossy(&output.stderr)
@@ -142,8 +142,8 @@ fn execute(cmd: &mut Command, stdin: Option<&[u8]>, should_fail: bool) -> Result
     } else if should_fail {
         bail!(
             "{cmd:?} succeeded instead of failed
-            stdout: {}
-            stderr: {}",
+                stdout: {}
+                stderr: {}",
             String::from_utf8_lossy(&output.stdout),
             String::from_utf8_lossy(&output.stderr)
         );
diff --git a/tests/cli/multiple-packages-multiple-worlds.wit b/tests/cli/multiple-packages-missing-main-world.wit
similarity index 95%
rename from tests/cli/multiple-packages-multiple-worlds.wit
rename to tests/cli/multiple-packages-missing-main-world.wit
index 792a4353..85ec034d 100644
--- a/tests/cli/multiple-packages-multiple-worlds.wit
+++ b/tests/cli/multiple-packages-missing-main-world.wit
@@ -1,4 +1,5 @@
 // FAIL: component embed --dummy --wat %
+package test:root;
 
 package test:foo1 {
   interface bar {
diff --git a/tests/cli/multiple-packages-missing-main-world.wit.stderr b/tests/cli/multiple-packages-missing-main-world.wit.stderr
new file mode 100644
index 00000000..129f9029
--- /dev/null
+++ b/tests/cli/multiple-packages-missing-main-world.wit.stderr
@@ -0,0 +1,1 @@
+error: The main package `test:root` contains no worlds
diff --git a/tests/cli/multiple-packages-multiple-main-worlds.wit b/tests/cli/multiple-packages-multiple-main-worlds.wit
new file mode 100644
index 00000000..277c3c20
--- /dev/null
+++ b/tests/cli/multiple-packages-multiple-main-worlds.wit
@@ -0,0 +1,33 @@
+// FAIL: component embed --dummy --wat %
+package test:root;
+
+package test:foo1 {
+  interface bar {
+    baz: func(s: string) -> string;
+  }
+}
+
+package test:foo2 {
+  interface bar {
+    baz: func(s: string) -> string;
+  }
+  world some-other-world {
+    import test:foo1/bar;
+    export bar;
+  }
+}
+
+package test:hola {
+  world hello {
+    import test:foo1/bar;
+    export test:foo2/bar;
+  }
+}
+
+world w1 {
+  export test:foo1/bar;
+}
+
+world w2 {
+  export test:foo2/bar;
+}
\ No newline at end of file
diff --git a/tests/cli/multiple-packages-multiple-worlds.wit.stderr b/tests/cli/multiple-packages-multiple-main-worlds.wit.stderr
similarity index 56%
rename from tests/cli/multiple-packages-multiple-worlds.wit.stderr
rename to tests/cli/multiple-packages-multiple-main-worlds.wit.stderr
index 728b9c8e..8352cb23 100644
--- a/tests/cli/multiple-packages-multiple-worlds.wit.stderr
+++ b/tests/cli/multiple-packages-multiple-main-worlds.wit.stderr
@@ -1,3 +1,3 @@
 error: multiple worlds found; one must be explicitly chosen:
-  test:foo2/some-other-world
-  test:hola/hello
+  test:root/w1
+  test:root/w2
diff --git a/tests/cli/multiple-packages-multiple-worlds-in-package.wit b/tests/cli/multiple-packages-multiple-worlds-in-package.wit
index eacfd470..88bf6690 100644
--- a/tests/cli/multiple-packages-multiple-worlds-in-package.wit
+++ b/tests/cli/multiple-packages-multiple-worlds-in-package.wit
@@ -1,5 +1,6 @@
 // FAIL: component embed --dummy --wat %
 
+package foo:root;
 package test:foo1 {
   interface bar {
     baz: func(s: string) -> string;
diff --git a/tests/cli/multiple-packages-multiple-worlds-in-package.wit.stderr b/tests/cli/multiple-packages-multiple-worlds-in-package.wit.stderr
index 7eeb6c05..3a79c685 100644
--- a/tests/cli/multiple-packages-multiple-worlds-in-package.wit.stderr
+++ b/tests/cli/multiple-packages-multiple-worlds-in-package.wit.stderr
@@ -1,3 +1,1 @@
-error: multiple worlds found; one must be explicitly chosen:
-  test:hola/hello
-  test:hola/some-other-world
+error: The main package `foo:root` contains no worlds
diff --git a/tests/cli/multiple-packages-one-world.wit b/tests/cli/multiple-packages-one-world.wit
index b789cef3..4ead947a 100644
--- a/tests/cli/multiple-packages-one-world.wit
+++ b/tests/cli/multiple-packages-one-world.wit
@@ -1,5 +1,5 @@
 // RUN: component embed --dummy --wat % | component wit
-
+package foo:root;
 package test:foo1 {
   interface bar {
     baz: func(s: string) -> string;
@@ -12,9 +12,7 @@ package test:foo2 {
   }
 }
 
-package test:hola {
-  world hello {
-    import test:foo1/bar;
-    export test:foo2/bar;
-  }
-}
+world hello {
+  import test:foo1/bar;
+  export test:foo2/bar;
+}
\ No newline at end of file
diff --git a/tests/cli/multiple-packages-one-world.wit.stdout b/tests/cli/multiple-packages-one-world.wit.stdout
index 343d9bd7..96952d2b 100644
--- a/tests/cli/multiple-packages-one-world.wit.stdout
+++ b/tests/cli/multiple-packages-one-world.wit.stdout
@@ -1,17 +1,14 @@
-package root:root {
-  world root {
-    import test:foo1/bar;
-
-    export test:foo2/bar;
-  }
-}
+package root:root;
 
+world root {
+  import test:foo1/bar;
 
+  export test:foo2/bar;
+}
 package test:foo1 {
   interface bar {
     baz: func(s: string) -> string;
   }
-
 }
 
 
@@ -19,11 +16,10 @@ package test:foo2 {
   interface bar {
     baz: func(s: string) -> string;
   }
-
 }
 
 
-package test:hola {
+package foo:root {
   world hello {
     import test:foo1/bar;
 
diff --git a/tests/cli/print-core-wasm-wit-multiple-packages.wit b/tests/cli/print-core-wasm-wit-multiple-packages.wit
index 66a2ac2d..d4ff9242 100644
--- a/tests/cli/print-core-wasm-wit-multiple-packages.wit
+++ b/tests/cli/print-core-wasm-wit-multiple-packages.wit
@@ -1,5 +1,6 @@
 // RUN: component embed --dummy --world=bar:bar/my-world % | component wit
 
+package foo:root;
 package foo:foo {
   interface my-interface {
     foo: func();
diff --git a/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout b/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout
index 57c65fce..8cd62ef6 100644
--- a/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout
+++ b/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout
@@ -1,15 +1,12 @@
-package root:root {
-  world root {
-    import bar:bar/my-interface;
-  }
-}
-
+package root:root;
 
+world root {
+  import bar:bar/my-interface;
+}
 package bar:bar {
   interface my-interface {
     foo: func();
   }
-
   world my-world {
     import my-interface;
   }
diff --git a/tests/cli/print-core-wasm-wit.wit.stdout b/tests/cli/print-core-wasm-wit.wit.stdout
index c1eb1f63..3cb25875 100644
--- a/tests/cli/print-core-wasm-wit.wit.stdout
+++ b/tests/cli/print-core-wasm-wit.wit.stdout
@@ -1,15 +1,12 @@
-package root:root {
-  world root {
-    import foo:foo/my-interface;
-  }
-}
-
+package root:root;
 
+world root {
+  import foo:foo/my-interface;
+}
 package foo:foo {
   interface my-interface {
     foo: func();
   }
-
   world my-world {
     import my-interface;
   }
diff --git a/tests/cli/semver-check-different-packages.wit b/tests/cli/semver-check-different-packages.wit
index 2a666e5e..9418f414 100644
--- a/tests/cli/semver-check-different-packages.wit
+++ b/tests/cli/semver-check-different-packages.wit
@@ -1,5 +1,6 @@
 // FAIL: component semver-check % --prev a:b/prev --new c:d/new
 
+package foo:root;
 package a:b {
   world prev {}
 }
diff --git a/tests/cli/semver-check-multiple-packages.wit b/tests/cli/semver-check-multiple-packages.wit
index 49f23c39..b661b258 100644
--- a/tests/cli/semver-check-multiple-packages.wit
+++ b/tests/cli/semver-check-multiple-packages.wit
@@ -1,5 +1,6 @@
 // RUN: component semver-check % --prev a:b/prev --new a:b/next
 
+package foo:root;
 package a:b {
   world prev {}
 
diff --git a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit
index 980d99db..84b3260d 100644
--- a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit
+++ b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit
@@ -1,5 +1,6 @@
 // RUN: component wit % --out-dir %tmpdir | component wit %tmpdir
 
+package foo:root;
 package a:b@0.2.0 {
   interface foo {}
 }
diff --git a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout
index f306ee9c..de87f531 100644
--- a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout
+++ b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout
@@ -1,14 +1,15 @@
+/// RUN: component wit % --out-dir %tmpdir | component wit %tmpdir
+package foo:root;
+
 package a:b {
   interface foo {
   }
-
 }
 
 
 package a:b@0.2.0 {
   interface foo {
   }
-
 }
 
 
diff --git a/tests/cli/wit-directory-output-in-deps-folder.wit b/tests/cli/wit-directory-output-in-deps-folder.wit
index 58ff346e..1ca01fe7 100644
--- a/tests/cli/wit-directory-output-in-deps-folder.wit
+++ b/tests/cli/wit-directory-output-in-deps-folder.wit
@@ -1,5 +1,6 @@
 // RUN: component embed --dummy % --world a:b/c | component wit --out-dir %tmpdir
 
+package foo:root;
 package a:b {
   interface foo {}
 
diff --git a/tests/cli/wit-directory-output-valid.wit b/tests/cli/wit-directory-output-valid.wit
index 01e60dca..f8489b5d 100644
--- a/tests/cli/wit-directory-output-valid.wit
+++ b/tests/cli/wit-directory-output-valid.wit
@@ -1,5 +1,6 @@
 // RUN: component wit % --out-dir %tmpdir | component wit %tmpdir
 
+package foo:root;
 package a:b {
   interface foo {}
 
@@ -11,4 +12,4 @@ package a:b {
 
 package a:c {
   interface foo {}
-}
+}
\ No newline at end of file
diff --git a/tests/cli/wit-directory-output-valid.wit.stdout b/tests/cli/wit-directory-output-valid.wit.stdout
index 33576bc7..e21621c7 100644
--- a/tests/cli/wit-directory-output-valid.wit.stdout
+++ b/tests/cli/wit-directory-output-valid.wit.stdout
@@ -1,14 +1,15 @@
+/// RUN: component wit % --out-dir %tmpdir | component wit %tmpdir
+package foo:root;
+
 package a:c {
   interface foo {
   }
-
 }
 
 
 package a:b {
   interface foo {
   }
-
   world c {
     import foo;
     import a:c/foo;
diff --git a/tests/cli/wit-directory-output.wit b/tests/cli/wit-directory-output.wit
index 88d6f83d..45554808 100644
--- a/tests/cli/wit-directory-output.wit
+++ b/tests/cli/wit-directory-output.wit
@@ -1,5 +1,7 @@
 // RUN: component wit % --out-dir %tmpdir
 
+package foo:root;
+
 package a:b {
   interface foo {}
 
diff --git a/tests/cli/wit-directory-output.wit.stdout b/tests/cli/wit-directory-output.wit.stdout
index 9524cea5..ff7f551b 100644
--- a/tests/cli/wit-directory-output.wit.stdout
+++ b/tests/cli/wit-directory-output.wit.stdout
@@ -1,2 +1,3 @@
-Writing: %tmpdir/c.wit
-Writing: %tmpdir/b.wit
+Writing: %tmpdir/deps/c.wit
+Writing: %tmpdir/deps/b.wit
+Writing: %tmpdir/root.wit
diff --git a/tests/cli/world-merging-add-imports.wit.stdout b/tests/cli/world-merging-add-imports.wit.stdout
index b0e52a26..04998711 100644
--- a/tests/cli/world-merging-add-imports.wit.stdout
+++ b/tests/cli/world-merging-add-imports.wit.stdout
@@ -1,19 +1,16 @@
-package root:root {
-  world root {
-    import a: interface {
-    }
-    import a:b/b;
-    import c: func() -> t;
-    use a:b/b.{t};
+package root:root;
+
+world root {
+  import a: interface {
   }
+  import a:b/b;
+  import c: func() -> t;
+  use a:b/b.{t};
 }
-
-
 package a:b {
   interface b {
     type t = u32;
   }
-
   world into {
   }
   world %from {
diff --git a/tests/cli/world-merging-same-imports.wit.stdout b/tests/cli/world-merging-same-imports.wit.stdout
index 2b10e904..6ad405c9 100644
--- a/tests/cli/world-merging-same-imports.wit.stdout
+++ b/tests/cli/world-merging-same-imports.wit.stdout
@@ -1,19 +1,16 @@
-package root:root {
-  world root {
-    import a: interface {
-    }
-    import a:b/b;
-    import c: func();
-    use a:b/b.{t};
+package root:root;
+
+world root {
+  import a: interface {
   }
+  import a:b/b;
+  import c: func();
+  use a:b/b.{t};
 }
-
-
 package a:b {
   interface b {
     type t = u32;
   }
-
   world into {
     import a: interface {
     }
diff --git a/tests/cli/world-merging.wit.stdout b/tests/cli/world-merging.wit.stdout
index dd928bfc..abcfaefc 100644
--- a/tests/cli/world-merging.wit.stdout
+++ b/tests/cli/world-merging.wit.stdout
@@ -1,22 +1,19 @@
-package root:root {
-  world root {
-    import a:b/x;
-    import a: func() -> t;
-    use a:b/x.{t};
+package root:root;
 
-    export y: func();
-    export a:b/x;
-    export x: interface {
-    }
+world root {
+  import a:b/x;
+  import a: func() -> t;
+  use a:b/x.{t};
+
+  export y: func();
+  export a:b/x;
+  export x: interface {
   }
 }
-
-
 package a:b {
   interface x {
     type t = u32;
   }
-
   world foo {
     import x;
     import a: func() -> t;