blob: 02efb938676d2d4c2aad92903a15c9efc196e0d2 [file] [log] [blame] [edit]
use std::cmp::Ordering;
use std::collections::hash_map;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::mem;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, ensure, Context, Result};
use id_arena::{Arena, Id};
use indexmap::{IndexMap, IndexSet};
use semver::Version;
#[cfg(feature = "serde")]
use serde_derive::Serialize;
use crate::ast::lex::Span;
use crate::ast::{parse_use_path, ParsedUsePath};
#[cfg(feature = "serde")]
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,
};
mod clone;
/// Representation of a fully resolved set of WIT packages.
///
/// This structure contains a graph of WIT packages and all of their contents
/// merged together into the contained arenas. All items are sorted
/// topologically and everything here is fully resolved, so with a `Resolve` no
/// name lookups are necessary and instead everything is index-based.
///
/// Working with a WIT package requires inserting it into a `Resolve` to ensure
/// that all of its dependencies are satisfied. This will give the full picture
/// of that package's types and such.
///
/// Each item in a `Resolve` has a parent link to trace it back to the original
/// package as necessary.
#[derive(Default, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Resolve {
/// All knowns 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`.
///
/// 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`.
///
/// Types are topologically sorted such that any type referenced from one
/// type is guaranteed to be defined previously. Otherwise though these are
/// not sorted by interface for example.
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arena"))]
pub types: Arena<TypeDef>,
/// All knowns packages within this `Resolve`.
///
/// This list of packages is not sorted. Sorted packages can be queried
/// through [`Resolve::topological_packages`].
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arena"))]
pub packages: Arena<Package>,
/// A map of package names to the ID of the package with that name.
#[cfg_attr(feature = "serde", serde(skip))]
pub package_names: IndexMap<PackageName, PackageId>,
/// Activated features for this [`Resolve`].
///
/// This set of features is empty by default. This is consulted for
/// `@unstable` annotations in loaded WIT documents. Any items with
/// `@unstable` are filtered out unless their feature is present within this
/// set.
#[cfg_attr(feature = "serde", serde(skip))]
pub features: IndexSet<String>,
/// Activate all features for this [`Resolve`].
#[cfg_attr(feature = "serde", serde(skip))]
pub all_features: bool,
}
/// A WIT package within a `Resolve`.
///
/// A package is a collection of interfaces and worlds. Packages additionally
/// have a unique identifier that affects generated components and uniquely
/// identifiers this particular package.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Package {
/// A unique name corresponding to this package.
pub name: PackageName,
/// Documentation associated with this package.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))]
pub docs: Docs,
/// All interfaces contained in this packaged, keyed by the interface's
/// name.
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id_map"))]
pub interfaces: IndexMap<String, InterfaceId>,
/// All worlds contained in this package, keyed by the world's name.
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id_map"))]
pub worlds: IndexMap<String, WorldId>,
}
pub type PackageId = Id<Package>;
enum ParsedFile {
#[cfg(feature = "decoding")]
Package(PackageId),
Unresolved(UnresolvedPackageGroup),
}
/// Visitor helper for performing topological sort on a group of packages.
fn visit<'a>(
pkg: &'a UnresolvedPackage,
pkg_details_map: &'a BTreeMap<PackageName, (UnresolvedPackage, usize)>,
order: &mut IndexSet<PackageName>,
visiting: &mut HashSet<&'a PackageName>,
source_maps: &[SourceMap],
) -> Result<()> {
if order.contains(&pkg.name) {
return Ok(());
}
match pkg_details_map.get(&pkg.name) {
Some(pkg_details) => {
let (_, source_maps_index) = pkg_details;
source_maps[*source_maps_index].rewrite_error(|| {
for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() {
let span = pkg.foreign_dep_spans[i];
if !visiting.insert(dep) {
bail!(Error::new(span, "package depends on itself"));
}
if let Some(dep) = pkg_details_map.get(dep) {
let (dep_pkg, _) = dep;
visit(dep_pkg, pkg_details_map, order, visiting, source_maps)?;
}
assert!(visiting.remove(dep));
}
assert!(order.insert(pkg.name.clone()));
Ok(())
})
}
None => panic!("No pkg_details found for package when doing topological sort"),
}
}
impl Resolve {
/// Creates a new [`Resolve`] with no packages/items inside of it.
pub fn new() -> Resolve {
Resolve::default()
}
/// 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.
/// * 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.
///
/// 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.
///
/// 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<Path>) -> Result<(PackageId, Vec<PathBuf>)> {
self._push_path(path.as_ref())
}
fn _push_path(&mut self, path: &Path) -> Result<(PackageId, Vec<PathBuf>)> {
if path.is_dir() {
self.push_dir(path).with_context(|| {
format!(
"failed to resolve directory while parsing WIT for path [{}]",
path.display()
)
})
} else {
let id = self.push_file(path)?;
Ok((id, vec![path.to_path_buf()]))
}
}
fn sort_unresolved_packages(
&mut self,
main: UnresolvedPackageGroup,
deps: Vec<UnresolvedPackageGroup>,
) -> Result<(PackageId, Vec<PathBuf>)> {
let mut pkg_details_map = BTreeMap::new();
let mut source_maps = Vec::new();
let mut insert = |group: UnresolvedPackageGroup| {
let UnresolvedPackageGroup {
main,
nested,
source_map,
} = group;
let i = source_maps.len();
source_maps.push(source_map);
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
// and otherwise determine the order that packages must be added to
// this `Resolve`.
let mut order = IndexSet::new();
let mut visiting = HashSet::new();
for pkg_details in pkg_details_map.values() {
let (pkg, _) = pkg_details;
visit(
pkg,
&pkg_details_map,
&mut order,
&mut visiting,
&source_maps,
)?;
}
// 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 main_pkg_id = None;
for name in order {
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);
}
}
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
/// a fully resolved [`PackageId`] list as a result.
///
/// The directory itself is parsed with [`UnresolvedPackageGroup::parse_dir`]
/// 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<Path>) -> Result<(PackageId, Vec<PathBuf>)> {
self._push_dir(path.as_ref())
}
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 deps = path.join("deps");
let deps = self
.parse_deps_dir(&deps)
.with_context(|| format!("failed to parse dependency directory: {}", deps.display()))?;
self.sort_unresolved_packages(top_pkg, deps)
}
fn parse_deps_dir(&mut self, path: &Path) -> Result<Vec<UnresolvedPackageGroup>> {
let mut ret = Vec::new();
if !path.exists() {
return Ok(ret);
}
let mut entries = path
.read_dir()
.and_then(|i| i.collect::<std::io::Result<Vec<_>>>())
.context("failed to read directory")?;
entries.sort_by_key(|e| e.file_name());
for dep in entries {
let path = dep.path();
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.
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();
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(ret)
}
/// Parses the contents of `path` from the filesystem and pushes the result
/// into this `Resolve`.
///
/// The `path` referenced here can be one of:
///
/// * A WIT file. Note that in this case this single WIT file will be the
/// entire package and any dependencies it has must already be in `self`.
/// * A WIT package encoded as WebAssembly, either in text or binary form.
/// In this the package and all of its dependencies are automatically
/// inserted into `self`.
///
/// 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<PackageId> {
match self._push_file(path.as_ref())? {
#[cfg(feature = "decoding")]
ParsedFile::Package(id) => Ok(id),
ParsedFile::Unresolved(pkg) => self.push_group(pkg),
}
}
fn _push_file(&mut self, path: &Path) -> Result<ParsedFile> {
let contents = std::fs::read(path)
.with_context(|| format!("failed to read path for WIT [{}]", path.display()))?;
// If decoding is enabled at compile time then try to see if this is a
// wasm file.
#[cfg(feature = "decoding")]
{
use crate::decoding::{decode, DecodedWasm};
#[cfg(feature = "wat")]
let is_wasm = wat::Detect::from_bytes(&contents).is_wasm();
#[cfg(not(feature = "wat"))]
let is_wasm = wasmparser::Parser::is_component(&contents);
if is_wasm {
#[cfg(feature = "wat")]
let contents = wat::parse_bytes(&contents).map_err(|mut e| {
e.set_path(path);
e
})?;
match decode(&contents)? {
DecodedWasm::Component(..) => {
bail!("found an actual component instead of an encoded WIT package in wasm")
}
DecodedWasm::WitPackage(resolve, pkg) => {
let remap = self.merge(resolve)?;
return Ok(ParsedFile::Package(remap.packages[pkg.index()]));
}
}
}
}
// If this wasn't a wasm file then assume it's a WIT file.
let text = match std::str::from_utf8(&contents) {
Ok(s) => s,
Err(_) => bail!("input file is not valid utf-8 [{}]", path.display()),
};
let pkgs = UnresolvedPackageGroup::parse(path, text)?;
Ok(ParsedFile::Unresolved(pkgs))
}
/// Appends a new [`UnresolvedPackage`] to this [`Resolve`], creating a
/// fully resolved package with no dangling references.
///
/// All the dependencies of `unresolved` must already have been loaded
/// within this `Resolve` via previous calls to `push` or other methods such
/// as [`Resolve::push_path`].
///
/// 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.
pub fn push(
&mut self,
unresolved: UnresolvedPackage,
source_map: &SourceMap,
) -> Result<PackageId> {
source_map.rewrite_error(|| Remap::default().append(self, unresolved))
}
/// Appends new [`UnresolvedPackageGroup`] to this [`Resolve`], creating a
/// fully resolved package with no dangling references.
///
/// 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 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
/// [`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<Path>, contents: &str) -> Result<PackageId> {
self.push_group(UnresolvedPackageGroup::parse(path.as_ref(), contents)?)
}
pub fn all_bits_valid(&self, ty: &Type) -> bool {
match ty {
Type::U8
| Type::S8
| Type::U16
| Type::S16
| Type::U32
| Type::S32
| Type::U64
| Type::S64
| Type::F32
| Type::F64 => true,
Type::Bool | Type::Char | Type::String => false,
Type::Id(id) => match &self.types[*id].kind {
TypeDefKind::List(_)
| TypeDefKind::Variant(_)
| TypeDefKind::Enum(_)
| TypeDefKind::Option(_)
| TypeDefKind::Result(_)
| TypeDefKind::Future(_)
| TypeDefKind::Stream(_) => false,
TypeDefKind::Type(t) => self.all_bits_valid(t),
TypeDefKind::Handle(h) => match h {
crate::Handle::Own(_) => true,
crate::Handle::Borrow(_) => true,
},
TypeDefKind::Resource => false,
TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)),
TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)),
// FIXME: this could perhaps be `true` for multiples-of-32 but
// seems better to probably leave this as unconditionally
// `false` for now, may want to reconsider later?
TypeDefKind::Flags(_) => false,
TypeDefKind::Unknown => unreachable!(),
},
}
}
/// Merges all the contents of a different `Resolve` into this one. The
/// `Remap` structure returned provides a mapping from all old indices to
/// new indices
///
/// This operation can fail if `resolve` disagrees with `self` about the
/// packages being inserted. Otherwise though this will additionally attempt
/// to "union" packages found in `resolve` with those found in `self`.
/// Unioning packages is keyed on the name/url of packages for those with
/// URLs present. If found then it's assumed that both `Resolve` instances
/// were originally created from the same contents and are two views
/// of the same package.
pub fn merge(&mut self, resolve: Resolve) -> Result<Remap> {
log::trace!(
"merging {} packages into {} packages",
resolve.packages.len(),
self.packages.len()
);
let mut map = MergeMap::new(&resolve, &self);
map.build()?;
let MergeMap {
package_map,
interface_map,
type_map,
world_map,
interfaces_to_add,
worlds_to_add,
..
} = map;
// With a set of maps from ids in `resolve` to ids in `self` the next
// operation is to start moving over items and building a `Remap` to
// update ids.
//
// Each component field of `resolve` is moved into `self` so long as
// its ID is not within one of the maps above. If it's present in a map
// above then that means the item is already present in `self` so a new
// one need not be added. If it's not present in a map that means it's
// not present in `self` so it must be added to an arena.
//
// When adding an item to an arena one of the `remap.update_*` methods
// is additionally called to update all identifiers from pointers within
// `resolve` to becoming pointers within `self`.
//
// Altogether this should weave all the missing items in `self` from
// `resolve` into one structure while updating all identifiers to
// be local within `self`.
let mut remap = Remap::default();
let Resolve {
types,
worlds,
interfaces,
packages,
package_names,
features: _,
..
} = resolve;
let mut moved_types = Vec::new();
for (id, mut ty) in types {
let new_id = match type_map.get(&id).copied() {
Some(id) => {
update_stability(&ty.stability, &mut self.types[id].stability)?;
id
}
None => {
log::debug!("moving type {:?}", ty.name);
moved_types.push(id);
remap.update_typedef(self, &mut ty, None)?;
self.types.alloc(ty)
}
};
assert_eq!(remap.types.len(), id.index());
remap.types.push(Some(new_id));
}
let mut moved_interfaces = Vec::new();
for (id, mut iface) in interfaces {
let new_id = match interface_map.get(&id).copied() {
Some(id) => {
update_stability(&iface.stability, &mut self.interfaces[id].stability)?;
id
}
None => {
log::debug!("moving interface {:?}", iface.name);
moved_interfaces.push(id);
remap.update_interface(self, &mut iface, None)?;
self.interfaces.alloc(iface)
}
};
assert_eq!(remap.interfaces.len(), id.index());
remap.interfaces.push(Some(new_id));
}
let mut moved_worlds = Vec::new();
for (id, mut world) in worlds {
let new_id = match world_map.get(&id).copied() {
Some(id) => {
update_stability(&world.stability, &mut self.worlds[id].stability)?;
id
}
None => {
log::debug!("moving world {}", world.name);
moved_worlds.push(id);
let mut update = |map: &mut IndexMap<WorldKey, WorldItem>| -> Result<_> {
for (mut name, mut item) in mem::take(map) {
remap.update_world_key(&mut name, None)?;
match &mut item {
WorldItem::Function(f) => remap.update_function(self, f, None)?,
WorldItem::Interface { id, .. } => {
*id = remap.map_interface(*id, None)?
}
WorldItem::Type(i) => *i = remap.map_type(*i, None)?,
}
map.insert(name, item);
}
Ok(())
};
update(&mut world.imports)?;
update(&mut world.exports)?;
self.worlds.alloc(world)
}
};
assert_eq!(remap.worlds.len(), id.index());
remap.worlds.push(Some(new_id));
}
for (id, mut pkg) in packages {
let new_id = match package_map.get(&id).copied() {
Some(id) => id,
None => {
for (_, id) in pkg.interfaces.iter_mut() {
*id = remap.map_interface(*id, None)?;
}
for (_, id) in pkg.worlds.iter_mut() {
*id = remap.map_world(*id, None)?;
}
self.packages.alloc(pkg)
}
};
assert_eq!(remap.packages.len(), id.index());
remap.packages.push(new_id);
}
for (name, id) in package_names {
let id = remap.packages[id.index()];
if let Some(prev) = self.package_names.insert(name, id) {
assert_eq!(prev, id);
}
}
// Fixup all "parent" links now.
//
// Note that this is only done for items that are actually moved from
// `resolve` into `self`, which is tracked by the various `moved_*`
// lists built incrementally above. The ids in the `moved_*` lists
// are ids within `resolve`, so they're translated through `remap` to
// ids within `self`.
for id in moved_worlds {
let id = remap.map_world(id, None)?;
if let Some(pkg) = self.worlds[id].package.as_mut() {
*pkg = remap.packages[pkg.index()];
}
}
for id in moved_interfaces {
let id = remap.map_interface(id, None)?;
if let Some(pkg) = self.interfaces[id].package.as_mut() {
*pkg = remap.packages[pkg.index()];
}
}
for id in moved_types {
let id = remap.map_type(id, None)?;
match &mut self.types[id].owner {
TypeOwner::Interface(id) => *id = remap.map_interface(*id, None)?,
TypeOwner::World(id) => *id = remap.map_world(*id, None)?,
TypeOwner::None => {}
}
}
// And finally process items that were present in `resolve` but were
// not present in `self`. This is only done for merged packages as
// documents may be added to `self.documents` but wouldn't otherwise be
// present in the `documents` field of the corresponding package.
for (name, pkg, iface) in interfaces_to_add {
let prev = self.packages[pkg]
.interfaces
.insert(name, remap.map_interface(iface, None)?);
assert!(prev.is_none());
}
for (name, pkg, world) in worlds_to_add {
let prev = self.packages[pkg]
.worlds
.insert(name, remap.map_world(world, None)?);
assert!(prev.is_none());
}
log::trace!("now have {} packages", self.packages.len());
#[cfg(debug_assertions)]
self.assert_valid();
Ok(remap)
}
/// Merges the world `from` into the world `into`.
///
/// This will attempt to merge one world into another, unioning all of its
/// imports and exports together. This is an operation performed by
/// `wit-component`, for example where two different worlds from two
/// different libraries were linked into the same core wasm file and are
/// producing a singular world that will be the final component's
/// interface.
///
/// This operation can fail if the imports/exports overlap.
pub fn merge_worlds(&mut self, from: WorldId, into: WorldId) -> Result<()> {
let mut new_imports = Vec::new();
let mut new_exports = Vec::new();
let from_world = &self.worlds[from];
let into_world = &self.worlds[into];
log::trace!("merging {} into {}", from_world.name, into_world.name);
// First walk over all the imports of `from` world and figure out what
// to do with them.
//
// If the same item exists in `from` and `into` then merge it together
// below with `merge_world_item` which basically asserts they're the
// same. Otherwise queue up a new import since if `from` has more
// imports than `into` then it's fine to add new imports.
for (name, from_import) in from_world.imports.iter() {
let name_str = self.name_world_key(name);
match into_world.imports.get(name) {
Some(into_import) => {
log::trace!("info/from shared import on `{name_str}`");
self.merge_world_item(from_import, into_import)
.with_context(|| format!("failed to merge world import {name_str}"))?;
}
None => {
log::trace!("new import: `{name_str}`");
new_imports.push((name.clone(), from_import.clone()));
}
}
}
// Build a set of interfaces which are required to be imported because
// of `into`'s exports. This set is then used below during
// `ensure_can_add_world_export`.
//
// This is the set of interfaces which exports depend on that are
// themselves not exports.
let mut must_be_imported = HashMap::new();
for (key, export) in into_world.exports.iter() {
for dep in self.world_item_direct_deps(export) {
if into_world.exports.contains_key(&WorldKey::Interface(dep)) {
continue;
}
self.foreach_interface_dep(dep, &mut |id| {
must_be_imported.insert(id, key.clone());
});
}
}
// Next walk over exports of `from` and process these similarly to
// imports.
for (name, from_export) in from_world.exports.iter() {
let name_str = self.name_world_key(name);
match into_world.exports.get(name) {
Some(into_export) => {
log::trace!("info/from shared export on `{name_str}`");
self.merge_world_item(from_export, into_export)
.with_context(|| format!("failed to merge world export {name_str}"))?;
}
None => {
log::trace!("new export `{name_str}`");
// See comments in `ensure_can_add_world_export` for why
// this is slightly different than imports.
self.ensure_can_add_world_export(
into_world,
name,
from_export,
&must_be_imported,
)
.with_context(|| {
format!("failed to add export `{}`", self.name_world_key(name))
})?;
new_exports.push((name.clone(), from_export.clone()));
}
}
}
// For all the new imports and exports they may need to be "cloned" to
// be able to belong to the new world. For example:
//
// * Anonymous interfaces have a `package` field which points to the
// package of the containing world, but `from` and `into` may not be
// in the same package.
//
// * Type imports have an `owner` field that point to `from`, but they
// now need to point to `into` instead.
//
// Cloning is no trivial task, however, so cloning is delegated to a
// submodule to perform a "deep" clone and copy items into new arena
// entries as necessary.
let mut cloner = clone::Cloner::new(self, TypeOwner::World(from), TypeOwner::World(into));
cloner.register_world_type_overlap(from, into);
for (name, item) in new_imports.iter_mut().chain(&mut new_exports) {
cloner.world_item(name, item);
}
// Insert any new imports and new exports found first.
let into_world = &mut self.worlds[into];
for (name, import) in new_imports {
let prev = into_world.imports.insert(name, import);
assert!(prev.is_none());
}
for (name, export) in new_exports {
let prev = into_world.exports.insert(name, export);
assert!(prev.is_none());
}
#[cfg(debug_assertions)]
self.assert_valid();
Ok(())
}
fn merge_world_item(&self, from: &WorldItem, into: &WorldItem) -> Result<()> {
let mut map = MergeMap::new(self, self);
match (from, into) {
(WorldItem::Interface { id: from, .. }, WorldItem::Interface { id: into, .. }) => {
// If these imports are the same that can happen, for
// example, when both worlds to `import foo:bar/baz;`. That
// foreign interface will point to the same interface within
// `Resolve`.
if from == into {
return Ok(());
}
// .. otherwise this MUST be a case of
// `import foo: interface { ... }`. If `from != into` but
// both `from` and `into` have the same name then the
// `WorldKey::Interface` case is ruled out as otherwise
// they'd have different names.
//
// In the case of an anonymous interface all we can do is
// ensure that the interfaces both match, so use `MergeMap`
// for that.
map.build_interface(*from, *into)
.context("failed to merge interfaces")?;
}
// Like `WorldKey::Name` interfaces for functions and types the
// structure is asserted to be the same.
(WorldItem::Function(from), WorldItem::Function(into)) => {
map.build_function(from, into)
.context("failed to merge functions")?;
}
(WorldItem::Type(from), WorldItem::Type(into)) => {
map.build_type_id(*from, *into)
.context("failed to merge types")?;
}
// Kind-level mismatches are caught here.
(WorldItem::Interface { .. }, _)
| (WorldItem::Function { .. }, _)
| (WorldItem::Type { .. }, _) => {
bail!("different kinds of items");
}
}
assert!(map.interfaces_to_add.is_empty());
assert!(map.worlds_to_add.is_empty());
Ok(())
}
/// This method ensures that the world export of `name` and `item` can be
/// added to the world `into` without changing the meaning of `into`.
///
/// All dependencies of world exports must either be:
///
/// * An export themselves
/// * An import with all transitive dependencies of the import also imported
///
/// It's not possible to depend on an import which then also depends on an
/// export at some point, for example. This method ensures that if `name`
/// and `item` are added that this property is upheld.
fn ensure_can_add_world_export(
&self,
into: &World,
name: &WorldKey,
item: &WorldItem,
must_be_imported: &HashMap<InterfaceId, WorldKey>,
) -> Result<()> {
assert!(!into.exports.contains_key(name));
let name = self.name_world_key(name);
// First make sure that all of this item's dependencies are either
// exported or the entire chain of imports rooted at that dependency are
// all imported.
for dep in self.world_item_direct_deps(item) {
if into.exports.contains_key(&WorldKey::Interface(dep)) {
continue;
}
self.ensure_not_exported(into, dep)
.with_context(|| format!("failed validating export of `{name}`"))?;
}
// Second make sure that this item, if it's an interface, will not alter
// the meaning of the preexisting world by ensuring that it's not in the
// set of "must be imported" items.
if let WorldItem::Interface { id, .. } = item {
if let Some(export) = must_be_imported.get(&id) {
let export_name = self.name_world_key(export);
bail!(
"export `{export_name}` depends on `{name}` \
previously as an import which will change meaning \
if `{name}` is added as an export"
);
}
}
Ok(())
}
fn ensure_not_exported(&self, world: &World, id: InterfaceId) -> Result<()> {
let key = WorldKey::Interface(id);
let name = self.name_world_key(&key);
if world.exports.contains_key(&key) {
bail!(
"world exports `{name}` but it's also transitively used by an \
import \
which means that this is not valid"
)
}
for dep in self.interface_direct_deps(id) {
self.ensure_not_exported(world, dep)
.with_context(|| format!("failed validating transitive import dep `{name}`"))?;
}
Ok(())
}
/// Returns an iterator of all the direct interface dependencies of this
/// `item`.
///
/// Note that this doesn't include transitive dependencies, that must be
/// followed manually.
fn world_item_direct_deps(&self, item: &WorldItem) -> impl Iterator<Item = InterfaceId> + '_ {
let mut interface = None;
let mut ty = None;
match item {
WorldItem::Function(_) => {}
WorldItem::Type(id) => ty = Some(*id),
WorldItem::Interface { id, .. } => interface = Some(*id),
}
interface
.into_iter()
.flat_map(move |id| self.interface_direct_deps(id))
.chain(ty.and_then(|t| self.type_interface_dep(t)))
}
/// Invokes `f` with `id` and all transitive interface dependencies of `id`.
///
/// Note that `f` may be called with the same id multiple times.
fn foreach_interface_dep(&self, id: InterfaceId, f: &mut dyn FnMut(InterfaceId)) {
f(id);
for dep in self.interface_direct_deps(id) {
self.foreach_interface_dep(dep, f);
}
}
/// Returns the ID of the specified `interface`.
///
/// Returns `None` for unnamed interfaces.
pub fn id_of(&self, interface: InterfaceId) -> Option<String> {
let interface = &self.interfaces[interface];
Some(self.id_of_name(interface.package.unwrap(), interface.name.as_ref()?))
}
/// Returns the "canonicalized interface name" of `interface`.
///
/// Returns `None` for unnamed interfaces. See `BuildTargets.md` in the
/// upstream component model repository for more information about this.
pub fn canonicalized_id_of(&self, interface: InterfaceId) -> Option<String> {
let interface = &self.interfaces[interface];
Some(self.canonicalized_id_of_name(interface.package.unwrap(), interface.name.as_ref()?))
}
/// Convert a world to an "importized" version where the world is updated
/// in-place to reflect what it would look like to be imported.
///
/// This is a transformation which is used as part of the process of
/// importing a component today. For example when a component depends on
/// another component this is useful for generating WIT which can be use to
/// represent the component being imported. The general idea is that this
/// function will update the `world_id` specified such it imports the
/// functionality that it previously exported. The world will be left with
/// no exports.
///
/// This world is then suitable for merging into other worlds or generating
/// bindings in a context that is importing the original world. This
/// is intended to be used as part of language tooling when depending on
/// other components.
pub fn importize(&mut self, world_id: WorldId, out_world_name: Option<String>) -> Result<()> {
// Rename the world to avoid having it get confused with the original
// name of the world. Add `-importized` to it for now. Precisely how
// this new world is created may want to be updated over time if this
// becomes problematic.
let world = &mut self.worlds[world_id];
let pkg = &mut self.packages[world.package.unwrap()];
pkg.worlds.shift_remove(&world.name);
if let Some(name) = out_world_name {
world.name = name.clone();
pkg.worlds.insert(name, world_id);
} else {
world.name.push_str("-importized");
pkg.worlds.insert(world.name.clone(), world_id);
}
// Trim all non-type definitions from imports. Types can be used by
// exported functions, for example, so they're preserved.
world.imports.retain(|_, item| match item {
WorldItem::Type(_) => true,
_ => false,
});
for (name, export) in mem::take(&mut world.exports) {
match (name.clone(), world.imports.insert(name, export)) {
// no previous item? this insertion was ok
(_, None) => {}
// cannot overwrite an import with an export
(WorldKey::Name(name), Some(_)) => {
bail!("world export `{name}` conflicts with import of same name");
}
// Exports already don't overlap each other and the only imports
// preserved above were types so this shouldn't be reachable.
(WorldKey::Interface(_), _) => unreachable!(),
}
}
// Fill out any missing transitive interface imports by elaborating this
// world which does that for us.
self.elaborate_world(world_id)?;
#[cfg(debug_assertions)]
self.assert_valid();
Ok(())
}
/// Returns the ID of the specified `name` within the `pkg`.
pub fn id_of_name(&self, pkg: PackageId, name: &str) -> String {
let package = &self.packages[pkg];
let mut base = String::new();
base.push_str(&package.name.namespace);
base.push_str(":");
base.push_str(&package.name.name);
base.push_str("/");
base.push_str(name);
if let Some(version) = &package.name.version {
base.push_str(&format!("@{version}"));
}
base
}
/// Returns the "canonicalized interface name" of the specified `name`
/// within the `pkg`.
///
/// See `BuildTargets.md` in the upstream component model repository for
/// more information about this.
pub fn canonicalized_id_of_name(&self, pkg: PackageId, name: &str) -> String {
let package = &self.packages[pkg];
let mut base = String::new();
base.push_str(&package.name.namespace);
base.push_str(":");
base.push_str(&package.name.name);
base.push_str("/");
base.push_str(name);
if let Some(version) = &package.name.version {
base.push_str("@");
let string = PackageName::version_compat_track_string(version);
base.push_str(&string);
}
base
}
/// 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 `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 `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 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`,
/// 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.
///
/// # 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 id = resolve.push_str(
/// "./my-test.wit",
/// r#"
/// package example:wit1;
///
/// world foo {
/// // ...
/// }
/// "#,
/// )?;
/// 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 id = resolve.push_str(
/// "./my-test.wit",
/// r#"
/// package example:wit2;
///
/// world foo { /* ... */ }
///
/// world bar { /* ... */ }
/// "#,
/// )?;
/// 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.
///
/// // 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(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`.
/// resolve.push_str(
/// "./my-test.wit",
/// r#"
/// package example:[email protected];
///
/// world foo { /* ... */ }
/// "#,
/// )?;
/// 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.
/// resolve.push_str(
/// "./my-test.wit",
/// r#"
/// package example:[email protected];
///
/// world foo { /* ... */ }
/// "#,
/// )?;
/// assert!(resolve.select_world(id, Some("example:wit5/foo")).is_err());
/// assert!(resolve.select_world(id, Some("example:wit5/[email protected]")).is_ok());
/// assert!(resolve.select_world(id, Some("example:wit5/[email protected]")).is_ok());
///
/// Ok(())
/// }
/// ```
pub fn select_world(&self, package: PackageId, world: Option<&str>) -> Result<WorldId> {
let world_path = match world {
Some(world) => Some(
parse_use_path(world)
.with_context(|| format!("failed to parse world specifier `{world}`"))?,
),
None => None,
};
let (pkg, world_name) = match world_path {
Some(ParsedUsePath::Name(name)) => (package, name),
Some(ParsedUsePath::Package(pkg, interface)) => {
let pkg = match self.package_names.get(&pkg) {
Some(pkg) => *pkg,
None => {
let mut candidates = self.package_names.iter().filter(|(name, _)| {
pkg.version.is_none()
&& pkg.name == name.name
&& pkg.namespace == name.namespace
&& name.version.is_some()
});
let candidate = candidates.next();
if let Some((c2, _)) = candidates.next() {
let (c1, _) = candidate.unwrap();
bail!(
"package name `{pkg}` is available at both \
versions {} and {} but which is not specified",
c1.version.as_ref().unwrap(),
c2.version.as_ref().unwrap(),
);
}
match candidate {
Some((_, id)) => *id,
None => bail!("unknown package `{pkg}`"),
}
}
};
(pkg, interface.to_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
.get(&world_name)
.copied()
.ok_or_else(|| anyhow!("no world named `{world_name}` in package"))
}
/// Assigns a human readable name to the `WorldKey` specified.
pub fn name_world_key(&self, key: &WorldKey) -> String {
match key {
WorldKey::Name(s) => s.to_string(),
WorldKey::Interface(i) => self.id_of(*i).expect("unexpected anonymous interface"),
}
}
/// Same as [`Resolve::name_world_key`] except that `WorldKey::Interfaces`
/// uses [`Resolve::canonicalized_id_of`].
pub fn name_canonicalized_world_key(&self, key: &WorldKey) -> String {
match key {
WorldKey::Name(s) => s.to_string(),
WorldKey::Interface(i) => self
.canonicalized_id_of(*i)
.expect("unexpected anonymous interface"),
}
}
/// Returns the interface that `id` uses a type from, if it uses a type from
/// a different interface than `id` is defined within.
///
/// If `id` is not a use-of-a-type or it's using a type in the same
/// interface then `None` is returned.
pub fn type_interface_dep(&self, id: TypeId) -> Option<InterfaceId> {
let ty = &self.types[id];
let dep = match ty.kind {
TypeDefKind::Type(Type::Id(id)) => id,
_ => return None,
};
let other = &self.types[dep];
if ty.owner == other.owner {
None
} else {
match other.owner {
TypeOwner::Interface(id) => Some(id),
_ => unreachable!(),
}
}
}
/// Returns an iterator of all interfaces that the interface `id` depends
/// on.
///
/// Interfaces may depend on others for type information to resolve type
/// imports.
///
/// Note that the returned iterator may yield the same interface as a
/// dependency multiple times. Additionally only direct dependencies of `id`
/// are yielded, not transitive dependencies.
pub fn interface_direct_deps(&self, id: InterfaceId) -> impl Iterator<Item = InterfaceId> + '_ {
self.interfaces[id]
.types
.iter()
.filter_map(move |(_name, ty)| self.type_interface_dep(*ty))
}
/// Returns an iterator of all packages that the package `id` depends
/// on.
///
/// Packages may depend on others for type information to resolve type
/// imports or interfaces to resolve worlds.
///
/// Note that the returned iterator may yield the same package as a
/// dependency multiple times. Additionally only direct dependencies of `id`
/// are yielded, not transitive dependencies.
pub fn package_direct_deps(&self, id: PackageId) -> impl Iterator<Item = PackageId> + '_ {
let pkg = &self.packages[id];
pkg.interfaces
.iter()
.flat_map(move |(_name, id)| self.interface_direct_deps(*id))
.chain(pkg.worlds.iter().flat_map(move |(_name, id)| {
let world = &self.worlds[*id];
world
.imports
.iter()
.chain(world.exports.iter())
.filter_map(move |(_name, item)| match item {
WorldItem::Interface { id, .. } => Some(*id),
WorldItem::Function(_) => None,
WorldItem::Type(t) => self.type_interface_dep(*t),
})
}))
.filter_map(move |iface_id| {
let pkg = self.interfaces[iface_id].package?;
if pkg == id {
None
} else {
Some(pkg)
}
})
}
/// Returns a topological ordering of packages contained in this `Resolve`.
///
/// This returns a list of `PackageId` such that when visited in order it's
/// guaranteed that all dependencies will have been defined by prior items
/// in the list.
pub fn topological_packages(&self) -> Vec<PackageId> {
let mut pushed = vec![false; self.packages.len()];
let mut order = Vec::new();
for (id, _) in self.packages.iter() {
self.build_topological_package_ordering(id, &mut pushed, &mut order);
}
order
}
fn build_topological_package_ordering(
&self,
id: PackageId,
pushed: &mut Vec<bool>,
order: &mut Vec<PackageId>,
) {
if pushed[id.index()] {
return;
}
for dep in self.package_direct_deps(id) {
self.build_topological_package_ordering(dep, pushed, order);
}
order.push(id);
pushed[id.index()] = true;
}
#[doc(hidden)]
pub fn assert_valid(&self) {
let mut package_interfaces = Vec::new();
let mut package_worlds = Vec::new();
for (id, pkg) in self.packages.iter() {
let mut interfaces = HashSet::new();
for (name, iface) in pkg.interfaces.iter() {
assert!(interfaces.insert(*iface));
let iface = &self.interfaces[*iface];
assert_eq!(name, iface.name.as_ref().unwrap());
assert_eq!(iface.package.unwrap(), id);
}
package_interfaces.push(pkg.interfaces.values().copied().collect::<HashSet<_>>());
let mut worlds = HashSet::new();
for (name, world) in pkg.worlds.iter() {
assert!(worlds.insert(*world));
let world = &self.worlds[*world];
assert_eq!(*name, world.name);
assert_eq!(world.package.unwrap(), id);
}
package_worlds.push(pkg.worlds.values().copied().collect::<HashSet<_>>());
}
let mut interface_types = Vec::new();
for (id, iface) in self.interfaces.iter() {
assert!(self.packages.get(iface.package.unwrap()).is_some());
if iface.name.is_some() {
assert!(package_interfaces[iface.package.unwrap().index()].contains(&id));
}
for (name, ty) in iface.types.iter() {
let ty = &self.types[*ty];
assert_eq!(ty.name.as_ref(), Some(name));
assert_eq!(ty.owner, TypeOwner::Interface(id));
}
interface_types.push(iface.types.values().copied().collect::<HashSet<_>>());
for (name, f) in iface.functions.iter() {
assert_eq!(*name, f.name);
}
}
let mut world_types = Vec::new();
for (id, world) in self.worlds.iter() {
log::debug!("validating world {}", &world.name);
if let Some(package) = world.package {
assert!(self.packages.get(package).is_some());
assert!(package_worlds[package.index()].contains(&id));
}
assert!(world.includes.is_empty());
let mut types = HashSet::new();
for (name, item) in world.imports.iter().chain(world.exports.iter()) {
log::debug!("validating world item: {}", self.name_world_key(name));
match item {
WorldItem::Interface { id, .. } => {
// anonymous interfaces must belong to the same package
// as the world's package.
if matches!(name, WorldKey::Name(_)) {
assert_eq!(self.interfaces[*id].package, world.package);
}
}
WorldItem::Function(f) => {
assert!(!matches!(name, WorldKey::Interface(_)));
assert_eq!(f.name, name.clone().unwrap_name());
}
WorldItem::Type(ty) => {
assert!(!matches!(name, WorldKey::Interface(_)));
assert!(types.insert(*ty));
let ty = &self.types[*ty];
assert_eq!(ty.name, Some(name.clone().unwrap_name()));
assert_eq!(ty.owner, TypeOwner::World(id));
}
}
}
self.assert_world_elaborated(world);
world_types.push(types);
}
for (ty_id, ty) in self.types.iter() {
match ty.owner {
TypeOwner::Interface(id) => {
assert!(self.interfaces.get(id).is_some());
assert!(interface_types[id.index()].contains(&ty_id));
}
TypeOwner::World(id) => {
assert!(self.worlds.get(id).is_some());
assert!(world_types[id.index()].contains(&ty_id));
}
TypeOwner::None => {}
}
}
self.assert_topologically_sorted();
}
fn assert_topologically_sorted(&self) {
let mut positions = IndexMap::new();
for id in self.topological_packages() {
let pkg = &self.packages[id];
log::debug!("pkg {}", pkg.name);
let prev = positions.insert(Some(id), IndexSet::new());
assert!(prev.is_none());
}
positions.insert(None, IndexSet::new());
for (id, iface) in self.interfaces.iter() {
log::debug!("iface {:?}", iface.name);
let ok = positions.get_mut(&iface.package).unwrap().insert(id);
assert!(ok);
}
for (_, world) in self.worlds.iter() {
log::debug!("world {:?}", world.name);
let my_package = world.package;
let my_package_pos = positions.get_index_of(&my_package).unwrap();
for (_, item) in world.imports.iter().chain(&world.exports) {
let id = match item {
WorldItem::Interface { id, .. } => *id,
_ => continue,
};
let other_package = self.interfaces[id].package;
let other_package_pos = positions.get_index_of(&other_package).unwrap();
assert!(other_package_pos <= my_package_pos);
}
}
for (_id, ty) in self.types.iter() {
log::debug!("type {:?} {:?}", ty.name, ty.owner);
let other_id = match ty.kind {
TypeDefKind::Type(Type::Id(ty)) => ty,
_ => continue,
};
let other = &self.types[other_id];
if ty.kind == other.kind {
continue;
}
let my_interface = match ty.owner {
TypeOwner::Interface(id) => id,
_ => continue,
};
let other_interface = match other.owner {
TypeOwner::Interface(id) => id,
_ => continue,
};
let my_package = self.interfaces[my_interface].package;
let other_package = self.interfaces[other_interface].package;
let my_package_pos = positions.get_index_of(&my_package).unwrap();
let other_package_pos = positions.get_index_of(&other_package).unwrap();
if my_package_pos == other_package_pos {
let interfaces = &positions[&my_package];
let my_interface_pos = interfaces.get_index_of(&my_interface).unwrap();
let other_interface_pos = interfaces.get_index_of(&other_interface).unwrap();
assert!(other_interface_pos <= my_interface_pos);
} else {
assert!(other_package_pos < my_package_pos);
}
}
}
fn assert_world_elaborated(&self, world: &World) {
for (key, item) in world.imports.iter() {
log::debug!(
"asserting elaborated world import {}",
self.name_world_key(key)
);
match item {
WorldItem::Type(t) => self.assert_world_imports_type_deps(world, key, *t),
// All types referred to must be imported.
WorldItem::Function(f) => self.assert_world_function_imports_types(world, key, f),
// All direct dependencies of this interface must be imported.
WorldItem::Interface { id, .. } => {
for dep in self.interface_direct_deps(*id) {
assert!(
world.imports.contains_key(&WorldKey::Interface(dep)),
"world import of {} is missing transitive dep of {}",
self.name_world_key(key),
self.id_of(dep).unwrap(),
);
}
}
}
}
for (key, item) in world.exports.iter() {
log::debug!(
"asserting elaborated world export {}",
self.name_world_key(key)
);
match item {
// Types referred to by this function must be imported.
WorldItem::Function(f) => self.assert_world_function_imports_types(world, key, f),
// Dependencies of exported interfaces must also be exported, or
// if imported then that entire chain of imports must be
// imported and not exported.
WorldItem::Interface { id, .. } => {
for dep in self.interface_direct_deps(*id) {
let dep_key = WorldKey::Interface(dep);
if world.exports.contains_key(&dep_key) {
continue;
}
self.foreach_interface_dep(dep, &mut |dep| {
let dep_key = WorldKey::Interface(dep);
assert!(
world.imports.contains_key(&dep_key),
"world should import {} (required by {})",
self.name_world_key(&dep_key),
self.name_world_key(key),
);
assert!(
!world.exports.contains_key(&dep_key),
"world should not export {} (required by {})",
self.name_world_key(&dep_key),
self.name_world_key(key),
);
});
}
}
// exported types not allowed at this time
WorldItem::Type(_) => unreachable!(),
}
}
}
fn assert_world_imports_type_deps(&self, world: &World, key: &WorldKey, ty: TypeId) {
// If this is a `use` statement then the referred-to interface must be
// imported into this world.
let ty = &self.types[ty];
if let TypeDefKind::Type(Type::Id(other)) = ty.kind {
if let TypeOwner::Interface(id) = self.types[other].owner {
let key = WorldKey::Interface(id);
assert!(world.imports.contains_key(&key));
return;
}
}
// ... otherwise any named type that this type refers to, one level
// deep, must be imported into this world under that name.
let mut visitor = MyVisit(self, Vec::new());
visitor.visit_type_def(self, ty);
for ty in visitor.1 {
let ty = &self.types[ty];
let Some(name) = ty.name.clone() else {
continue;
};
let dep_key = WorldKey::Name(name);
assert!(
world.imports.contains_key(&dep_key),
"world import `{}` should also force an import of `{}`",
self.name_world_key(key),
self.name_world_key(&dep_key),
);
}
struct MyVisit<'a>(&'a Resolve, Vec<TypeId>);
impl TypeIdVisitor for MyVisit<'_> {
fn before_visit_type_id(&mut self, id: TypeId) -> bool {
self.1.push(id);
// recurse into unnamed types to look at all named types
self.0.types[id].name.is_none()
}
}
}
/// This asserts that all types referred to by `func` are imported into
/// `world` under `WorldKey::Name`. Note that this is only applicable to
/// named type
fn assert_world_function_imports_types(&self, world: &World, key: &WorldKey, func: &Function) {
for ty in func
.parameter_and_result_types()
.chain(func.kind.resource().map(Type::Id))
{
let Type::Id(id) = ty else {
continue;
};
self.assert_world_imports_type_deps(world, key, id);
}
}
fn include_stability(&self, stability: &Stability, pkg_id: &PackageId) -> Result<bool> {
Ok(match stability {
Stability::Unknown => true,
// NOTE: deprecations are intentionally omitted -- an existing `@since` takes precedence over `@deprecated`
Stability::Stable { since, .. } => {
let Some(p) = self.packages.get(*pkg_id) else {
// We can't check much without a package (possibly dealing with an item in an `UnresolvedPackage`),
// @since version & deprecations can't be checked because there's no package version to compare to.
//
// Feature requirements on stabilized features are ignored in resolved packages, so we do the same here.
return Ok(true);
};
// Use of feature gating with version specifiers inside a package that is not versioned is not allowed
let package_version = p.name.version.as_ref().with_context(|| format!("package [{}] contains a feature gate with a version specifier, so it must have a version", p.name))?;
// If the version on the feature gate is:
// - released, then we can include it
// - unreleased, then we must check the feature (if present)
ensure!(
since <= package_version,
"feature gate cannot reference unreleased version {since} of package [{}] (current version {package_version})",
p.name
);
true
}
Stability::Unstable { feature, .. } => {
self.features.contains(feature) || self.all_features
}
})
}
/// Performs the "elaboration process" necessary for the `world_id`
/// specified to ensure that all of its transitive imports are listed.
///
/// This function will take the unordered lists of the specified world's
/// imports and exports and "elaborate" them to ensure that they're
/// topographically sorted where all transitively required interfaces by
/// imports, or exports, are listed. This will additionally validate that
/// the exports are all valid and present, specifically with the restriction
/// noted on `elaborate_world_exports`.
///
/// The world is mutated in-place in this `Resolve`.
fn elaborate_world(&mut self, world_id: WorldId) -> Result<()> {
// First process all imports. This is easier than exports since the only
// requirement here is that all interfaces need to be added with a
// topological order between them.
let mut new_imports = IndexMap::new();
let world = &self.worlds[world_id];
for (name, item) in world.imports.iter() {
match item {
// Interfaces get their dependencies added first followed by the
// interface itself.
WorldItem::Interface { id, stability } => {
self.elaborate_world_import(&mut new_imports, name.clone(), *id, &stability);
}
// Functions are added as-is since their dependence on types in
// the world should already be satisfied.
WorldItem::Function(_) => {
let prev = new_imports.insert(name.clone(), item.clone());
assert!(prev.is_none());
}
// Types may depend on an interface, in which case a (possibly)
// recursive addition of that interface happens here. Afterwards
// the type itself can be added safely.
WorldItem::Type(id) => {
if let Some(dep) = self.type_interface_dep(*id) {
self.elaborate_world_import(
&mut new_imports,
WorldKey::Interface(dep),
dep,
&self.types[*id].stability,
);
}
let prev = new_imports.insert(name.clone(), item.clone());
assert!(prev.is_none());
}
}
}
// Exports are trickier than imports, notably to uphold the invariant
// required by `elaborate_world_exports`. To do this the exports are
// partitioned into interfaces/functions. All functions are added to
// the new exports list during this loop but interfaces are all deferred
// to be handled in the `elaborate_world_exports` function.
let mut new_exports = IndexMap::new();
let mut export_interfaces = IndexMap::new();
for (name, item) in world.exports.iter() {
match item {
WorldItem::Interface { id, stability } => {
let prev = export_interfaces.insert(*id, (name.clone(), stability));
assert!(prev.is_none());
}
WorldItem::Function(_) => {
let prev = new_exports.insert(name.clone(), item.clone());
assert!(prev.is_none());
}
WorldItem::Type(_) => unreachable!(),
}
}
self.elaborate_world_exports(&export_interfaces, &mut new_imports, &mut new_exports)?;
// And with all that done the world is updated in-place with
// imports/exports.
log::trace!("imports = {:?}", new_imports);
log::trace!("exports = {:?}", new_exports);
let world = &mut self.worlds[world_id];
world.imports = new_imports;
world.exports = new_exports;
Ok(())
}
fn elaborate_world_import(
&self,
imports: &mut IndexMap<WorldKey, WorldItem>,
key: WorldKey,
id: InterfaceId,
stability: &Stability,
) {
if imports.contains_key(&key) {
return;
}
for dep in self.interface_direct_deps(id) {
self.elaborate_world_import(imports, WorldKey::Interface(dep), dep, stability);
}
let prev = imports.insert(
key,
WorldItem::Interface {
id,
stability: stability.clone(),
},
);
assert!(prev.is_none());
}
/// This function adds all of the interfaces in `export_interfaces` to the
/// list of exports of the `world` specified.
///
/// This method is more involved than adding imports because it is fallible.
/// Chiefly what can happen is that the dependencies of all exports must be
/// satisfied by other exports or imports, but not both. For example given a
/// situation such as:
///
/// ```wit
/// interface a {
/// type t = u32
/// }
/// interface b {
/// use a.{t}
/// }
/// interface c {
/// use a.{t}
/// use b.{t as t2}
/// }
/// ```
///
/// where `c` depends on `b` and `a` where `b` depends on `a`, then the
/// purpose of this method is to reject this world:
///
/// ```wit
/// world foo {
/// export a
/// export c
/// }
/// ```
///
/// The reasoning here is unfortunately subtle and is additionally the
/// subject of WebAssembly/component-model#208. Effectively the `c`
/// interface depends on `b`, but it's not listed explicitly as an import,
/// so it's then implicitly added as an import. This then transitively
/// depends on `a` so it's also added as an import. At this point though `c`
/// also depends on `a`, and it's also exported, so naively it should depend
/// on the export and not implicitly add an import. This means though that
/// `c` has access to two copies of `a`, one imported and one exported. This
/// is not valid, especially in the face of resource types.
///
/// Overall this method is tasked with rejecting the above world by walking
/// over all the exports and adding their dependencies. Each dependency is
/// recorded with whether it's required to be imported, and then if an
/// export is added for something that's required to be an error then the
/// operation fails.
fn elaborate_world_exports(
&self,
export_interfaces: &IndexMap<InterfaceId, (WorldKey, &Stability)>,
imports: &mut IndexMap<WorldKey, WorldItem>,
exports: &mut IndexMap<WorldKey, WorldItem>,
) -> Result<()> {
let mut required_imports = HashSet::new();
for (id, (key, stability)) in export_interfaces.iter() {
let name = self.name_world_key(&key);
let ok = add_world_export(
self,
imports,
exports,
export_interfaces,
&mut required_imports,
*id,
key,
true,
stability,
);
if !ok {
bail!(
// FIXME: this is not a great error message and basically no
// one will know what to do when it gets printed. Improving
// this error message, however, is a chunk of work that may
// not be best spent doing this at this time, so I'm writing
// this comment instead.
//
// More-or-less what should happen here is that a "path"
// from this interface to the conflicting interface should
// be printed. It should be explained why an import is being
// injected, why that's conflicting with an export, and
// ideally with a suggestion of "add this interface to the
// export list to fix this error".
//
// That's a lot of info that's not easy to get at without
// more refactoring, so it's left to a future date in the
// hopes that most folks won't actually run into this for
// the time being.
InvalidTransitiveDependency(name),
);
}
}
return Ok(());
fn add_world_export(
resolve: &Resolve,
imports: &mut IndexMap<WorldKey, WorldItem>,
exports: &mut IndexMap<WorldKey, WorldItem>,
export_interfaces: &IndexMap<InterfaceId, (WorldKey, &Stability)>,
required_imports: &mut HashSet<InterfaceId>,
id: InterfaceId,
key: &WorldKey,
add_export: bool,
stability: &Stability,
) -> bool {
if exports.contains_key(key) {
if add_export {
return true;
} else {
return false;
}
}
// If this is an import and it's already in the `required_imports`
// set then we can skip it as we've already visited this interface.
if !add_export && required_imports.contains(&id) {
return true;
}
let ok = resolve.interface_direct_deps(id).all(|dep| {
let key = WorldKey::Interface(dep);
let add_export = add_export && export_interfaces.contains_key(&dep);
add_world_export(
resolve,
imports,
exports,
export_interfaces,
required_imports,
dep,
&key,
add_export,
stability,
)
});
if !ok {
return false;
}
let item = WorldItem::Interface {
id,
stability: stability.clone(),
};
if add_export {
if required_imports.contains(&id) {
return false;
}
exports.insert(key.clone(), item);
} else {
required_imports.insert(id);
imports.insert(key.clone(), item);
}
true
}
}
/// Remove duplicate imports from a world if they import from the same
/// interface with semver-compatible versions.
///
/// This will merge duplicate interfaces present at multiple versions in
/// both a world by selecting the larger version of the two interfaces. This
/// requires that the interfaces are indeed semver-compatible and it means
/// that some imports might be removed and replaced. Note that this is only
/// done within a single semver track, for example the world imports 0.2.0
/// and 0.2.1 then the result afterwards will be that it imports
/// 0.2.1. If, however, 0.3.0 where imported then the final result would
/// import both 0.2.0 and 0.3.0.
pub fn merge_world_imports_based_on_semver(&mut self, world_id: WorldId) -> Result<()> {
let world = &self.worlds[world_id];
// The first pass here is to build a map of "semver tracks" where they
// key is per-interface and the value is the maximal version found in
// that semver-compatible-track plus the interface which is the maximal
// version.
//
// At the same time a `to_remove` set is maintained to remember what
// interfaces are being removed from `from` and `into`. All of
// `to_remove` are placed with a known other version.
let mut semver_tracks = HashMap::new();
let mut to_remove = HashSet::new();
for (key, _) in world.imports.iter() {
let iface_id = match key {
WorldKey::Interface(id) => *id,
WorldKey::Name(_) => continue,
};
let (track, version) = match self.semver_track(iface_id) {
Some(track) => track,
None => continue,
};
log::debug!(
"{} is on track {}/{}",
self.id_of(iface_id).unwrap(),
track.0,
track.1,
);
match semver_tracks.entry(track.clone()) {
hash_map::Entry::Vacant(e) => {
e.insert((version, iface_id));
}
hash_map::Entry::Occupied(mut e) => match version.cmp(&e.get().0) {
Ordering::Greater => {
to_remove.insert(e.get().1);
e.insert((version, iface_id));
}
Ordering::Equal => {}
Ordering::Less => {
to_remove.insert(iface_id);
}
},
}
}
// Build a map of "this interface is replaced with this interface" using
// the results of the loop above.
let mut replacements = HashMap::new();
for id in to_remove {
let (track, _) = self.semver_track(id).unwrap();
let (_, latest) = semver_tracks[&track];
let prev = replacements.insert(id, latest);
assert!(prev.is_none());
}
// Validate that `merge_world_item` succeeds for merging all removed
// interfaces with their replacement. This is a double-check that the
// semver version is actually correct and all items present in the old
// interface are in the new.
for (to_replace, replace_with) in replacements.iter() {
self.merge_world_item(
&WorldItem::Interface {
id: *to_replace,
stability: Default::default(),
},
&WorldItem::Interface {
id: *replace_with,
stability: Default::default(),
},
)
.with_context(|| {
let old_name = self.id_of(*to_replace).unwrap();
let new_name = self.id_of(*replace_with).unwrap();
format!(
"failed to upgrade `{old_name}` to `{new_name}`, was \
this semver-compatible update not semver compatible?"
)
})?;
}
for (to_replace, replace_with) in replacements.iter() {
log::debug!(
"REPLACE {} => {}",
self.id_of(*to_replace).unwrap(),
self.id_of(*replace_with).unwrap(),
);
}
// Finally perform the actual transformation of the imports/exports.
// Here all imports are removed if they're replaced and otherwise all
// imports have their dependencies updated, possibly transitively, to
// point to the new interfaces in `replacements`.
//
// Afterwards exports are additionally updated, but only their
// dependencies on imports which were remapped. Exports themselves are
// not deduplicated and/or removed.
for (key, item) in mem::take(&mut self.worlds[world_id].imports) {
if let WorldItem::Interface { id, .. } = item {
if replacements.contains_key(&id) {
continue;
}
}
self.update_interface_deps_of_world_item(&item, &replacements);
let prev = self.worlds[world_id].imports.insert(key, item);
assert!(prev.is_none());
}
for (key, item) in mem::take(&mut self.worlds[world_id].exports) {
self.update_interface_deps_of_world_item(&item, &replacements);
let prev = self.worlds[world_id].exports.insert(key, item);
assert!(prev.is_none());
}
// Run through `elaborate_world` to reorder imports as appropriate and
// fill anything back in if it's actually required by exports. For now
// this doesn't tamper with exports at all. Also note that this is
// applied to all worlds in this `Resolve` because interfaces were
// modified directly.
let ids = self.worlds.iter().map(|(id, _)| id).collect::<Vec<_>>();
for world_id in ids {
self.elaborate_world(world_id).with_context(|| {
let name = &self.worlds[world_id].name;
format!(
"failed to elaborate world `{name}` after deduplicating imports \
based on semver"
)
})?;
}
#[cfg(debug_assertions)]
self.assert_valid();
Ok(())
}
fn update_interface_deps_of_world_item(
&mut self,
item: &WorldItem,
replacements: &HashMap<InterfaceId, InterfaceId>,
) {
match *item {
WorldItem::Type(t) => self.update_interface_dep_of_type(t, &replacements),
WorldItem::Interface { id, .. } => {
let types = self.interfaces[id]
.types
.values()
.copied()
.collect::<Vec<_>>();
for ty in types {
self.update_interface_dep_of_type(ty, &replacements);
}
}
WorldItem::Function(_) => {}
}
}
/// Returns the "semver track" of an interface plus the interface's version.
///
/// This function returns `None` if the interface `id` has a package without
/// a version. If the version is present, however, the first element of the
/// tuple returned is a "semver track" for the specific interface. The
/// version listed in `PackageName` will be modified so all
/// semver-compatible versions are listed the same way.
///
/// The second element in the returned tuple is this interface's package's
/// version.
fn semver_track(&self, id: InterfaceId) -> Option<((PackageName, String), &Version)> {
let iface = &self.interfaces[id];
let pkg = &self.packages[iface.package?];
let version = pkg.name.version.as_ref()?;
let mut name = pkg.name.clone();
name.version = Some(PackageName::version_compat_track(version));
Some(((name, iface.name.clone()?), version))
}
/// If `ty` is a definition where it's a `use` from another interface, then
/// change what interface it's using from according to the pairs in the
/// `replacements` map.
fn update_interface_dep_of_type(
&mut self,
ty: TypeId,
replacements: &HashMap<InterfaceId, InterfaceId>,
) {
let to_replace = match self.type_interface_dep(ty) {
Some(id) => id,
None => return,
};
let replace_with = match replacements.get(&to_replace) {
Some(id) => id,
None => return,
};
let dep = match self.types[ty].kind {
TypeDefKind::Type(Type::Id(id)) => id,
_ => return,
};
let name = self.types[dep].name.as_ref().unwrap();
// Note the infallible name indexing happening here. This should be
// previously validated with `merge_world_item` to succeed.
let replacement_id = self.interfaces[*replace_with].types[name];
self.types[ty].kind = TypeDefKind::Type(Type::Id(replacement_id));
}
/// Returns the core wasm module/field names for the specified `import`.
///
/// This function will return the core wasm module/field that can be used to
/// use `import` with the name `mangling` scheme specified as well. This can
/// be useful for bindings generators, for example, and these names are
/// recognized by `wit-component` and `wasm-tools component new`.
pub fn wasm_import_name(&self, mangling: Mangling, import: WasmImport<'_>) -> (String, String) {
match mangling {
Mangling::Standard32 => match import {
WasmImport::Func { interface, func } => {
let module = match interface {
Some(key) => format!("cm32p2|{}", self.name_canonicalized_world_key(key)),
None => format!("cm32p2"),
};
(module, func.name.clone())
}
WasmImport::ResourceIntrinsic {
interface,
resource,
intrinsic,
} => {
let name = self.types[resource].name.as_ref().unwrap();
let (prefix, name) = match intrinsic {
ResourceIntrinsic::ImportedDrop => ("", format!("{name}_drop")),
ResourceIntrinsic::ExportedDrop => ("_ex_", format!("{name}_drop")),
ResourceIntrinsic::ExportedNew => ("_ex_", format!("{name}_new")),
ResourceIntrinsic::ExportedRep => ("_ex_", format!("{name}_rep")),
};
let module = match interface {
Some(key) => {
format!("cm32p2|{prefix}{}", self.name_canonicalized_world_key(key))
}
None => {
assert_eq!(prefix, "");
format!("cm32p2")
}
};
(module, name)
}
},
Mangling::Legacy => match import {
WasmImport::Func { interface, func } => {
let module = match interface {
Some(key) => self.name_world_key(key),
None => format!("$root"),
};
(module, func.name.clone())
}
WasmImport::ResourceIntrinsic {
interface,
resource,
intrinsic,
} => {
let name = self.types[resource].name.as_ref().unwrap();
let (prefix, name) = match intrinsic {
ResourceIntrinsic::ImportedDrop => ("", format!("[resource-drop]{name}")),
ResourceIntrinsic::ExportedDrop => {
("[export]", format!("[resource-drop]{name}"))
}
ResourceIntrinsic::ExportedNew => {
("[export]", format!("[resource-new]{name}"))
}
ResourceIntrinsic::ExportedRep => {
("[export]", format!("[resource-rep]{name}"))
}
};
let module = match interface {
Some(key) => format!("{prefix}{}", self.name_world_key(key)),
None => {
assert_eq!(prefix, "");
format!("$root")
}
};
(module, name)
}
},
}
}
/// Returns the core wasm export name for the specified `import`.
///
/// This is the same as [`Resovle::wasm_import_name`], except for exports.
pub fn wasm_export_name(&self, mangling: Mangling, import: WasmExport<'_>) -> String {
match mangling {
Mangling::Standard32 => match import {
WasmExport::Func {
interface,
func,
post_return,
} => {
let mut name = String::from("cm32p2|");
if let Some(interface) = interface {
let s = self.name_canonicalized_world_key(interface);
name.push_str(&s);
}
name.push_str("|");
name.push_str(&func.name);
if post_return {
name.push_str("_post");
}
name
}
WasmExport::ResourceDtor {
interface,
resource,
} => {
let name = self.types[resource].name.as_ref().unwrap();
let interface = self.name_canonicalized_world_key(interface);
format!("cm32p2|{interface}|{name}_dtor")
}
WasmExport::Memory => "cm32p2_memory".to_string(),
WasmExport::Initialize => "cm32p2_initialize".to_string(),
WasmExport::Realloc => "cm32p2_realloc".to_string(),
},
Mangling::Legacy => match import {
WasmExport::Func {
interface,
func,
post_return,
} => {
let mut name = String::new();
if post_return {
name.push_str("cabi_post_");
}
if let Some(interface) = interface {
let s = self.name_world_key(interface);
name.push_str(&s);
name.push_str("#");
}
name.push_str(&func.name);
name
}
WasmExport::ResourceDtor {
interface,
resource,
} => {
let name = self.types[resource].name.as_ref().unwrap();
let interface = self.name_world_key(interface);
format!("{interface}#[dtor]{name}")
}
WasmExport::Memory => "memory".to_string(),
WasmExport::Initialize => "_initialize".to_string(),
WasmExport::Realloc => "cabi_realloc".to_string(),
},
}
}
}
/// Possible imports that can be passed to [`Resolve::wasm_import_name`].
#[derive(Debug)]
pub enum WasmImport<'a> {
/// A WIT function is being imported. Optionally from an interface.
Func {
/// The name of the interface that the function is being imported from.
///
/// If the function is imported directly from the world then this is
/// `Noen`.
interface: Option<&'a WorldKey>,
/// The function being imported.
func: &'a Function,
},
/// A resource-related intrinsic is being imported.
ResourceIntrinsic {
/// The optional interface to import from, same as `WasmImport::Func`.
interface: Option<&'a WorldKey>,
/// The resource that's being operated on.
resource: TypeId,
/// The intrinsic that's being imported.
intrinsic: ResourceIntrinsic,
},
}
/// Intrinsic definitions to go with [`WasmImport::ResourceIntrinsic`] which
/// also goes with [`Resolve::wasm_import_name`].
#[derive(Debug)]
pub enum ResourceIntrinsic {
ImportedDrop,
ExportedDrop,
ExportedNew,
ExportedRep,
}
/// Different kinds of exports that can be passed to
/// [`Resolve::wasm_export_name`] to export from core wasm modules.
#[derive(Debug)]
pub enum WasmExport<'a> {
/// A WIT function is being exported, optionally from an interface.
Func {
/// An optional interface which owns `func`. Use `None` for top-level
/// world function.
interface: Option<&'a WorldKey>,
/// The function being exported.
func: &'a Function,
/// Whether or not this is a post-return function or not.
post_return: bool,
},
/// A destructor for a resource exported from this module.
ResourceDtor {
/// The interface that owns the resource.
interface: &'a WorldKey,
/// The resource itself that the destructor is for.
resource: TypeId,
},
/// Linear memory, the one that the canonical ABI uses.
Memory,
/// An initialization function (not the core wasm `start`).
Initialize,
/// The general-purpose realloc hook.
Realloc,
}
/// Structure returned by [`Resolve::merge`] which contains mappings from
/// old-ids to new-ids after the merge.
#[derive(Default)]
pub struct Remap {
pub types: Vec<Option<TypeId>>,
pub interfaces: Vec<Option<InterfaceId>>,
pub worlds: Vec<Option<WorldId>>,
pub packages: Vec<PackageId>,
/// A cache of anonymous `own<T>` handles for resource types.
///
/// The appending operation of `Remap` is the one responsible for
/// translating references to `T` where `T` is a resource into `own<T>`
/// instead. This map is used to deduplicate the `own<T>` types generated
/// to generate as few as possible.
///
/// The key of this map is the resource id `T` in the new resolve, and
/// the value is the `own<T>` type pointing to `T`.
own_handles: HashMap<TypeId, TypeId>,
type_has_borrow: Vec<Option<bool>>,
}
fn apply_map<T>(map: &[Option<Id<T>>], id: Id<T>, desc: &str, span: Option<Span>) -> Result<Id<T>> {
match map.get(id.index()) {
Some(Some(id)) => Ok(*id),
Some(None) => {
let msg = format!(
"found a reference to a {desc} which is excluded \
due to its feature not being activated"
);
match span {
Some(span) => Err(Error::new(span, msg).into()),
None => bail!("{msg}"),
}
}
None => panic!("request to remap a {desc} that has not yet been registered"),
}
}
impl Remap {
pub fn map_type(&self, id: TypeId, span: Option<Span>) -> Result<TypeId> {
apply_map(&self.types, id, "type", span)
}
pub fn map_interface(&self, id: InterfaceId, span: Option<Span>) -> Result<InterfaceId> {
apply_map(&self.interfaces, id, "interface", span)
}
pub fn map_world(&self, id: WorldId, span: Option<Span>) -> Result<WorldId> {
apply_map(&self.worlds, id, "world", span)
}
fn append(
&mut self,
resolve: &mut Resolve,
unresolved: UnresolvedPackage,
) -> Result<PackageId> {
self.process_foreign_deps(resolve, &unresolved)?;
let foreign_types = self.types.len();
let foreign_interfaces = self.interfaces.len();
let foreign_worlds = self.worlds.len();
let pkgid = resolve.packages.alloc(Package {
name: unresolved.name.clone(),
docs: unresolved.docs.clone(),
interfaces: Default::default(),
worlds: Default::default(),
});
let prev = resolve.package_names.insert(unresolved.name.clone(), pkgid);
assert!(prev.is_none());
// Copy over all types first, updating any intra-type references. Note
// that types are sorted topologically which means this iteration
// order should be sufficient. Also note though that the interface
// owner of a type isn't updated here due to interfaces not being known
// yet.
assert_eq!(unresolved.types.len(), unresolved.type_spans.len());
for ((id, mut ty), span) in unresolved
.types
.into_iter()
.zip(&unresolved.type_spans)
.skip(foreign_types)
{
if !resolve
.include_stability(&ty.stability, &pkgid)
.with_context(|| {
format!(
"failed to process feature gate for type [{}] in package [{}]",
ty.name.as_ref().map(String::as_str).unwrap_or("<unknown>"),
resolve.packages[pkgid].name,
)
})?
{
self.types.push(None);
continue;
}
self.update_typedef(resolve, &mut ty, Some(*span))?;
let new_id = resolve.types.alloc(ty);
assert_eq!(self.types.len(), id.index());
let new_id = match resolve.types[new_id] {
// If this is an `own<T>` handle then either replace it with a
// preexisting `own<T>` handle which may have been generated in
// `update_ty`. If that doesn't exist though then insert it into
// the `own_handles` cache.
TypeDef {
name: None,
owner: TypeOwner::None,
kind: TypeDefKind::Handle(Handle::Own(id)),
docs: _,
stability: _,
} => *self.own_handles.entry(id).or_insert(new_id),
// Everything not-related to `own<T>` doesn't get its ID
// modified.
_ => new_id,
};
self.types.push(Some(new_id));
}
// Next transfer all interfaces into `Resolve`, updating type ids
// referenced along the way.
assert_eq!(
unresolved.interfaces.len(),
unresolved.interface_spans.len()
);
for ((id, mut iface), span) in unresolved
.interfaces
.into_iter()
.zip(&unresolved.interface_spans)
.skip(foreign_interfaces)
{
if !resolve
.include_stability(&iface.stability, &pkgid)
.with_context(|| {
format!(
"failed to process feature gate for interface [{}] in package [{}]",
iface
.name
.as_ref()
.map(String::as_str)
.unwrap_or("<unknown>"),
resolve.packages[pkgid].name,
)
})?
{
self.interfaces.push(None);
continue;
}
assert!(iface.package.is_none());
iface.package = Some(pkgid);
self.update_interface(resolve, &mut iface, Some(span))?;
let new_id = resolve.interfaces.alloc(iface);
assert_eq!(self.interfaces.len(), id.index());
self.interfaces.push(Some(new_id));
}
// Now that interfaces are identified go back through the types and
// update their interface owners.
for (i, id) in self.types.iter().enumerate().skip(foreign_types) {
let id = match id {
Some(id) => *id,
None => continue,
};
match &mut resolve.types[id].owner {
TypeOwner::Interface(id) => {
let span = unresolved.type_spans[i];
*id = self.map_interface(*id, Some(span))
.with_context(|| {
"this type is not gated by a feature but its interface is gated by a feature"
})?;
}
TypeOwner::World(_) | TypeOwner::None => {}
}
}
// Perform a weighty step of full resolution of worlds. This will fully
// expand imports/exports for a world and create the topological
// ordering necessary for this.
//
// This is done after types/interfaces are fully settled so the
// transitive relation between interfaces, through types, is understood
// here.
assert_eq!(unresolved.worlds.len(), unresolved.world_spans.len());
for ((id, mut world), span) in unresolved
.worlds
.into_iter()
.zip(&unresolved.world_spans)
.skip(foreign_worlds)
{
if !resolve
.include_stability(&world.stability, &pkgid)
.with_context(|| {
format!(
"failed to process feature gate for world [{}] in package [{}]",
world.name, resolve.packages[pkgid].name,
)
})?
{
self.worlds.push(None);
continue;
}
self.update_world(&mut world, resolve, &pkgid, &span)?;
let new_id = resolve.worlds.alloc(world);
assert_eq!(self.worlds.len(), id.index());
self.worlds.push(Some(new_id));
resolve.elaborate_world(new_id).with_context(|| {
Error::new(
span.span,
format!(
"failed to elaborate world imports/exports of `{}`",
resolve.worlds[new_id].name
),
)
})?;
}
// As with interfaces, now update the ids of world-owned types.
for (i, id) in self.types.iter().enumerate().skip(foreign_types) {
let id = match id {
Some(id) => *id,
None => continue,
};
match &mut resolve.types[id].owner {
TypeOwner::World(id) => {
let span = unresolved.type_spans[i];
*id = self.map_world(*id, Some(span))
.with_context(|| {
"this type is not gated by a feature but its interface is gated by a feature"
})?;
}
TypeOwner::Interface(_) | TypeOwner::None => {}
}
}
// Fixup "parent" ids now that everything has been identified
for id in self.interfaces.iter().skip(foreign_interfaces) {
let id = match id {
Some(id) => *id,
None => continue,
};
let iface = &mut resolve.interfaces[id];
iface.package = Some(pkgid);
if let Some(name) = &iface.name {
let prev = resolve.packages[pkgid].interfaces.insert(name.clone(), id);
assert!(prev.is_none());
}
}
for id in self.worlds.iter().skip(foreign_worlds) {
let id = match id {
Some(id) => *id,
None => continue,
};
let world = &mut resolve.worlds[id];
world.package = Some(pkgid);
let prev = resolve.packages[pkgid]
.worlds
.insert(world.name.clone(), id);
assert!(prev.is_none());
}
Ok(pkgid)
}
fn process_foreign_deps(
&mut self,
resolve: &mut Resolve,
unresolved: &UnresolvedPackage,
) -> Result<()> {
// Invert the `foreign_deps` map to be keyed by world id to get
// used in the loops below.
let mut world_to_package = HashMap::new();
let mut interface_to_package = HashMap::new();
for (i, (pkg_name, worlds_or_ifaces)) in unresolved.foreign_deps.iter().enumerate() {
for (name, item) in worlds_or_ifaces {
match item {
AstItem::Interface(unresolved_interface_id) => {
let prev = interface_to_package.insert(
*unresolved_interface_id,
(pkg_name, name, unresolved.foreign_dep_spans[i]),
);
assert!(prev.is_none());
}
AstItem::World(unresolved_world_id) => {
let prev = world_to_package.insert(
*unresolved_world_id,
(pkg_name, name, unresolved.foreign_dep_spans[i]),
);
assert!(prev.is_none());
}
}
}
}
// Connect all interfaces referred to in `interface_to_package`, which
// are at the front of `unresolved.interfaces`, to interfaces already
// contained within `resolve`.
self.process_foreign_interfaces(unresolved, &interface_to_package, resolve)?;
// Connect all worlds referred to in `world_to_package`, which
// are at the front of `unresolved.worlds`, to worlds already
// contained within `resolve`.
self.process_foreign_worlds(unresolved, &world_to_package, resolve)?;
// Finally, iterate over all foreign-defined types and determine
// what they map to.
self.process_foreign_types(unresolved, resolve)?;
for (id, span) in unresolved.required_resource_types.iter() {
let mut id = self.map_type(*id, Some(*span))?;
loop {
match resolve.types[id].kind {
TypeDefKind::Type(Type::Id(i)) => id = i,
TypeDefKind::Resource => break,
_ => bail!(Error::new(
*span,
format!("type used in a handle must be a resource"),
)),
}
}
}
#[cfg(debug_assertions)]
resolve.assert_valid();
Ok(())
}
fn process_foreign_interfaces(
&mut self,
unresolved: &UnresolvedPackage,
interface_to_package: &HashMap<InterfaceId, (&PackageName, &String, Span)>,
resolve: &mut Resolve,
) -> Result<(), anyhow::Error> {
for (unresolved_iface_id, unresolved_iface) in unresolved.interfaces.iter() {
let (pkg_name, interface, span) = match interface_to_package.get(&unresolved_iface_id) {
Some(items) => *items,
// All foreign interfaces are defined first, so the first one
// which is defined in a non-foreign document means that all
// further interfaces will be non-foreign as well.
None => break,
};
let pkgid = resolve
.package_names
.get(pkg_name)
.copied()
.ok_or_else(|| Error::new(span, "package not found"))?;
// Functions can't be imported so this should be empty.
assert!(unresolved_iface.functions.is_empty());
let pkg = &resolve.packages[pkgid];
let span = &unresolved.interface_spans[unresolved_iface_id.index()];
let iface_id = pkg
.interfaces
.get(interface)
.copied()
.ok_or_else(|| Error::new(span.span, "interface not found in package"))?;
assert_eq!(self.interfaces.len(), unresolved_iface_id.index());
self.interfaces.push(Some(iface_id));
}
for (id, _) in unresolved.interfaces.iter().skip(self.interfaces.len()) {
assert!(
interface_to_package.get(&id).is_none(),
"found foreign interface after local interface"
);
}
Ok(())
}
fn process_foreign_worlds(
&mut self,
unresolved: &UnresolvedPackage,
world_to_package: &HashMap<WorldId, (&PackageName, &String, Span)>,
resolve: &mut Resolve,
) -> Result<(), anyhow::Error> {
for (unresolved_world_id, _) in unresolved.worlds.iter() {
let (pkg_name, world, span) = match world_to_package.get(&unresolved_world_id) {
Some(items) => *items,
// Same as above, all worlds are foreign until we find a
// non-foreign one.
None => break,
};
let pkgid = resolve
.package_names
.get(pkg_name)
.copied()
.ok_or_else(|| Error::new(span, "package not found"))?;
let pkg = &resolve.packages[pkgid];
let span = &unresolved.world_spans[unresolved_world_id.index()];
let world_id = pkg
.worlds
.get(world)
.copied()
.ok_or_else(|| Error::new(span.span, "world not found in package"))?;
assert_eq!(self.worlds.len(), unresolved_world_id.index());
self.worlds.push(Some(world_id));
}
for (id, _) in unresolved.worlds.iter().skip(self.worlds.len()) {
assert!(
world_to_package.get(&id).is_none(),
"found foreign world after local world"
);
}
Ok(())
}
fn process_foreign_types(
&mut self,
unresolved: &UnresolvedPackage,
resolve: &mut Resolve,
) -> Result<(), anyhow::Error> {
for (unresolved_type_id, unresolved_ty) in unresolved.types.iter() {
// All "Unknown" types should appear first so once we're no longer
// in unknown territory it's package-defined types so break out of
// this loop.
match unresolved_ty.kind {
TypeDefKind::Unknown => {}
_ => break,
}
let unresolved_iface_id = match unresolved_ty.owner {
TypeOwner::Interface(id) => id,
_ => unreachable!(),
};
let iface_id = self.map_interface(unresolved_iface_id, None)?;
let name = unresolved_ty.name.as_ref().unwrap();
let span = unresolved.unknown_type_spans[unresolved_type_id.index()];
let type_id = *resolve.interfaces[iface_id]
.types
.get(name)
.ok_or_else(|| {
Error::new(span, format!("type `{name}` not defined in interface"))
})?;
assert_eq!(self.types.len(), unresolved_type_id.index());
self.types.push(Some(type_id));
}
for (_, ty) in unresolved.types.iter().skip(self.types.len()) {
if let TypeDefKind::Unknown = ty.kind {
panic!("unknown type after defined type");
}
}
Ok(())
}
fn update_typedef(
&mut self,
resolve: &mut Resolve,
ty: &mut TypeDef,
span: Option<Span>,
) -> Result<()> {
// NB: note that `ty.owner` is not updated here since interfaces
// haven't been mapped yet and that's done in a separate step.
use crate::TypeDefKind::*;
match &mut ty.kind {
Handle(handle) => match handle {
crate::Handle::Own(ty) | crate::Handle::Borrow(ty) => {
self.update_type_id(ty, span)?
}
},
Resource => {}
Record(r) => {
for field in r.fields.iter_mut() {
self.update_ty(resolve, &mut field.ty, span)
.with_context(|| format!("failed to update field `{}`", field.name))?;
}
}
Tuple(t) => {
for ty in t.types.iter_mut() {
self.update_ty(resolve, ty, span)?;
}
}
Variant(v) => {
for case in v.cases.iter_mut() {
if let Some(t) = &mut case.ty {
self.update_ty(resolve, t, span)?;
}
}
}
Option(t) => self.update_ty(resolve, t, span)?,
Result(r) => {
if let Some(ty) = &mut r.ok {
self.update_ty(resolve, ty, span)?;
}
if let Some(ty) = &mut r.err {
self.update_ty(resolve, ty, span)?;
}
}
List(t) => self.update_ty(resolve, t, span)?,
Future(Some(t)) => self.update_ty(resolve, t, span)?,
Stream(t) => {
if let Some(ty) = &mut t.element {
self.update_ty(resolve, ty, span)?;
}
if let Some(ty) = &mut t.end {
self.update_ty(resolve, ty, span)?;
}
}
// Note that `update_ty` is specifically not used here as typedefs
// because for the `type a = b` form that doesn't force `a` to be a
// handle type if `b` is a resource type, instead `a` is
// simultaneously usable as a resource and a handle type
Type(crate::Type::Id(id)) => self.update_type_id(id, span)?,
Type(_) => {}
// nothing to do for these as they're just names or empty
Flags(_) | Enum(_) | Future(None) => {}
Unknown => unreachable!(),
}
Ok(())
}
fn update_ty(
&mut self,
resolve: &mut Resolve,
ty: &mut Type,
span: Option<Span>,
) -> Result<()> {
let id = match ty {
Type::Id(id) => id,
_ => return Ok(()),
};
self.update_type_id(id, span)?;
// If `id` points to a `Resource` type then this means that what was
// just discovered was a reference to what will implicitly become an
// `own<T>` handle. This `own` handle is implicitly allocated here
// and handled during the merging process.
let mut cur = *id;
let points_to_resource = loop {
match resolve.types[cur].kind {
TypeDefKind::Type(Type::Id(id)) => cur = id,
TypeDefKind::Resource => break true,
_ => break false,
}
};
if points_to_resource {
*id = *self.own_handles.entry(*id).or_insert_with(|| {
resolve.types.alloc(TypeDef {
name: None,
owner: TypeOwner::None,
kind: TypeDefKind::Handle(Handle::Own(*id)),
docs: Default::default(),
stability: Default::default(),
})
});
}
Ok(())
}
fn update_type_id(&self, id: &mut TypeId, span: Option<Span>) -> Result<()> {
*id = self.map_type(*id, span)?;
Ok(())
}
fn update_interface(
&mut self,
resolve: &mut Resolve,
iface: &mut Interface,
spans: Option<&InterfaceSpan>,
) -> Result<()> {
iface.types.retain(|_, ty| self.types[ty.index()].is_some());
let iface_pkg_id = iface.package.as_ref().unwrap_or_else(|| {
panic!(
"unexpectedly missing package on interface [{}]",
iface
.name
.as_ref()
.map(String::as_str)
.unwrap_or("<unknown>"),
)
});
// NB: note that `iface.doc` is not updated here since interfaces
// haven't been mapped yet and that's done in a separate step.
for (_name, ty) in iface.types.iter_mut() {
self.update_type_id(ty, spans.map(|s| s.span))?;
}
if let Some(spans) = spans {
assert_eq!(iface.functions.len(), spans.funcs.len());
}
for (i, (func_name, func)) in iface.functions.iter_mut().enumerate() {
if !resolve
.include_stability(&func.stability, iface_pkg_id)
.with_context(|| {
format!(
"failed to process feature gate for function [{func_name}] in package [{}]",
resolve.packages[*iface_pkg_id].name,
)
})?
{
continue;
}
let span = spans.map(|s| s.funcs[i]);
self.update_function(resolve, func, span)
.with_context(|| format!("failed to update function `{}`", func.name))?;
}
// Filter out all of the existing functions in interface which fail the
// `include_stability()` check, as they shouldn't be available.
for (name, func) in mem::take(&mut iface.functions) {
if resolve.include_stability(&func.stability, iface_pkg_id)? {
iface.functions.insert(name, func);
}
}
Ok(())
}
fn update_function(
&mut self,
resolve: &mut Resolve,
func: &mut Function,
span: Option<Span>,
) -> Result<()> {
match &mut func.kind {
FunctionKind::Freestanding => {}
FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => {
self.update_type_id(id, span)?;
}
}
for (_, ty) in func.params.iter_mut() {
self.update_ty(resolve, ty, span)?;
}
match &mut func.results {
Results::Named(named) => {
for (_, ty) in named.iter_mut() {
self.update_ty(resolve, ty, span)?;
}
}
Results::Anon(ty) => self.update_ty(resolve, ty, span)?,
}
for ty in func.results.iter_types() {
if !self.type_has_borrow(resolve, ty) {
continue;
}
match span {
Some(span) => {
bail!(Error::new(
span,
format!(
"function returns a type which contains \
a `borrow<T>` which is not supported"
)
))
}
None => unreachable!(),
}
}
Ok(())
}
fn update_world(
&mut self,
world: &mut World,
resolve: &mut Resolve,
pkg_id: &PackageId,
spans: &WorldSpan,
) -> Result<()> {
assert_eq!(world.imports.len(), spans.imports.len());
assert_eq!(world.exports.len(), spans.exports.len());
// Rewrite imports/exports with their updated versions. Note that this
// may involve updating the key of the imports/exports maps so this
// starts by emptying them out and then everything is re-inserted.
let imports = mem::take(&mut world.imports).into_iter();
let imports = imports.zip(&spans.imports).map(|p| (p, true));
let exports = mem::take(&mut world.exports).into_iter();
let exports = exports.zip(&spans.exports).map(|p| (p, false));
for (((mut name, mut item), span), import) in imports.chain(exports) {
// Update the `id` eagerly here so `item.stability(..)` below
// works.
if let WorldItem::Type(id) = &mut item {
*id = self.map_type(*id, Some(*span))?;
}
let stability = item.stability(resolve);
if !resolve
.include_stability(stability, pkg_id)
.with_context(|| {
format!(
"failed to process imported world item type [{}] in package [{}]",
resolve.name_world_key(&name),
resolve.packages[*pkg_id].name,
)
})?
{
continue;
}
self.update_world_key(&mut name, Some(*span))?;
match &mut item {
WorldItem::Interface { id, .. } => {
*id = self.map_interface(*id, Some(*span))?;
}
WorldItem::Function(f) => {
self.update_function(resolve, f, Some(*span))?;
}
WorldItem::Type(_) => {
// already mapped above
}
}
let dst = if import {
&mut world.imports
} else {
&mut world.exports
};
let prev = dst.insert(name, item);
assert!(prev.is_none());
}
// Resolve all `include` statements of the world which will add more
// entries to the imports/exports list for this world.
assert_eq!(world.includes.len(), spans.includes.len());
let includes = mem::take(&mut world.includes);
let include_names = mem::take(&mut world.include_names);
for (((stability, include_world), span), names) in includes
.into_iter()
.zip(&spans.includes)
.zip(&include_names)
{
if !resolve
.include_stability(&stability, pkg_id)
.with_context(|| {
format!(
"failed to process feature gate for included world [{}] in package [{}]",
resolve.worlds[include_world].name.as_str(),
resolve.packages[*pkg_id].name
)
})?
{
continue;
}
self.resolve_include(world, include_world, names, *span, resolve)?;
}
Ok(())
}
fn update_world_key(&self, key: &mut WorldKey, span: Option<Span>) -> Result<()> {
match key {
WorldKey::Name(_) => {}
WorldKey::Interface(id) => {
*id = self.map_interface(*id, span)?;
}
}
Ok(())
}
fn resolve_include(
&self,
world: &mut World,
include_world: WorldId,
names: &[IncludeName],
span: Span,
resolve: &Resolve,
) -> Result<()> {
let include_world_id = self.map_world(include_world, Some(span))?;
let include_world = &resolve.worlds[include_world_id];
let mut names_ = names.to_owned();
// remove all imports and exports that match the names we're including
for import in include_world.imports.iter() {
self.remove_matching_name(import, &mut names_);
}
for export in include_world.exports.iter() {
self.remove_matching_name(export, &mut names_);
}
if !names_.is_empty() {
bail!(Error::new(
span,
format!("no import or export kebab-name `{}`. Note that an ID does not support renaming", names_[0].name),
));
}
// copy the imports and exports from the included world into the current world
for import in include_world.imports.iter() {
self.resolve_include_item(names, &mut world.imports, import, span, "import")?;
}
for export in include_world.exports.iter() {
self.resolve_include_item(names, &mut world.exports, export, span, "export")?;
}
Ok(())
}
fn resolve_include_item(
&self,
names: &[IncludeName],
items: &mut IndexMap<WorldKey, WorldItem>,
item: (&WorldKey, &WorldItem),
span: Span,
item_type: &str,
) -> Result<()> {
match item.0 {
WorldKey::Name(n) => {
let n = if let Some(found) = names
.into_iter()
.find(|include_name| include_name.name == n.clone())
{
found.as_.clone()
} else {
n.clone()
};
let prev = items.insert(WorldKey::Name(n.clone()), item.1.clone());
if prev.is_some() {
bail!(Error::new(
span,
format!("{item_type} of `{n}` shadows previously {item_type}ed items"),
))
}
}
key @ WorldKey::Interface(_) => {
let prev = items.entry(key.clone()).or_insert(item.1.clone());
match (&item.1, prev) {
(
WorldItem::Interface {
id: aid,
stability: astability,
},
WorldItem::Interface {
id: bid,
stability: bstability,
},
) => {
assert_eq!(*aid, *bid);
update_stability(astability, bstability)?;
}
(WorldItem::Interface { .. }, _) => unreachable!(),
(WorldItem::Function(_), _) => unreachable!(),
(WorldItem::Type(_), _) => unreachable!(),
}
}
};
Ok(())
}
fn remove_matching_name(&self, item: (&WorldKey, &WorldItem), names: &mut Vec<IncludeName>) {
match item.0 {
WorldKey::Name(n) => {
names.retain(|name| name.name != n.clone());
}
_ => {}
}
}
fn type_has_borrow(&mut self, resolve: &Resolve, ty: &Type) -> bool {
let id = match ty {
Type::Id(id) => *id,
_ => return false,
};
if let Some(Some(has_borrow)) = self.type_has_borrow.get(id.index()) {
return *has_borrow;
}
let result = self.typedef_has_borrow(resolve, &resolve.types[id]);
if self.type_has_borrow.len() <= id.index() {
self.type_has_borrow.resize(id.index() + 1, None);
}
self.type_has_borrow[id.index()] = Some(result);
result
}
fn typedef_has_borrow(&mut self, resolve: &Resolve, ty: &TypeDef) -> bool {
match &ty.kind {
TypeDefKind::Type(t) => self.type_has_borrow(resolve, t),
TypeDefKind::Variant(v) => v
.cases
.iter()
.filter_map(|case| case.ty.as_ref())
.any(|ty| self.type_has_borrow(resolve, ty)),
TypeDefKind::Handle(Handle::Borrow(_)) => true,
TypeDefKind::Handle(Handle::Own(_)) => false,
TypeDefKind::Resource => false,
TypeDefKind::Record(r) => r
.fields
.iter()
.any(|case| self.type_has_borrow(resolve, &case.ty)),
TypeDefKind::Flags(_) => false,
TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.type_has_borrow(resolve, t)),
TypeDefKind::Enum(_) => false,
TypeDefKind::List(ty) | TypeDefKind::Future(Some(ty)) | TypeDefKind::Option(ty) => {
self.type_has_borrow(resolve, ty)
}
TypeDefKind::Result(r) => [&r.ok, &r.err]
.iter()
.filter_map(|t| t.as_ref())
.any(|t| self.type_has_borrow(resolve, t)),
TypeDefKind::Stream(r) => [&r.element, &r.end]
.iter()
.filter_map(|t| t.as_ref())
.any(|t| self.type_has_borrow(resolve, t)),
TypeDefKind::Future(None) => false,
TypeDefKind::Unknown => unreachable!(),
}
}
}
struct MergeMap<'a> {
/// A map of package ids in `from` to those in `into` for those that are
/// found to be equivalent.
package_map: HashMap<PackageId, PackageId>,
/// A map of interface ids in `from` to those in `into` for those that are
/// found to be equivalent.
interface_map: HashMap<InterfaceId, InterfaceId>,
/// A map of type ids in `from` to those in `into` for those that are
/// found to be equivalent.
type_map: HashMap<TypeId, TypeId>,
/// A map of world ids in `from` to those in `into` for those that are
/// found to be equivalent.
world_map: HashMap<WorldId, WorldId>,
/// A list of documents that need to be added to packages in `into`.
///
/// The elements here are:
///
/// * The name of the interface/world
/// * The ID within `into` of the package being added to
/// * The ID within `from` of the item being added.
interfaces_to_add: Vec<(String, PackageId, InterfaceId)>,
worlds_to_add: Vec<(String, PackageId, WorldId)>,
/// Which `Resolve` is being merged from.
from: &'a Resolve,
/// Which `Resolve` is being merged into.
into: &'a Resolve,
}
impl<'a> MergeMap<'a> {
fn new(from: &'a Resolve, into: &'a Resolve) -> MergeMap<'a> {
MergeMap {
package_map: Default::default(),
interface_map: Default::default(),
type_map: Default::default(),
world_map: Default::default(),
interfaces_to_add: Default::default(),
worlds_to_add: Default::default(),
from,
into,
}
}
fn build(&mut self) -> Result<()> {
for from_id in self.from.topological_packages() {
let from = &self.from.packages[from_id];
let into_id = match self.into.package_names.get(&from.name) {
Some(id) => *id,
// This package, according to its name and url, is not present
// in `self` so it needs to get added below.
None => {
log::trace!("adding unique package {}", from.name);
continue;
}
};
log::trace!("merging duplicate package {}", from.name);
self.build_package(from_id, into_id).with_context(|| {
format!("failed to merge package `{}` into existing copy", from.name)
})?;
}
Ok(())
}
fn build_package(&mut self, from_id: PackageId, into_id: PackageId) -> Result<()> {
let prev = self.package_map.insert(from_id, into_id);
assert!(prev.is_none());
let from = &self.from.packages[from_id];
let into = &self.into.packages[into_id];
// If an interface is present in `from_id` but not present in `into_id`
// then it can be copied over wholesale. That copy is scheduled to
// happen within the `self.interfaces_to_add` list.
for (name, from_interface_id) in from.interfaces.iter() {
let into_interface_id = match into.interfaces.get(name) {
Some(id) => *id,
None => {
log::trace!("adding unique interface {}", name);
self.interfaces_to_add
.push((name.clone(), into_id, *from_interface_id));
continue;
}
};
log::trace!("merging duplicate interfaces {}", name);
self.build_interface(*from_interface_id, into_interface_id)
.with_context(|| format!("failed to merge interface `{name}`"))?;
}
for (name, from_world_id) in from.worlds.iter() {
let into_world_id = match into.worlds.get(name) {
Some(id) => *id,
None => {
log::trace!("adding unique world {}", name);
self.worlds_to_add
.push((name.clone(), into_id, *from_world_id));
continue;
}
};
log::trace!("merging duplicate worlds {}", name);
self.build_world(*from_world_id, into_world_id)
.with_context(|| format!("failed to merge world `{name}`"))?;
}
Ok(())
}
fn build_interface(&mut self, from_id: InterfaceId, into_id: InterfaceId) -> Result<()> {
let prev = self.interface_map.insert(from_id, into_id);
assert!(prev.is_none());
let from_interface = &self.from.interfaces[from_id];
let into_interface = &self.into.interfaces[into_id];
// Unlike documents/interfaces above if an interface in `from`
// differs from the interface in `into` then that's considered an
// error. Changing interfaces can reflect changes in imports/exports
// which may not be expected so it's currently required that all
// interfaces, when merged, exactly match.
//
// One case to consider here, for example, is that if a world in
// `into` exports the interface `into_id` then if `from_id` were to
// add more items into `into` then it would unexpectedly require more
// items to be exported which may not work. In an import context this
// might work since it's "just more items available for import", but
// for now a conservative route of "interfaces must match" is taken.
for (name, from_type_id) in from_interface.types.iter() {
let into_type_id = *into_interface
.types
.get(name)
.ok_or_else(|| anyhow!("expected type `{name}` to be present"))?;
let prev = self.type_map.insert(*from_type_id, into_type_id);
assert!(prev.is_none());
self.build_type_id(*from_type_id, into_type_id)
.with_context(|| format!("mismatch in type `{name}`"))?;
}
for (name, from_func) in from_interface.functions.iter() {
let into_func = match into_interface.functions.get(name) {
Some(func) => func,
None => bail!("expected function `{name}` to be present"),
};
self.build_function(from_func, into_func)
.with_context(|| format!("mismatch in function `{name}`"))?;
}
Ok(())
}
fn build_type_id(&mut self, from_id: TypeId, into_id: TypeId) -> Result<()> {
// FIXME: ideally the types should be "structurally
// equal" but that's not trivial to do in the face of
// resources.
let _ = from_id;
let _ = into_id;
Ok(())
}
fn build_type(&mut self, from_ty: &Type, into_ty: &Type) -> Result<()> {
match (from_ty, into_ty) {
(Type::Id(from), Type::Id(into)) => {
self.build_type_id(*from, *into)?;
}
(from, into) if from != into => bail!("different kinds of types"),
_ => {}
}
Ok(())
}
fn build_function(&mut self, from_func: &Function, into_func: &Function) -> Result<()> {
if from_func.name != into_func.name {
bail!(
"different function names `{}` and `{}`",
from_func.name,
into_func.name
);
}
match (&from_func.kind, &into_func.kind) {
(FunctionKind::Freestanding, FunctionKind::Freestanding) => {}
(FunctionKind::Method(from), FunctionKind::Method(into))
| (FunctionKind::Constructor(from), FunctionKind::Constructor(into))
| (FunctionKind::Static(from), FunctionKind::Static(into)) => self
.build_type_id(*from, *into)
.context("different function kind types")?,
(FunctionKind::Method(_), _)
| (FunctionKind::Constructor(_), _)
| (FunctionKind::Static(_), _)
| (FunctionKind::Freestanding, _) => {
bail!("different function kind types")
}
}
if from_func.params.len() != into_func.params.len() {
bail!("different number of function parameters");
}
for ((from_name, from_ty), (into_name, into_ty)) in
from_func.params.iter().zip(&into_func.params)
{
if from_name != into_name {
bail!("different function parameter names: {from_name} != {into_name}");
}
self.build_type(from_ty, into_ty)
.with_context(|| format!("different function parameter types for `{from_name}`"))?;
}
if from_func.results.len() != into_func.results.len() {
bail!("different number of function results");
}
for (from_ty, into_ty) in from_func
.results
.iter_types()
.zip(into_func.results.iter_types())
{
self.build_type(from_ty, into_ty)
.context("different function result types")?;
}
Ok(())
}
fn build_world(&mut self, from_id: WorldId, into_id: WorldId) -> Result<()> {
let prev = self.world_map.insert(from_id, into_id);
assert!(prev.is_none());
let from_world = &self.from.worlds[from_id];
let into_world = &self.into.worlds[into_id];
// Same as interfaces worlds are expected to exactly match to avoid
// unexpectedly changing a particular component's view of imports and
// exports.
//
// FIXME: this should probably share functionality with
// `Resolve::merge_worlds` to support adding imports but not changing
// exports.
if from_world.imports.len() != into_world.imports.len() {
bail!("world contains different number of imports than expected");
}
if from_world.exports.len() != into_world.exports.len() {
bail!("world contains different number of exports than expected");
}
for (from_name, from) in from_world.imports.iter() {
let into_name = self.map_name(from_name);
let name_str = self.from.name_world_key(from_name);
let into = into_world
.imports
.get(&into_name)
.ok_or_else(|| anyhow!("import `{name_str}` not found in target world"))?;
self.match_world_item(from, into)
.with_context(|| format!("import `{name_str}` didn't match target world"))?;
}
for (from_name, from) in from_world.exports.iter() {
let into_name = self.map_name(from_name);
let name_str = self.from.name_world_key(from_name);
let into = into_world
.exports
.get(&into_name)
.ok_or_else(|| anyhow!("export `{name_str}` not found in target world"))?;
self.match_world_item(from, into)
.with_context(|| format!("export `{name_str}` didn't match target world"))?;
}
Ok(())
}
fn map_name(&self, from_name: &WorldKey) -> WorldKey {
match from_name {
WorldKey::Name(s) => WorldKey::Name(s.clone()),
WorldKey::Interface(id) => {
WorldKey::Interface(self.interface_map.get(id).copied().unwrap_or(*id))
}
}
}
fn match_world_item(&mut self, from: &WorldItem, into: &WorldItem) -> Result<()> {
match (from, into) {
(WorldItem::Interface { id: from, .. }, WorldItem::Interface { id: into, .. }) => {
match (
&self.from.interfaces[*from].name,
&self.into.interfaces[*into].name,
) {
// If one interface is unnamed then they must both be
// unnamed and they must both have the same structure for
// now.
(None, None) => self.build_interface(*from, *into)?,
// Otherwise both interfaces must be named and they must
// have been previously found to be equivalent. Note that
// if either is unnamed it won't be present in
// `interface_map` so this'll return an error.
_ => {
if self.interface_map.get(&from) != Some(&into) {
bail!("interfaces are not the same");
}
}
}
}
(WorldItem::Function(from), WorldItem::Function(into)) => {
let _ = (from, into);
// FIXME: should assert an check that `from` structurally
// matches `into`
}
(WorldItem::Type(from), WorldItem::Type(into)) => {
// FIXME: should assert an check that `from` structurally
// matches `into`
let prev = self.type_map.insert(*from, *into);
assert!(prev.is_none());
}
(WorldItem::Interface { .. }, _)
| (WorldItem::Function(_), _)
| (WorldItem::Type(_), _) => {
bail!("world items do not have the same type")
}
}
Ok(())
}
}
/// Updates stability annotations when merging `from` into `into`.
///
/// This is done to keep up-to-date stability information if possible.
/// Components for example don't carry stability information but WIT does so
/// this tries to move from "unknown" to stable/unstable if possible.
fn update_stability(from: &Stability, into: &mut Stability) -> Result<()> {
// If `from` is unknown or the two stability annotations are equal then
// there's nothing to do here.
if from == into || from.is_unknown() {
return Ok(());
}
// Otherwise if `into` is unknown then inherit the stability listed in
// `from`.
if into.is_unknown() {
*into = from.clone();
return Ok(());
}
// Failing all that this means that the two attributes are different so
// generate an error.
bail!("mismatch in stability attributes")
}
/// An error that can be returned during "world elaboration" during various
/// [`Resolve`] operations.
///
/// Methods on [`Resolve`] which mutate its internals, such as
/// [`Resolve::push_dir`] or [`Resolve::importize`] can fail if `world` imports
/// in WIT packages are invalid. This error indicates one of these situations
/// where an invalid dependency graph between imports and exports are detected.
///
/// Note that at this time this error is subtle and not easy to understand, and
/// work needs to be done to explain this better and additionally provide a
/// better error message. For now though this type enables callers to test for
/// the exact kind of error emitted.
#[derive(Debug, Clone)]
pub struct InvalidTransitiveDependency(String);
impl fmt::Display for InvalidTransitiveDependency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"interface `{}` transitively depends on an interface in \
incompatible ways",
self.0
)
}
}
impl std::error::Error for InvalidTransitiveDependency {}
#[cfg(test)]
mod tests {
use crate::Resolve;
use anyhow::Result;
#[test]
fn select_world() -> Result<()> {
let mut resolve = Resolve::default();
resolve.push_str(
"test.wit",
r#"
package foo:bar@0.1.0;
world foo {}
"#,
)?;
resolve.push_str(
"test.wit",
r#"
package foo:baz@0.1.0;
world foo {}
"#,
)?;
resolve.push_str(
"test.wit",
r#"
package foo:baz@0.2.0;
world foo {}
"#,
)?;
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, Some("foo:bar/[email protected]"))
.is_ok());
assert!(resolve.select_world(dummy, Some("foo:baz/foo")).is_err());
assert!(resolve
.select_world(dummy, Some("foo:baz/[email protected]"))
.is_ok());
assert!(resolve
.select_world(dummy, Some("foo:baz/[email protected]"))
.is_ok());
Ok(())
}
}