Skip to content

Commit 902e289

Browse files
authored
Add a function to extract a world from a document (#928)
This pulls in bytecodealliance/wit-bindgen#494 as an "official" function to the `wit-parser` crate. I've written an analogue for that function in many locations so the hope is that all bindings generators can standardize on the necessary idioms by using this function. For example the Wasmtime bindings generator will want to update to use this as well.
1 parent c242e9a commit 902e289

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

crates/wit-component/tests/components.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{anyhow, Result};
1+
use anyhow::Result;
22
use pretty_assertions::assert_eq;
33
use std::{fs, path::Path};
44
use wasm_encoder::{Encode, Section};
@@ -99,11 +99,7 @@ fn read_core_module(path: &Path) -> Result<Vec<u8>> {
9999
UnresolvedPackage::parse_file(&interface)?,
100100
&Default::default(),
101101
)?;
102-
let doc = *resolve.packages[pkg].documents.iter().next().unwrap().1;
103-
let doc = &resolve.documents[doc];
104-
let world = doc
105-
.default_world
106-
.ok_or_else(|| anyhow!("no default world specified"))?;
102+
let world = resolve.select_world(pkg, None)?;
107103
let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8)?;
108104

109105
let section = wasm_encoder::CustomSection {

crates/wit-parser/src/resolve.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
Document, DocumentId, Error, Function, Interface, InterfaceId, Results, Type, TypeDef,
44
TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem,
55
};
6-
use anyhow::{bail, Context, Result};
6+
use anyhow::{anyhow, bail, Context, Result};
77
use id_arena::{Arena, Id};
88
use indexmap::{IndexMap, IndexSet};
99
use std::collections::{HashMap, HashSet};
@@ -376,6 +376,65 @@ impl Resolve {
376376
.push(interface.name.as_ref()?);
377377
Some(base.to_string())
378378
}
379+
380+
/// Attempts to locate a default world for the `pkg` specified within this
381+
/// [`Resolve`]. Optionally takes a string-based `world` "specifier" to
382+
/// resolve the world.
383+
///
384+
/// This is intended for use by bindings generators and such as the default
385+
/// logic for locating a world within a package used for binding. The
386+
/// `world` argument is typically a user-specified argument (which again is
387+
/// optional and not required) where the `pkg` is determined ambiently by
388+
/// the integration.
389+
///
390+
/// If `world` is `None` (e.g. not specified by a user) then the package
391+
/// must have exactly one `default world` within its documents, otherwise an
392+
/// error will be returned. If `world` is `Some` then it's a `.`-separated
393+
/// name where the first element is the name of the document and the second,
394+
/// optional, element is the name of the `world`. For example the name `foo`
395+
/// would mean the `default world` of the `foo` document. The name `foo.bar`
396+
/// would mean the world named `bar` in the `foo` document.
397+
pub fn select_world(&self, pkg: PackageId, world: Option<&str>) -> Result<WorldId> {
398+
match world {
399+
Some(world) => {
400+
let mut parts = world.splitn(2, '.');
401+
let doc = parts.next().unwrap();
402+
let world = parts.next();
403+
let doc = *self.packages[pkg]
404+
.documents
405+
.get(doc)
406+
.ok_or_else(|| anyhow!("no document named `{doc}` in package"))?;
407+
match world {
408+
Some(name) => self.documents[doc]
409+
.worlds
410+
.get(name)
411+
.copied()
412+
.ok_or_else(|| anyhow!("no world named `{name}` in document")),
413+
None => self.documents[doc]
414+
.default_world
415+
.ok_or_else(|| anyhow!("no default world in document")),
416+
}
417+
}
418+
None => {
419+
if self.packages[pkg].documents.is_empty() {
420+
bail!("no documents found in package")
421+
}
422+
423+
let mut unique_default_world = None;
424+
for (_name, doc) in &self.documents {
425+
if let Some(default_world) = doc.default_world {
426+
if unique_default_world.is_some() {
427+
bail!("multiple default worlds found in package, one must be specified")
428+
} else {
429+
unique_default_world = Some(default_world);
430+
}
431+
}
432+
}
433+
434+
unique_default_world.ok_or_else(|| anyhow!("no default world in package"))
435+
}
436+
}
437+
}
379438
}
380439

381440
/// Structure returned by [`Resolve::merge`] which contains mappings from

src/bin/wasm-tools/component.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ pub struct EmbedOpts {
156156
/// world`, or it can be a `foo/bar` name where `foo` names a document and
157157
/// `bar` names a world within that document.
158158
#[clap(short, long)]
159-
world: String,
159+
world: Option<String>,
160160

161161
/// Don't read a core wasm module as input, instead generating a "dummy"
162162
/// module as a placeholder.
@@ -180,25 +180,7 @@ impl EmbedOpts {
180180
Some(self.io.parse_input_wasm()?)
181181
};
182182
let (resolve, id) = parse_wit(&self.wit)?;
183-
184-
let mut parts = self.world.split('/');
185-
let doc = match parts.next() {
186-
Some(name) => match resolve.packages[id].documents.get(name) {
187-
Some(doc) => *doc,
188-
None => bail!("no document named `{name}` in package"),
189-
},
190-
None => bail!("invalid `--world` argument"),
191-
};
192-
let world = match parts.next() {
193-
Some(name) => match resolve.documents[doc].worlds.get(name) {
194-
Some(world) => *world,
195-
None => bail!("no world named `{name}` in document"),
196-
},
197-
None => match resolve.documents[doc].default_world {
198-
Some(world) => world,
199-
None => bail!("no default world found in document"),
200-
},
201-
};
183+
let world = resolve.select_world(id, self.world.as_deref())?;
202184

203185
let encoded = wit_component::metadata::encode(
204186
&resolve,

0 commit comments

Comments
 (0)