diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 1dc8bb80d3..dde3fa3c8a 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use pretty_assertions::assert_eq; use std::{fs, path::Path}; use wasm_encoder::{Encode, Section}; @@ -99,11 +99,7 @@ fn read_core_module(path: &Path) -> Result> { UnresolvedPackage::parse_file(&interface)?, &Default::default(), )?; - let doc = *resolve.packages[pkg].documents.iter().next().unwrap().1; - let doc = &resolve.documents[doc]; - let world = doc - .default_world - .ok_or_else(|| anyhow!("no default world specified"))?; + let world = resolve.select_world(pkg, None)?; let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8)?; let section = wasm_encoder::CustomSection { diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 49d97693a2..e178de10fe 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -3,7 +3,7 @@ use crate::{ Document, DocumentId, Error, Function, Interface, InterfaceId, Results, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, }; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use id_arena::{Arena, Id}; use indexmap::{IndexMap, IndexSet}; use std::collections::{HashMap, HashSet}; @@ -376,6 +376,65 @@ impl Resolve { .push(interface.name.as_ref()?); Some(base.to_string()) } + + /// Attempts to locate a default world for the `pkg` specified within this + /// [`Resolve`]. Optionally takes a string-based `world` "specifier" to + /// resolve the world. + /// + /// This is intended for use by bindings generators and such as the default + /// logic for locating a world within a package used for binding. The + /// `world` argument is typically a user-specified argument (which again is + /// optional and not required) where the `pkg` is determined ambiently by + /// the integration. + /// + /// If `world` is `None` (e.g. not specified by a user) then the package + /// must have exactly one `default world` within its documents, otherwise an + /// error will be returned. If `world` is `Some` then it's a `.`-separated + /// name where the first element is the name of the document and the second, + /// optional, element is the name of the `world`. For example the name `foo` + /// would mean the `default world` of the `foo` document. The name `foo.bar` + /// would mean the world named `bar` in the `foo` document. + pub fn select_world(&self, pkg: PackageId, world: Option<&str>) -> Result { + match world { + Some(world) => { + let mut parts = world.splitn(2, '.'); + let doc = parts.next().unwrap(); + let world = parts.next(); + let doc = *self.packages[pkg] + .documents + .get(doc) + .ok_or_else(|| anyhow!("no document named `{doc}` in package"))?; + match world { + Some(name) => self.documents[doc] + .worlds + .get(name) + .copied() + .ok_or_else(|| anyhow!("no world named `{name}` in document")), + None => self.documents[doc] + .default_world + .ok_or_else(|| anyhow!("no default world in document")), + } + } + None => { + if self.packages[pkg].documents.is_empty() { + bail!("no documents found in package") + } + + let mut unique_default_world = None; + for (_name, doc) in &self.documents { + if let Some(default_world) = doc.default_world { + if unique_default_world.is_some() { + bail!("multiple default worlds found in package, one must be specified") + } else { + unique_default_world = Some(default_world); + } + } + } + + unique_default_world.ok_or_else(|| anyhow!("no default world in package")) + } + } + } } /// Structure returned by [`Resolve::merge`] which contains mappings from diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index fc29813a9f..be3d34bc60 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -156,7 +156,7 @@ pub struct EmbedOpts { /// world`, or it can be a `foo/bar` name where `foo` names a document and /// `bar` names a world within that document. #[clap(short, long)] - world: String, + world: Option, /// Don't read a core wasm module as input, instead generating a "dummy" /// module as a placeholder. @@ -180,25 +180,7 @@ impl EmbedOpts { Some(self.io.parse_input_wasm()?) }; let (resolve, id) = parse_wit(&self.wit)?; - - let mut parts = self.world.split('/'); - let doc = match parts.next() { - Some(name) => match resolve.packages[id].documents.get(name) { - Some(doc) => *doc, - None => bail!("no document named `{name}` in package"), - }, - None => bail!("invalid `--world` argument"), - }; - let world = match parts.next() { - Some(name) => match resolve.documents[doc].worlds.get(name) { - Some(world) => *world, - None => bail!("no world named `{name}` in document"), - }, - None => match resolve.documents[doc].default_world { - Some(world) => world, - None => bail!("no default world found in document"), - }, - }; + let world = resolve.select_world(id, self.world.as_deref())?; let encoded = wit_component::metadata::encode( &resolve,