Repository: bytecodealliance/wasm-tools
Pull request #1875 (2024-10-21 20:55:38)

wit-parser package not found: explain error better
Hopefully this helps people understand why a package is not found, e.g. did you mismatch the version string? did you put it in the wrong directory structure so its not found that way?

The downcast rewrite is ugly, perhaps there's a better way to do that but I did it the dumbest way possible. The alternative is to make this a separate constructor of Error.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index a5b45006..e64411ad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2321,6 +2321,7 @@ dependencies = [
 name = "wit-encoder"
 version = "0.219.1"
 dependencies = [
+ "anyhow",
  "id-arena",
  "indoc",
  "pretty_assertions",
diff --git a/crates/wasm-wave/Cargo.toml b/crates/wasm-wave/Cargo.toml
index 9291ab0c..6bb4a25e 100644
--- a/crates/wasm-wave/Cargo.toml
+++ b/crates/wasm-wave/Cargo.toml
@@ -17,7 +17,7 @@ wit = ["dep:wit-parser"]
 
 [dependencies]
 indexmap.workspace = true
-logos = "0.14.0"
+logos = { version = "0.14.2", features = ["forbid_unsafe"] }
 thiserror = "1.0.48"
 wit-parser = { workspace = true, optional = true }
 
diff --git a/crates/wit-encoder/Cargo.toml b/crates/wit-encoder/Cargo.toml
index 1f8a0700..cae10c59 100644
--- a/crates/wit-encoder/Cargo.toml
+++ b/crates/wit-encoder/Cargo.toml
@@ -33,4 +33,5 @@ wit-parser = { workspace = true, optional = true }
 id-arena = { workspace = true, optional = true }
 
 [dev-dependencies]
+anyhow = { workspace = true }
 indoc = { workspace = true }
diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs
index a9306c6f..98fded85 100644
--- a/crates/wit-encoder/src/from_parser.rs
+++ b/crates/wit-encoder/src/from_parser.rs
@@ -1,10 +1,10 @@
-use id_arena::Id;
-
 use crate::{
-    Enum, Flags, Interface, InterfaceItem, Package, PackageName, Params, Record, Resource,
+    Enum, Flags, Ident, Interface, InterfaceItem, Package, PackageName, Params, Record, Resource,
     ResourceFunc, Result_, Results, StandaloneFunc, Tuple, Type, TypeDef, TypeDefKind, Variant,
     World, WorldItem,
 };
+use id_arena::Id;
+use wit_parser::PackageId;
 
 pub fn packages_from_parsed(resolve: &wit_parser::Resolve) -> Vec<Package> {
     let converter = Converter::new(resolve);
@@ -24,15 +24,16 @@ impl<'a> Converter<'a> {
         self.resolve
             .packages
             .iter()
-            .map(|(_, p)| self.convert_package(p))
+            .map(|(p_id, p)| self.convert_package(p_id, p))
             .collect()
     }
 
-    fn convert_package(&self, package: &wit_parser::Package) -> Package {
+    fn convert_package(&self, package_id: PackageId, package: &wit_parser::Package) -> Package {
         let mut output = Package::new(self.convert_package_name(&package.name));
         for (_, id) in &package.interfaces {
             let interface = self.resolve.interfaces.get(*id).unwrap();
             output.interface(self.convert_interface(
+                package_id,
                 interface,
                 None,
                 wit_parser::TypeOwner::Interface(*id),
@@ -40,7 +41,7 @@ impl<'a> Converter<'a> {
         }
         for (_, id) in &package.worlds {
             let world = self.resolve.worlds.get(*id).unwrap();
-            output.world(self.convert_world(world, wit_parser::TypeOwner::World(*id)));
+            output.world(self.convert_world(package_id, world, wit_parser::TypeOwner::World(*id)));
         }
         output
     }
@@ -53,33 +54,30 @@ impl<'a> Converter<'a> {
         )
     }
 
-    fn convert_world(&self, world: &wit_parser::World, owner: wit_parser::TypeOwner) -> World {
+    fn convert_world(
+        &self,
+        package_id: PackageId,
+        world: &wit_parser::World,
+        owner: wit_parser::TypeOwner,
+    ) -> World {
         let mut output = World::new(world.name.clone());
 
         for (key, item) in &world.imports {
             match item {
                 wit_parser::WorldItem::Interface { id, .. } => {
                     let interface = self.resolve.interfaces.get(*id).unwrap();
-                    output.item(match &interface.name {
-                        Some(name) => {
-                            // standalone
-                            WorldItem::named_interface_import(name.clone())
-                        }
-                        None => {
-                            // inlined
-                            let name = match key {
-                                wit_parser::WorldKey::Name(name) => name.clone(),
-                                wit_parser::WorldKey::Interface(_) => {
-                                    unreachable!("inlined interface must have a kye name")
-                                }
-                            };
-                            WorldItem::inline_interface_import(self.convert_interface(
-                                interface,
-                                Some(name),
-                                owner,
-                            ))
-                        }
-                    });
+                    let ident = self.interface_ident(package_id, Some(key), interface);
+
+                    if interface.name.is_some() {
+                        output.item(WorldItem::named_interface_import(ident))
+                    } else {
+                        output.item(WorldItem::inline_interface_import(self.convert_interface(
+                            package_id,
+                            interface,
+                            Some(ident),
+                            owner,
+                        )))
+                    }
                 }
                 wit_parser::WorldItem::Function(func) => {
                     if let Some(func) = self.standalone_func_convert(func) {
@@ -95,26 +93,17 @@ impl<'a> Converter<'a> {
             match item {
                 wit_parser::WorldItem::Interface { id, .. } => {
                     let interface = self.resolve.interfaces.get(*id).unwrap();
-                    output.item(match &interface.name {
-                        Some(name) => {
-                            // standalone
-                            WorldItem::named_interface_export(name.clone())
-                        }
-                        None => {
-                            // inlined
-                            let name = match key {
-                                wit_parser::WorldKey::Name(name) => name.clone(),
-                                wit_parser::WorldKey::Interface(_) => {
-                                    unreachable!("inlined interface must have a kye name")
-                                }
-                            };
-                            WorldItem::inline_interface_export(self.convert_interface(
-                                interface,
-                                Some(name),
-                                owner,
-                            ))
-                        }
-                    });
+                    let ident = self.interface_ident(package_id, Some(key), interface);
+                    if interface.name.is_some() {
+                        output.item(WorldItem::named_interface_export(ident));
+                    } else {
+                        output.item(WorldItem::inline_interface_export(self.convert_interface(
+                            package_id,
+                            interface,
+                            Some(ident),
+                            owner,
+                        )));
+                    }
                 }
                 wit_parser::WorldItem::Function(func) => {
                     if let Some(func) = self.standalone_func_convert(func) {
@@ -132,15 +121,17 @@ impl<'a> Converter<'a> {
 
     fn convert_interface(
         &self,
+        package_id: PackageId,
         interface: &wit_parser::Interface,
-        inlined_name: Option<String>,
+        inlined_name: Option<Ident>,
         owner: wit_parser::TypeOwner,
     ) -> Interface {
-        let mut output = Interface::new(interface.name.clone().unwrap_or_else(|| {
-            inlined_name
-                .clone()
-                .expect("inlined interface must pass in inlined_name")
-        }));
+        let mut output =
+            Interface::new(interface.name.clone().map(Ident::new).unwrap_or_else(|| {
+                inlined_name
+                    .clone()
+                    .expect("inlined interface must pass in inlined_name")
+            }));
 
         for (_, func) in &interface.functions {
             if let Some(func) = self.standalone_func_convert(func) {
@@ -166,24 +157,21 @@ impl<'a> Converter<'a> {
                     output.item(InterfaceItem::TypeDef(type_def));
                 }
             } else {
-                let interface_name = match underlying_type_def.owner {
-                    wit_parser::TypeOwner::Interface(id) => self
-                        .resolve
-                        .interfaces
-                        .get(id)
-                        .unwrap()
-                        .name
-                        .clone()
-                        .expect("can't use type from inline interface"),
+                let interface_ident = match underlying_type_def.owner {
+                    wit_parser::TypeOwner::Interface(id) => self.interface_ident(
+                        package_id,
+                        None,
+                        self.resolve.interfaces.get(id).unwrap(),
+                    ),
                     _ => panic!("Type not part of an interface"),
                 };
                 let local_type_name = type_def.name.clone().unwrap();
                 let underlying_local_type_name = underlying_type_def.name.clone().unwrap();
                 if underlying_local_type_name == local_type_name {
-                    output.use_type(interface_name, local_type_name, None);
+                    output.use_type(interface_ident, local_type_name, None);
                 } else {
                     output.use_type(
-                        interface_name,
+                        interface_ident,
                         underlying_local_type_name,
                         Some(local_type_name.into()),
                     );
@@ -488,6 +476,51 @@ impl<'a> Converter<'a> {
         }
         output
     }
+
+    fn interface_ident(
+        &self,
+        package_id: PackageId,
+        world_key: Option<&wit_parser::WorldKey>,
+        interface: &wit_parser::Interface,
+    ) -> Ident {
+        match &interface.name {
+            Some(name) => {
+                // Standalone
+                if interface.package == Some(package_id) {
+                    Ident::new(name.clone())
+                } else {
+                    let package = interface
+                        .package
+                        .map(|package_id| self.resolve.packages.get(package_id).unwrap());
+
+                    match package {
+                        Some(package) => Ident::new(format!(
+                            "{}:{}/{}{}",
+                            package.name.namespace,
+                            package.name.name,
+                            name,
+                            package
+                                .name
+                                .version
+                                .as_ref()
+                                .map(|version| format!("@{}", version))
+                                .unwrap_or_else(|| "".to_string())
+                        )),
+                        None => Ident::new(name.clone()),
+                    }
+                }
+            }
+            None => match world_key {
+                Some(world_key) => match world_key {
+                    wit_parser::WorldKey::Name(name) => Ident::new(name.clone()),
+                    wit_parser::WorldKey::Interface(_) => {
+                        unreachable!("inlined interface must have a world key name")
+                    }
+                },
+                None => panic!("inlined interface requires a world key"),
+            },
+        }
+    }
 }
 
 fn clean_func_name(resource_name: &str, method_name: &str) -> String {
diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs
index f1a66043..2eaeeba8 100644
--- a/crates/wit-encoder/src/world.rs
+++ b/crates/wit-encoder/src/world.rs
@@ -31,7 +31,7 @@ impl World {
         self.name = name.into();
     }
 
-    pub fn name(&mut self) -> &Ident {
+    pub fn name(&self) -> &Ident {
         &self.name
     }
 
@@ -254,7 +254,20 @@ impl WorldNamedInterface {
             docs: None,
         }
     }
-    pub fn docs(&mut self, docs: Option<impl Into<Docs>>) {
+
+    pub fn set_name(&mut self, name: impl Into<Ident>) {
+        self.name = name.into();
+    }
+
+    pub fn name(&self) -> &Ident {
+        &self.name
+    }
+
+    pub fn set_docs(&mut self, docs: Option<impl Into<Docs>>) {
         self.docs = docs.map(|d| d.into());
     }
+
+    pub fn docs(&self) -> Option<&Docs> {
+        self.docs.as_ref()
+    }
 }
diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs
index 8b49f2a7..881d2b2c 100644
--- a/crates/wit-parser/src/ast.rs
+++ b/crates/wit-parser/src/ast.rs
@@ -1,4 +1,4 @@
-use crate::{Error, UnresolvedPackageGroup};
+use crate::{Error, PackageNotFoundError, UnresolvedPackageGroup};
 use anyhow::{bail, Context, Result};
 use lex::{Span, Token, Tokenizer};
 use semver::Version;
@@ -1758,6 +1758,19 @@ impl SourceMap {
         if let Some(_) = err.downcast_mut::<Error>() {
             return Err(err);
         }
+        if let Some(notfound) = err.downcast_mut::<PackageNotFoundError>() {
+            if notfound.highlighted.is_none() {
+                let msg = self.highlight_err(
+                    notfound.span.start,
+                    Some(notfound.span.end),
+                    &format!("{notfound}"),
+                );
+                notfound.highlighted = Some(msg);
+            }
+        }
+        if let Some(_) = err.downcast_mut::<PackageNotFoundError>() {
+            return Err(err);
+        }
 
         if let Some(lex) = err.downcast_ref::<lex::Error>() {
             let pos = match lex {
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index 906f6a7a..9a0cc6f4 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -273,6 +273,52 @@ impl fmt::Display for Error {
 
 impl std::error::Error for Error {}
 
+#[derive(Debug)]
+struct PackageNotFoundError {
+    span: Span,
+    requested: PackageName,
+    known: Vec<PackageName>,
+    highlighted: Option<String>,
+}
+
+impl PackageNotFoundError {
+    pub fn new(span: Span, requested: PackageName, known: Vec<PackageName>) -> Self {
+        Self {
+            span,
+            requested,
+            known,
+            highlighted: None,
+        }
+    }
+}
+
+impl fmt::Display for PackageNotFoundError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(highlighted) = &self.highlighted {
+            return highlighted.fmt(f);
+        }
+        if self.known.is_empty() {
+            write!(
+                f,
+                "package '{}' not found. no known packages.",
+                self.requested
+            )?;
+        } else {
+            write!(
+                f,
+                "package '{}' not found. known packages:\n",
+                self.requested
+            )?;
+            for known in self.known.iter() {
+                write!(f, "    {known}\n")?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl std::error::Error for PackageNotFoundError {}
+
 impl UnresolvedPackageGroup {
     /// Parses the given string as a wit document.
     ///
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index 02efb938..665361c7 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -18,9 +18,9 @@ use crate::ast::{parse_use_path, ParsedUsePath};
 use crate::serde_::{serialize_arena, serialize_id_map};
 use crate::{
     AstItem, Docs, Error, Function, FunctionKind, Handle, IncludeName, Interface, InterfaceId,
-    InterfaceSpan, Mangling, PackageName, Results, SourceMap, Stability, Type, TypeDef,
-    TypeDefKind, TypeId, TypeIdVisitor, TypeOwner, UnresolvedPackage, UnresolvedPackageGroup,
-    World, WorldId, WorldItem, WorldKey, WorldSpan,
+    InterfaceSpan, Mangling, PackageName, PackageNotFoundError, Results, SourceMap, Stability,
+    Type, TypeDef, TypeDefKind, TypeId, TypeIdVisitor, TypeOwner, UnresolvedPackage,
+    UnresolvedPackageGroup, World, WorldId, WorldItem, WorldKey, WorldSpan,
 };
 
 mod clone;
@@ -41,21 +41,21 @@ mod clone;
 #[derive(Default, Clone, Debug)]
 #[cfg_attr(feature = "serde", derive(Serialize))]
 pub struct Resolve {
-    /// All knowns worlds within this `Resolve`.
+    /// All known worlds within this `Resolve`.
     ///
     /// Each world points at a `PackageId` which is stored below. No ordering is
     /// guaranteed between this list of worlds.
     #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arena"))]
     pub worlds: Arena<World>,
 
-    /// All knowns interfaces within this `Resolve`.
+    /// All known interfaces within this `Resolve`.
     ///
     /// Each interface points at a `PackageId` which is stored below. No
     /// ordering is guaranteed between this list of interfaces.
     #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arena"))]
     pub interfaces: Arena<Interface>,
 
-    /// All knowns types within this `Resolve`.
+    /// All known types within this `Resolve`.
     ///
     /// Types are topologically sorted such that any type referenced from one
     /// type is guaranteed to be defined previously. Otherwise though these are
@@ -63,7 +63,7 @@ pub struct Resolve {
     #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arena"))]
     pub types: Arena<TypeDef>,
 
-    /// All knowns packages within this `Resolve`.
+    /// All known packages within this `Resolve`.
     ///
     /// This list of packages is not sorted. Sorted packages can be queried
     /// through [`Resolve::topological_packages`].
@@ -115,6 +115,71 @@ pub struct Package {
 
 pub type PackageId = Id<Package>;
 
+/// All the sources used during resolving a directory or path.
+#[derive(Clone, Debug)]
+pub struct PackageSourceMap {
+    sources: Vec<Vec<PathBuf>>,
+    package_id_to_source_map_idx: BTreeMap<PackageId, usize>,
+}
+
+impl PackageSourceMap {
+    fn from_single_source(package_id: PackageId, source: &Path) -> Self {
+        Self {
+            sources: vec![vec![source.to_path_buf()]],
+            package_id_to_source_map_idx: BTreeMap::from([(package_id, 0)]),
+        }
+    }
+
+    fn from_source_maps(
+        source_maps: Vec<SourceMap>,
+        package_id_to_source_map_idx: BTreeMap<PackageId, usize>,
+    ) -> PackageSourceMap {
+        for (package_id, idx) in &package_id_to_source_map_idx {
+            if *idx >= source_maps.len() {
+                panic!(
+                    "Invalid source map index: {}, package id: {:?}, source maps size: {}",
+                    idx,
+                    package_id,
+                    source_maps.len()
+                )
+            }
+        }
+
+        Self {
+            sources: source_maps
+                .into_iter()
+                .map(|source_map| {
+                    source_map
+                        .source_files()
+                        .map(|path| path.to_path_buf())
+                        .collect()
+                })
+                .collect(),
+            package_id_to_source_map_idx,
+        }
+    }
+
+    /// All unique source paths.
+    pub fn paths(&self) -> impl Iterator<Item = &Path> {
+        // Usually any two source map should not have duplicated source paths,
+        // but it can happen, e.g. with using [`Resolve::push_str`] directly.
+        // To be sure we use a set for deduplication here.
+        self.sources
+            .iter()
+            .flatten()
+            .map(|path_buf| path_buf.as_ref())
+            .collect::<HashSet<&Path>>()
+            .into_iter()
+    }
+
+    /// Source paths for package
+    pub fn package_paths(&self, id: PackageId) -> Option<impl Iterator<Item = &Path>> {
+        self.package_id_to_source_map_idx
+            .get(&id)
+            .map(|&idx| self.sources[idx].iter().map(|path_buf| path_buf.as_ref()))
+    }
+}
+
 enum ParsedFile {
     #[cfg(feature = "decoding")]
     Package(PackageId),
@@ -176,26 +241,24 @@ impl Resolve {
     /// inserted packages into this `Resolve`. Resolution for packages is based
     /// on the name of each package and reference.
     ///
-    /// This method returns a list of `PackageId` elements and additionally a
-    /// list of `PathBuf` elements. The `PackageId` elements represent the "main
-    /// package" that was parsed. For example if a single WIT file was specified
-    /// this will be all the packages found in the file. For a directory this
-    /// will be all the packages in the directory itself, but not in the `deps`
-    /// directory. The list of `PackageId` values is useful to pass to
-    /// [`Resolve::select_world`] to take a user-specified world in a
+    /// This method returns a `PackageId` and additionally a `PackageSourceMap`.
+    /// The `PackageId` represent the main package that was parsed. For example if a single WIT
+    /// file was specified  this will be the main package found in the file. For a directory this
+    /// will be all the main package in the directory itself. The `PackageId` value is useful
+    /// to pass to [`Resolve::select_world`] to take a user-specified world in a
     /// conventional fashion and select which to use for bindings generation.
     ///
-    /// The returned list of `PathBuf` elements represents all files parsed
-    /// during this operation. This can be useful for systems that want to
-    /// rebuild or regenerate bindings based on files modified.
+    /// The returned [`PackageSourceMap`] contains all the sources used during this operation.
+    /// This can be useful for systems that want to rebuild or regenerate bindings based on files modified,
+    /// or for ones which like to identify the used files for a package.
     ///
     /// 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<(PackageId, Vec<PathBuf>)> {
+    pub fn push_path(&mut self, path: impl AsRef<Path>) -> Result<(PackageId, PackageSourceMap)> {
         self._push_path(path.as_ref())
     }
 
-    fn _push_path(&mut self, path: &Path) -> Result<(PackageId, Vec<PathBuf>)> {
+    fn _push_path(&mut self, path: &Path) -> Result<(PackageId, PackageSourceMap)> {
         if path.is_dir() {
             self.push_dir(path).with_context(|| {
                 format!(
@@ -205,7 +268,7 @@ impl Resolve {
             })
         } else {
             let id = self.push_file(path)?;
-            Ok((id, vec![path.to_path_buf()]))
+            Ok((id, PackageSourceMap::from_single_source(id, path)))
         }
     }
 
@@ -213,7 +276,7 @@ impl Resolve {
         &mut self,
         main: UnresolvedPackageGroup,
         deps: Vec<UnresolvedPackageGroup>,
-    ) -> Result<(PackageId, Vec<PathBuf>)> {
+    ) -> Result<(PackageId, PackageSourceMap)> {
         let mut pkg_details_map = BTreeMap::new();
         let mut source_maps = Vec::new();
 
@@ -269,8 +332,9 @@ package {name} is defined in two different locations:\n\
         }
 
         // 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 buffers for each `SourceMap` only once, even though multiple packages may reference
         // the same `SourceMap`.
+        let mut package_id_to_source_map_idx = BTreeMap::new();
         let mut main_pkg_id = None;
         for name in order {
             let (pkg, source_map_index) = pkg_details_map.remove(&name).unwrap();
@@ -281,15 +345,13 @@ package {name} is defined in two different locations:\n\
                 assert!(main_pkg_id.is_none());
                 main_pkg_id = Some(id);
             }
+            package_id_to_source_map_idx.insert(id, source_map_index);
         }
 
-        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))
+        Ok((
+            main_pkg_id.unwrap(),
+            PackageSourceMap::from_source_maps(source_maps, package_id_to_source_map_idx),
+        ))
     }
 
     /// Parses the filesystem directory at `path` as a WIT package and returns
@@ -314,24 +376,22 @@ package {name} is defined in two different locations:\n\
     ///
     /// In all cases entries in the `deps` folder are added to `self` first
     /// before adding files found in `path` itself. All WIT packages found are
-    /// candidates for name-based resolution that other packages may used.
-    ///
-    /// This function returns a tuple of two values. The first value is a list
-    /// of [`PackageId`] values which represents the WIT packages found within
-    /// `path`, but not those within `deps`. The `path` provided may contain
-    /// only a single WIT package but might also use the multi-package form of
-    /// WIT, and the returned list will indicate which was used. This argument
-    /// is useful for passing to [`Resolve::select_world`] for choosing
-    /// something to bindgen with.
-    ///
-    /// 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<(PackageId, Vec<PathBuf>)> {
+    /// candidates for name-based resolution that other packages may use.
+    ///
+    /// This function returns a tuple of two values. The first value is a
+    /// [`PackageId`], which represents the main WIT package found within
+    /// `path`. This argument is useful for passing to [`Resolve::select_world`]
+    /// for choosing something to bindgen with.
+    ///
+    /// The second value returned is a [`PackageSourceMap`], which contains all the sources
+    /// that were parsed during resolving. This can be useful for:
+    /// * build systems that want to rebuild bindings whenever one of the files changed
+    /// * or other tools, which want to identify the sources for the resolved packages
+    pub fn push_dir(&mut self, path: impl AsRef<Path>) -> Result<(PackageId, PackageSourceMap)> {
         self._push_dir(path.as_ref())
     }
 
-    fn _push_dir(&mut self, path: &Path) -> Result<(PackageId, Vec<PathBuf>)> {
+    fn _push_dir(&mut self, path: &Path) -> Result<(PackageId, PackageSourceMap)> {
         let top_pkg = UnresolvedPackageGroup::parse_dir(path)
             .with_context(|| format!("failed to parse package: {}", path.display()))?;
         let deps = path.join("deps");
@@ -2786,7 +2846,13 @@ impl Remap {
                 .package_names
                 .get(pkg_name)
                 .copied()
-                .ok_or_else(|| Error::new(span, "package not found"))?;
+                .ok_or_else(|| {
+                    PackageNotFoundError::new(
+                        span,
+                        pkg_name.clone(),
+                        resolve.package_names.keys().cloned().collect(),
+                    )
+                })?;
 
             // Functions can't be imported so this should be empty.
             assert!(unresolved_iface.functions.is_empty());


Test patch:
diff --git a/crates/wit-encoder/tests/deps.rs b/crates/wit-encoder/tests/deps.rs
new file mode 100644
index 00000000..428f17a1
--- /dev/null
+++ b/crates/wit-encoder/tests/deps.rs
@@ -0,0 +1,188 @@
+use anyhow::Result;
+use pretty_assertions::assert_eq;
+use wit_encoder::{packages_from_parsed, Package};
+use wit_parser::Resolve;
+
+const MAIN_PACKAGE_SOURCE: &str = indoc::indoc! {"
+    package foo:main;
+
+    interface main-interface-a {
+      use foo:dep-a/dep-a-interface-b.{ ra };
+    }
+
+    interface main-interface-b {}
+
+    interface main-interface-c {}
+
+    interface main-interface-d {}
+
+    interface main-interface-e {}
+
+    world main-world-a {
+      import foo:dep-c/dep-c-interface-a;
+      export foo:dep-c/dep-c-interface-b;
+      include foo:dep-c/dep-c-world-a;
+
+      import foo:dep-b/dep-b-interface-a@1.2.3;
+      export foo:dep-b/dep-b-interface-b@1.2.3;
+      include foo:dep-b/dep-b-world-b@1.2.3;
+
+      import main-interface-b;
+      export main-interface-a;
+      include main-world-b;
+    }
+
+    world main-world-b {
+      export main-interface-d;
+      import main-interface-d;
+    }
+"};
+
+const MAIN_PACKAGE_RESOLVED_ENCODED: &str = indoc::indoc! {"
+    package foo:main;
+
+    interface main-interface-a {
+      use foo:dep-a/dep-a-interface-b.{ ra };
+    }
+
+    interface main-interface-b {}
+
+    interface main-interface-c {}
+
+    interface main-interface-d {}
+
+    interface main-interface-e {}
+
+    world main-world-b {
+      import main-interface-d;
+      export main-interface-d;
+    }
+
+    world main-world-a {
+      import foo:dep-c/dep-c-interface-a;
+      import foo:dep-b/dep-b-interface-a@1.2.3;
+      import main-interface-b;
+      import foo:dep-c/dep-c-interface-c;
+      import main-interface-d;
+      import foo:dep-a/dep-a-interface-b;
+      export foo:dep-c/dep-c-interface-b;
+      export foo:dep-b/dep-b-interface-b@1.2.3;
+      export main-interface-a;
+      export foo:dep-c/dep-c-interface-d;
+      export main-interface-d;
+    }
+"};
+
+const DEP_PACKAGE_A: &str = indoc::indoc! {"
+    package foo:dep-a;
+
+    interface dep-a-interface-b {
+      use foo:dep-b/dep-b-interface-a@1.2.3.{a};
+      type ra = a;
+    }
+
+    world dep-a-world {}
+"};
+
+const DEP_PACKAGE_B: &str = indoc::indoc! {"
+    package foo:dep-b@1.2.3;
+
+    interface dep-b-interface-a {
+      record a {
+        x: f32,
+        y: f32,
+      }
+    }
+
+    interface dep-b-interface-b {
+      use foo:dep-c/dep-c-interface-a.{a};
+    }
+
+    world dep-b-world-a {
+      export dep-b-interface-a;
+    }
+
+    world dep-b-world-b {}
+"};
+
+const DEP_PACKAGE_C: &str = indoc::indoc! {"
+    package foo:dep-c;
+
+    interface dep-c-interface-a {
+      record a {
+        x: f32,
+        y: f32,
+      }
+    }
+
+    interface dep-c-interface-b {}
+
+    interface dep-c-interface-c {}
+
+    interface dep-c-interface-d {}
+
+    world dep-c-world-a {
+      import dep-c-interface-c;
+      export dep-c-interface-d;
+    }
+"};
+
+#[test]
+fn resolve_encode_resolve_round_trip_with_use_and_include() {
+    let packages = packages_from_parsed(
+        &resolve_packages(&[
+            ("dep_c.wit", DEP_PACKAGE_C),
+            ("dep_b.wit", DEP_PACKAGE_B),
+            ("dep_a.wit", DEP_PACKAGE_A),
+            ("main_foo.wit", MAIN_PACKAGE_SOURCE),
+        ])
+        .unwrap(),
+    );
+
+    assert_eq!(packages.len(), 4);
+
+    assert_package_names(
+        &packages,
+        &["foo:dep-c", "foo:dep-b@1.2.3", "foo:dep-a", "foo:main"],
+    );
+
+    // Resolve should still work after rendering the encoded packages
+    let packages = packages_from_parsed(
+        &resolve_packages(&[
+            ("dep_c.wit", &packages[0].to_string()),
+            ("dep_b.wit", &packages[1].to_string()),
+            ("dep_a.wit", &packages[2].to_string()),
+            ("main_foo.wit", &packages[3].to_string()),
+        ])
+        .unwrap(),
+    );
+
+    // Check that no package is lost
+    assert_package_names(
+        &packages,
+        &["foo:dep-c", "foo:dep-b@1.2.3", "foo:dep-a", "foo:main"],
+    );
+
+    // Check for the encoded main wit
+    assert_eq!(MAIN_PACKAGE_RESOLVED_ENCODED, &packages[3].to_string());
+}
+
+fn resolve_packages(packages: &[(&str, &str)]) -> Result<Resolve> {
+    let mut resolve = Resolve::new();
+    for (name, wit) in packages {
+        resolve.push_str(name, wit)?;
+    }
+    Ok(resolve)
+}
+
+// The order of converted packages currently follows the order of pushes to the parser's package arena.
+// If this changes, then explicit sorting or search should be added to these tests
+fn assert_package_names(packages: &[Package], package_names: &[&str]) {
+    assert_eq!(
+        packages
+            .iter()
+            .map(|p| p.name().to_string())
+            .collect::<Vec<_>>(),
+        package_names
+    );
+}
diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
index 56b5c85e..5ca6c2d7 100644
--- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg6.wit.result
@@ -1,4 +1,6 @@
-failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg6]: package not found
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg6]: package 'foo:bar' not found. known packages:
+    foo:baz
+
      --> tests/ui/parse-fail/bad-pkg6/root.wit:3:7
       |
     3 |   use foo:bar/baz.{};
diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result
index c93df981..e2417d69 100644
--- a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result
+++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface4.wit.result
@@ -1,4 +1,4 @@
-package not found
+package 'some:dependency' not found. no known packages.
      --> tests/ui/parse-fail/unresolved-interface4.wit:6:10
       |
     6 |   import some:dependency/iface;