diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 057e755c51..56eecf06a4 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -2129,39 +2129,32 @@ impl ComponentEncoder { #[cfg(all(test, feature = "dummy-module"))] mod test { - use crate::{dummy_module, embed_component_metadata}; - use super::*; - use std::path::Path; - use wit_parser::UnresolvedPackageGroup; + use crate::{dummy_module, embed_component_metadata}; #[test] fn it_renames_imports() { let mut resolve = Resolve::new(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse( - Path::new("test.wit"), - r#" + let pkgs = resolve + .push_str( + "test.wit", + r#" package test:wit; interface i { -f: func(); + f: func(); } world test { -import i; -import foo: interface { -f: func(); -} + import i; + import foo: interface { + f: func(); + } } "#, - ) - .unwrap(); - let pkg = resolve.push(packages.remove(0), &source_map).unwrap(); - - let world = resolve.select_world(pkg, None).unwrap(); + ) + .unwrap(); + let world = resolve.select_world(&pkgs, 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 7d81335e23..ba175aa896 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -5,9 +5,9 @@ use std::str::FromStr; use std::{borrow::Cow, fmt::Display}; -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use wasm_encoder::{CanonicalOption, Encode, Section}; -use wit_parser::{parse_use_path, PackageId, ParsedUsePath, Resolve, WorldId}; +use wit_parser::{Resolve, WorldId}; mod encoding; mod gc; @@ -79,43 +79,6 @@ impl From for wasm_encoder::CanonicalOption { } } -/// Handles world name resolution for cases when multiple packages may have been resolved. If this -/// is the case, and we're dealing with input that contains a user-supplied world name (like via a -/// CLI command, for instance), we want to ensure that the world name follows the following rules: -/// -/// * If there is a single resolved package with a single world, the world name name MAY be -/// omitted. -/// * If there is a single resolved package with multiple worlds, the world name MUST be supplied, -/// but MAY or MAY NOT be fully-qualified. -/// * If there are multiple resolved packages, the world name MUST be fully-qualified. -pub fn resolve_world_from_name( - resolve: &Resolve, - resolved_packages: Vec, - world_name: Option<&str>, -) -> Result { - match resolved_packages.len() { - 0 => bail!("all of the supplied WIT source files were empty"), - 1 => resolve.select_world(resolved_packages[0], world_name.as_deref()), - _ => match world_name.as_deref() { - Some(name) => { - let world_path = parse_use_path(name).with_context(|| { - format!("failed to parse world specifier `{name}`") - })?; - match world_path { - ParsedUsePath::Name(name) => bail!("the world specifier must be of the fully-qualified, id-based form (ex: \"wasi:http/proxy\" rather than \"proxy\"); you used {name}"), - ParsedUsePath::Package(pkg_name, _) => { - match resolve.package_names.get(&pkg_name) { - Some(pkg_id) => resolve.select_world(pkg_id.clone(), world_name.as_deref()), - None => bail!("the world specifier you provided named {pkg_name}, but no package with that name was found"), - } - } - } - } - None => bail!("the supplied WIT source files describe multiple packages; please provide a fully-qualified world-specifier to the `embed` command"), - }, - } -} - /// A producer section to be added to all modules and components synthesized by /// this crate pub(crate) fn base_producers() -> wasm_metadata::Producers { @@ -145,11 +108,9 @@ pub fn embed_component_metadata( #[cfg(test)] mod tests { - use std::path::Path; - use anyhow::Result; use wasmparser::Payload; - use wit_parser::{Resolve, UnresolvedPackageGroup}; + use wit_parser::Resolve; use super::{embed_component_metadata, StringEncoding}; @@ -184,12 +145,8 @@ world test-world {} // Parse pre-canned WIT to build resolver let mut resolver = Resolve::default(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse(&Path::new("in-code.wit"), COMPONENT_WIT)?; - let pkg_id = resolver.push(packages.remove(0), &source_map)?; - let world = resolver.select_world(pkg_id, Some("test-world").into())?; + let pkgs = resolver.push_str("in-code.wit", COMPONENT_WIT)?; + let world = resolver.select_world(&pkgs, 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 59cb7353ee..f3e43614a7 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -42,7 +42,7 @@ //! the three arguments originally passed to `encode`. use crate::validation::BARE_FUNC_MODULE_NAME; -use crate::{resolve_world_from_name, DecodedWasm, StringEncoding}; +use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use std::borrow::Cow; @@ -264,7 +264,7 @@ impl Bindgen { DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"), }; resolve = r; - world = resolve_world_from_name(&resolve, pkgs, Some(world_name.into()))?; + world = resolve.select_world(&pkgs, Some(world_name.into()))?; } // Current format where `data` is a wasm component itself. diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 87917cce30..de2ff79c67 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -245,7 +245,7 @@ fn read_core_module(path: &Path, resolve: &Resolve, pkg: PackageId) -> Result Result<()> { let packages = if is_dir { resolve.push_dir(path)?.0 } else { - resolve.append(UnresolvedPackageGroup::parse_file(path)?)? + resolve.push_file(path)? }; for package in packages { @@ -107,8 +107,7 @@ fn assert_print(resolve: &Resolve, pkg_ids: &[PackageId], path: &Path, is_dir: b assert_output(&expected, &output)?; } - UnresolvedPackageGroup::parse("foo.wit".as_ref(), &output) - .context("failed to parse printed 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 56757f9750..f1d9ac35dc 100644 --- a/crates/wit-component/tests/linking.rs +++ b/crates/wit-component/tests/linking.rs @@ -1,8 +1,7 @@ use { anyhow::{Context, Result}, - std::path::Path, wit_component::StringEncoding, - wit_parser::{Resolve, UnresolvedPackageGroup}, + wit_parser::Resolve, }; const FOO: &str = r#" @@ -141,12 +140,8 @@ fn encode(wat: &str, wit: Option<&str>) -> Result> { if let Some(wit) = wit { let mut resolve = Resolve::default(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse(Path::new("wit"), wit)?; - let pkg = resolve.push(packages.remove(0), &source_map)?; - let world = resolve.select_world(pkg, None)?; + let pkgs = resolve.push_str("test.wit", wit)?; + let world = resolve.select_world(&pkgs, None)?; wit_component::embed_component_metadata( &mut module, diff --git a/crates/wit-component/tests/targets.rs b/crates/wit-component/tests/targets.rs index 8dac12d304..7c9581a5c4 100644 --- a/crates/wit-component/tests/targets.rs +++ b/crates/wit-component/tests/targets.rs @@ -1,6 +1,6 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use std::{fs, path::Path}; -use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId}; +use wit_parser::{Resolve, WorldId}; /// Tests whether a component targets a world. /// @@ -22,7 +22,7 @@ use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId}; /// Run the test with the environment variable `BLESS` set to update `error.txt`. /// /// Each test is effectively executing as: -/// ```wasm-tools component targets -w foobar test.wit test.wat``` +/// `wasm-tools component targets -w foobar test.wit test.wat` #[test] fn targets() -> Result<()> { drop(env_logger::try_init()); @@ -79,23 +79,10 @@ fn load_test_wit(path: &Path) -> Result<(Resolve, WorldId)> { const TEST_TARGET_WORLD_ID: &str = "foobar"; let test_wit_path = path.join("test.wit"); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse_file(&test_wit_path) - .context("failed to parse WIT package")?; - if packages.is_empty() { - bail!("Files were completely empty - are you sure these are the files you're looking for?") - } - if packages.len() > 1 { - bail!("Multi-package targeting tests are not yet supported.") - } - let mut resolve = Resolve::default(); - let package_id = resolve.push(packages.remove(0), &source_map)?; - + let pkgs = resolve.push_file(&test_wit_path)?; let world_id = resolve - .select_world(package_id, Some(TEST_TARGET_WORLD_ID)) + .select_world(&pkgs, 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 860fd1082c..1930eabfc6 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_id = resolver.push_path("tests/wit/parse-dir/wit")?.0[0]; + let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit")?; assert!(resolver - .select_world(package_id, "foo-world".into()) + .select_world(&package_ids, "foo-world".into()) .is_ok()); Ok(()) @@ -23,10 +23,8 @@ fn parse_wit_file() -> Result<()> { drop(env_logger::try_init()); let mut resolver = Resolve::default(); - let package_id = resolver - .push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")? - .0[0]; - resolver.select_world(package_id, "bar-world".into())?; + let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?; + resolver.select_world(&package_ids, "bar-world".into())?; assert!(resolver .interfaces .iter() diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 72e0857bce..2ab630cc69 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -228,10 +228,11 @@ impl UnresolvedPackageGroup { /// Parses the given string as a wit document. /// /// The `path` argument is used for error reporting. The `contents` provided - /// will not be able to use `pkg` use paths to other documents. - pub fn parse(path: &Path, contents: &str) -> Result { + /// are considered to be the contents of `path`. This function does not read + /// the filesystem. + pub fn parse(path: impl AsRef, contents: &str) -> Result { let mut map = SourceMap::default(); - map.push(path, contents); + map.push(path.as_ref(), contents); map.parse() } @@ -240,7 +241,8 @@ impl UnresolvedPackageGroup { /// The path provided is inferred whether it's a file or a directory. A file /// is parsed with [`UnresolvedPackageGroup::parse_file`] and a directory is /// parsed with [`UnresolvedPackageGroup::parse_dir`]. - pub fn parse_path(path: &Path) -> Result { + pub fn parse_path(path: impl AsRef) -> Result { + let path = path.as_ref(); if path.is_dir() { UnresolvedPackageGroup::parse_dir(path) } else { @@ -250,9 +252,10 @@ impl UnresolvedPackageGroup { /// Parses a WIT package from the file provided. /// - /// The WIT package returned will be a single-document package and will not - /// be able to use `pkg` paths to other documents. - pub fn parse_file(path: &Path) -> Result { + /// The return value represents all packages found in the WIT file which + /// might be either one or multiple depending on the syntax used. + pub fn parse_file(path: impl AsRef) -> Result { + let path = path.as_ref(); let contents = std::fs::read_to_string(path) .with_context(|| format!("failed to read file {path:?}"))?; Self::parse(path, &contents) @@ -260,9 +263,12 @@ impl UnresolvedPackageGroup { /// Parses a WIT package from the directory provided. /// - /// All files with the extension `*.wit` or `*.wit.md` will be loaded from - /// `path` into the returned package. - pub fn parse_dir(path: &Path) -> Result { + /// This method will look at all files under the `path` specified. All + /// `*.wit` files are parsed and assumed to be part of the same package + /// grouping. This is useful when a WIT package is split across multiple + /// files. + pub fn parse_dir(path: impl AsRef) -> Result { + let path = path.as_ref(); let mut map = SourceMap::default(); let cx = || format!("failed to read directory {path:?}"); for entry in path.read_dir().with_context(&cx)? { @@ -281,7 +287,7 @@ impl UnresolvedPackageGroup { Some(name) => name, None => continue, }; - if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") { + if !filename.ends_with(".wit") { continue; } map.push_file(&path)?; diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index a41290a6aa..c5a2f237c1 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -154,23 +154,35 @@ impl Resolve { Resolve::default() } - /// Parse a WIT package from the input `path`. + /// Parse WIT packages from the input `path`. /// /// The input `path` can be one of: /// /// * A directory containing a WIT package with an optional `deps` directory /// for any dependent WIT packages it references. - /// * A single standalone WIT file depending on what's already in `Resolve`. + /// * A single standalone WIT file. /// * A wasm-encoded WIT package as a single file in the wasm binary format. /// * A wasm-encoded WIT package as a single file in the wasm text format. /// - /// The `PackageId` of the parsed package is returned. For more information - /// see [`Resolve::push_dir`] and [`Resolve::push_file`]. This method will - /// automatically call the appropriate method based on what kind of - /// filesystem entry `path` is. + /// In all of these cases packages are allowed to depend on previously + /// inserted packages into this `Resolve`. Resolution for packages is based + /// on the name of each package and reference. /// - /// Returns the top-level [`PackageId`] as well as a list of all files read - /// during this parse. + /// 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 + /// 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. + /// + /// More information can also be found at [`Resolve::push_dir`] and + /// [`Resolve::push_file`]. pub fn push_path(&mut self, path: impl AsRef) -> Result<(Vec, Vec)> { self._push_path(path.as_ref()) } @@ -247,25 +259,42 @@ impl Resolve { } /// Parses the filesystem directory at `path` as a WIT package and returns - /// the fully resolved [`PackageId`] as a result. + /// a fully resolved [`PackageId`] list as a result. /// /// The directory itself is parsed with [`UnresolvedPackageGroup::parse_dir`] - /// which has more information on the layout of the directory. This method, - /// however, additionally supports an optional `deps` dir where dependencies - /// can be located. - /// - /// All entries in the `deps` directory are inspected and parsed as follows: - /// - /// * Any directories inside of `deps` are assumed to be another WIT package - /// and are parsed with [`UnresolvedPackageGroup::parse_dir`]. - /// * WIT files (`*.wit`) are parsed with [`UnresolvedPackageGroup::parse_file`]. - /// * WebAssembly files (`*.wasm` or `*.wat`) are assumed to be WIT packages - /// encoded to wasm and are parsed and inserted into `self`. - /// - /// This function returns the [`PackageId`]s of the root parsed packages at - /// `path`, along with a list of all paths that were consumed during parsing - /// for the root package and all dependency packages, for each package encountered. - pub fn push_dir(&mut self, path: &Path) -> Result<(Vec, Vec)> { + /// and then all packages found are inserted into this `Resolve`. The `path` + /// specified may have a `deps` subdirectory which is probed automatically + /// for any other WIT dependencies. + /// + /// The `deps` folder may contain: + /// + /// * `$path/deps/my-package/*.wit` - a directory that may contain multiple + /// WIT files. This is parsed with [`UnresolvedPackageGroup::parse_dir`] + /// and then inserted into this [`Resolve`]. Note that cannot recursively + /// contain a `deps` directory. + /// * `$path/deps/my-package.wit` - a single-file WIT package. This is + /// parsed with [`Resolve::push_file`] and then added to `self` for + /// name reoslution. + /// * `$path/deps/my-package.{wasm,wat}` - a wasm-encoded WIT package either + /// in the text for binary format. + /// + /// 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) -> Result<(Vec, Vec)> { + let path = path.as_ref(); let deps_path = path.join("deps"); let unresolved_deps = self.parse_deps_dir(&deps_path).with_context(|| { format!( @@ -342,13 +371,14 @@ impl Resolve { /// In this the package and all of its dependencies are automatically /// inserted into `self`. /// - /// In both situations the `PackageId` of the resulting resolved package is - /// returned from this method. + /// 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) -> Result> { match self._push_file(path.as_ref())? { #[cfg(feature = "decoding")] ParsedFile::Package(id) => Ok(vec![id]), - ParsedFile::Unresolved(pkgs) => self.append(pkgs), + ParsedFile::Unresolved(pkgs) => self.push_group(pkgs), } } @@ -413,22 +443,32 @@ impl Resolve { source_map.rewrite_error(|| Remap::default().append(self, unresolved)) } - /// Appends new [`UnresolvedPackageSet`] to this [`Resolve`], creating a + /// Appends new [`UnresolvedPackageGroup`] to this [`Resolve`], creating a /// fully resolved package with no dangling references. /// - /// The `deps` argument indicates that the named dependencies in - /// `unresolved` to packages are resolved by the mapping specified. - /// /// Any dependency resolution error or otherwise world-elaboration error /// will be returned here, if successful a package identifier is returned /// which corresponds to the package that was just inserted. /// - /// The returned [PackageId]s are listed in topologically sorted order. - pub fn append(&mut self, unresolved_groups: UnresolvedPackageGroup) -> Result> { + /// The returned [`PackageId`]s are listed in topologically sorted order. + pub fn push_group( + &mut self, + unresolved_groups: UnresolvedPackageGroup, + ) -> Result> { let (pkg_ids, _) = self.sort_unresolved_packages(vec![unresolved_groups])?; Ok(pkg_ids) } + /// Convenience method for combining [`UnresolvedPackageGroup::parse`] and + /// [`Resolve::push_group`]. + /// + /// 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, contents: &str) -> Result> { + self.push_group(UnresolvedPackageGroup::parse(path.as_ref(), contents)?) + } + pub fn all_bits_valid(&self, ty: &Type) -> bool { match ty { Type::U8 @@ -798,46 +838,154 @@ impl Resolve { base } - /// Attempts to locate a world given the "default" package `pkg` and the + /// Attempts to locate a world given the "default" set of `packages` and the /// optional string specifier `world`. /// /// This method is intended to be used by bindings generation tools to - /// select a world from either `pkg` or a package in this `Resolve`. + /// select a world from either `packages` or a package in this `Resolve`. + /// The `packages` list is a return value from methods such as + /// [`push_path`](Resolve::push_path), [`push_dir`](Resolve::push_dir), + /// [`push_file`](Resolve::push_file), [`push_group`](Resolve::push_group), + /// or [`push_str`](Resolve::push_str). The return values of those methods + /// are the "main package list" which is specified by the user and is used + /// as a heuristic for world selection. /// - /// If `world` is `None` then `pkg` must have precisely one world which will - /// be returned. + /// If `world` is `None` then `packages` must have one entry and that + /// package must have exactly one world. If this is the case then that world + /// will be returned, otherwise an error will be returned. /// /// If `world` is `Some` then it can either be: /// - /// * A kebab-name of a world contained within `pkg` which is being - /// selected, such as `"the-world"`. + /// * A kebab-name of a world such as `"the-world"`. In this situation + /// the `packages` list must have only a single entry. If `packages` has + /// no entries or more than one, or if the kebab-name does not exist in + /// the one package specified, then an error will be returned. /// /// * An ID-based form of a world which is selected within this `Resolve`, - /// ignoring `pkg`. For example `"wasi:http/proxy"`. + /// for example `"wasi:http/proxy"`. In this situation the `packages` + /// array is ignored and the ID specified is use to lookup a package. Note + /// that a version does not need to be specified in this string if there's + /// only one package of the same name and it has a version. In this + /// situation the version can be omitted. /// /// If successful the corresponding `WorldId` is returned, otherwise an /// error is returned. - pub fn select_world(&self, pkg: PackageId, world: Option<&str>) -> Result { - let world = match world { - Some(world) => world, - None => { - let pkg = &self.packages[pkg]; - match pkg.worlds.len() { - 0 => bail!("no worlds found in package `{}`", pkg.name), - 1 => return Ok(*pkg.worlds.values().next().unwrap()), - _ => bail!( - "multiple worlds found in package `{}`: one must be explicitly chosen", - pkg.name - ), - } - } + /// + /// # Examples + /// + /// ``` + /// use anyhow::Result; + /// use wit_parser::Resolve; + /// + /// fn main() -> Result<()> { + /// let mut resolve = Resolve::default(); + /// + /// // For inputs which have a single package and only one world `None` + /// // can be specified. + /// let ids = resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit1; + /// + /// world foo { + /// // ... + /// } + /// "#, + /// )?; + /// assert!(resolve.select_world(&ids, None).is_ok()); + /// + /// // For inputs which have a single package and multiple worlds then + /// // a world must be specified. + /// let ids = resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit2; + /// + /// world foo { /* ... */ } + /// + /// 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()); + /// + /// // 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()); + /// + /// // When selecting with a version it's ok to drop the version when + /// // there's only a single copy of that package in `Resolve`. + /// resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit5@1.0.0; + /// + /// world foo { /* ... */ } + /// "#, + /// )?; + /// assert!(resolve.select_world(&[], 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. + /// resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit5@2.0.0; + /// + /// 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()); + /// + /// Ok(()) + /// } + /// ``` + pub fn select_world(&self, packages: &[PackageId], world: Option<&str>) -> Result { + let world_path = match world { + Some(world) => Some( + parse_use_path(world) + .with_context(|| format!("failed to parse world specifier `{world}`"))?, + ), + None => None, }; - let path = parse_use_path(world) - .with_context(|| format!("failed to parse world specifier `{world}`"))?; - let (pkg, world) = match path { - ParsedUsePath::Name(name) => (pkg, name), - ParsedUsePath::Package(pkg, interface) => { + 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::Package(pkg, interface)) => { let pkg = match self.package_names.get(&pkg) { Some(pkg) => *pkg, None => { @@ -863,14 +1011,35 @@ impl Resolve { } } }; - (pkg, interface) - } + (pkg, interface.to_string()) + } + None => match packages { + [] => bail!("no packages were specified nor is a world specified"), + [one] => { + let pkg = &self.packages[*one]; + match pkg.worlds.len() { + 0 => bail!("no worlds found in package `{}`", pkg.name), + 1 => return Ok(*pkg.worlds.values().next().unwrap()), + _ => bail!( + "multiple worlds found in package `{}`: one must be explicitly chosen", + pkg.name + ), + } + } + [..] => { + bail!( + "the supplied WIT source files describe multiple packages; \ + please provide a fully-qualified world-specifier select \ + a world amongst these packages" + ) + } + }, }; let pkg = &self.packages[pkg]; pkg.worlds - .get(&world) + .get(&world_name) .copied() - .ok_or_else(|| anyhow!("no world named `{world}` in package")) + .ok_or_else(|| anyhow!("no world named `{world_name}` in package")) } /// Assigns a human readable name to the `WorldKey` specified. @@ -2512,63 +2681,60 @@ impl<'a> MergeMap<'a> { #[cfg(test)] mod tests { - use crate::{PackageId, Resolve}; + use crate::Resolve; + use anyhow::Result; #[test] - fn select_world() { + fn select_world() -> Result<()> { let mut resolve = Resolve::default(); - parse_into( - &mut resolve, + resolve.push_str( + "test.wit", r#" package foo:bar@0.1.0; world foo {} "#, - ); - parse_into( - &mut resolve, + )?; + resolve.push_str( + "test.wit", r#" package foo:baz@0.1.0; world foo {} "#, - ); - parse_into( - &mut resolve, + )?; + resolve.push_str( + "test.wit", r#" package foo:baz@0.2.0; world foo {} "#, - ); + )?; - let dummy = parse_into( - &mut resolve, + let dummy = resolve.push_str( + "test.wit", r#" package foo:dummy; world foo {} "#, - ); + )?; - 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()); - } - - fn parse_into(resolve: &mut Resolve, wit: &str) -> PackageId { - let pkgs = crate::UnresolvedPackageGroup::parse("input.wit".as_ref(), wit).unwrap(); - resolve.append(pkgs).unwrap()[0] + Ok(()) } } diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index 19967992e1..b15cd8c415 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -90,7 +90,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, wasm: &[u8]) { 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.append(unresolved).unwrap(); + let id = new_resolve.push_group(unresolved).unwrap(); last = Some(id.last().unwrap().to_owned()); } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 5d759df482..d546f82883 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -11,10 +11,9 @@ use wasm_tools::Output; use wasmparser::WasmFeatures; use wat::Detect; use wit_component::{ - embed_component_metadata, resolve_world_from_name, ComponentEncoder, DecodedWasm, Linker, - StringEncoding, WitPrinter, + embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter, }; -use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup}; +use wit_parser::{PackageId, Resolve}; /// WebAssembly wit-based component tooling. #[derive(Parser)] @@ -285,7 +284,7 @@ impl EmbedOpts { Some(self.io.parse_input_wasm()?) }; let (resolve, pkg_ids) = self.resolve.load()?; - let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?; + let world = resolve.select_world(&pkg_ids, self.world.as_deref())?; let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world)); embed_component_metadata( @@ -590,8 +589,7 @@ impl WitOpts { }; let mut resolve = WitResolve::resolve_with_features(&self.features, self.all_features); - let pkgs = UnresolvedPackageGroup::parse(path, input)?; - let ids = resolve.append(pkgs)?; + let ids = resolve.push_str(path, input)?; Ok(DecodedWasm::WitPackages(resolve, ids)) } } @@ -723,7 +721,7 @@ impl TargetsOpts { /// Executes the application. fn run(self) -> Result<()> { let (resolve, pkg_ids) = self.resolve.load()?; - let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?; + let world = resolve.select_world(&pkg_ids, self.world.as_deref())?; let component_to_test = self.input.parse_wasm()?; wit_component::targets(&resolve, world, &component_to_test)?; @@ -763,8 +761,8 @@ impl SemverCheckOpts { fn run(self) -> Result<()> { let (resolve, pkg_ids) = self.resolve.load()?; - let prev = resolve_world_from_name(&resolve, pkg_ids.clone(), Some(self.prev).as_deref())?; - let new = resolve_world_from_name(&resolve, pkg_ids, Some(self.new).as_deref())?; + let prev = resolve.select_world(&pkg_ids, Some(self.prev.as_str()))?; + let new = resolve.select_world(&pkg_ids, Some(self.new.as_str()))?; wit_component::semver_check(resolve, prev, new)?; Ok(()) }