diff --git a/Cargo.lock b/Cargo.lock index e232e3a..0e807ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3858,9 +3858,10 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "wac-graph", "wac-parser", "wac-resolver", - "wasmparser 0.202.0", + "wac-types", "wasmprinter 0.202.0", "wat", ] @@ -3881,6 +3882,7 @@ dependencies = [ "thiserror", "wac-types", "wasm-encoder 0.202.0", + "wasm-metadata", "wasmparser 0.202.0", "wasmprinter 0.202.0", "wat", @@ -3931,6 +3933,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", + "wac-graph", "wac-parser", "wac-types", "warg-client", diff --git a/Cargo.toml b/Cargo.toml index 0e3a076..86d89e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ keywords = ["webassembly", "wasm", "components", "component-model"] repository = "https://github.com/bytecodealliance/wac" [dependencies] +wac-types = { workspace = true } +wac-graph = { workspace = true } wac-resolver = { workspace = true, default-features = false } wac-parser = { workspace = true, default-features = false } anyhow = { workspace = true } @@ -30,7 +32,6 @@ owo-colors = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } wat = { workspace = true } -wasmparser = { workspace = true } wasmprinter = { workspace = true } thiserror = { workspace = true } indexmap = { workspace = true } diff --git a/crates/wac-graph/Cargo.toml b/crates/wac-graph/Cargo.toml index 84bc52b..d882c1b 100644 --- a/crates/wac-graph/Cargo.toml +++ b/crates/wac-graph/Cargo.toml @@ -21,6 +21,7 @@ semver = { workspace = true } serde = { workspace = true, optional = true } wasm-encoder = { workspace = true } log = { workspace = true } +wasm-metadata = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/crates/wac-graph/src/encoding.rs b/crates/wac-graph/src/encoding.rs index 3efd2a2..8ba11cc 100644 --- a/crates/wac-graph/src/encoding.rs +++ b/crates/wac-graph/src/encoding.rs @@ -1,5 +1,6 @@ -use crate::{NodeId, PackageId}; +use crate::PackageId; use indexmap::IndexMap; +use petgraph::graph::NodeIndex; use std::collections::HashMap; use wac_types::{ CoreExtern, DefinedType, DefinedTypeId, Enum, Flags, FuncResult, FuncTypeId, InterfaceId, @@ -116,11 +117,11 @@ pub struct State { /// The current encoding scope. pub current: Scope, /// A map of nodes in the graph to their encoded indexes. - pub node_indexes: HashMap, + pub node_indexes: HashMap, /// The map of package identifiers to encoded components (either imported or defined). pub packages: HashMap, /// A map of instantiation nodes to a list of their encoded implicitly imported arguments. - pub implicit_args: HashMap>, + pub implicit_args: HashMap>, } impl State { diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 949ef5e..f7d0b60 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -1,17 +1,23 @@ use crate::encoding::{State, TypeEncoder}; -use indexmap::{IndexMap, IndexSet}; -use petgraph::{algo::toposort, graphmap::DiGraphMap, Direction}; +use indexmap::IndexMap; +use petgraph::{ + dot::{Config, Dot}, + graph::NodeIndex, + stable_graph::StableDiGraph, + visit::{Dfs, EdgeRef, IntoNodeIdentifiers, Reversed, VisitMap, Visitable}, + Direction, +}; use semver::Version; use std::{ borrow::Cow, collections::{HashMap, HashSet}, - fmt::Write, - str::FromStr, + fmt::{self, Write}, + ops::Index, }; use thiserror::Error; use wac_types::{ - BorrowedKey, BorrowedPackageKey, ExternKind, ItemKind, Package, PackageKey, SubtypeCheck, - SubtypeChecker, Type, TypeAggregator, Types, + BorrowedKey, BorrowedPackageKey, DefinedType, ItemKind, Package, PackageKey, SubtypeChecker, + Type, TypeAggregator, Types, ValueType, }; use wasm_encoder::{ Alias, ComponentBuilder, ComponentExportKind, ComponentNameSection, ComponentTypeRef, @@ -22,110 +28,72 @@ use wasmparser::{ BinaryReaderError, Validator, WasmFeatures, }; -struct PackagePath<'a> { - package: &'a str, - version: Option, - segments: &'a str, -} - -impl<'a> PackagePath<'a> { - fn new(path: &'a str) -> GraphResult { - let (package, segments) = - path.split_once('/') - .ok_or_else(|| Error::UnqualifiedPackagePath { - path: path.to_string(), - })?; - - let (package, version) = package - .split_once('@') - .map(|(n, v)| (n, Version::from_str(v).map(Some).map_err(|e| (v, e)))) - .unwrap_or((package, Ok(None))); - - let version = version.map_err(|(version, error)| Error::InvalidPackageVersion { - version: version.to_string(), - error, - })?; - - Ok(Self { - package, - version, - segments, - }) - } -} - -/// Represents an error that can occur when working with a composition graph. +/// Represents an error that can occur when defining a type in +/// a composition graph. #[derive(Debug, Error)] -pub enum Error { - /// The specified type was not defined in the graph. - #[error("the specified type was not defined in the graph.")] - TypeNotDefined { - /// The type that was not defined in the graph. - ty: Type, - }, +pub enum DefineTypeError { + /// The provided type has already been defined in the graph. + #[error("the provided type has already been defined in the graph")] + TypeAlreadyDefined, /// A resource type cannot be defined in the graph. #[error("a resource type cannot be defined in the graph")] CannotDefineResource, - /// The specified package path is not fully-qualified. - #[error("package path `{path}` is not a fully-qualified package path")] - UnqualifiedPackagePath { - /// The path that was invalid. - path: String, + /// The specified type name conflicts with an existing export. + #[error("type name `{name}` conflicts with an existing export of the same name")] + ExportConflict { + /// The name of the existing export. + name: String, }, - /// The specified package version is invalid. - #[error("package version `{version}` is not a valid semantic version")] - InvalidPackageVersion { - /// The version that was invalid. - version: String, - /// The error that occurred while parsing the package version. + /// The specified type name is not a valid extern name. + #[error("type name `{name}` is not valid")] + InvalidExternName { + /// The name of the type. + name: String, + /// The underlying validation error. #[source] - error: semver::Error, - }, - /// The specified package has not been registered. - #[error("package `{package}` has not been registered with the graph (use the `CompositionGraph::register_package` method)")] - PackageNotRegistered { - /// The unknown package. - package: PackageKey, - }, - /// The package does not export an item for the specified path. - #[error("package `{package}` does not export an item for path `{path}`")] - PackageMissingExport { - /// The package with the missing export. - package: String, - /// The path that was missing. - path: String, - }, - /// The specified package identifier is invalid. - #[error("the specified package identifier is invalid")] - InvalidPackageId, - /// The specified node identifier is invalid. - #[error("the specified node identifier is invalid")] - InvalidNodeId, - /// The package is already registered in the graph. - #[error("package `{key}` has already been registered in the graph")] - PackageAlreadyRegistered { - /// The key representing the already registered package - key: PackageKey, + source: anyhow::Error, }, - /// An extern name already exists in the graph. - #[error("{kind} name `{name}` already exists in the graph")] - ExternAlreadyExists { - /// The kind of extern. - kind: ExternKind, +} + +/// Represents an error that can occur when importing an item in +/// a composition graph. +#[derive(Debug, Error)] +pub enum ImportError { + /// An import name already exists in the graph. + #[error("import name `{name}` already exists in the graph")] + ImportAlreadyExists { /// The name of the existing extern. name: String, + /// The node that is already imported by that name. + node: NodeId, }, - /// An invalid extern name was given. - #[error("{kind} name `{name}` is not a valid extern name")] - InvalidExternName { - /// The name of the export. + /// An invalid import name was given. + #[error("import name `{name}` is not valid")] + InvalidImportName { + /// The invalid name. name: String, - /// The kind of extern. - kind: ExternKind, /// The underlying validation error. #[source] source: anyhow::Error, }, +} + +/// Represents an error that can occur when registering a +/// package with a composition graph. +#[derive(Debug, Error)] +pub enum RegisterPackageError { + /// The package is already registered in the graph. + #[error("package `{key}` has already been registered in the graph")] + PackageAlreadyRegistered { + /// The key representing the already registered package + key: PackageKey, + }, +} + +/// Represents an error that can occur when aliasing an instance +/// export in a composition graph. +#[derive(Debug, Error)] +pub enum AliasError { /// The provided source node is not an instance. #[error("expected source node to be an instance, but the node is a {kind}")] NodeIsNotAnInstance { @@ -134,12 +102,6 @@ pub enum Error { /// The kind of the node. kind: String, }, - /// The node is not an instantiation. - #[error("the specified node is not an instantiation")] - NodeIsNotAnInstantiation { - /// The node that is not an instantiation. - node: NodeId, - }, /// The instance does not export an item with the given name. #[error("instance does not have an export named `{export}`")] InstanceMissingExport { @@ -148,6 +110,50 @@ pub enum Error { /// The export that was missing. export: String, }, +} + +/// Represents an error that can occur when exporting a node from +/// a composition graph. +#[derive(Debug, Error)] +pub enum ExportError { + /// An export name already exists in the graph. + #[error("an export with the name `{name}` already exists")] + ExportAlreadyExists { + /// The name of the existing export. + name: String, + /// The node that is already exported with that name. + node: NodeId, + }, + /// An invalid export name was given. + #[error("export name `{name}` is not valid")] + InvalidExportName { + /// The invalid name. + name: String, + /// The underlying validation error. + #[source] + source: anyhow::Error, + }, +} + +/// Represents an error that can occur when unexporting a node in +/// a composition graph. +#[derive(Debug, Error)] +pub enum UnexportError { + /// The node cannot be unexported as it is a type definition. + #[error("cannot unexport a type definition")] + MustExportDefinition, +} + +/// Represents an error that can occur when setting an instantiation +/// argument in a composition graph. +#[derive(Debug, Error)] +pub enum InstantiationArgumentError { + /// The node is not an instantiation. + #[error("the specified node is not an instantiation")] + NodeIsNotAnInstantiation { + /// The node that is not an instantiation. + node: NodeId, + }, /// The provided argument name is invalid. #[error("argument name `{name}` is not an import of package `{package}`")] InvalidArgumentName { @@ -175,24 +181,26 @@ pub enum Error { /// The name of the argument. name: String, }, +} + +/// Represents an error that can occur when encoding a composition graph. +#[derive(Debug, Error)] +pub enum EncodeError { + /// The encoding of the graph failed validation. + #[error("encoding produced a component that failed validation")] + ValidationFailure { + /// The source of the validation error. + #[source] + source: BinaryReaderError, + }, /// The graph contains a cycle. #[error("the graph contains a cycle and cannot be encoded")] GraphContainsCycle { /// The node where the cycle was detected. node: NodeId, }, - /// The encoding of the graph failed validation. - #[error("the encoding of the graph failed validation")] - EncodingValidationFailure { - /// The source of the validation error. - #[source] - source: BinaryReaderError, - }, - /// The node cannot be unmarked from export as it is a type definition. - #[error("the node cannot be unmarked from export as it is a type definition")] - MustExportDefinition, /// An implicit import on an instantiation conflicts with an explicit import node. - #[error("an instantiation of package `{package}` implicitly imports an item named `{name}`, but it conflicts with an explicit import node of the same name")] + #[error("an instantiation of package `{package}` implicitly imports an item named `{name}`, but it conflicts with an explicit import of the same name")] ImplicitImportConflict { /// The existing import node. import: NodeId, @@ -203,19 +211,24 @@ pub enum Error { /// The name of the conflicting import. name: String, }, - /// An error occurred while merging an import type. - #[error("failed to merge the type definition for import `{import}` due to conflicting types")] + /// An error occurred while merging an implicit import type. + #[error("failed to merge the type definition for implicit import `{import}` due to conflicting types")] ImportTypeMergeConflict { /// The name of the import. import: String, + /// The first conflicting instantiation node that introduced the implicit import. + first: NodeId, + /// The second conflicting instantiation node. + second: NodeId, /// The type merge error. #[source] source: anyhow::Error, }, } -/// An alias for the result type used by the composition graph. -pub type GraphResult = std::result::Result; +/// Represents an error that can occur when decoding a composition graph. +#[derive(Debug, Error)] +pub enum DecodeError {} /// An identifier for a package in a composition graph. #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -230,17 +243,17 @@ pub struct PackageId { /// An identifier for a node in a composition graph. #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub struct NodeId { - /// The index into the graph's node list. - index: usize, - /// The generation of the node. - /// - /// This is used to invalidate identifiers on node removal. - generation: usize, +pub struct NodeId(NodeIndex); + +impl fmt::Display for NodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.index().fmt(f) + } } +/// Represents the kind of a node in a composition graph. #[derive(Debug, Clone)] -enum NodeKind { +pub enum NodeKind { /// The node is a type definition. Definition, /// The node is an import of an item. @@ -260,8 +273,6 @@ enum NodeKind { struct RegisteredPackage { /// The underlying package. package: Option, - /// The nodes associated with the package. - nodes: HashSet, /// The generation of the package. /// /// The generation is incremented each time a package is removed from the graph. @@ -274,14 +285,14 @@ impl RegisteredPackage { fn new(generation: usize) -> Self { Self { package: None, - nodes: Default::default(), generation, } } } +/// Represents a node in a composition graph. #[derive(Debug, Clone)] -struct NodeData { +pub struct Node { /// The node kind. kind: NodeKind, /// The package associated with the node, if any. @@ -294,11 +305,9 @@ struct NodeData { name: Option, /// The name to use for exporting the node. export: Option, - /// The aliased nodes from this node. - aliases: HashMap, } -impl NodeData { +impl Node { fn new(kind: NodeKind, item_kind: ItemKind, package: Option) -> Self { Self { kind, @@ -306,17 +315,52 @@ impl NodeData { package, name: None, export: None, - aliases: Default::default(), } } - fn import_name(&self) -> Option<&str> { + /// Gets the kind of the node. + pub fn kind(&self) -> &NodeKind { + &self.kind + } + + /// Gets the package id associated with the node. + /// + /// Returns `None` if the node is not directly associated with a package. + pub fn package(&self) -> Option { + self.package + } + + /// Gets the item kind of the node. + pub fn item_kind(&self) -> ItemKind { + self.item_kind + } + + /// Gets the name of the node. + /// + /// Node names are encoded in a `names` custom section. + /// + /// Returns `None` if the node is unnamed. + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Gets the import name of the node. + /// + /// Returns `Some` if the node is an import or `None` if the node is not an import. + pub fn import_name(&self) -> Option<&str> { match &self.kind { NodeKind::Import(name) => Some(name), _ => None, } } + /// Gets the export name of the node. + /// + /// Returns `None` if the node is not exported. + pub fn export_name(&self) -> Option<&str> { + self.export.as_deref() + } + fn add_satisfied_arg(&mut self, index: usize) { match &mut self.kind { NodeKind::Instantiation(satisfied) => { @@ -345,30 +389,8 @@ impl NodeData { } } -/// Represents a node in a composition graph. -#[derive(Debug, Clone)] -struct Node { - /// The data associated with the node. - data: Option, - /// The generation of the node. - /// - /// The generation is incremented each time the node is removed from the graph. - /// - /// This ensures referring node identifiers are invalidated when a node is removed. - generation: usize, -} - -impl Node { - fn new(generation: usize) -> Self { - Self { - data: None, - generation, - } - } -} - /// Represents an edge in a composition graph. -#[derive(Debug, Clone)] +#[derive(Clone)] enum Edge { /// The edge is from an instance to an aliased exported item. /// @@ -376,25 +398,37 @@ enum Edge { Alias(usize), /// The edge is from any node to an instantiation node. /// - /// The set contains import indexes on the target node that are - /// satisfied by the source node. - Argument(IndexSet), + /// The data is the import index on the instantiation node being + /// satisfied by the argument. + Argument(usize), + /// A dependency from one type to another. + Dependency, +} + +impl fmt::Debug for Edge { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Edge::Alias(_) => write!(f, "aliased export"), + Edge::Argument(_) => write!(f, "argument to"), + Edge::Dependency => write!(f, "dependency of"), + } + } } /// Represents information about a node in a composition graph. pub struct NodeInfo<'a> { - /// The types collection for the node's item kind. - pub types: &'a Types, /// The item kind of the node. pub kind: ItemKind, /// The optional name of the node. pub name: Option<&'a str>, - /// The aliases from this node. - pub aliases: &'a HashMap, + /// The package associated with the node. + pub package: Option, /// The export name of the node. /// /// If the node is not exported, this field will be `None`. pub export: Option<&'a str>, + /// Whether or not the node is from a type definition. + pub definition: bool, } /// Represents a composition graph. @@ -411,8 +445,10 @@ pub struct NodeInfo<'a> { /// * an *instantiation node* representing an instantiation of a package. /// * an *alias node* representing an alias of an exported item from an instance. /// -/// There are two supported edge types: +/// There are three supported edge types: /// +/// * a *type dependency* edge from a type definition node to any dependent defined types; +/// type dependency edges are implicitly added to the graph when a type is defined. /// * an *alias edge* from an any node that is an instance to an alias node; alias edges are /// implicitly added to the graph when an alias node is added. /// * an *instantiation argument edge* from any node to an instantiation node; instantiation @@ -424,21 +460,19 @@ pub struct NodeInfo<'a> { #[derive(Default, Clone)] pub struct CompositionGraph { /// The underlying graph data structure. - graph: DiGraphMap, - /// The import nodes of the graph. - imports: HashMap, - /// The set of used export names. - exports: IndexMap, + graph: StableDiGraph, + /// The map of import names to node indices. + imports: HashMap, + /// The map of export names to node ids. + exports: IndexMap, + /// The map of defined types to node ids. + defined: HashMap, /// The map of package keys to package ids. package_map: HashMap, /// The registered packages. packages: Vec, /// The list of free entries in the packages list. free_packages: Vec, - /// The nodes in the graph. - nodes: Vec, - /// The list of free entries in the nodes list. - free_nodes: Vec, /// The types that are directly defined by the graph. types: Types, /// The cache used for subtype checks. @@ -463,27 +497,45 @@ impl CompositionGraph { &mut self.types } + /// Gets the nodes in the graph. + pub fn nodes(&self) -> impl Iterator + '_ { + self.graph.node_weights() + } + /// Gets the identifiers for every node in the graph. - pub fn nodes(&self) -> impl Iterator + '_ { - self.nodes.iter().enumerate().filter_map(|(i, n)| { - n.data.as_ref()?; - Some(NodeId { - index: i, - generation: n.generation, - }) - }) + pub fn node_ids(&self) -> impl Iterator + '_ { + self.graph.node_indices().map(NodeId) + } + + /// Gets the packages currently registered with the graph. + pub fn packages(&self) -> impl Iterator + '_ { + self.packages.iter().filter_map(|p| p.package.as_ref()) } /// Registers a package with the graph. /// /// Packages are used to create instantiations, but are not directly /// a part of the graph. - pub fn register_package(&mut self, package: wac_types::Package) -> GraphResult { + /// + /// # Panics + /// + /// Panics if the given package's type is not contained within the + /// graph's types collection. + pub fn register_package( + &mut self, + package: wac_types::Package, + ) -> Result { let key = PackageKey::new(&package); if self.package_map.contains_key(&key) { - return Err(Error::PackageAlreadyRegistered { key }); + return Err(RegisterPackageError::PackageAlreadyRegistered { key }); } + assert!( + self.types.contains(Type::World(package.ty())), + "the package type is not present in the types collection" + ); + + log::debug!("registering package `{key}` with the graph"); let id = self.alloc_package(package); let prev = self.package_map.insert(key, id); assert!(prev.is_none()); @@ -493,27 +545,43 @@ impl CompositionGraph { /// Unregisters a package from the graph. /// /// Any edges and nodes associated with the package are also removed. - pub fn unregister_package(&mut self, package: PackageId) -> GraphResult<()> { - if self.get_package(package).is_none() { - return Err(Error::InvalidPackageId); - } + /// + /// # Panics + /// + /// Panics if the given package identifier is invalid. + pub fn unregister_package(&mut self, package: PackageId) { + assert_eq!( + self.packages + .get(package.index) + .expect("invalid package id") + .generation, + package.generation, + "invalid package id" + ); - self.free_package(package); - Ok(()) - } + // Remove all nodes associated with the package + self.graph + .retain_nodes(|g, i| g[i].package != Some(package)); - /// Gets a package that was registered with the graph. - pub fn get_package(&self, package: PackageId) -> Option<&Package> { - let PackageId { index, generation } = package; - let entry = &self.packages[index]; - if entry.generation != generation { - return None; - } + // Remove the package from the package map + let entry = &mut self.packages[package.index]; + let key = entry.package.as_ref().unwrap().key(); + log::debug!("unregistering package `{key}` with the graph"); + let prev = self.package_map.remove(&key as &dyn BorrowedKey); + assert!( + prev.is_some(), + "package should exist in the package map (this is a bug)" + ); - entry.package.as_ref() + // Finally free the package + *entry = RegisteredPackage::new(entry.generation.wrapping_add(1)); + self.free_packages.push(package.index); } /// Gets the registered package of the given package name and version. + /// + /// Returns `None` if a package with the specified name and version has + /// not been registered with the graph. pub fn get_package_by_name( &self, name: &str, @@ -528,30 +596,45 @@ impl CompositionGraph { /// /// The graph must not already have a node exported with the same name. /// - /// The specified type must have been added to the graph's type collection. - pub fn define_type(&mut self, name: impl Into, ty: Type) -> GraphResult { - if !self.types.contains(ty) { - return Err(Error::TypeNotDefined { ty }); + /// This method will implicitly add dependency edges to other defined + /// types. + /// + /// If the provided type has already been defined, the previous node + /// will be returned and an additional export name will be associated + /// with the node. + /// + /// # Panics + /// + /// This method panics if the provided type is not contained within the + /// graph's types collection. + pub fn define_type( + &mut self, + name: impl Into, + ty: Type, + ) -> Result { + assert!( + self.types.contains(ty), + "type not contained in types collection" + ); + + if self.defined.contains_key(&ty) { + return Err(DefineTypeError::TypeAlreadyDefined); } if let Type::Resource(_) = ty { - return Err(Error::CannotDefineResource); + return Err(DefineTypeError::CannotDefineResource); } let name = name.into(); if self.exports.contains_key(&name) { - return Err(Error::ExternAlreadyExists { - kind: ExternKind::Export, - name, - }); + return Err(DefineTypeError::ExportConflict { name }); } // Ensure that the given name is a valid extern name ComponentName::new(&name, 0).map_err(|e| { let msg = e.to_string(); - Error::InvalidExternName { + DefineTypeError::InvalidExternName { name: name.to_string(), - kind: ExternKind::Export, source: anyhow::anyhow!( "{msg}", msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) @@ -559,68 +642,126 @@ impl CompositionGraph { } })?; - let mut data = NodeData::new(NodeKind::Definition, ItemKind::Type(ty), None); - data.export = Some(name.clone()); + let mut node = Node::new(NodeKind::Definition, ItemKind::Type(ty), None); + node.export = Some(name.clone()); + let index = self.graph.add_node(node); + log::debug!( + "adding type definition `{name}` to the graph as node index {index}", + index = index.index() + ); + + // Add dependency edges between the given type and any referenced defined types + ty.visit_defined_types(&self.types, &mut |_, id| { + let dep_ty = Type::Value(ValueType::Defined(id)); + if dep_ty != ty { + if let Some(dep) = self.defined.get(&dep_ty) { + if !self + .graph + .edges_connecting(*dep, index) + .any(|e| matches!(e.weight(), Edge::Dependency)) + { + log::debug!( + "adding dependency edge from type `{from}` (dependency) to type `{name}` (dependent)", + from = self.graph[*dep].export.as_ref().unwrap() + ); + self.graph.add_edge(*dep, index, Edge::Dependency); + } + } + } + + Ok(()) + })?; + + // Add dependency edges to any existing defined types that reference this one + for (other_ty, other) in &self.defined { + other_ty.visit_defined_types(&self.types, &mut |_, id| { + let dep_ty = Type::Value(ValueType::Defined(id)); + if dep_ty == ty + && !self + .graph + .edges_connecting(index, *other) + .any(|e| matches!(e.weight(), Edge::Dependency)) + { + log::debug!( + "adding dependency edge from type `{name}` (dependency) to type `{to}` (dependent)", + to = self.graph[index].export.as_ref().unwrap(), + ); + self.graph.add_edge(index, *other, Edge::Dependency); + } - let id = self.alloc_node(data); - self.graph.add_node(id); + Ok(()) + })?; + } - let prev = self.exports.insert(name, id); + self.defined.insert(ty, index); + let prev = self.exports.insert(name, index); assert!(prev.is_none()); - Ok(id) + Ok(NodeId(index)) } /// Adds an *import node* to the graph. /// - /// If an import with the same name already exists, an error is returned. + /// If the provided import name is invalid or if an import already exists + /// with the same name, an error is returned. /// - /// The specified item kind must already have been defined in the graph. - pub fn import(&mut self, name: impl Into, kind: ItemKind) -> GraphResult { - let ty = kind.ty(); - if !self.types.contains(ty) { - return Err(Error::TypeNotDefined { ty }); - } - - self.add_import(name, None, kind) - } - - /// Adds an *import node* to the graph with the item kind specified by package path. - /// - /// An error is returned if an import with the same name already exists or if the - /// specified package path is invalid. - pub fn import_by_path(&mut self, name: impl Into, path: &str) -> GraphResult { - let path = PackagePath::new(path)?; - let (package_id, package) = self - .get_package_by_name(path.package, path.version.as_ref()) - .ok_or_else(|| { - let package = - BorrowedPackageKey::from_name_and_version(path.package, path.version.as_ref()); - Error::PackageNotRegistered { - package: package.into_owned(), - } - })?; + /// # Panics + /// + /// This method panics if the provided kind is not contained within the + /// graph's types collection. + pub fn import( + &mut self, + name: impl Into, + kind: ItemKind, + ) -> Result { + assert!( + self.types.contains(kind.ty()), + "provided type is not in the types collection" + ); - let mut item_kind = None; - for segment in path.segments.split('/') { - item_kind = match item_kind { - Some(ItemKind::Instance(id)) => package.types()[id].exports.get(segment).copied(), - None => package.export(segment), - _ => None, - }; + let name = name.into(); + if let Some(existing) = self.imports.get(&name) { + return Err(ImportError::ImportAlreadyExists { + name, + node: NodeId(*existing), + }); + } - if item_kind.is_none() { - break; + // Ensure that the given import name is a valid extern name + ComponentName::new(&name, 0).map_err(|e| { + let msg = e.to_string(); + ImportError::InvalidImportName { + name: name.to_string(), + source: anyhow::anyhow!( + "{msg}", + msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) + ), } - } + })?; - let item_kind = item_kind - .ok_or_else(|| Error::PackageMissingExport { - package: path.package.to_string(), - path: path.segments.to_string(), - })? - .promote(); + let node = Node::new(NodeKind::Import(name.clone()), kind, None); + let index = self.graph.add_node(node); + log::debug!( + "adding import `{name}` to the graph as node index {index}", + index = index.index() + ); + let prev = self.imports.insert(name, index); + assert!(prev.is_none()); + Ok(NodeId(index)) + } - self.add_import(name, Some(package_id), item_kind) + /// Gets the name used by an import node. + /// + /// Returns `None` if the specified node is not an import node. + /// + /// # Panics + /// + /// Panics if the specified node id is invalid. + pub fn get_import_name(&self, node: NodeId) -> Option<&str> { + let node = self.graph.node_weight(node.0).expect("invalid node id"); + match &node.kind { + NodeKind::Import(name) => Some(name), + _ => None, + } } /// Adds an *instantiation node* to the graph. @@ -628,14 +769,24 @@ impl CompositionGraph { /// Initially the instantiation will have no satisfied arguments. /// /// Use `set_instantiation_argument` to set an argument on an instantiation node. - pub fn instantiate(&mut self, package: PackageId) -> GraphResult { - let pkg = self.get_package(package).ok_or(Error::InvalidPackageId)?; - let node = self.alloc_node(NodeData::new( + /// + /// # Panics + /// + /// This method panics if the provided package id is invalid. + pub fn instantiate(&mut self, package: PackageId) -> NodeId { + let pkg = &self[package]; + let node = Node::new( NodeKind::Instantiation(Default::default()), ItemKind::Instance(pkg.instance_type()), Some(package), - )); - Ok(self.graph.add_node(node)) + ); + let index = self.graph.add_node(node); + log::debug!( + "adding instantiation of package `{key}` to the graph as node index {index}", + key = self[package].key(), + index = index.index() + ); + NodeId(index) } /// Adds an *alias node* to the graph. @@ -646,26 +797,24 @@ impl CompositionGraph { /// If an alias already exists for the export, the existing alias node will be returned. /// /// An implicit *alias edge* will be added from the instance to the alias node. - pub fn alias_instance_export(&mut self, instance: NodeId, export: &str) -> GraphResult { - let instance_data = self - .get_node(instance) - .ok_or(Error::InvalidNodeId)? - .data - .as_ref() - .unwrap(); - - if let Some(id) = instance_data.aliases.get(export) { - return Ok(*id); - } + /// + /// # Panics + /// + /// Panics if the provided node id is invalid. + pub fn alias_instance_export( + &mut self, + instance: NodeId, + export: &str, + ) -> Result { + let instance_node = self.graph.node_weight(instance.0).expect("invalid node id"); // Ensure the source is an instance - let types = self.node_types(instance_data); - let exports = match instance_data.item_kind { - ItemKind::Instance(id) => &types[id].exports, + let exports = match instance_node.item_kind { + ItemKind::Instance(id) => &self.types[id].exports, _ => { - return Err(Error::NodeIsNotAnInstance { + return Err(AliasError::NodeIsNotAnInstance { node: instance, - kind: instance_data.item_kind.desc(types).to_string(), + kind: instance_node.item_kind.desc(&self.types).to_string(), }); } }; @@ -674,42 +823,45 @@ impl CompositionGraph { let (index, _, kind) = exports .get_full(export) - .ok_or_else(|| Error::InstanceMissingExport { + .ok_or_else(|| AliasError::InstanceMissingExport { node: instance, export: export.to_string(), })?; - // Allocate the alias node - let aliased = self.alloc_node(NodeData::new(NodeKind::Alias, *kind, instance_data.package)); - - let prev = self.nodes[instance.index] - .data - .as_mut() - .unwrap() - .aliases - .insert(export.to_string(), aliased); - assert!(prev.is_none()); + // Check to see if there already is an edge for this alias + for e in self.graph.edges_directed(instance.0, Direction::Outgoing) { + assert_eq!(e.source(), instance.0); + if let Edge::Alias(i) = e.weight() { + if *i == index { + return Ok(NodeId(e.target())); + } + } + } - self.graph.add_node(aliased); - let prev = self.graph.add_edge(instance, aliased, Edge::Alias(index)); - assert!(prev.is_none()); - Ok(aliased) + // Allocate the alias node + let node = Node::new(NodeKind::Alias, *kind, instance_node.package); + let node_index = self.graph.add_node(node); + log::debug!( + "adding alias for export `{export}` to the graph as node index {index}", + index = node_index.index() + ); + self.graph + .add_edge(instance.0, node_index, Edge::Alias(index)); + Ok(NodeId(node_index)) } /// Gets the source node and export name of an alias node. /// - /// Returns `None` if the given id is invalid or if the node is not an alias. + /// Returns `None` if the node is not an alias. pub fn get_alias_source(&self, alias: NodeId) -> Option<(NodeId, &str)> { - for (s, t, edge) in self.graph.edges_directed(alias, Direction::Incoming) { - assert_eq!(t, alias); + for e in self.graph.edges_directed(alias.0, Direction::Incoming) { + assert_eq!(e.target(), alias.0); - if let Edge::Alias(index) = edge { - let data = self.node_data(s); - match data.item_kind { + if let Edge::Alias(index) = e.weight() { + match self.graph[e.source()].item_kind { ItemKind::Instance(id) => { - let types = self.node_types(data); - let export = types[id].exports.get_index(*index).unwrap().0; - return Some((s, export)); + let export = self.types[id].exports.get_index(*index).unwrap().0; + return Some((NodeId(e.source()), export)); } _ => panic!("alias source should be an instance"), } @@ -730,71 +882,59 @@ impl CompositionGraph { instantiation: NodeId, ) -> impl Iterator { self.graph - .edges_directed(instantiation, Direction::Incoming) - .filter_map(|(s, t, e)| { - let target = self.node_data(t); + .edges_directed(instantiation.0, Direction::Incoming) + .filter_map(|e| { + let target = &self.graph[e.target()]; let imports = match target.kind { NodeKind::Instantiation(_) => { let package = &self.packages[target.package?.index].package.as_ref()?; - &package.types()[package.ty()].imports + &self.types[package.ty()].imports } _ => return None, }; - match e { - Edge::Alias(_) => None, - Edge::Argument(indexmap) => Some( - indexmap - .iter() - .map(move |i| (imports.get_index(*i).unwrap().0.as_ref(), s)), - ), + match e.weight() { + Edge::Alias(_) | Edge::Dependency => None, + Edge::Argument(i) => Some(( + imports.get_index(*i).unwrap().0.as_ref(), + NodeId(e.source()), + )), } }) - .flatten() - } - - /// Gets information about a node in the graph. - /// - /// Returns `None` if the specified node identifier is invalid. - pub fn get_node_info(&self, node: NodeId) -> Option { - self.get_node(node)?; - let data = self.node_data(node); - - Some(NodeInfo { - types: self.node_types(data), - kind: data.item_kind, - name: data.name.as_deref(), - aliases: &data.aliases, - export: data.export.as_deref(), - }) } /// Sets the name of a node in the graph. /// /// Node names are recorded in a `names` custom section when the graph is encoded. - pub fn set_node_name(&mut self, node: NodeId, name: impl Into) -> GraphResult<()> { - self.get_node(node).ok_or(Error::InvalidNodeId)?; - self.node_data_mut(node).name = Some(name.into()); - Ok(()) + /// + /// # Panics + /// + /// This method panics if the provided node id is invalid. + pub fn set_node_name(&mut self, node: NodeId, name: impl Into) { + let node = &mut self.graph[node.0]; + node.name = Some(name.into()); } /// Marks the given node for export when the composition graph is encoded. - pub fn export(&mut self, node: NodeId, name: impl Into) -> GraphResult<()> { - self.get_node(node).ok_or(Error::InvalidNodeId)?; - + /// + /// Returns an error if the provided export name is invalid. + /// + /// # Panics + /// + /// This method panics if the provided node id is invalid. + pub fn export(&mut self, node: NodeId, name: impl Into) -> Result<(), ExportError> { let name = name.into(); - if self.exports.contains_key(&name) { - return Err(Error::ExternAlreadyExists { - kind: ExternKind::Export, + if let Some(existing) = self.exports.get(&name) { + return Err(ExportError::ExportAlreadyExists { name, + node: NodeId(*existing), }); } let map_reader_err = |e: BinaryReaderError| { let msg = e.to_string(); - Error::InvalidExternName { + ExportError::InvalidExportName { name: name.to_string(), - kind: ExternKind::Export, source: anyhow::anyhow!( "{msg}", msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) @@ -807,35 +947,44 @@ impl CompositionGraph { ComponentNameKind::Hash(_) | ComponentNameKind::Url(_) | ComponentNameKind::Dependency(_) => { - return Err(Error::InvalidExternName { + return Err(ExportError::InvalidExportName { name: name.to_string(), - kind: ExternKind::Export, source: anyhow::anyhow!("export name cannot be a hash, url, or dependency"), }); } _ => {} }; - self.node_data_mut(node).export = Some(name.clone()); - - let prev = self.exports.insert(name, node); + log::debug!("exporting node {index} as `{name}`", index = node.0.index()); + self.graph[node.0].export = Some(name.clone()); + let prev = self.exports.insert(name, node.0); assert!(prev.is_none()); Ok(()) } - /// Unmarks the given node from being exported from an encoding of the graph. + /// Gets the node being exported by the given name. /// - /// The node cannot be a _type definition node_ as type definitions are - /// always exported. - pub fn unexport(&mut self, node: NodeId) -> GraphResult<()> { - self.get_node(node).ok_or(Error::InvalidNodeId)?; + /// Returns `None` if there is no node exported by that name. + pub fn get_export(&self, name: &str) -> Option { + self.exports.get(name).map(|i| NodeId(*i)) + } - let data = self.node_data_mut(node); - if let NodeKind::Definition = data.kind { - return Err(Error::MustExportDefinition); + /// Unmarks the given node from being exported from an encoding of the graph. + /// + /// Returns an error if the given node is a type definition, as type + /// definitions must be exported. + /// + /// # Panics + /// + /// This method panics if the provided node id is invalid. + pub fn unexport(&mut self, node: NodeId) -> Result<(), UnexportError> { + let node = &mut self.graph[node.0]; + if let NodeKind::Definition = node.kind { + return Err(UnexportError::MustExportDefinition); } - if let Some(name) = data.export.take() { + if let Some(name) = node.export.take() { + log::debug!("unmarked node for export as `{name}`"); let removed = self.exports.swap_remove(&name); assert!(removed.is_some()); } @@ -847,16 +996,60 @@ impl CompositionGraph { /// /// All incoming and outgoing edges of the node are also removed. /// + /// If the node has dependent defined types, the dependent define + /// types are also removed. + /// /// If the node has aliases, the aliased nodes are also removed. /// - /// Returns `true` if the node was removed, otherwise returns `false`. - pub fn remove_node(&mut self, node: NodeId) -> bool { - if !self.graph.remove_node(node) { - return false; + /// # Panics + /// + /// This method panics if the provided node id is invalid. + pub fn remove_node(&mut self, node: NodeId) { + // Recursively remove any dependent nodes + for node in self + .graph + .edges_directed(node.0, Direction::Outgoing) + .filter_map(|e| { + assert_eq!(e.source(), node.0); + match e.weight() { + Edge::Alias(_) | Edge::Dependency => Some(NodeId(e.target())), + Edge::Argument(_) => None, + } + }) + .collect::>() + { + self.remove_node(node); + } + + // Remove the node from the graph + log::debug!( + "removing node {index} from the graph", + index = node.0.index() + ); + let node = self.graph.remove_node(node.0).expect("invalid node id"); + + // Remove any import entry + if let Some(name) = node.import_name() { + log::debug!("removing import node `{name}`"); + let removed = self.imports.remove(name); + assert!(removed.is_some()); } - self.free_node(node, false); - true + // Remove any export entry + if let Some(name) = &node.export { + log::debug!("removing export of node as `{name}`"); + let removed = self.exports.swap_remove(name); + assert!(removed.is_some()); + } + + if let NodeKind::Definition = node.kind { + log::debug!( + "removing type definition `{name}`", + name = node.name.as_ref().unwrap() + ); + let removed = self.defined.remove(&node.item_kind.ty()); + assert!(removed.is_some()); + } } /// Sets an argument of an instantiation node to the provided argument @@ -875,67 +1068,65 @@ impl CompositionGraph { /// /// If an edge already exists between the argument and the instantiation /// node, this method returns `Ok(_)`. + /// + /// # Panics + /// + /// This method will panic if the provided node ids are invalid. pub fn set_instantiation_argument( &mut self, instantiation: NodeId, argument_name: &str, argument: NodeId, - ) -> GraphResult<()> { + ) -> Result<(), InstantiationArgumentError> { fn add_edge( graph: &mut CompositionGraph, argument: NodeId, instantiation: NodeId, argument_name: &str, cache: &mut HashSet<(ItemKind, ItemKind)>, - ) -> GraphResult<()> { + ) -> Result<(), InstantiationArgumentError> { // Ensure the target is an instantiation node - let instantiation_data = graph - .get_node(instantiation) - .ok_or(Error::InvalidNodeId)? - .data - .as_ref() - .unwrap(); + let instantiation_node = &graph.graph[instantiation.0]; - if !matches!(instantiation_data.kind, NodeKind::Instantiation(_)) { - return Err(Error::NodeIsNotAnInstantiation { + if !matches!(instantiation_node.kind, NodeKind::Instantiation(_)) { + return Err(InstantiationArgumentError::NodeIsNotAnInstantiation { node: instantiation, }); } // Ensure the argument is a valid import of the target package - let instantiation_types = graph.node_types(instantiation_data); - let package = graph.packages[instantiation_data.package.unwrap().index] + let package = graph.packages[instantiation_node.package.unwrap().index] .package .as_ref() .unwrap(); - let package_type = &package.types()[package.ty()]; + let package_type = &graph.types[package.ty()]; // Ensure the argument isn't already satisfied let (argument_index, _, expected_argument_kind) = package_type .imports .get_full(argument_name) - .ok_or(Error::InvalidArgumentName { + .ok_or(InstantiationArgumentError::InvalidArgumentName { node: instantiation, name: argument_name.to_string(), package: package.name().to_string(), })?; - for (s, t, edge) in graph + for e in graph .graph - .edges_directed(instantiation, Direction::Incoming) + .edges_directed(instantiation.0, Direction::Incoming) { - assert_eq!(t, instantiation); - match edge { - Edge::Alias(_) => { - panic!("incoming alias edges should not exist for instantiation nodes") + assert_eq!(e.target(), instantiation.0); + match e.weight() { + Edge::Alias(_) | Edge::Dependency => { + panic!("unexpected edge for an instantiation") } - Edge::Argument(set) => { - if set.contains(&argument_index) { - if s == argument { + Edge::Argument(i) => { + if *i == argument_index { + if e.source() == argument.0 { return Ok(()); } - return Err(Error::ArgumentAlreadyPassed { + return Err(InstantiationArgumentError::ArgumentAlreadyPassed { node: instantiation, name: argument_name.to_string(), }); @@ -945,53 +1136,27 @@ impl CompositionGraph { } // Perform a subtype check on the source and target - let argument_data = graph - .get_node(argument) - .ok_or(Error::InvalidNodeId)? - .data - .as_ref() - .unwrap(); - let argument_types = graph.node_types(argument_data); + let argument_node = &graph.graph[argument.0]; let mut checker = SubtypeChecker::new(cache); checker .is_subtype( - argument_data.item_kind, - argument_types, + argument_node.item_kind, + &graph.types, *expected_argument_kind, - instantiation_types, - SubtypeCheck::Covariant, + &graph.types, ) - .map_err(|e| Error::ArgumentTypeMismatch { + .map_err(|e| InstantiationArgumentError::ArgumentTypeMismatch { name: argument_name.to_string(), source: e, })?; // Finally, insert the argument edge - if let Some(edge) = graph.graph.edge_weight_mut(argument, instantiation) { - match edge { - Edge::Alias(_) => { - panic!("alias edges should not exist for instantiation nodes") - } - Edge::Argument(set) => { - let inserted = set.insert(argument_index); - assert!(inserted); - } - } - } else { - let mut set = IndexSet::new(); - set.insert(argument_index); - graph - .graph - .add_edge(argument, instantiation, Edge::Argument(set)); - } - - graph.nodes[instantiation.index] - .data - .as_mut() - .unwrap() - .add_satisfied_arg(argument_index); + graph + .graph + .add_edge(argument.0, instantiation.0, Edge::Argument(argument_index)); + graph.graph[instantiation.0].add_satisfied_arg(argument_index); Ok(()) } @@ -1012,64 +1177,58 @@ impl CompositionGraph { /// The provided instantiation node must be an instantiation. /// /// The argument name must be a valid import on the instantiation node. + /// + /// # Panics + /// + /// This method will panic if the provided node ids are invalid. pub fn unset_instantiation_argument( &mut self, instantiation: NodeId, argument_name: &str, argument: NodeId, - ) -> GraphResult<()> { + ) -> Result<(), InstantiationArgumentError> { // Ensure the target is an instantiation node - let instantiation_data = self - .get_node(instantiation) - .ok_or(Error::InvalidNodeId)? - .data - .as_ref() - .unwrap(); - if !matches!(instantiation_data.kind, NodeKind::Instantiation(_)) { - return Err(Error::NodeIsNotAnInstantiation { + let instantiation_node = &self.graph[instantiation.0]; + if !matches!(instantiation_node.kind, NodeKind::Instantiation(_)) { + return Err(InstantiationArgumentError::NodeIsNotAnInstantiation { node: instantiation, }); } // Ensure the argument is a valid import of the target package - let package = self.packages[instantiation_data.package.unwrap().index] + let package = self.packages[instantiation_node.package.unwrap().index] .package .as_ref() .unwrap(); - let package_type = &package.types()[package.ty()]; + let package_type = &self.types[package.ty()]; - let argument_index = - package_type - .imports - .get_index_of(argument_name) - .ok_or(Error::InvalidArgumentName { - node: instantiation, - name: argument_name.to_string(), - package: package.name().to_string(), - })?; + let argument_index = package_type.imports.get_index_of(argument_name).ok_or( + InstantiationArgumentError::InvalidArgumentName { + node: instantiation, + name: argument_name.to_string(), + package: package.name().to_string(), + }, + )?; // Finally remove the argument edge if a connection exists - let remove_edge = if let Some(edge) = self.graph.edge_weight_mut(argument, instantiation) { - match edge { - Edge::Alias(_) => { - panic!("alias edges should not exist for instantiation nodes") + let mut edge = None; + for e in self.graph.edges_connecting(argument.0, instantiation.0) { + match e.weight() { + Edge::Alias(_) | Edge::Dependency => { + panic!("unexpected edge for an instantiation") } - Edge::Argument(set) => { - set.swap_remove(&argument_index); - self.nodes[instantiation.index] - .data - .as_mut() - .unwrap() - .remove_satisfied_arg(argument_index); - set.is_empty() + Edge::Argument(i) => { + if *i == argument_index { + edge = Some(e.id()); + break; + } } } - } else { - false - }; + } - if remove_edge { - self.graph.remove_edge(argument, instantiation); + if let Some(edge) = edge { + self.graph[instantiation.0].remove_satisfied_arg(argument_index); + self.graph.remove_edge(edge); } Ok(()) @@ -1078,7 +1237,7 @@ impl CompositionGraph { /// Encodes the composition graph as a new WebAssembly component. /// /// An error will be returned if the graph contains a dependency cycle. - pub fn encode(&self, options: EncodeOptions) -> GraphResult> { + pub fn encode(&self, options: EncodeOptions) -> Result, EncodeError> { let bytes = CompositionGraphEncoder::new(self).encode(options)?; if options.validate { @@ -1087,52 +1246,17 @@ impl CompositionGraph { ..Default::default() }) .validate_all(&bytes) - .map_err(|e| Error::EncodingValidationFailure { source: e })?; + .map_err(|e| EncodeError::ValidationFailure { source: e })?; } Ok(bytes) } /// Decodes a composition graph from the bytes of a WebAssembly component. - pub fn decode(_data: &[u8]) -> GraphResult { + pub fn decode(_data: &[u8]) -> Result { todo!("decoding of composition graphs is not yet implemented") } - fn add_import( - &mut self, - name: impl Into, - package: Option, - kind: ItemKind, - ) -> GraphResult { - let name = name.into(); - if self.imports.contains_key(&name) { - return Err(Error::ExternAlreadyExists { - kind: ExternKind::Import, - name, - }); - } - - // Ensure that the given import name is a valid extern name - ComponentName::new(&name, 0).map_err(|e| { - let msg = e.to_string(); - Error::InvalidExternName { - name: name.to_string(), - kind: ExternKind::Import, - source: anyhow::anyhow!( - "{msg}", - msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) - ), - } - })?; - - let id = self.alloc_node(NodeData::new(NodeKind::Import(name.clone()), kind, package)); - self.graph.add_node(id); - - let prev = self.imports.insert(name, id); - assert!(prev.is_none()); - Ok(id) - } - fn alloc_package(&mut self, package: wac_types::Package) -> PackageId { let (index, entry) = if let Some(index) = self.free_packages.pop() { let entry = &mut self.packages[index]; @@ -1151,134 +1275,87 @@ impl CompositionGraph { generation: entry.generation, } } +} - fn free_package(&mut self, id: PackageId) { - debug_assert_eq!( - self.packages[id.index].generation, id.generation, - "invalid package identifier" - ); - - // Free all nodes associated with the package - let nodes = std::mem::take(&mut self.packages[id.index].nodes); - for node in nodes { - let removed = self.graph.remove_node(node); - assert!(removed); - self.free_node(node, true); - } - - // Remove the package from the package map - let entry = &mut self.packages[id.index]; - let prev = self - .package_map - .remove(&BorrowedPackageKey::new(entry.package.as_ref().unwrap()) as &dyn BorrowedKey); - assert!(prev.is_some()); +impl Index for CompositionGraph { + type Output = Node; - // Finally free the package - *entry = RegisteredPackage::new(entry.generation.wrapping_add(1)); - self.free_packages.push(id.index); + fn index(&self, index: NodeId) -> &Self::Output { + &self.graph[index.0] } +} - fn alloc_node(&mut self, data: NodeData) -> NodeId { - let (index, node) = if let Some(index) = self.free_nodes.pop() { - let node = &mut self.nodes[index]; - assert!(node.data.is_none()); - (index, node) - } else { - let index = self.nodes.len(); - self.nodes.push(Node::new(0)); - (index, &mut self.nodes[index]) - }; - - let id = NodeId { - index, - generation: node.generation, - }; - - if let Some(package) = data.package { - debug_assert_eq!( - self.packages[package.index].generation, package.generation, - "invalid package identifier" - ); - - let added = self.packages[package.index].nodes.insert(id); - assert!(added); - } - - node.data = Some(data); - id - } +impl Index for CompositionGraph { + type Output = Package; - fn get_node(&self, id: NodeId) -> Option<&Node> { - let NodeId { index, generation } = id; - let node = self.nodes.get(index)?; - if node.generation != generation { - return None; + fn index(&self, index: PackageId) -> &Self::Output { + let PackageId { index, generation } = index; + let entry = self.packages.get(index).expect("invalid package id"); + if entry.generation != generation { + panic!("invalid package id"); } - assert!(node.data.is_some()); - Some(node) + entry.package.as_ref().unwrap() } +} - fn free_node(&mut self, id: NodeId, package_removed: bool) { - debug_assert_eq!( - self.nodes[id.index].generation, id.generation, - "invalid node identifier" - ); +impl fmt::Debug for CompositionGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node_attr = |_, (i, node): (_, &Node)| { + let label = match &node.kind { + NodeKind::Definition => format!( + r#"type definition \"{name}\""#, + name = node.export.as_ref().unwrap() + ), + NodeKind::Import(name) => format!(r#"import \"{name}\""#), + NodeKind::Instantiation(_) => { + let package = &self[node.package.unwrap()]; + format!(r#"instantiation of package \"{key}\""#, key = package.key()) + } + NodeKind::Alias => { + let (_, source) = self.get_alias_source(NodeId(i)).unwrap(); + format!(r#"alias of export \"{source}\""#) + } + }; - // Free the node - let next = self.nodes[id.index].generation.wrapping_add(1); - let node = std::mem::replace(&mut self.nodes[id.index], Node::new(next)); - let data = node.data.unwrap(); - - // If we're not freeing the node as a result of removing a package, - // then remove it from the package and also recurse on any aliases. - if !package_removed { - // Remove the node from the package - if let Some(pkg) = data.package { - debug_assert_eq!( - self.packages[pkg.index].generation, pkg.generation, - "invalid package identifier" - ); - - let removed = self.packages[pkg.index].nodes.remove(&id); - assert!(removed); - } + let mut desc = String::new(); + write!( + &mut desc, + r#"label = "{label}"; kind = "{kind}""#, + kind = node.item_kind.desc(&self.types) + ) + .unwrap(); - // Recursively remove any alias nodes from the graph - for alias in data.aliases.values() { - self.remove_node(*alias); + if let Some(export) = &node.export { + write!(&mut desc, r#"; export = "{export}""#).unwrap(); } - } - - // Remove any import entries - if let Some(name) = data.import_name() { - let removed = self.imports.remove(name); - assert!(removed.is_some()); - } - // Finally, add the node to the free list - self.free_nodes.push(id.index); - } + desc + }; - fn node_data(&self, id: NodeId) -> &NodeData { - self.nodes[id.index].data.as_ref().unwrap() - } + let dot = Dot::with_attr_getters( + &self.graph, + &[Config::NodeNoLabel], + &|_, _| String::new(), + &node_attr, + ); - fn node_data_mut(&mut self, id: NodeId) -> &mut NodeData { - self.nodes[id.index].data.as_mut().unwrap() + write!(f, "{:?}", dot) } +} - fn node_types(&self, data: &NodeData) -> &Types { - data.package - .and_then(|id| self.get_package(id)) - .map(|p| p.types()) - .unwrap_or(&self.types) - } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +/// Information about the tool that processed the graph. +pub struct Processor<'a> { + /// The name of the tool that processed the graph. + pub name: &'a str, + /// The version of the tool that processed the graph. + pub version: &'a str, } /// The options for encoding a composition graph. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct EncodeOptions { +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct EncodeOptions<'a> { /// Whether or not to define instantiated components. /// /// If `false`, components will be imported instead. @@ -1290,13 +1367,17 @@ pub struct EncodeOptions { /// /// Defaults to `true`. pub validate: bool, + + /// Information about the processor of the composition graph. + pub processor: Option>, } -impl Default for EncodeOptions { +impl Default for EncodeOptions<'_> { fn default() -> Self { Self { define_components: true, validate: true, + processor: None, } } } @@ -1309,48 +1390,117 @@ impl<'a> CompositionGraphEncoder<'a> { Self(graph) } - fn encode(self, options: EncodeOptions) -> GraphResult> { + fn encode(self, options: EncodeOptions) -> Result, EncodeError> { let mut state = State::new(); // First populate the state with the implicit instantiation arguments self.populate_implicit_args(&mut state)?; // Encode each node in the graph in topographical order - for node in toposort(&self.0.graph, None) - .map_err(|e| Error::GraphContainsCycle { node: e.node_id() })? + for n in self + .toposort() + .map_err(|n| EncodeError::GraphContainsCycle { node: NodeId(n) })? { - let data = self.0.node_data(node); - let index = match &data.kind { - NodeKind::Definition => self.definition(&mut state, data), + let node = &self.0.graph[n]; + let index = match &node.kind { + NodeKind::Definition => self.definition(&mut state, node), NodeKind::Import(name) => { - let types = self.0.node_types(data); - self.import(&mut state, name, types, data.item_kind) + self.import(&mut state, name, &self.0.types, node.item_kind) } - NodeKind::Instantiation(_) => self.instantiation(&mut state, node, data, options), - NodeKind::Alias => self.alias(&mut state, node, data), + NodeKind::Instantiation(_) => self.instantiation(&mut state, n, node, options), + NodeKind::Alias => self.alias(&mut state, n), }; - let prev = state.node_indexes.insert(node, index); + let prev = state.node_indexes.insert(n, index); assert!(prev.is_none()); } - // Encode the exports - for (name, node) in &self.0.exports { + // Encode the exports, skipping any definitions as they've + // already been exported + for (name, node) in self + .0 + .exports + .iter() + .filter(|(_, n)| !matches!(self.0.graph[**n].kind, NodeKind::Definition)) + { let index = state.node_indexes[node]; - let data = self.0.node_data(*node); + let node = &self.0.graph[*node]; state .builder() - .export(name, data.item_kind.into(), index, None); + .export(name, node.item_kind.into(), index, None); } let mut builder = std::mem::take(state.builder()); self.encode_names(&state, &mut builder); + if let Some(processor) = &options.processor { + let mut section = wasm_metadata::Producers::empty(); + section.add("processed-by", processor.name, processor.version); + builder.raw_custom_section(§ion.raw_custom_section()); + } + Ok(builder.finish()) } - fn populate_implicit_args(&self, state: &mut State) -> GraphResult<()> { + /// Performs a toposort of the composition graph. + /// + /// This differs from `toposort` in `petgraph` in that the + /// nodes are iterated in *reverse order*, resulting in the + /// returned topologically-sorted set to be in index order for + /// independent nodes. + fn toposort(&self) -> Result, NodeIndex> { + let graph = &self.0.graph; + let mut dfs = Dfs::empty(graph); + dfs.reset(graph); + let mut finished = graph.visit_map(); + + let mut finish_stack = Vec::new(); + for i in graph.node_identifiers().rev() { + if dfs.discovered.is_visited(&i) { + continue; + } + dfs.stack.push(i); + while let Some(&nx) = dfs.stack.last() { + if dfs.discovered.visit(nx) { + // First time visiting `nx`: Push neighbors, don't pop `nx` + for succ in graph.neighbors(nx) { + if succ == nx { + // self cycle + return Err(nx); + } + if !dfs.discovered.is_visited(&succ) { + dfs.stack.push(succ); + } + } + } else { + dfs.stack.pop(); + if finished.visit(nx) { + // Second time: All reachable nodes must have been finished + finish_stack.push(nx); + } + } + } + } + finish_stack.reverse(); + + dfs.reset(graph); + for &i in &finish_stack { + dfs.move_to(i); + let mut cycle = false; + while let Some(j) = dfs.next(Reversed(graph)) { + if cycle { + return Err(j); + } + cycle = true; + } + } + + Ok(finish_stack) + } + + fn populate_implicit_args(&self, state: &mut State) -> Result<(), EncodeError> { let mut aggregator = TypeAggregator::default(); + let mut instantiations = HashMap::new(); let mut arguments = Vec::new(); let mut encoded = HashMap::new(); let mut cache = Default::default(); @@ -1359,38 +1509,42 @@ impl<'a> CompositionGraphEncoder<'a> { log::debug!("populating implicit imports"); // Enumerate the instantiation nodes and populate the import types - for node in self.0.nodes() { - let data = self.0.node_data(node); - if !matches!(data.kind, NodeKind::Instantiation(_)) { + for index in self.0.graph.node_indices() { + let node = &self.0.graph[index]; + if !matches!(node.kind, NodeKind::Instantiation(_)) { continue; } - let package = self.0.get_package(data.package.unwrap()).unwrap(); - let world = &package.types()[package.ty()]; + let package = &self.0[node.package.unwrap()]; + let world = &self.0.types[package.ty()]; // Go through the unsatisfied arguments and import them for (_, (name, kind)) in world .imports .iter() .enumerate() - .filter(|(i, _)| !data.is_arg_satisfied(*i)) + .filter(|(i, _)| !node.is_arg_satisfied(*i)) { if let Some(import) = self.0.imports.get(name).copied() { - return Err(Error::ImplicitImportConflict { - import, - instantiation: node, + return Err(EncodeError::ImplicitImportConflict { + import: NodeId(import), + instantiation: NodeId(index), package: PackageKey::new(package), name: name.to_string(), }); } + instantiations.entry(name).or_insert(index); + aggregator = aggregator - .aggregate(name, package.types(), *kind, &mut checker) - .map_err(|e| Error::ImportTypeMergeConflict { + .aggregate(name, &self.0.types, *kind, &mut checker) + .map_err(|e| EncodeError::ImportTypeMergeConflict { import: name.clone(), + first: NodeId(instantiations[&name]), + second: NodeId(index), source: e, })?; - arguments.push((node, name)); + arguments.push((index, name)); } } @@ -1414,24 +1568,37 @@ impl<'a> CompositionGraphEncoder<'a> { Ok(()) } - fn definition(&self, state: &mut State, data: &NodeData) -> u32 { - let types = self.0.node_types(data); - let name = data.export.as_deref().unwrap(); + fn definition(&self, state: &mut State, node: &Node) -> u32 { + let name = node.export.as_deref().unwrap(); log::debug!( "encoding definition for {kind} `{name}`", - kind = data.item_kind.desc(types) + kind = node.item_kind.desc(&self.0.types) ); - let encoder = TypeEncoder::new(types); - let (ty, index) = match data.item_kind { + // Check to see if the type is already + let encoder = TypeEncoder::new(&self.0.types); + let (ty, index) = match node.item_kind { ItemKind::Type(ty) => match ty { Type::Resource(_) => panic!("resources cannot be defined"), - Type::Func(id) => (ty, encoder.ty(state, Type::Func(id), None)), - Type::Value(id) => (ty, encoder.ty(state, Type::Value(id), None)), + Type::Func(_) => (ty, encoder.ty(state, ty, None)), + Type::Value(vt) => { + // Check for an alias and use the existing index + if let ValueType::Defined(id) = vt { + if let DefinedType::Alias(aliased @ ValueType::Defined(_)) = + &self.0.types()[id] + { + (ty, state.current.type_indexes[&Type::Value(*aliased)]) + } else { + (ty, encoder.ty(state, ty, None)) + } + } else { + (ty, encoder.ty(state, ty, None)) + } + } Type::Interface(id) => (ty, encoder.interface(state, id)), Type::World(id) => (ty, encoder.world(state, id)), - Type::Module(id) => (ty, encoder.ty(state, Type::Module(id), None)), + Type::Module(_) => (ty, encoder.ty(state, ty, None)), }, _ => panic!("only types can be defined"), }; @@ -1458,9 +1625,8 @@ impl<'a> CompositionGraphEncoder<'a> { } } - let encoder = TypeEncoder::new(types); - // Defer to special handling if the item being imported is a resource + let encoder = TypeEncoder::new(types); if let ItemKind::Type(Type::Resource(id)) = kind { return encoder.import_resource(state, name, id); } @@ -1511,13 +1677,13 @@ impl<'a> CompositionGraphEncoder<'a> { fn instantiation( &self, state: &mut State, - node: NodeId, - data: &NodeData, + index: NodeIndex, + node: &Node, options: EncodeOptions, ) -> u32 { - let package_id = data.package.expect("instantiation requires a package"); + let package_id = node.package.expect("instantiation requires a package"); let package = self.0.packages[package_id.index].package.as_ref().unwrap(); - let imports = &package.types()[package.ty()].imports; + let imports = &self.0.types[package.ty()].imports; let component_index = if let Some(index) = state.packages.get(&package_id) { *index @@ -1525,7 +1691,7 @@ impl<'a> CompositionGraphEncoder<'a> { let index = if options.define_components { state.builder().component_raw(package.bytes()) } else { - let encoder = TypeEncoder::new(package.types()); + let encoder = TypeEncoder::new(&self.0.types); let ty = encoder.component(state, package.ty()); state.builder().import( &Self::package_import_name(package), @@ -1541,24 +1707,24 @@ impl<'a> CompositionGraphEncoder<'a> { arguments.extend( self.0 .graph - .edges_directed(node, Direction::Incoming) - .flat_map(|(s, _, e)| { - let kind = self.0.node_data(s).item_kind.into(); - let index = state.node_indexes[&s]; - match e { - Edge::Alias(_) => panic!("expected only argument edges"), - Edge::Argument(i) => i.iter().map(move |i| { - ( - Cow::Borrowed(imports.get_index(*i).unwrap().0.as_str()), - kind, - index, - ) - }), + .edges_directed(index, Direction::Incoming) + .map(|e| { + let kind = self.0.graph[e.source()].item_kind.into(); + let index = state.node_indexes[&e.source()]; + match e.weight() { + Edge::Alias(_) | Edge::Dependency => { + panic!("unexpected edge for an instantiation") + } + Edge::Argument(i) => ( + Cow::Borrowed(imports.get_index(*i).unwrap().0.as_str()), + kind, + index, + ), } }), ); - if let Some(implicit) = state.implicit_args.remove(&node) { + if let Some(implicit) = state.implicit_args.remove(&index) { arguments.extend(implicit.into_iter().map(|(n, k, i)| (n.into(), k, i))); } @@ -1577,25 +1743,24 @@ impl<'a> CompositionGraphEncoder<'a> { index } - fn alias(&self, state: &mut State, node: NodeId, data: &NodeData) -> u32 { + fn alias(&self, state: &mut State, node: NodeIndex) -> u32 { let (source, export) = self .0 - .get_alias_source(node) + .get_alias_source(NodeId(node)) .expect("alias should have a source"); - let source_data = self.0.node_data(source); - let types = self.0.node_types(data); - let exports = match source_data.item_kind { - ItemKind::Instance(id) => &types[id].exports, + let source_node = &self.0[source]; + let exports = match source_node.item_kind { + ItemKind::Instance(id) => &self.0.types[id].exports, _ => panic!("expected the source of an alias to be an instance"), }; let kind = exports[export]; - let instance = state.node_indexes[&source]; + let instance = state.node_indexes[&source.0]; log::debug!( "encoding alias for {kind} export `{export}` of instance index {instance}", - kind = kind.desc(types), + kind = kind.desc(&self.0.types), ); let index = state.builder().alias(Alias::InstanceExport { @@ -1606,7 +1771,7 @@ impl<'a> CompositionGraphEncoder<'a> { log::debug!( "alias of export `{export}` encoded to {kind} index {index}", - kind = kind.desc(types) + kind = kind.desc(&self.0.types) ); index } @@ -1632,10 +1797,10 @@ impl<'a> CompositionGraphEncoder<'a> { let mut modules = NameMap::new(); let mut values = NameMap::new(); - for node in self.0.nodes() { - let data = self.0.node_data(node); - if let Some(name) = &data.name { - let map = match data.item_kind { + for index in self.0.graph.node_indices() { + let node = &self.0.graph[index]; + if let Some(name) = &node.name { + let map = match node.item_kind { ItemKind::Type(_) => &mut types, ItemKind::Func(_) => &mut funcs, ItemKind::Instance(_) => &mut instances, @@ -1644,7 +1809,7 @@ impl<'a> CompositionGraphEncoder<'a> { ItemKind::Value(_) => &mut values, }; - let index = state.node_indexes[&node]; + let index = state.node_indexes[&index]; map.append(index, name) } } @@ -1684,22 +1849,6 @@ mod test { use super::*; use wac_types::{DefinedType, PrimitiveType, Resource, ValueType}; - #[test] - fn it_errors_with_type_not_defined() { - let mut graph = CompositionGraph::new(); - // Define the type in a different type collection - let mut types = Types::new(); - let id = types.add_defined_type(DefinedType::Alias(ValueType::Primitive( - PrimitiveType::Bool, - ))); - assert!(matches!( - graph - .define_type("foo", Type::Value(ValueType::Defined(id))) - .unwrap_err(), - Error::TypeNotDefined { .. } - )); - } - #[test] fn it_adds_a_type_definition() { let mut graph = CompositionGraph::new(); @@ -1722,75 +1871,10 @@ mod test { }); assert!(matches!( graph.define_type("foo", Type::Resource(id)).unwrap_err(), - Error::CannotDefineResource + DefineTypeError::CannotDefineResource )); } - #[test] - fn it_validates_package_ids() { - let mut graph = CompositionGraph::new(); - let old = graph - .register_package( - Package::from_bytes("foo:bar", None, wat::parse_str("(component)").unwrap()) - .unwrap(), - ) - .unwrap(); - - assert_eq!(old.index, 0); - assert_eq!(old.generation, 0); - - graph.unregister_package(old).unwrap(); - - let new = graph - .register_package( - Package::from_bytes("foo:bar", None, wat::parse_str("(component)").unwrap()) - .unwrap(), - ) - .unwrap(); - - assert_eq!(new.index, 0); - assert_eq!(new.generation, 1); - - assert!(matches!( - graph.instantiate(old).unwrap_err(), - Error::InvalidPackageId, - )); - - graph.instantiate(new).unwrap(); - } - - #[test] - fn it_validates_node_ids() { - let mut graph = CompositionGraph::new(); - let pkg = graph - .register_package( - Package::from_bytes( - "foo:bar", - None, - wat::parse_str(r#"(component (import "foo" (func)) (export "foo" (func 0)))"#) - .unwrap(), - ) - .unwrap(), - ) - .unwrap(); - - let old = graph.instantiate(pkg).unwrap(); - assert_eq!(old.index, 0); - assert_eq!(old.generation, 0); - - assert!(graph.remove_node(old)); - let new = graph.instantiate(pkg).unwrap(); - assert_eq!(new.index, 0); - assert_eq!(new.generation, 1); - - assert!(matches!( - graph.alias_instance_export(old, "foo").unwrap_err(), - Error::InvalidNodeId, - )); - - graph.alias_instance_export(new, "foo").unwrap(); - } - #[test] fn it_must_export_a_type_definition() { let mut graph = CompositionGraph::new(); @@ -1802,7 +1886,7 @@ mod test { .unwrap(); assert!(matches!( graph.unexport(id).unwrap_err(), - Error::MustExportDefinition + UnexportError::MustExportDefinition )); } } diff --git a/crates/wac-graph/src/lib.rs b/crates/wac-graph/src/lib.rs index 4186d4f..f77f947 100644 --- a/crates/wac-graph/src/lib.rs +++ b/crates/wac-graph/src/lib.rs @@ -11,21 +11,19 @@ //! // Register the packages with the graph //! // It is assumed that `my:package1` exports a function named `a`, //! // while `my:package2` imports a function named `b`. -//! let package1 = graph.register_package( -//! Package::from_file("my:package1", None, "package1.wasm")? -//! )?; -//! let package2 = graph.register_package( -//! Package::from_file("my:package2", None, "package2.wasm")? -//! )?; +//! let pkg = Package::from_file("my:package1", None, "package1.wasm", graph.types_mut())?; +//! let package1 = graph.register_package(pkg)?; +//! let pkg = Package::from_file("my:package2", None, "package2.wasm", graph.types_mut())?; +//! let package2 = graph.register_package(pkg)?; //! //! // Instantiate package `my:package1` -//! let instantiation1 = graph.instantiate(package1)?; +//! let instantiation1 = graph.instantiate(package1); //! //! // Alias the `a` export of the `my:package1` instance //! let a = graph.alias_instance_export(instantiation1, "a")?; //! //! // Instantiate package `my:package2` -//! let instantiation2 = graph.instantiate(package2)?; +//! let instantiation2 = graph.instantiate(package2); //! //! // Set argument `b` of the instantiation of `my:package2` to `a` //! graph.set_instantiation_argument(instantiation2, "b", a)?; diff --git a/crates/wac-graph/tests/encoding.rs b/crates/wac-graph/tests/encoding.rs index 3b9d745..846bb9a 100644 --- a/crates/wac-graph/tests/encoding.rs +++ b/crates/wac-graph/tests/encoding.rs @@ -16,9 +16,18 @@ use wit_parser::Resolve; #[derive(Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] enum Node { - Import { name: String, path: String }, - Instantiation { package: usize }, - Alias { source: usize, export: String }, + Import { + name: String, + package: usize, + export: String, + }, + Instantiation { + package: usize, + }, + Alias { + source: usize, + export: String, + }, } /// Represents an argument to connect to an instantiation node. @@ -103,9 +112,7 @@ impl GraphFile { .with_context(|| format!("invalid node index {node} referenced in name {index} for test case `{test_case}`", node = name.node)) .copied()?; - graph - .set_node_name(id, &name.name) - .with_context(|| format!("failed to set node name for node {node} in name {index} for test case `{test_case}`", node = name.node))?; + graph.set_node_name(id, &name.name); } Ok(graph) @@ -170,13 +177,18 @@ impl GraphFile { ), }; - let package = Package::from_bytes(&package.name, package.version.as_ref(), bytes) - .with_context(|| { - format!( - "failed to decode package `{path}` for test case `{test_case}`", - path = path.display() - ) - })?; + let package = Package::from_bytes( + &package.name, + package.version.as_ref(), + bytes, + graph.types_mut(), + ) + .with_context(|| { + format!( + "failed to decode package `{path}` for test case `{test_case}`", + path = path.display() + ) + })?; let id = graph.register_package(package).with_context(|| { format!( @@ -200,8 +212,18 @@ impl GraphFile { let mut nodes = HashMap::new(); for (index, node) in self.nodes.iter().enumerate() { let id = match node { - Node::Import { name, path } => { - graph.import_by_path(name, path).with_context(|| { + Node::Import { + name, + package, + export, + } => { + let id = packages.get(package).with_context(|| { + format!("invalid package index {package} referenced in node {index} for test case `{test_case}`") + }).copied()?; + let kind = graph.types()[graph[id].ty()].exports.get(export).copied().with_context(|| { + format!("invalid package export `{export}` referenced in node {index} for test case `{test_case}`") + })?.promote(); + graph.import(name, kind).with_context(|| { format!("failed to add import node {index} for test case `{test_case}`") })? } @@ -213,11 +235,7 @@ impl GraphFile { }) .copied()?; - graph.instantiate(package).with_context(|| { - format!( - "failed to add instantiation node {index} for test case `{test_case}`" - ) - })? + graph.instantiate(package) } Node::Alias { source, export } => { let source = nodes.get(source).with_context(|| { @@ -320,6 +338,7 @@ fn encoding() -> Result<()> { // more readable and to test encoding a bit more. define_components: false, validate: true, + ..Default::default() }) .context("failed to encode the graph") }); diff --git a/crates/wac-graph/tests/graphs/argument-already-satisfied/graph.json b/crates/wac-graph/tests/graphs/argument-already-satisfied/graph.json index e69215b..ffdebfa 100644 --- a/crates/wac-graph/tests/graphs/argument-already-satisfied/graph.json +++ b/crates/wac-graph/tests/graphs/argument-already-satisfied/graph.json @@ -9,12 +9,14 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "import", "name": "bar", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "instantiation", diff --git a/crates/wac-graph/tests/graphs/argument-type-mismatch/graph.json b/crates/wac-graph/tests/graphs/argument-type-mismatch/graph.json index 7ad7140..e0bd2d7 100644 --- a/crates/wac-graph/tests/graphs/argument-type-mismatch/graph.json +++ b/crates/wac-graph/tests/graphs/argument-type-mismatch/graph.json @@ -9,7 +9,8 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "instantiation", diff --git a/crates/wac-graph/tests/graphs/export-already-exists/error.txt b/crates/wac-graph/tests/graphs/export-already-exists/error.txt index 8ef03d6..46bb278 100644 --- a/crates/wac-graph/tests/graphs/export-already-exists/error.txt +++ b/crates/wac-graph/tests/graphs/export-already-exists/error.txt @@ -1,4 +1,4 @@ failed to export node 0 in export 1 for test case `export-already-exists` Caused by: - export name `foo` already exists in the graph + an export with the name `foo` already exists diff --git a/crates/wac-graph/tests/graphs/implicit-import-conflict/error.txt b/crates/wac-graph/tests/graphs/implicit-import-conflict/error.txt index efa03a4..594d2b3 100644 --- a/crates/wac-graph/tests/graphs/implicit-import-conflict/error.txt +++ b/crates/wac-graph/tests/graphs/implicit-import-conflict/error.txt @@ -1,4 +1,4 @@ failed to encode the graph Caused by: - an instantiation of package `test:foo` implicitly imports an item named `foo`, but it conflicts with an explicit import node of the same name + an instantiation of package `test:foo` implicitly imports an item named `foo`, but it conflicts with an explicit import of the same name diff --git a/crates/wac-graph/tests/graphs/implicit-import-conflict/graph.json b/crates/wac-graph/tests/graphs/implicit-import-conflict/graph.json index 3f95052..b32e1bb 100644 --- a/crates/wac-graph/tests/graphs/implicit-import-conflict/graph.json +++ b/crates/wac-graph/tests/graphs/implicit-import-conflict/graph.json @@ -9,7 +9,8 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "instantiation", diff --git a/crates/wac-graph/tests/graphs/import-already-exists/graph.json b/crates/wac-graph/tests/graphs/import-already-exists/graph.json index 2c211c1..c7aba88 100644 --- a/crates/wac-graph/tests/graphs/import-already-exists/graph.json +++ b/crates/wac-graph/tests/graphs/import-already-exists/graph.json @@ -9,12 +9,14 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" } ] } diff --git a/crates/wac-graph/tests/graphs/invalid-export-name/error.txt b/crates/wac-graph/tests/graphs/invalid-export-name/error.txt index efdb40e..37a9b11 100644 --- a/crates/wac-graph/tests/graphs/invalid-export-name/error.txt +++ b/crates/wac-graph/tests/graphs/invalid-export-name/error.txt @@ -1,5 +1,5 @@ failed to export node 0 in export 0 for test case `invalid-export-name` Caused by: - 0: export name `!` is not a valid extern name + 0: export name `!` is not valid 1: `!` is not in kebab case diff --git a/crates/wac-graph/tests/graphs/invalid-import-name/error.txt b/crates/wac-graph/tests/graphs/invalid-import-name/error.txt index 0e38ed5..6c8e47b 100644 --- a/crates/wac-graph/tests/graphs/invalid-import-name/error.txt +++ b/crates/wac-graph/tests/graphs/invalid-import-name/error.txt @@ -1,5 +1,5 @@ failed to add import node 0 for test case `invalid-import-name` Caused by: - 0: import name `NOT_VALID` is not a valid extern name + 0: import name `NOT_VALID` is not valid 1: `NOT_VALID` is not in kebab case diff --git a/crates/wac-graph/tests/graphs/invalid-import-name/graph.json b/crates/wac-graph/tests/graphs/invalid-import-name/graph.json index 886fa01..90e3fad 100644 --- a/crates/wac-graph/tests/graphs/invalid-import-name/graph.json +++ b/crates/wac-graph/tests/graphs/invalid-import-name/graph.json @@ -9,7 +9,8 @@ { "type": "import", "name": "NOT_VALID", - "path": "test:foo/bar" + "package": 0, + "export": "bar" } ] -} +} \ No newline at end of file diff --git a/crates/wac-graph/tests/graphs/invalid-package-version/error.txt b/crates/wac-graph/tests/graphs/invalid-package-version/error.txt deleted file mode 100644 index a1d85a2..0000000 --- a/crates/wac-graph/tests/graphs/invalid-package-version/error.txt +++ /dev/null @@ -1,5 +0,0 @@ -failed to add import node 0 for test case `invalid-package-version` - -Caused by: - 0: package version `nope` is not a valid semantic version - 1: unexpected character 'n' while parsing major version number diff --git a/crates/wac-graph/tests/graphs/invalid-package-version/graph.json b/crates/wac-graph/tests/graphs/invalid-package-version/graph.json deleted file mode 100644 index 6901cf3..0000000 --- a/crates/wac-graph/tests/graphs/invalid-package-version/graph.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "nodes": [ - { - "type": "import", - "name": "foo", - "path": "foo:bar@nope/baz" - } - ] -} diff --git a/crates/wac-graph/tests/graphs/merged-func-results/encoded.wat b/crates/wac-graph/tests/graphs/merged-func-results/encoded.wat index 2a42617..39c4347 100644 --- a/crates/wac-graph/tests/graphs/merged-func-results/encoded.wat +++ b/crates/wac-graph/tests/graphs/merged-func-results/encoded.wat @@ -17,27 +17,17 @@ (type (;0;) (record (field "foo" u8))) (export (;1;) "foo" (type (eq 0))) (type (;2;) (func (result 1))) - (export (;0;) "my-func2" (func (type 2))) + (export (;0;) "my-func1" (func (type 2))) ) ) (import "example:example/my-interface" (instance (;0;) (type 0))) - (type (;1;) - (instance - (type (;0;) (record (field "foo" u8))) - (export (;1;) "foo" (type (eq 0))) - (type (;2;) (func (result 1))) - (export (;0;) "my-func2" (func (type 2))) - ) - ) - (export (;1;) "example:example/my-interface" (instance (type 1))) ) ) - (import "unlocked-dep=" (component (;0;) (type 1))) + (import "unlocked-dep=" (component (;0;) (type 1))) (instance (;1;) (instantiate 0 (with "example:example/my-interface" (instance 0)) ) ) - (alias export 1 "example:example/my-interface" (instance (;2;))) (type (;2;) (component (type (;0;) @@ -45,16 +35,26 @@ (type (;0;) (record (field "foo" u8))) (export (;1;) "foo" (type (eq 0))) (type (;2;) (func (result 1))) - (export (;0;) "my-func1" (func (type 2))) + (export (;0;) "my-func2" (func (type 2))) ) ) (import "example:example/my-interface" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (record (field "foo" u8))) + (export (;1;) "foo" (type (eq 0))) + (type (;2;) (func (result 1))) + (export (;0;) "my-func2" (func (type 2))) + ) + ) + (export (;1;) "example:example/my-interface" (instance (type 1))) ) ) - (import "unlocked-dep=" (component (;1;) (type 2))) - (instance (;3;) (instantiate 1 + (import "unlocked-dep=" (component (;1;) (type 2))) + (instance (;2;) (instantiate 1 (with "example:example/my-interface" (instance 0)) ) ) - (export (;4;) "example:example/my-interface" (instance 2)) + (alias export 2 "example:example/my-interface" (instance (;3;))) + (export (;4;) "example:example/my-interface" (instance 3)) ) diff --git a/crates/wac-graph/tests/graphs/not-an-instance/graph.json b/crates/wac-graph/tests/graphs/not-an-instance/graph.json index 1ac8cf6..0426fb6 100644 --- a/crates/wac-graph/tests/graphs/not-an-instance/graph.json +++ b/crates/wac-graph/tests/graphs/not-an-instance/graph.json @@ -9,7 +9,8 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "alias", diff --git a/crates/wac-graph/tests/graphs/not-instantiation/graph.json b/crates/wac-graph/tests/graphs/not-instantiation/graph.json index 3936014..7df43aa 100644 --- a/crates/wac-graph/tests/graphs/not-instantiation/graph.json +++ b/crates/wac-graph/tests/graphs/not-instantiation/graph.json @@ -9,12 +9,14 @@ { "type": "import", "name": "foo", - "path": "test:foo/bar" + "package": 0, + "export": "bar" }, { "type": "import", "name": "bar", - "path": "test:foo/bar" + "package": 0, + "export": "bar" } ], "arguments": [ diff --git a/crates/wac-graph/tests/graphs/package-missing-export/error.txt b/crates/wac-graph/tests/graphs/package-missing-export/error.txt deleted file mode 100644 index af206af..0000000 --- a/crates/wac-graph/tests/graphs/package-missing-export/error.txt +++ /dev/null @@ -1,4 +0,0 @@ -failed to add import node 0 for test case `package-missing-export` - -Caused by: - package `test:foo` does not export an item for path `bar` diff --git a/crates/wac-graph/tests/graphs/package-missing-export/foo.wat b/crates/wac-graph/tests/graphs/package-missing-export/foo.wat deleted file mode 100644 index e5627d1..0000000 --- a/crates/wac-graph/tests/graphs/package-missing-export/foo.wat +++ /dev/null @@ -1 +0,0 @@ -(component) diff --git a/crates/wac-graph/tests/graphs/package-missing-export/graph.json b/crates/wac-graph/tests/graphs/package-missing-export/graph.json deleted file mode 100644 index 6d6806a..0000000 --- a/crates/wac-graph/tests/graphs/package-missing-export/graph.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "packages": [ - { - "name": "test:foo", - "path": "foo.wat" - } - ], - "nodes": [ - { - "type": "import", - "name": "foo", - "path": "test:foo/bar" - } - ] -} diff --git a/crates/wac-graph/tests/graphs/simple/graph.json b/crates/wac-graph/tests/graphs/simple/graph.json index f48e87b..096928f 100644 --- a/crates/wac-graph/tests/graphs/simple/graph.json +++ b/crates/wac-graph/tests/graphs/simple/graph.json @@ -13,7 +13,8 @@ { "type": "import", "name": "my-foo", - "path": "test:foo/f" + "package": 0, + "export": "f" }, { "type": "instantiation", diff --git a/crates/wac-graph/tests/graphs/type-aggregation-error/error.txt b/crates/wac-graph/tests/graphs/type-aggregation-error/error.txt index b3d4a40..7420680 100644 --- a/crates/wac-graph/tests/graphs/type-aggregation-error/error.txt +++ b/crates/wac-graph/tests/graphs/type-aggregation-error/error.txt @@ -1,6 +1,6 @@ failed to encode the graph Caused by: - 0: failed to merge the type definition for import `foo` due to conflicting types + 0: failed to merge the type definition for implicit import `foo` due to conflicting types 1: mismatched type for export `baz` 2: expected function, found instance diff --git a/crates/wac-graph/tests/graphs/unknown-package/error.txt b/crates/wac-graph/tests/graphs/unknown-package/error.txt deleted file mode 100644 index b4fdf90..0000000 --- a/crates/wac-graph/tests/graphs/unknown-package/error.txt +++ /dev/null @@ -1,4 +0,0 @@ -failed to add import node 0 for test case `unknown-package` - -Caused by: - package `foo:bar@1.2.3` has not been registered with the graph (use the `CompositionGraph::register_package` method) diff --git a/crates/wac-graph/tests/graphs/unknown-package/graph.json b/crates/wac-graph/tests/graphs/unknown-package/graph.json deleted file mode 100644 index 77a3ab2..0000000 --- a/crates/wac-graph/tests/graphs/unknown-package/graph.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "nodes": [ - { - "type": "import", - "name": "foo", - "path": "foo:bar@1.2.3/baz" - } - ] -} diff --git a/crates/wac-graph/tests/graphs/unqualified-package-path/error.txt b/crates/wac-graph/tests/graphs/unqualified-package-path/error.txt deleted file mode 100644 index beb2ce0..0000000 --- a/crates/wac-graph/tests/graphs/unqualified-package-path/error.txt +++ /dev/null @@ -1,4 +0,0 @@ -failed to add import node 0 for test case `unqualified-package-path` - -Caused by: - package path `nope` is not a fully-qualified package path diff --git a/crates/wac-graph/tests/graphs/unqualified-package-path/graph.json b/crates/wac-graph/tests/graphs/unqualified-package-path/graph.json deleted file mode 100644 index 6836fd1..0000000 --- a/crates/wac-graph/tests/graphs/unqualified-package-path/graph.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "nodes": [ - { - "type": "import", - "name": "foo", - "path": "nope" - } - ] -} diff --git a/crates/wac-parser/src/ast.rs b/crates/wac-parser/src/ast.rs index 61fb04b..0353832 100644 --- a/crates/wac-parser/src/ast.rs +++ b/crates/wac-parser/src/ast.rs @@ -1,9 +1,14 @@ //! Module for the AST implementation. -use crate::lexer::{self, Lexer, LexerResult, Token}; +use crate::{ + lexer::{self, Lexer, LexerResult, Token}, + resolution::{AstResolver, Resolution, ResolutionResult}, +}; +use indexmap::IndexMap; use miette::{Diagnostic, SourceSpan}; use serde::Serialize; use std::fmt; +use wac_graph::types::BorrowedPackageKey; mod export; mod expr; @@ -347,6 +352,16 @@ impl<'a> Document<'a> { statements, }) } + + /// Resolves the document. + /// + /// The returned resolution contains an encodable composition graph. + pub fn resolve( + &self, + packages: IndexMap, Vec>, + ) -> ResolutionResult { + AstResolver::new(self).resolve(packages) + } } /// Represents a statement in the AST. diff --git a/crates/wac-parser/src/lexer.rs b/crates/wac-parser/src/lexer.rs index 03be667..33265cd 100644 --- a/crates/wac-parser/src/lexer.rs +++ b/crates/wac-parser/src/lexer.rs @@ -471,7 +471,7 @@ impl<'a> Lexer<'a> { // we can't properly show the "end of input" span. // For now, have the span point at the last byte in the source. // See: https://github.com/zkat/miette/issues/219 - span.start -= 1; + span.start = span.start.saturating_sub(1); span.end = span.start + 1; } diff --git a/crates/wac-parser/src/lib.rs b/crates/wac-parser/src/lib.rs index c786f08..f054676 100644 --- a/crates/wac-parser/src/lib.rs +++ b/crates/wac-parser/src/lib.rs @@ -2,8 +2,8 @@ #![deny(missing_docs)] -pub mod ast; +mod ast; pub mod lexer; -mod resolution; +pub mod resolution; -pub use resolution::*; +pub use ast::*; diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index b6287da..a537144 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -1,93 +1,31 @@ -//! Module for resolving WAC documents. +//! Module for resolving WAC ASTs. -use self::{ast::AstResolver, encoding::Encoder, package::Package}; -use id_arena::{Arena, Id}; -use indexmap::IndexMap; +use crate::{ast, Document}; +use indexmap::{IndexMap, IndexSet}; use miette::{Diagnostic, SourceSpan}; use semver::Version; -use serde::{Serialize, Serializer}; -use std::{fmt, sync::Arc}; - -mod ast; -mod encoding; -mod package; -mod types; - -pub use encoding::EncodingOptions; -pub use package::PackageKey; -pub use types::*; - -fn serialize_arena(arena: &Arena, serializer: S) -> std::result::Result -where - S: Serializer, - T: Serialize, -{ - use serde::ser::SerializeSeq; - - let mut s = serializer.serialize_seq(Some(arena.len()))?; - for (_, e) in arena.iter() { - s.serialize_element(e)?; - } - - s.end() -} - -fn serialize_id_value_map( - map: &IndexMap>, - serializer: S, -) -> std::result::Result -where - S: Serializer, - K: Serialize, - T: Serialize, -{ - use serde::ser::SerializeMap; - - let mut s = serializer.serialize_map(Some(map.len()))?; - for (k, v) in map { - s.serialize_entry(k, &v.index())?; - } - - s.end() -} - -fn serialize_id(id: &Id, serializer: S) -> std::result::Result -where - S: Serializer, - T: Serialize, -{ - id.index().serialize(serializer) -} - -fn serialize_optional_id( - id: &Option>, - serializer: S, -) -> std::result::Result -where - S: Serializer, - T: Serialize, -{ - match id { - Some(id) => serializer.serialize_some(&id.index()), - None => serializer.serialize_none(), - } -} - -/// Represents a kind of an extern item. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum ExternKind { - /// The item is an import. - Import, - /// The item is an export. - Export, -} +use std::{ + collections::{HashMap, HashSet}, + fmt, +}; +use wac_graph::{ + types::{ + BorrowedPackageKey, DefinedType, Enum, ExternKind, Flags, FuncKind, FuncResult, FuncType, + FuncTypeId, Interface, InterfaceId, ItemKind, Package, PackageKey, PrimitiveType, Record, + Resource, ResourceAlias, ResourceId, SubtypeChecker, Type, UsedType, ValueType, Variant, + World, WorldId, + }, + CompositionGraph, DefineTypeError, EncodeError, EncodeOptions, ExportError, ImportError, + InstantiationArgumentError, NodeId, NodeKind, PackageId, Processor, +}; +use wasmparser::BinaryReaderError; -impl fmt::Display for ExternKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Import => write!(f, "import"), - Self::Export => write!(f, "export"), - } +fn method_extern_name(resource: &str, name: &str, kind: FuncKind) -> String { + match kind { + FuncKind::Free => unreachable!("a resource method cannot be a free function"), + FuncKind::Method => format!("[method]{resource}.{name}"), + FuncKind::Static => format!("[static]{resource}.{name}"), + FuncKind::Constructor => format!("[constructor]{resource}"), } } @@ -436,15 +374,15 @@ pub enum Error { source: anyhow::Error, }, /// A package is missing an export. - #[error("{prev}package `{name}` has no export named `{export}`", prev = ParentPathDisplay(.kind, .path))] + #[error("{prev}package `{package}` has no export named `{export}`", prev = ParentPathDisplay(.kind, .path))] PackageMissingExport { - /// The name of the package. - name: String, - /// The name of the export. + /// The package missing the export. + package: String, + /// The name of the missing export. export: String, - /// The kind of the item being accessed. + /// The kind of the item missing the export. kind: Option, - /// The path to the current item. + /// The path to the item missing the export. path: String, /// The span where the error occurred. #[label(primary, "unknown export `{export}`")] @@ -506,43 +444,28 @@ pub enum Error { #[label(primary, "missing argument `{name}`")] span: SourceSpan, }, - /// An instantiation argument conflict was encountered. - #[error("implicit instantiation argument `{name}` ({kind}) conflicts with an explicit import")] - InstantiationArgConflict { - /// The name of the argument. - name: String, - /// The kind of the argument. - kind: String, - /// The span where the error occurred. - #[label(primary, "conflicting instantiation here")] - span: SourceSpan, - /// The span where the explicit import occurred. - #[label("explicit import here")] - import: SourceSpan, - }, - /// An explicitly imported item conflicts with an implicit import from an instantiation. - #[error("import name `{name}` conflicts with an instance that was implicitly imported by an instantiation of `{package}`")] + /// An explicitly imported item conflicts with an item that was implicitly + /// imported from an instantiation. + #[error("import `{name}` conflicts with an item that was implicitly imported by an instantiation of `{package}`")] ImportConflict { /// The name of the argument. name: String, /// The package that first introduced the import. - package: String, - /// The span where the error occurred. - #[label(primary, "conflicting import here")] - span: SourceSpan, - /// The span where the previous instantiation occurred. - #[label("previous instantiation here")] + package: PackageKey, + /// The span of the conflicting import. + #[label(primary, "explicit import here")] + import: SourceSpan, + /// The span of the conflicting instantiation. + #[label("conflicting instantiation here")] instantiation: SourceSpan, }, /// An instantiation argument conflict was encountered. - #[error("failed to merge instantiation argument `{name}` with an instance that was implicitly imported by the instantiation of `{package}`")] + #[error( + "failed to merge the type definition for implicit import `{name}` due to conflicting types" + )] InstantiationArgMergeFailure { /// The name of the argument. name: String, - /// The name of the package that first introduced the import. - package: String, - /// The kind of the argument. - kind: String, /// The span where the error occurred. #[label(primary, "conflicting instantiation here")] span: SourceSpan, @@ -553,22 +476,6 @@ pub enum Error { #[source] source: anyhow::Error, }, - /// An unmergeable instantiation argument was encountered. - #[error("implicit instantiation argument `{name}` ({kind}) conflicts with an implicitly imported argument from the instantiation of `{package}`")] - UnmergeableInstantiationArg { - /// The name of the argument. - name: String, - /// The name of the package that first introduced the import. - package: String, - /// The kind of the argument. - kind: String, - /// The span where the error occurred. - #[label(primary, "conflicting instantiation here")] - span: SourceSpan, - /// The span where the previous instantiation occurred. - #[label("previous instantiation here")] - instantiation: SourceSpan, - }, /// An operation was performed on something that was not an instance. #[error("an instance is required to perform {operation}")] NotAnInstance { @@ -596,6 +503,18 @@ pub enum Error { #[label(primary, "an `as` clause is required")] span: SourceSpan, }, + /// A type declaration conflicts with an export of the same name + #[error("type declaration `{name}` conflicts with a previous export of the same name")] + DeclarationConflict { + /// The name of the type. + name: String, + /// The span where the error occurred. + #[label(primary, "conflicting type declaration `{name}`")] + span: SourceSpan, + /// The span of the previous export. + #[label("previous export is here")] + export: SourceSpan, + }, /// An export conflicts with a definition. #[error("export `{name}` conflicts with {kind} definition")] ExportConflict { @@ -721,187 +640,2147 @@ pub enum Error { #[source] source: anyhow::Error, }, + /// The encoding of the graph failed validation. + #[error("the encoding of the graph failed validation")] + ValidationFailure { + /// The source of the validation error. + #[source] + source: BinaryReaderError, + }, } /// Represents a resolution result. pub type ResolutionResult = std::result::Result; -/// Represents the kind of an item. -#[derive(Debug, Clone, Copy, Serialize, Hash, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum ItemKind { - /// The item is a type. +/// Represents a resolution of an WAC document. +pub struct Resolution<'a> { + /// The document the resolution is from. + document: &'a Document<'a>, + /// The resolved composition graph. + graph: CompositionGraph, + /// The map from node id to import span. + import_spans: HashMap, + /// The map from node id to instantiation span. + instantiation_spans: HashMap, +} + +impl<'a> Resolution<'a> { + /// Gets the document that was resolved. + pub fn document(&self) -> &Document { + self.document + } + + /// Gets the resolved composition graph. + pub fn graph(&self) -> &CompositionGraph { + &self.graph + } + + /// Encodes the resolution into a component. + /// + /// This method handles translating encoding errors into resolution + /// errors that contain source span information. + pub fn encode(&self, mut options: EncodeOptions) -> ResolutionResult> { + options.processor = options.processor.or(Some(Processor { + name: env!("CARGO_PKG_NAME"), + version: env!("CARGO_PKG_VERSION"), + })); + + self.graph.encode(options).map_err(|e| match e { + EncodeError::ValidationFailure { source } => Error::ValidationFailure { source }, + EncodeError::GraphContainsCycle { .. } => panic!("AST contained a cycle"), + EncodeError::ImplicitImportConflict { + import, + instantiation, + package, + name, + } => Error::ImportConflict { + name, + package, + import: self.import_spans[&import], + instantiation: self.instantiation_spans[&instantiation], + }, + EncodeError::ImportTypeMergeConflict { + import, + first, + second, + source, + } => Error::InstantiationArgMergeFailure { + name: import, + span: self.instantiation_spans[&second], + instantiation: self.instantiation_spans[&first], + source, + }, + }) + } + + /// Consumes the resolution and returns the underlying composition graph. + /// + /// Note that encoding the returned graph may still fail as a result of + /// merging implicit instantiation arguments. + pub fn into_graph(self) -> CompositionGraph { + self.graph + } +} + +#[derive(Debug, Copy, Clone)] +enum Item { + /// The item is a node in the composition graph. + Node(NodeId), + /// The item is a used type within an interface or world scope. + Use(Type), + /// The item is a type declaration not at root scope. + /// + /// At root scope, a type declaration is added to the graph. Type(Type), - /// The item is a resource. - Resource(#[serde(serialize_with = "serialize_id")] ResourceId), - /// The item is a function. - Func(#[serde(serialize_with = "serialize_id")] FuncId), - /// The item is a component instance. - Instance(#[serde(serialize_with = "serialize_id")] InterfaceId), - /// The item is an instantiation of a package. - Instantiation(#[serde(serialize_with = "serialize_id")] PackageId), - /// The item is a component. - Component(#[serde(serialize_with = "serialize_id")] WorldId), - /// The item is a core module. - Module(#[serde(serialize_with = "serialize_id")] ModuleId), - /// The item is a value. - Value(ValueType), } -impl ItemKind { - fn ty(&self) -> Option { +impl Item { + fn kind(&self, graph: &CompositionGraph) -> ItemKind { match self { - ItemKind::Type(ty) => Some(*ty), - ItemKind::Func(id) => Some(Type::Func(*id)), - ItemKind::Instance(id) => Some(Type::Interface(*id)), - ItemKind::Component(id) => Some(Type::World(*id)), - ItemKind::Module(id) => Some(Type::Module(*id)), - ItemKind::Value(ty) => Some(Type::Value(*ty)), - ItemKind::Resource(_) | ItemKind::Instantiation(_) => None, + Self::Node(id) => graph[*id].item_kind(), + Self::Use(ty) => ItemKind::Type(*ty), + Self::Type(ty) => ItemKind::Type(*ty), } } - fn as_str(&self, definitions: &Definitions) -> &'static str { + fn node(&self) -> NodeId { match self { - ItemKind::Resource(_) => "resource", - ItemKind::Func(_) => "function", - ItemKind::Type(ty) => ty.as_str(definitions), - ItemKind::Instance(_) | ItemKind::Instantiation(_) => "instance", - ItemKind::Component(_) => "component", - ItemKind::Module(_) => "module", - ItemKind::Value(_) => "value", + Self::Node(node) => *node, + _ => panic!("the item is not a node"), } } +} - /// Promote function types, instance types, and component types - /// to functions, instances, and components - fn promote(&self) -> Self { - match *self { - ItemKind::Type(Type::Func(id)) => ItemKind::Func(id), - ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id), - ItemKind::Type(Type::World(id)) => ItemKind::Component(id), - kind => kind, - } +#[derive(Default)] +struct Scope(IndexMap); + +impl Scope { + fn get(&self, name: &str) -> Option<(Item, SourceSpan)> { + self.0.get(name).copied() } } -/// Represents a item defining a type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Definition { - /// The name of the type. - pub name: String, - /// The kind of the item. - pub kind: ItemKind, +#[derive(Default)] +struct State { + scopes: Vec, + current: Scope, + graph: CompositionGraph, + instantiation_spans: HashMap, + import_spans: HashMap, + export_spans: HashMap, } -/// Represents an import item. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Import { - /// The import name. - pub name: String, - /// The kind of the import. - pub kind: ItemKind, -} +impl State { + fn register_name(&mut self, id: ast::Ident, item: Item) -> ResolutionResult<()> { + log::debug!( + "registering name `{id}` in the current scope", + id = id.string + ); -/// Represents an instance export alias item. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Alias { - /// The instance item being aliased. - #[serde(serialize_with = "serialize_id")] - pub item: ItemId, - /// The export name. - pub export: String, - /// The kind of the exported item. - pub kind: ItemKind, -} + if let Some((_, previous)) = self.current.0.insert(id.string.to_owned(), (item, id.span)) { + return Err(Error::DuplicateName { + name: id.string.to_owned(), + span: id.span, + previous, + }); + } -/// Represents an instantiated package item. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Instantiation { - /// The package being instantiated. - #[serde(serialize_with = "serialize_id")] - pub package: PackageId, - /// The arguments of the instantiation. - #[serde(serialize_with = "serialize_id_value_map")] - pub arguments: IndexMap, -} + if let Item::Node(node) = item { + // Use only the first name encountered for the node, ignoring + // aliasing in the form of `let x = y;` + if self.graph[node].name().is_none() { + self.graph.set_node_name(node, id.string.to_owned()); + } + } -/// Represents a composition item. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum Item { - /// The item comes from a use statement. - Use(ItemKind), - /// The item comes from a local definition. - Definition(Definition), - /// The item comes from an import, - Import(Import), - /// The item comes from an instance alias. - Alias(Alias), - /// The item comes from an instantiation. - Instantiation(Instantiation), -} + Ok(()) + } -impl Item { - /// Returns the kind of the item. - pub fn kind(&self) -> ItemKind { - match self { - Self::Use(kind) => *kind, - Self::Definition(definition) => definition.kind, - Self::Import(import) => import.kind, - Self::Alias(alias) => alias.kind, - Self::Instantiation(instantiation) => ItemKind::Instantiation(instantiation.package), + /// Gets an item by identifier from the root scope. + fn root_item(&self, id: &ast::Ident) -> ResolutionResult<(Item, SourceSpan)> { + self.root_scope() + .get(id.string) + .ok_or(Error::UndefinedName { + name: id.string.to_owned(), + span: id.span, + }) + } + + /// Gets a node from the local (current) scope. + fn local_item(&self, id: &ast::Ident) -> ResolutionResult<(Item, SourceSpan)> { + self.current.get(id.string).ok_or(Error::UndefinedName { + name: id.string.to_owned(), + span: id.span, + }) + } + + /// Gets an item by identifier from the local (current) scope or the root scope. + fn local_or_root_item(&self, id: &ast::Ident) -> ResolutionResult<(Item, SourceSpan)> { + if self.scopes.is_empty() { + return self.local_item(id); } + + if let Some((item, span)) = self.current.get(id.string) { + return Ok((item, span)); + } + + self.root_item(id) } -} -/// An identifier for items in a composition. -pub type ItemId = Id; - -/// An identifier for foreign packages in a composition. -pub type PackageId = Id; - -/// Represents a composition. -/// -/// A composition may be encoded into a WebAssembly component. -#[derive(Debug, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Composition { - /// The package name of the composition. - pub package: String, - /// The package version of the composition. - pub version: Option, - /// The definitions in the composition. - pub definitions: Definitions, - /// The foreign packages referenced in the composition. - #[serde(serialize_with = "serialize_arena")] - pub packages: Arena, - /// The items in the composition. - #[serde(serialize_with = "serialize_arena")] - pub items: Arena, - /// The map of import names to items. - #[serde(serialize_with = "serialize_id_value_map")] - pub imports: IndexMap, - /// The map of export names to items. - #[serde(serialize_with = "serialize_id_value_map")] - pub exports: IndexMap, + fn push_scope(&mut self) { + log::debug!("pushing new name scope"); + self.scopes.push(std::mem::take(&mut self.current)); + } + + fn pop_scope(&mut self) -> Scope { + log::debug!("popping name scope"); + std::mem::replace(&mut self.current, self.scopes.pop().unwrap()) + } + + fn root_scope(&self) -> &Scope { + self.scopes.first().unwrap_or(&self.current) + } } -impl Composition { - /// Creates a new composition from an AST document. - pub fn from_ast<'a>( - document: &'a crate::ast::Document<'a>, - packages: IndexMap, Arc>>, - ) -> ResolutionResult { - AstResolver::new(document, packages).resolve() +pub(crate) struct AstResolver<'a>(&'a Document<'a>); + +impl<'a> AstResolver<'a> { + pub fn new(ast: &'a Document) -> Self { + Self(ast) + } + + pub fn resolve( + mut self, + mut packages: IndexMap, Vec>, + ) -> ResolutionResult> { + let mut state = State::default(); + + for stmt in &self.0.statements { + match stmt { + ast::Statement::Import(i) => self.import_statement(&mut state, i, &mut packages)?, + ast::Statement::Type(t) => self.type_statement(&mut state, t, &mut packages)?, + ast::Statement::Let(l) => self.let_statement(&mut state, l, &mut packages)?, + ast::Statement::Export(e) => self.export_statement(&mut state, e, &mut packages)?, + } + } + + // If there's a target world in the directive, validate the composition + // conforms to the target + if let Some(path) = &self.0.directive.targets { + log::debug!("validating composition targets world `{}`", path.string); + let item = self.resolve_package_path(&mut state, path, &mut packages)?; + match item { + ItemKind::Type(Type::World(world)) => { + self.validate_target(&state, path, world)?; + } + _ => { + return Err(Error::NotWorld { + name: path.string.to_owned(), + kind: item.desc(state.graph.types()).to_owned(), + span: path.span, + }); + } + } + } + + Ok(Resolution { + document: self.0, + graph: state.graph, + import_spans: state.import_spans, + instantiation_spans: state.instantiation_spans, + }) + } + + fn import_statement( + &mut self, + state: &mut State, + stmt: &'a ast::ImportStatement<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult<()> { + log::debug!( + "resolving import statement for id `{id}`", + id = stmt.id.string + ); + + // Determine the import name to use + let (name, span) = match &stmt.name { + Some(name) => (name.as_str(), name.span()), + None => match &stmt.ty { + ast::ImportType::Package(p) => (p.string, p.span), + ast::ImportType::Func(_) | ast::ImportType::Interface(_) => { + (stmt.id.string, stmt.id.span) + } + ast::ImportType::Ident(id) => { + let (item, _) = state.local_item(id)?; + match item.kind(&state.graph) { + ItemKind::Instance(id) => match &state.graph.types()[id].id { + Some(id) => (id.as_str(), stmt.id.span), + None => (stmt.id.string, stmt.id.span), + }, + ItemKind::Component(id) => match &state.graph.types()[id].id { + Some(id) => (id.as_str(), stmt.id.span), + None => (stmt.id.string, stmt.id.span), + }, + ItemKind::Type(_) + | ItemKind::Func(_) + | ItemKind::Module(_) + | ItemKind::Value(_) => (stmt.id.string, stmt.id.span), + } + } + }, + }; + + let map_import_error = |state: &State, e: ImportError, span: SourceSpan| match e { + ImportError::ImportAlreadyExists { name, node } => Error::DuplicateExternName { + name, + kind: ExternKind::Import, + span, + previous: state.import_spans[&node], + help: if stmt.name.is_some() { + None + } else { + Some("consider using an `as` clause to use a different name".into()) + }, + }, + ImportError::InvalidImportName { name, source } => Error::InvalidExternName { + name, + kind: ExternKind::Import, + span, + source, + }, + }; + + // Determine the kind for the item to import + let name = name.to_string(); + let kind = match &stmt.ty { + ast::ImportType::Package(p) => self.resolve_package_path(state, p, packages)?, + ast::ImportType::Func(ty) => ItemKind::Func(self.func_type( + state, + &ty.params, + &ty.results, + FuncKind::Free, + None, + )?), + ast::ImportType::Interface(i) => { + ItemKind::Instance(self.inline_interface(state, i, packages)?) + } + ast::ImportType::Ident(id) => state.local_item(id)?.0.kind(&state.graph), + }; + + // Import the item + log::debug!("adding import `{name}` to the graph"); + let node = state + .graph + .import(name, kind.promote()) + .map_err(|e| map_import_error(state, e, span))?; + + state.import_spans.insert(node, span); + + // Register the local name + state.register_name(stmt.id, Item::Node(node)) + } + + fn type_statement( + &mut self, + state: &mut State, + stmt: &'a ast::TypeStatement<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult<()> { + log::debug!("resolving type statement"); + + let (id, ty) = match stmt { + ast::TypeStatement::Interface(i) => (i.id, self.interface_decl(state, i, packages)?), + ast::TypeStatement::World(w) => (w.id, self.world_decl(state, w, packages)?), + ast::TypeStatement::Type(t) => ( + *t.id(), + match t { + ast::TypeDecl::Variant(v) => self.variant_decl(state, v, false)?, + ast::TypeDecl::Record(r) => self.record_decl(state, r, false)?, + ast::TypeDecl::Flags(f) => self.flags_decl(state, f, false)?, + ast::TypeDecl::Enum(e) => self.enum_decl(state, e, false)?, + ast::TypeDecl::Alias(a) => self.type_alias(state, a, false)?, + }, + ), + }; + + log::debug!("adding type definition `{id}` to the graph", id = id.string); + let node = state + .graph + .define_type(id.string, ty) + .map_err(|e| match e { + DefineTypeError::TypeAlreadyDefined => panic!("type should not be already defined"), + DefineTypeError::CannotDefineResource => panic!("type should not be a resource"), + DefineTypeError::InvalidExternName { .. } => panic!("parsed an invalid type name"), + DefineTypeError::ExportConflict { name } => Error::DeclarationConflict { + name, + span: id.span, + export: state.export_spans[&state.graph.get_export(id.string).unwrap()], + }, + })?; + + state.export_spans.insert(node, id.span); + state.register_name(id, Item::Node(node)) } - /// Encode the composition into a WebAssembly component. - pub fn encode(&self, options: EncodingOptions) -> anyhow::Result> { - Encoder::new(self, options).encode() + fn let_statement( + &mut self, + state: &mut State, + stmt: &'a ast::LetStatement<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult<()> { + log::debug!("resolving let statement for id `{id}`", id = stmt.id.string); + let item = self.expr(state, &stmt.expr, packages)?; + state.register_name(stmt.id, item) + } + + fn export_statement( + &mut self, + state: &mut State, + stmt: &'a ast::ExportStatement<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult<()> { + log::debug!("resolving export statement"); + + let item = self.expr(state, &stmt.expr, packages)?; + match &stmt.options { + ast::ExportOptions::None => { + let name = self + .infer_export_name(state, item) + .ok_or(Error::ExportRequiresAs { + span: stmt.expr.span, + })?; + + self.export_item(state, item, name.to_owned(), stmt.expr.span, true)?; + } + ast::ExportOptions::Spread(span) => { + let exports = match item.kind(&state.graph) { + ItemKind::Instance(id) => state.graph.types()[id] + .exports + .keys() + .cloned() + .collect::>(), + kind => { + return Err(Error::NotAnInstance { + kind: kind.desc(state.graph.types()).to_string(), + operation: InstanceOperation::Spread, + span: stmt.expr.span, + }) + } + }; + + let mut exported = false; + for name in exports { + // Only export the item if it another item with the same name + // has not been already exported + if state.graph.get_export(&name).is_some() { + continue; + } + + let item = self + .alias_export( + state, + item, + &name, + stmt.expr.span, + InstanceOperation::Spread, + )? + .expect("expected a matching export name"); + + self.export_item(state, item, name, *span, false)?; + exported = true; + } + + if !exported { + return Err(Error::SpreadExportNoEffect { + span: stmt.expr.span, + }); + } + } + ast::ExportOptions::Rename(name) => { + self.export_item(state, item, name.as_str().to_owned(), name.span(), false)?; + } + } + + Ok(()) + } + + fn infer_export_name<'b>(&self, state: &'b State, item: Item) -> Option<&'b str> { + // If the item is an instance with an id, try the id. + if let ItemKind::Instance(id) = item.kind(&state.graph) { + if let Some(id) = &state.graph.types()[id].id { + return Some(id); + } + } + + // If the item comes from an import or an alias, try the name associated with it + let node = item.node(); + if let Some(name) = state.graph.get_import_name(node) { + Some(name) + } else if let Some((_, name)) = state.graph.get_alias_source(node) { + Some(name) + } else { + None + } + } + + fn export_item( + &self, + state: &mut State, + item: Item, + name: String, + span: SourceSpan, + show_hint: bool, + ) -> Result<(), Error> { + if let Some((item, prev_span)) = state.root_scope().get(&name) { + let node = &state.graph[item.node()]; + if let NodeKind::Definition = node.kind() { + return Err(Error::ExportConflict { + name, + kind: node.item_kind().desc(state.graph.types()).to_string(), + span, + definition: prev_span, + help: if !show_hint { + None + } else { + Some("consider using an `as` clause to use a different name".into()) + }, + }); + } + } + + let node = item.node(); + state.graph.export(item.node(), name).map_err(|e| match e { + ExportError::ExportAlreadyExists { name, node } => Error::DuplicateExternName { + name, + kind: ExternKind::Export, + span, + previous: state.export_spans[&node], + help: if !show_hint { + None + } else { + Some("consider using an `as` clause to use a different name".into()) + }, + }, + wac_graph::ExportError::InvalidExportName { name, source } => { + Error::InvalidExternName { + name, + kind: ExternKind::Export, + span, + source, + } + } + })?; + + state.export_spans.insert(node, span); + Ok(()) + } + + fn variant_decl( + &mut self, + state: &mut State, + decl: &ast::VariantDecl<'a>, + register_name: bool, + ) -> ResolutionResult { + log::debug!( + "resolving variant declaration for id `{id}`", + id = decl.id.string + ); + + let mut cases = IndexMap::new(); + for case in &decl.cases { + let ty = case.ty.as_ref().map(|ty| Self::ty(state, ty)).transpose()?; + if cases.insert(case.id.string.into(), ty).is_some() { + return Err(Error::DuplicateVariantCase { + case: case.id.string.to_string(), + name: decl.id.string.to_string(), + span: case.id.span, + }); + } + } + + let ty = Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Variant(Variant { cases })), + )); + + if register_name { + state.register_name(decl.id, Item::Type(ty))?; + } + + Ok(ty) + } + + fn record_decl( + &mut self, + state: &mut State, + decl: &ast::RecordDecl<'a>, + register_name: bool, + ) -> ResolutionResult { + log::debug!( + "resolving record declaration for id `{id}`", + id = decl.id.string + ); + + let mut fields = IndexMap::new(); + for field in &decl.fields { + let ty = Self::ty(state, &field.ty)?; + if fields.insert(field.id.string.into(), ty).is_some() { + return Err(Error::DuplicateRecordField { + field: field.id.string.to_string(), + name: decl.id.string.to_string(), + span: field.id.span, + }); + } + } + + let ty = Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Record(Record { fields })), + )); + + if register_name { + state.register_name(decl.id, Item::Type(ty))?; + } + + Ok(ty) + } + + fn flags_decl( + &mut self, + state: &mut State, + decl: &ast::FlagsDecl<'a>, + register_name: bool, + ) -> ResolutionResult { + log::debug!( + "resolving flags declaration for id `{id}`", + id = decl.id.string + ); + + let mut flags = IndexSet::new(); + for flag in &decl.flags { + if !flags.insert(flag.id.string.into()) { + return Err(Error::DuplicateFlag { + flag: flag.id.string.to_string(), + name: decl.id.string.to_string(), + span: flag.id.span, + }); + } + } + + let ty = Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Flags(Flags(flags))), + )); + + if register_name { + state.register_name(decl.id, Item::Type(ty))?; + } + + Ok(ty) + } + + fn enum_decl( + &mut self, + state: &mut State, + decl: &ast::EnumDecl<'a>, + register_name: bool, + ) -> ResolutionResult { + log::debug!( + "resolving enum declaration for id `{id}`", + id = decl.id.string + ); + + let mut cases = IndexSet::new(); + for case in &decl.cases { + if !cases.insert(case.id.string.to_owned()) { + return Err(Error::DuplicateEnumCase { + case: case.id.string.to_string(), + name: decl.id.string.to_string(), + span: case.id.span, + }); + } + } + + let ty = Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Enum(Enum(cases))), + )); + + if register_name { + state.register_name(decl.id, Item::Type(ty))?; + } + + Ok(ty) + } + + fn type_alias( + &mut self, + state: &mut State, + alias: &ast::TypeAlias<'a>, + register_name: bool, + ) -> ResolutionResult { + log::debug!("resolving type alias for id `{id}`", id = alias.id.string); + + let ty = match &alias.kind { + ast::TypeAliasKind::Func(f) => { + Type::Func(self.func_type(state, &f.params, &f.results, FuncKind::Free, None)?) + } + ast::TypeAliasKind::Type(ty) => match ty { + ast::Type::Ident(id) => { + let (item, _) = state.local_item(id)?; + match item.kind(&state.graph) { + ItemKind::Type(Type::Resource(id)) => { + let owner = state.graph.types()[id].alias.and_then(|a| a.owner); + Type::Resource(state.graph.types_mut().add_resource(Resource { + name: alias.id.string.to_owned(), + alias: Some(ResourceAlias { owner, source: id }), + })) + } + ItemKind::Type(Type::Value(ty)) => Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Alias(ty)), + )), + ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => { + let ty = state.graph.types()[id].clone(); + Type::Func(state.graph.types_mut().add_func_type(ty)) + } + kind => { + return Err(Error::InvalidAliasType { + name: id.string.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + span: id.span, + }); + } + } + } + _ => { + let ty = Self::ty(state, ty)?; + Type::Value(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Alias(ty)), + )) + } + }, + }; + + if register_name { + state.register_name(alias.id, Item::Type(ty))?; + } + + Ok(ty) + } + + fn func_type_ref( + &mut self, + state: &mut State, + r: &ast::FuncTypeRef<'a>, + kind: FuncKind, + ) -> ResolutionResult { + match r { + ast::FuncTypeRef::Func(ty) => { + self.func_type(state, &ty.params, &ty.results, kind, None) + } + ast::FuncTypeRef::Ident(id) => { + let (item, _) = state.local_item(id)?; + match item.kind(&state.graph) { + ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => Ok(id), + kind => Err(Error::NotFuncType { + name: id.string.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + span: id.span, + }), + } + } + } + } + + fn ty(state: &mut State, ty: &ast::Type<'a>) -> ResolutionResult { + match ty { + ast::Type::U8(_) => Ok(ValueType::Primitive(PrimitiveType::U8)), + ast::Type::S8(_) => Ok(ValueType::Primitive(PrimitiveType::S8)), + ast::Type::U16(_) => Ok(ValueType::Primitive(PrimitiveType::U16)), + ast::Type::S16(_) => Ok(ValueType::Primitive(PrimitiveType::S16)), + ast::Type::U32(_) => Ok(ValueType::Primitive(PrimitiveType::U32)), + ast::Type::S32(_) => Ok(ValueType::Primitive(PrimitiveType::S32)), + ast::Type::U64(_) => Ok(ValueType::Primitive(PrimitiveType::U64)), + ast::Type::S64(_) => Ok(ValueType::Primitive(PrimitiveType::S64)), + ast::Type::F32(_) => Ok(ValueType::Primitive(PrimitiveType::F32)), + ast::Type::F64(_) => Ok(ValueType::Primitive(PrimitiveType::F64)), + ast::Type::Char(_) => Ok(ValueType::Primitive(PrimitiveType::Char)), + ast::Type::Bool(_) => Ok(ValueType::Primitive(PrimitiveType::Bool)), + ast::Type::String(_) => Ok(ValueType::Primitive(PrimitiveType::String)), + ast::Type::Tuple(v, _) => { + let tuple = DefinedType::Tuple( + v.iter() + .map(|ty| Self::ty(state, ty)) + .collect::>()?, + ); + + Ok(ValueType::Defined( + state.graph.types_mut().add_defined_type(tuple), + )) + } + ast::Type::List(ty, _) => { + let ty = Self::ty(state, ty)?; + Ok(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::List(ty)), + )) + } + ast::Type::Option(ty, _) => { + let ty = Self::ty(state, ty)?; + Ok(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Option(ty)), + )) + } + ast::Type::Result { ok, err, .. } => { + let ok = ok.as_ref().map(|t| Self::ty(state, t)).transpose()?; + let err = err.as_ref().map(|t| Self::ty(state, t)).transpose()?; + Ok(ValueType::Defined( + state + .graph + .types_mut() + .add_defined_type(DefinedType::Result { ok, err }), + )) + } + ast::Type::Borrow(id, _) => { + let (item, _) = state.local_item(id)?; + let kind = item.kind(&state.graph); + if let ItemKind::Type(Type::Resource(id)) = kind { + return Ok(ValueType::Borrow(id)); + } + + Err(Error::NotResourceType { + name: id.string.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + span: id.span, + }) + } + ast::Type::Ident(id) => { + let (item, _) = state.local_item(id)?; + let kind = item.kind(&state.graph); + match kind { + ItemKind::Type(Type::Resource(id)) => Ok(ValueType::Own(id)), + ItemKind::Type(Type::Value(ty)) => Ok(ty), + _ => Err(Error::NotValueType { + name: id.string.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + span: id.span, + }), + } + } + } + } + + fn id(&self, name: &str) -> String { + format!( + "{pkg}/{name}{version}", + pkg = self.0.directive.package.name, + version = if let Some(version) = &self.0.directive.package.version { + format!("@{version}") + } else { + String::new() + } + ) + } + + fn interface_decl( + &mut self, + state: &mut State, + decl: &'a ast::InterfaceDecl<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + log::debug!( + "resolving interface declaration for id `{id}`", + id = decl.id.string + ); + state.push_scope(); + + let mut ty = Interface { + id: Some(self.id(decl.id.string)), + uses: Default::default(), + exports: Default::default(), + }; + + self.interface_items(state, Some(decl.id.string), &decl.items, packages, &mut ty)?; + + state.pop_scope(); + + Ok(Type::Interface(state.graph.types_mut().add_interface(ty))) + } + + fn world_decl( + &mut self, + state: &mut State, + decl: &'a ast::WorldDecl<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + log::debug!( + "resolving world declaration for id `{id}`", + id = decl.id.string + ); + state.push_scope(); + + let mut ty = World { + id: Some(self.id(decl.id.string)), + uses: Default::default(), + imports: Default::default(), + exports: Default::default(), + }; + + self.world_items(state, decl.id.string, &decl.items, packages, &mut ty)?; + + state.pop_scope(); + + Ok(Type::World(state.graph.types_mut().add_world(ty))) + } + + fn world_items( + &mut self, + state: &mut State, + world: &'a str, + items: &'a [ast::WorldItem<'a>], + packages: &mut IndexMap, Vec>, + ty: &mut World, + ) -> ResolutionResult<()> { + let mut includes = Vec::new(); + for item in items { + match item { + ast::WorldItem::Use(u) => { + self.use_type(state, u, &mut ty.uses, &mut ty.imports, packages, true)? + } + ast::WorldItem::Type(decl) => { + self.item_type_decl(state, decl, &mut ty.imports)?; + } + ast::WorldItem::Import(i) => { + self.world_item_path(state, &i.path, ExternKind::Import, world, packages, ty)? + } + ast::WorldItem::Export(e) => { + self.world_item_path(state, &e.path, ExternKind::Export, world, packages, ty)? + } + ast::WorldItem::Include(i) => { + // We delay processing includes until after all other items have been processed + includes.push(i); + } + } + } + + // Process the includes now that all imports and exports have been processed. + // This allows us to detect conflicts only in explicitly defined items. + for i in includes { + self.world_include(state, i, world, packages, ty)?; + } + + Ok(()) + } + + fn world_item_path( + &mut self, + state: &mut State, + path: &'a ast::WorldItemPath<'a>, + kind: ExternKind, + world: &'a str, + packages: &mut IndexMap, Vec>, + ty: &mut World, + ) -> ResolutionResult<()> { + let (k, v) = match path { + ast::WorldItemPath::Named(named) => { + check_name(named.id.string, named.id.span, ty, world, kind)?; + + ( + named.id.string.into(), + match &named.ty { + ast::ExternType::Ident(id) => { + let (item, _) = state.local_or_root_item(id)?; + match item.kind(&state.graph) { + ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id), + ItemKind::Type(Type::Func(id)) => ItemKind::Func(id), + kind => { + return Err(Error::NotFuncOrInterface { + name: id.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: id.span, + }); + } + } + } + ast::ExternType::Func(f) => ItemKind::Func(self.func_type( + state, + &f.params, + &f.results, + FuncKind::Free, + None, + )?), + ast::ExternType::Interface(i) => { + ItemKind::Instance(self.inline_interface(state, i, packages)?) + } + }, + ) + } + ast::WorldItemPath::Ident(id) => { + let (item, _) = state.root_item(id)?; + match item.kind(&state.graph) { + ItemKind::Type(Type::Interface(iface_ty_id)) => { + let iface_id = state.graph.types()[iface_ty_id] + .id + .as_ref() + .expect("expected an interface id"); + check_name(iface_id, id.span, ty, world, kind)?; + (iface_id.clone(), ItemKind::Instance(iface_ty_id)) + } + kind => { + return Err(Error::NotInterface { + name: id.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: id.span, + }); + } + } + } + + ast::WorldItemPath::Package(p) => { + match self.resolve_package_path(state, p, packages)? { + ItemKind::Type(Type::Interface(id)) => { + let name = state.graph.types()[id] + .id + .as_ref() + .expect("expected an interface id"); + check_name(name, p.span, ty, world, kind)?; + (name.clone(), ItemKind::Instance(id)) + } + kind => { + return Err(Error::NotInterface { + name: p.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: p.span, + }); + } + } + } + }; + + if kind == ExternKind::Import { + ty.imports.insert(k, v); + } else { + ty.exports.insert(k, v); + } + + return Ok(()); + + fn check_name( + name: &str, + span: SourceSpan, + ty: &World, + world: &str, + kind: ExternKind, + ) -> ResolutionResult<()> { + let exists: bool = if kind == ExternKind::Import { + ty.imports.contains_key(name) + } else { + ty.exports.contains_key(name) + }; + + if exists { + return Err(Error::DuplicateWorldItem { + kind, + name: name.to_owned(), + world: world.to_owned(), + span, + }); + } + + Ok(()) + } + } + + fn world_include( + &mut self, + state: &mut State, + include: &'a ast::WorldInclude<'a>, + world: &'a str, + packages: &mut IndexMap, Vec>, + ty: &mut World, + ) -> ResolutionResult<()> { + log::debug!("resolving include of world `{world}`"); + let mut replacements = HashMap::new(); + for item in &include.with { + let prev = replacements.insert(item.from.string, item); + if prev.is_some() { + return Err(Error::DuplicateWorldIncludeName { + name: item.from.string.to_owned(), + span: item.from.span, + }); + } + } + + let id = match &include.world { + ast::WorldRef::Ident(id) => { + let (item, _) = state.root_item(id)?; + match item.kind(&state.graph) { + ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, + kind => { + return Err(Error::NotWorld { + name: id.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: id.span, + }); + } + } + } + ast::WorldRef::Package(path) => { + match self.resolve_package_path(state, path, packages)? { + ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, + kind => { + return Err(Error::NotWorld { + name: path.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: path.span, + }); + } + } + } + }; + + let other = &state.graph.types()[id]; + for (name, item) in &other.imports { + let name = replace_name( + include, + world, + ty, + name, + ExternKind::Import, + &mut replacements, + )?; + ty.imports.entry(name).or_insert(*item); + } + + for (name, item) in &other.exports { + let name = replace_name( + include, + world, + ty, + name, + ExternKind::Export, + &mut replacements, + )?; + ty.exports.entry(name).or_insert(*item); + } + + if let Some(missing) = replacements.values().next() { + return Err(Error::MissingWorldInclude { + world: include.world.name().to_owned(), + name: missing.from.string.to_owned(), + span: missing.from.span, + }); + } + + return Ok(()); + + fn replace_name<'a>( + include: &ast::WorldInclude<'a>, + world: &'a str, + ty: &mut World, + name: &str, + kind: ExternKind, + replacements: &mut HashMap<&str, &ast::WorldIncludeItem<'a>>, + ) -> ResolutionResult { + // Check for a id, which doesn't get replaced. + if name.contains(':') { + return Ok(name.to_owned()); + } + + let (name, span) = replacements + .remove(name) + .map(|i| (i.to.string, i.to.span)) + .unwrap_or_else(|| (name, include.world.span())); + + let exists = if kind == ExternKind::Import { + ty.imports.contains_key(name) + } else { + ty.exports.contains_key(name) + }; + + if exists { + return Err(Error::WorldIncludeConflict { + kind, + name: name.to_owned(), + from: include.world.name().to_owned(), + to: world.to_owned(), + span, + help: if !include.with.is_empty() { + None + } else { + Some("consider using a `with` clause to use a different name".into()) + }, + }); + } + + Ok(name.to_owned()) + } + } + + fn inline_interface( + &mut self, + state: &mut State, + iface: &'a ast::InlineInterface<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + log::debug!("resolving inline interface"); + + state.push_scope(); + + let mut ty = Interface { + id: None, + uses: Default::default(), + exports: Default::default(), + }; + + self.interface_items(state, None, &iface.items, packages, &mut ty)?; + + state.pop_scope(); + + Ok(state.graph.types_mut().add_interface(ty)) + } + + fn interface_items( + &mut self, + state: &mut State, + name: Option<&'a str>, + items: &'a [ast::InterfaceItem<'a>], + packages: &mut IndexMap, Vec>, + ty: &mut Interface, + ) -> ResolutionResult<()> { + for item in items { + match item { + ast::InterfaceItem::Use(u) => { + self.use_type(state, u, &mut ty.uses, &mut ty.exports, packages, false)? + } + ast::InterfaceItem::Type(decl) => { + self.item_type_decl(state, decl, &mut ty.exports)?; + } + ast::InterfaceItem::Export(e) => { + let kind = ItemKind::Func(self.func_type_ref(state, &e.ty, FuncKind::Free)?); + if ty.exports.insert(e.id.string.into(), kind).is_some() { + return Err(Error::DuplicateInterfaceExport { + name: e.id.string.to_owned(), + interface_name: name.map(ToOwned::to_owned), + span: e.id.span, + }); + } + } + } + } + + Ok(()) + } + + fn use_type( + &mut self, + state: &mut State, + use_type: &'a ast::Use<'a>, + uses: &mut IndexMap, + externs: &mut IndexMap, + packages: &mut IndexMap, Vec>, + in_world: bool, + ) -> ResolutionResult<()> { + let (interface, name) = match &use_type.path { + ast::UsePath::Package(path) => { + match self.resolve_package_path(state, path, packages)? { + ItemKind::Type(Type::Interface(id)) => (id, path.string), + kind => { + return Err(Error::NotInterface { + name: path.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: path.span, + }); + } + } + } + ast::UsePath::Ident(id) => { + let (item, _) = state.root_item(id)?; + let kind = item.kind(&state.graph); + match kind { + ItemKind::Type(Type::Interface(iface_ty_id)) => (iface_ty_id, id.string), + _ => { + return Err(Error::NotInterface { + name: id.string.to_owned(), + kind: kind.desc(state.graph.types()).to_owned(), + span: id.span, + }); + } + } + } + }; + + for item in &use_type.items { + let ident = item.as_id.unwrap_or(item.id); + let kind = state.graph.types()[interface] + .exports + .get(item.id.string) + .ok_or(Error::UndefinedInterfaceType { + name: item.id.string.to_string(), + interface_name: name.to_string(), + span: item.id.span, + })?; + + match kind { + ItemKind::Type(ty @ Type::Resource(_)) | ItemKind::Type(ty @ Type::Value(_)) => { + if externs.contains_key(ident.string) { + return Err(Error::UseConflict { + name: ident.string.to_string(), + kind: if in_world { + ExternKind::Import + } else { + ExternKind::Export + }, + span: ident.span, + help: if item.as_id.is_some() { + None + } else { + Some("consider using an `as` clause to use a different name".into()) + }, + }); + } + + uses.insert( + ident.string.into(), + UsedType { + interface, + name: item.as_id.map(|_| item.id.string.to_string()), + }, + ); + externs.insert(ident.string.into(), *kind); + state.register_name(ident, Item::Use(*ty))?; + } + _ => { + return Err(Error::NotInterfaceValueType { + name: item.id.string.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + interface_name: name.to_string(), + span: item.id.span, + }); + } + } + } + + Ok(()) + } + + fn item_type_decl( + &mut self, + state: &mut State, + decl: &'a ast::ItemTypeDecl, + externs: &mut IndexMap, + ) -> ResolutionResult<()> { + let (insert, ty) = match decl { + ast::ItemTypeDecl::Resource(r) => (false, self.resource_decl(state, r, externs)?), + ast::ItemTypeDecl::Variant(v) => (true, self.variant_decl(state, v, true)?), + ast::ItemTypeDecl::Record(r) => (true, self.record_decl(state, r, true)?), + ast::ItemTypeDecl::Flags(f) => (true, self.flags_decl(state, f, true)?), + ast::ItemTypeDecl::Enum(e) => (true, self.enum_decl(state, e, true)?), + ast::ItemTypeDecl::Alias(a) => (true, self.type_alias(state, a, true)?), + }; + + if insert { + let prev = externs.insert(decl.id().string.into(), ItemKind::Type(ty)); + assert!(prev.is_none(), "duplicate type in scope"); + } + + Ok(()) + } + + fn resource_decl( + &mut self, + state: &mut State, + decl: &ast::ResourceDecl<'a>, + externs: &mut IndexMap, + ) -> ResolutionResult { + log::debug!( + "resolving resource declaration for id `{id}`", + id = decl.id.string + ); + + // Define the resource before resolving the methods + let id = state.graph.types_mut().add_resource(Resource { + name: decl.id.string.to_owned(), + alias: None, + }); + + let ty = Type::Resource(id); + state.register_name(decl.id, Item::Type(ty))?; + + // We must add the resource to the externs before any methods + let prev = externs.insert(decl.id.string.into(), ItemKind::Type(ty)); + assert!(prev.is_none()); + + let mut names = HashSet::new(); + for method in &decl.methods { + let (name, ty) = match method { + ast::ResourceMethod::Constructor(ast::Constructor { span, params, .. }) => { + if !names.insert("") { + return Err(Error::DuplicateResourceConstructor { + resource: decl.id.string.to_string(), + span: *span, + }); + } + + ( + method_extern_name(decl.id.string, "", FuncKind::Constructor), + self.func_type( + state, + params, + &ast::ResultList::Empty, + FuncKind::Constructor, + Some(id), + )?, + ) + } + ast::ResourceMethod::Method(ast::Method { + id: method_id, + is_static, + ty, + .. + }) => { + let kind = if *is_static { + FuncKind::Static + } else { + FuncKind::Method + }; + + if !names.insert(method_id.string) { + return Err(Error::DuplicateResourceMethod { + name: method_id.string.to_string(), + resource: decl.id.string.to_string(), + span: method_id.span, + }); + } + + ( + method_extern_name(decl.id.string, method_id.string, kind), + self.func_type(state, &ty.params, &ty.results, kind, Some(id))?, + ) + } + }; + + let prev = externs.insert(name, ItemKind::Func(ty)); + assert!(prev.is_none()); + } + + Ok(ty) + } + + fn func_type( + &mut self, + state: &mut State, + func_params: &[ast::NamedType<'a>], + func_results: &ast::ResultList<'a>, + kind: FuncKind, + resource: Option, + ) -> ResolutionResult { + let mut params = IndexMap::new(); + + if kind == FuncKind::Method { + params.insert("self".into(), ValueType::Borrow(resource.unwrap())); + } + + for param in func_params { + if params + .insert(param.id.string.into(), Self::ty(state, ¶m.ty)?) + .is_some() + { + return Err(Error::DuplicateParameter { + name: param.id.string.to_string(), + kind, + span: param.id.span, + }); + } + } + + let results = match func_results { + ast::ResultList::Empty => { + if kind == FuncKind::Constructor { + Some(FuncResult::Scalar(ValueType::Own(resource.unwrap()))) + } else { + None + } + } + ast::ResultList::Named(results) => { + let mut list = IndexMap::new(); + for result in results { + let value_type = Self::ty(state, &result.ty)?; + if value_type.contains_borrow(state.graph.types()) { + return Err(Error::BorrowInResult { + span: result.ty.span(), + }); + } + + if list + .insert(result.id.string.to_owned(), value_type) + .is_some() + { + return Err(Error::DuplicateResult { + name: result.id.string.to_string(), + kind, + span: result.id.span, + }); + } + } + Some(FuncResult::List(list)) + } + ast::ResultList::Scalar(ty) => { + let value_type = Self::ty(state, ty)?; + if value_type.contains_borrow(state.graph.types()) { + return Err(Error::BorrowInResult { span: ty.span() }); + } + Some(FuncResult::Scalar(value_type)) + } + }; + + Ok(state + .graph + .types_mut() + .add_func_type(FuncType { params, results })) + } + + fn expr( + &mut self, + state: &mut State, + expr: &'a ast::Expr<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + let mut item = self.primary_expr(state, &expr.primary, packages)?; + + let mut parent_span = expr.primary.span(); + for expr in &expr.postfix { + item = self.postfix_expr(state, item, expr, parent_span)?; + parent_span = expr.span(); + } + + Ok(item) + } + + fn primary_expr( + &mut self, + state: &mut State, + expr: &'a ast::PrimaryExpr<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + match expr { + ast::PrimaryExpr::New(e) => self.new_expr(state, e, packages), + ast::PrimaryExpr::Nested(e) => self.expr(state, &e.inner, packages), + ast::PrimaryExpr::Ident(i) => Ok(state.local_item(i)?.0), + } + } + + fn new_expr( + &mut self, + state: &mut State, + expr: &'a ast::NewExpr<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + log::debug!( + "resolving new expression for package `{pkg}`", + pkg = BorrowedPackageKey::from_name_and_version( + expr.package.name, + expr.package.version.as_ref() + ) + ); + + if expr.package.name == self.0.directive.package.name { + return Err(Error::UnknownPackage { + name: expr.package.name.to_string(), + span: expr.package.span, + }); + } + + let pkg = self.resolve_package( + state, + expr.package.name, + expr.package.version.as_ref(), + expr.package.span, + packages, + )?; + let ty = state.graph[pkg].ty(); + let expected = state.graph.types()[ty] + .imports + .keys() + .cloned() + .collect::>(); + let mut require_all = true; + + let mut arguments: IndexMap = Default::default(); + for (i, arg) in expr.arguments.iter().enumerate() { + let (name, item, span) = match arg { + ast::InstantiationArgument::Inferred(id) => { + self.inferred_instantiation_arg(state, id, ty)? + } + ast::InstantiationArgument::Spread(_) => { + // Spread arguments will be processed after all other arguments + continue; + } + ast::InstantiationArgument::Named(arg) => { + self.named_instantiation_arg(state, arg, ty, packages)? + } + ast::InstantiationArgument::Fill(span) => { + if i != (expr.arguments.len() - 1) { + return Err(Error::FillArgumentNotLast { span: *span }); + } + + require_all = false; + continue; + } + }; + + let prev = arguments.insert(name.clone(), (item, span)); + if prev.is_some() { + return Err(Error::DuplicateInstantiationArg { name, span }); + } + } + + // Process the spread arguments + for arg in &expr.arguments { + if let ast::InstantiationArgument::Spread(id) = arg { + self.spread_instantiation_arg(state, id, &expected, &mut arguments)?; + } + } + + // Create the instantiation node + log::debug!( + "adding instantiation for package `{pkg}` to the graph", + pkg = BorrowedPackageKey::from_name_and_version( + expr.package.name, + expr.package.version.as_ref() + ) + ); + let instantiation = state.graph.instantiate(pkg); + let prev = state + .instantiation_spans + .insert(instantiation, expr.package.span); + assert!(prev.is_none()); + + // Set the arguments on the instantiation + for (name, (argument, span)) in &arguments { + log::debug!("adding argument edge `{name}`"); + state + .graph + .set_instantiation_argument(instantiation, name, argument.node()) + .map_err(|e| match e { + InstantiationArgumentError::NodeIsNotAnInstantiation { .. } => { + panic!("node should be an instantiation") + } + InstantiationArgumentError::ArgumentAlreadyPassed { .. } => { + panic!("argument should not already be passed") + } + InstantiationArgumentError::InvalidArgumentName { + node: _, + name, + package, + } => Error::MissingComponentImport { + package, + import: name, + span: *span, + }, + InstantiationArgumentError::ArgumentTypeMismatch { name, source } => { + Error::MismatchedInstantiationArg { + name, + span: *span, + source, + } + } + })?; + } + + // If `...` was not present in the argument list, error if there are + // any missing arguments; implicit arguments will be added during encoding + if require_all { + let world = &state.graph.types()[ty]; + if let Some((name, _)) = world + .imports + .iter() + .find(|(n, _)| !arguments.contains_key(n.as_str())) + { + return Err(Error::MissingInstantiationArg { + name: name.clone(), + package: expr.package.string.to_string(), + span: expr.package.span, + }); + } + } + + Ok(Item::Node(instantiation)) + } + + fn find_matching_interface_name<'b>( + name: &str, + externs: &'b IndexMap, + ) -> Option<&'b str> { + // If the given name exists directly, don't treat it as an interface name + if externs.contains_key(name) { + return None; + } + + // Fall back to searching for a matching interface name, provided it is not ambiguous + // For example, match `foo:bar/baz` if `baz` is the identifier and the only match + let mut matches = externs.iter().filter(|(n, _)| match n.rfind('/') { + Some(start) => { + let mut n = &n[start + 1..]; + if let Some(index) = n.find('@') { + n = &n[..index]; + } + n == name + } + None => false, + }); + + let (name, _) = matches.next()?; + if matches.next().is_some() { + // More than one match, the name is ambiguous + return None; + } + + Some(name) + } + + fn named_instantiation_arg( + &mut self, + state: &mut State, + arg: &'a ast::NamedInstantiationArgument<'a>, + world: WorldId, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult<(String, Item, SourceSpan)> { + let item = self.expr(state, &arg.expr, packages)?; + + let name = match &arg.name { + ast::InstantiationArgumentName::Ident(ident) => Self::find_matching_interface_name( + ident.string, + &state.graph.types()[world].imports, + ) + .unwrap_or(ident.string), + ast::InstantiationArgumentName::String(name) => name.value, + }; + + Ok((name.to_owned(), item, arg.name.span())) + } + + fn inferred_instantiation_arg( + &mut self, + state: &mut State, + ident: &ast::Ident<'a>, + world: WorldId, + ) -> ResolutionResult<(String, Item, SourceSpan)> { + let (item, _) = state.local_item(ident)?; + let world = &state.graph.types()[world]; + let kind = item.kind(&state.graph); + + // If the item is an instance with an id, try the id. + if let ItemKind::Instance(id) = kind { + if let Some(id) = &state.graph.types()[id].id { + if world.imports.contains_key(id.as_str()) { + return Ok((id.clone(), item, ident.span)); + } + } + } + + // If the item comes from an import or an alias, try the name associated with it + let node = item.node(); + if let Some(name) = state.graph.get_import_name(node) { + if world.imports.contains_key(name) { + return Ok((name.to_string(), item, ident.span)); + } + } else if let Some((_, name)) = state.graph.get_alias_source(node) { + if world.imports.contains_key(name) { + return Ok((name.to_string(), item, ident.span)); + } + } + + // Fall back to searching for a matching interface name, provided it is not ambiguous + // For example, match `foo:bar/baz` if `baz` is the identifier and the only match + if let Some(name) = Self::find_matching_interface_name(ident.string, &world.imports) { + return Ok((name.to_owned(), item, ident.span)); + } + + // Finally default to the id itself + Ok((ident.string.to_owned(), item, ident.span)) + } + + fn spread_instantiation_arg( + &mut self, + state: &mut State, + id: &ast::Ident, + expected: &IndexSet, + arguments: &mut IndexMap, + ) -> ResolutionResult<()> { + let item = state.local_item(id)?.0; + + // The item must be an instance for a spread + match item.kind(&state.graph) { + ItemKind::Instance(_) => {} + kind => { + return Err(Error::NotAnInstance { + kind: kind.desc(state.graph.types()).to_string(), + operation: InstanceOperation::Spread, + span: id.span, + }) + } + } + + let mut spread = false; + for name in expected { + // Check if the argument was already provided + if arguments.contains_key(name) { + continue; + } + + // Alias a matching export of the instance + if let Some(aliased) = + self.alias_export(state, item, name, id.span, InstanceOperation::Spread)? + { + spread = true; + arguments.insert(name.clone(), (aliased, id.span)); + } + } + + if !spread { + return Err(Error::SpreadInstantiationNoMatch { span: id.span }); + } + + Ok(()) + } + + fn postfix_expr( + &mut self, + state: &mut State, + item: Item, + expr: &ast::PostfixExpr, + parent_span: SourceSpan, + ) -> ResolutionResult { + match expr { + ast::PostfixExpr::Access(expr) => { + let exports = match item.kind(&state.graph) { + ItemKind::Instance(id) => &state.graph.types()[id].exports, + kind => { + return Err(Error::NotAnInstance { + kind: kind.desc(state.graph.types()).to_string(), + operation: InstanceOperation::Access, + span: parent_span, + }) + } + }; + + let name = Self::find_matching_interface_name(expr.id.string, exports) + .unwrap_or(expr.id.string) + .to_string(); + + self.alias_export(state, item, &name, parent_span, InstanceOperation::Access)? + .ok_or_else(|| Error::MissingInstanceExport { + name, + span: expr.span, + }) + } + ast::PostfixExpr::NamedAccess(expr) => self + .alias_export( + state, + item, + expr.string.value, + parent_span, + InstanceOperation::Access, + )? + .ok_or_else(|| Error::MissingInstanceExport { + name: expr.string.value.to_owned(), + span: expr.span, + }), + } + } + + fn alias_export( + &self, + state: &mut State, + item: Item, + name: &str, + span: SourceSpan, + operation: InstanceOperation, + ) -> ResolutionResult> { + let exports = match item.kind(&state.graph) { + ItemKind::Instance(id) => &state.graph.types()[id].exports, + kind => { + return Err(Error::NotAnInstance { + kind: kind.desc(state.graph.types()).to_string(), + operation, + span, + }) + } + }; + + if exports.get(name).is_none() { + return Ok(None); + } + + let node = state + .graph + .alias_instance_export(item.node(), name) + .expect("alias should be created"); + + // Insert the span if the node isn't new + Ok(Some(Item::Node(node))) + } + + fn resolve_package_path( + &mut self, + state: &mut State, + path: &'a ast::PackagePath<'a>, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + log::debug!("resolving package export `{path}`", path = path.string); + + // Check for reference to local item + if path.name == self.0.directive.package.name { + return self.resolve_local_path(state, path); + } + + let id = self.resolve_package( + state, + path.name, + path.version.as_ref(), + path.package_name_span(), + packages, + )?; + + let package = &state.graph[id]; + let mut current = 0; + let mut parent_ty = None; + let mut found = None; + for (i, (segment, _)) in path.segment_spans().enumerate() { + current = i; + + // Look up the first segment in the package definitions + if i == 0 { + found = package.definitions().get(segment).copied(); + continue; + } + + // Otherwise, project into the parent based on the current segment + let export = match found.unwrap() { + // The parent is an interface or instance + ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { + state.graph.types()[id].exports.get(segment).copied() + } + // The parent is a world or component or component instantiation + ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => { + state.graph.types()[id].exports.get(segment).copied() + } + _ => None, + }; + + parent_ty = found.map(|kind| kind.desc(state.graph.types())); + found = export; + if found.is_none() { + break; + } + } + + found.ok_or_else(|| { + let segments = path.segment_spans().enumerate(); + let mut prev_path = String::new(); + for (i, (segment, span)) in segments { + if i == current { + return Error::PackageMissingExport { + package: path.name.to_string(), + export: segment.to_string(), + kind: parent_ty.map(ToOwned::to_owned), + path: prev_path, + span, + }; + } + + if !prev_path.is_empty() { + prev_path.push('/'); + } + + prev_path.push_str(segment); + } + + unreachable!("path segments should never be empty") + }) + } + + fn resolve_local_path( + &self, + state: &State, + path: &ast::PackagePath<'a>, + ) -> ResolutionResult { + log::debug!("resolving local path `{path}`", path = path.string); + + let mut segments = path.segment_spans(); + let (segment, span) = segments.next().unwrap(); + let (item, _) = state.root_item(&ast::Ident { + string: segment, + span, + })?; + + let mut current = segment; + let mut kind = item.kind(&state.graph); + for (segment, span) in segments { + let exports = match kind { + ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { + &state.graph.types()[id].exports + } + ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => { + &state.graph.types()[id].exports + } + _ => { + return Err(Error::PackagePathMissingExport { + name: current.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + export: segment.to_string(), + span, + }); + } + }; + + kind = + exports + .get(segment) + .copied() + .ok_or_else(|| Error::PackagePathMissingExport { + name: current.to_string(), + kind: kind.desc(state.graph.types()).to_string(), + export: segment.to_string(), + span, + })?; + + current = segment; + } + + Ok(kind) + } + + fn resolve_package( + &mut self, + state: &mut State, + name: &'a str, + version: Option<&'a Version>, + span: SourceSpan, + packages: &mut IndexMap, Vec>, + ) -> ResolutionResult { + match state.graph.get_package_by_name(name, version) { + Some((id, _)) => Ok(id), + None => { + let bytes = packages + .swap_remove(&BorrowedPackageKey::from_name_and_version(name, version)) + .ok_or_else(|| Error::UnknownPackage { + name: name.to_string(), + span, + })?; + + log::debug!("registering package `{name}` with the graph"); + let package = Package::from_bytes(name, version, bytes, state.graph.types_mut()) + .map_err(|e| Error::PackageParseFailure { + name: name.to_string(), + span, + source: e, + })?; + + Ok(state + .graph + .register_package(package) + .expect("package should not exist")) + } + } + } + + fn validate_target( + &self, + state: &State, + path: &ast::PackagePath, + world: WorldId, + ) -> ResolutionResult<()> { + let world = &state.graph.types()[world]; + let mut cache = Default::default(); + let mut checker = SubtypeChecker::new(&mut cache); + + // The output is allowed to import a subset of the world's imports + checker.invert(); + for (name, import) in state + .graph + .node_ids() + .filter_map(|n| match &state.graph[n].kind() { + NodeKind::Import(name) => Some((name, n)), + _ => None, + }) + { + let expected = world + .imports + .get(name) + .ok_or_else(|| Error::ImportNotInTarget { + name: name.clone(), + world: path.string.to_owned(), + span: state.import_spans[&import], + })?; + + checker + .is_subtype( + expected.promote(), + state.graph.types(), + state.graph[import].item_kind(), + state.graph.types(), + ) + .map_err(|e| Error::TargetMismatch { + kind: ExternKind::Import, + name: name.clone(), + world: path.string.to_owned(), + span: state.import_spans[&import], + source: e, + })?; + } + + checker.revert(); + + // The output must export every export in the world + for (name, expected) in &world.exports { + let export = + state + .graph + .get_export(name) + .ok_or_else(|| Error::MissingTargetExport { + name: name.clone(), + world: path.string.to_owned(), + kind: expected.desc(state.graph.types()).to_string(), + span: path.span, + })?; + + checker + .is_subtype( + state.graph[export].item_kind(), + state.graph.types(), + expected.promote(), + state.graph.types(), + ) + .map_err(|e| Error::TargetMismatch { + kind: ExternKind::Export, + name: name.clone(), + world: path.string.to_owned(), + span: state.export_spans[&export], + source: e, + })?; + } + + Ok(()) } } diff --git a/crates/wac-parser/src/resolution/ast.rs b/crates/wac-parser/src/resolution/ast.rs deleted file mode 100644 index ed16c90..0000000 --- a/crates/wac-parser/src/resolution/ast.rs +++ /dev/null @@ -1,2335 +0,0 @@ -use super::{ - package::{Package, PackageKey}, - Composition, DefinedType, Definitions, Enum, Error, ExternKind, Flags, Func, FuncId, FuncKind, - FuncResult, Interface, InterfaceId, ItemKind, PrimitiveType, Record, ResolutionResult, - Resource, ResourceId, SubtypeChecker, Type, ValueType, Variant, World, WorldId, -}; -use crate::{ast, method_extern_name, InstanceOperation, Item, ItemId, PackageId, UsedType}; -use anyhow::Context; -use id_arena::Arena; -use indexmap::{IndexMap, IndexSet}; -use miette::SourceSpan; -use semver::Version; -use std::{ - collections::{hash_map, HashMap, HashSet}, - sync::Arc, -}; -use wasmparser::{ - names::{ComponentName, ComponentNameKind}, - BinaryReaderError, -}; - -#[derive(Default)] -struct Scope { - names: IndexMap, - items: Arena, -} - -impl Scope { - fn get(&self, name: &str) -> Option<(ItemId, SourceSpan)> { - self.names.get(name).copied() - } -} - -struct Import<'a> { - /// The package that caused the import. - /// This is `None` for explicit imports. - package: Option<&'a str>, - /// The span where the import was first introduced. - span: SourceSpan, - /// The imported item. - item: ItemId, -} - -struct Export { - /// The span where the export was first introduced. - span: SourceSpan, - /// The exported item. - item: ItemId, -} - -struct State<'a> { - scopes: Vec, - current: Scope, - packages: Arena, - /// The map of package name to id. - package_map: HashMap, PackageId>, - /// The map of instance items and their aliased items. - aliases: HashMap>, - /// The map of imported items. - /// This is used to keep track of implicit imports and merge them together. - imports: IndexMap>, - /// The map of exported items. - exports: IndexMap, -} - -impl<'a> State<'a> { - fn new() -> Self { - Self { - scopes: Default::default(), - current: Default::default(), - packages: Default::default(), - package_map: Default::default(), - aliases: Default::default(), - imports: Default::default(), - exports: Default::default(), - } - } - - // Gets an item by identifier from the root scope. - fn root_item(&self, id: &ast::Ident<'a>) -> ResolutionResult<(ItemId, &Item)> { - let scope = self.root_scope(); - - let id = scope - .get(id.string) - .ok_or(Error::UndefinedName { - name: id.string.to_owned(), - span: id.span, - })? - .0; - - Ok((id, &scope.items[id])) - } - - /// Gets an item by identifier from the local (current) scope. - fn local_item(&self, id: &ast::Ident<'a>) -> ResolutionResult<(ItemId, &Item)> { - let id = self - .current - .get(id.string) - .ok_or(Error::UndefinedName { - name: id.string.to_owned(), - span: id.span, - })? - .0; - - Ok((id, &self.current.items[id])) - } - - /// Gets an item by identifier from the local (current) scope or the root scope. - fn local_or_root_item(&self, id: &ast::Ident<'a>) -> ResolutionResult<(ItemId, &Item)> { - if self.scopes.is_empty() { - return self.local_item(id); - } - - if let Some((id, _)) = self.current.get(id.string) { - return Ok((id, &self.current.items[id])); - } - - self.root_item(id) - } - - fn push_scope(&mut self) { - log::debug!("pushing new name scope"); - self.scopes.push(std::mem::take(&mut self.current)); - } - - fn pop_scope(&mut self) -> Scope { - log::debug!("popping name scope"); - std::mem::replace(&mut self.current, self.scopes.pop().unwrap()) - } - - fn root_scope(&self) -> &Scope { - self.scopes.first().unwrap_or(&self.current) - } - - fn register_name(&mut self, id: ast::Ident<'a>, item: ItemId) -> ResolutionResult<()> { - log::debug!( - "registering name `{id}` for item {item} in the current scope", - id = id.string, - item = item.index() - ); - if let Some((_, span)) = self - .current - .names - .insert(id.string.to_owned(), (item, id.span)) - { - return Err(Error::DuplicateName { - name: id.string.to_owned(), - span: id.span, - previous: span, - }); - } - - Ok(()) - } -} - -pub struct AstResolver<'a> { - document: &'a ast::Document<'a>, - definitions: Definitions, - packages: IndexMap, Arc>>, -} - -impl<'a> AstResolver<'a> { - pub fn new( - document: &'a ast::Document<'a>, - packages: IndexMap, Arc>>, - ) -> Self { - Self { - document, - definitions: Default::default(), - packages, - } - } - - pub fn resolve(mut self) -> ResolutionResult { - let mut state = State::new(); - - for stmt in &self.document.statements { - match stmt { - ast::Statement::Import(i) => self.import_statement(&mut state, i)?, - ast::Statement::Type(t) => self.type_statement(&mut state, t)?, - ast::Statement::Let(l) => self.let_statement(&mut state, l)?, - ast::Statement::Export(e) => self.export_statement(&mut state, e)?, - } - } - - // If there's a target world in the directive, validate the composition - // conforms to the target - if let Some(path) = &self.document.directive.targets { - log::debug!("validating composition targets world `{}`", path.string); - let item = self.resolve_package_export(&mut state, path)?; - match item { - ItemKind::Type(Type::World(world)) => { - self.validate_target(&state, path, world)?; - } - _ => { - return Err(Error::NotWorld { - name: path.string.to_owned(), - kind: item.as_str(&self.definitions).to_owned(), - span: path.span, - }); - } - } - } - - assert!(state.scopes.is_empty()); - - Ok(Composition { - package: self.document.directive.package.name.to_owned(), - version: self.document.directive.package.version.clone(), - definitions: self.definitions, - packages: state.packages, - items: state.current.items, - imports: state - .imports - .into_iter() - .map(|(k, v)| (k, v.item)) - .collect(), - exports: state - .exports - .into_iter() - .map(|(k, v)| (k, v.item)) - .collect(), - }) - } - - fn import_statement( - &mut self, - state: &mut State<'a>, - stmt: &'a ast::ImportStatement<'a>, - ) -> ResolutionResult<()> { - log::debug!( - "resolving import statement for id `{id}`", - id = stmt.id.string - ); - let (kind, span) = match &stmt.ty { - ast::ImportType::Package(p) => (self.resolve_package_export(state, p)?, p.span), - ast::ImportType::Func(ty) => ( - ItemKind::Func(self.func_type( - state, - &ty.params, - &ty.results, - FuncKind::Free, - None, - )?), - stmt.id.span, - ), - ast::ImportType::Interface(i) => (self.inline_interface(state, i)?, stmt.id.span), - ast::ImportType::Ident(id) => (state.local_item(id)?.1.kind(), stmt.id.span), - }; - - // Promote any types to their corresponding item kind - let kind = kind.promote(); - - let (name, span) = if let Some(name) = &stmt.name { - // Override the span to the `as` clause string - (name.as_str(), name.span()) - } else { - // If the item is an instance with an id, use the id - if let ItemKind::Instance(id) = kind { - if let Some(id) = &self.definitions.interfaces[id].id { - (id.as_str(), span) - } else { - (stmt.id.string, span) - } - } else { - (stmt.id.string, span) - } - }; - - // Validate the import name - ComponentName::new(name, 0).map_err(|e| { - let msg = e.to_string(); - Error::InvalidExternName { - name: name.to_string(), - kind: ExternKind::Import, - span, - source: anyhow::anyhow!( - "{msg}", - msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) - ), - } - })?; - - if let Some(existing) = state.imports.get(name) { - match &state.current.items[existing.item] { - Item::Import { .. } => { - if let Some(Import { - package: Some(package), - span: prev_span, - .. - }) = state.imports.get(name) - { - return Err(Error::ImportConflict { - name: name.to_string(), - package: package.to_string(), - span, - instantiation: *prev_span, - }); - } - - return Err(Error::DuplicateExternName { - name: name.to_owned(), - kind: ExternKind::Import, - span, - previous: existing.span, - help: if stmt.name.is_some() { - None - } else { - Some("consider using an `as` clause to use a different name".into()) - }, - }); - } - _ => unreachable!(), - } - } - - let id = state.current.items.alloc(Item::Import(super::Import { - name: name.to_owned(), - kind, - })); - - state.imports.insert( - name.to_owned(), - Import { - package: None, - span, - item: id, - }, - ); - - state.register_name(stmt.id, id) - } - - fn type_statement( - &mut self, - state: &mut State<'a>, - stmt: &'a ast::TypeStatement<'a>, - ) -> ResolutionResult<()> { - log::debug!("resolving type statement"); - - let (id, item) = match stmt { - ast::TypeStatement::Interface(i) => (i.id, self.interface_decl(state, i)?), - ast::TypeStatement::World(w) => (w.id, self.world_decl(state, w)?), - ast::TypeStatement::Type(t) => (*t.id(), self.type_decl(state, t)?), - }; - - let prev = state.exports.insert( - id.string.to_owned(), - Export { - span: id.span, - item, - }, - ); - assert!(prev.is_none()); - Ok(()) - } - - fn let_statement( - &mut self, - state: &mut State<'a>, - stmt: &'a ast::LetStatement<'a>, - ) -> ResolutionResult<()> { - log::debug!( - "resolving type statement for id `{id}`", - id = stmt.id.string - ); - let item = self.expr(state, &stmt.expr)?; - state.register_name(stmt.id, item) - } - - fn export_statement( - &mut self, - state: &mut State<'a>, - stmt: &'a ast::ExportStatement<'a>, - ) -> ResolutionResult<()> { - log::debug!("resolving export statement"); - - let item = self.expr(state, &stmt.expr)?; - - match &stmt.options { - ast::ExportOptions::None => { - let name = self - .infer_export_name(state, item) - .ok_or(Error::ExportRequiresAs { - span: stmt.expr.span, - })?; - - self.export_item(state, item, name.to_owned(), stmt.expr.span, true)?; - } - ast::ExportOptions::Spread(span) => { - let exports = - self.instance_exports(state, item, stmt.expr.span, InstanceOperation::Spread)?; - - let mut exported = false; - for name in exports.keys() { - // Only export the item if it another item with the same name - // has not been already exported - if state.exports.contains_key(name) { - continue; - } - - let item = self - .alias_export(state, item, exports, name)? - .expect("expected a matching export name"); - - self.export_item(state, item, name.clone(), *span, false)?; - exported = true; - } - - if !exported { - return Err(Error::SpreadExportNoEffect { - span: stmt.expr.span, - }); - } - } - ast::ExportOptions::Rename(name) => { - self.export_item(state, item, name.as_str().to_owned(), name.span(), false)?; - } - } - - Ok(()) - } - - fn export_item( - &self, - state: &mut State<'a>, - item: ItemId, - name: String, - span: SourceSpan, - show_hint: bool, - ) -> Result<(), Error> { - let map_err = |e: BinaryReaderError| { - let msg = e.to_string(); - Error::InvalidExternName { - name: name.clone(), - kind: ExternKind::Export, - span, - source: anyhow::anyhow!( - "{msg}", - msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) - ), - } - }; - - match ComponentName::new(&name, 0).map_err(map_err)?.kind() { - ComponentNameKind::Hash(_) - | ComponentNameKind::Url(_) - | ComponentNameKind::Dependency(_) => { - return Err(Error::InvalidExternName { - name: name.to_string(), - kind: ExternKind::Export, - span, - source: anyhow::anyhow!("export name cannot be a hash, url, or dependency"), - }); - } - _ => {} - } - - if let Some((item_id, prev_span)) = state.root_scope().get(&name) { - let item = &state.current.items[item_id]; - if let Item::Definition(definition) = item { - return Err(Error::ExportConflict { - name: name.to_owned(), - kind: definition.kind.as_str(&self.definitions).to_string(), - span, - definition: prev_span, - help: if !show_hint { - None - } else { - Some("consider using an `as` clause to use a different name".into()) - }, - }); - } - } - - if let Some(existing) = state.exports.get(&name) { - return Err(Error::DuplicateExternName { - name: name.to_owned(), - kind: ExternKind::Export, - span, - previous: existing.span, - help: if !show_hint { - None - } else { - Some("consider using an `as` clause to use a different name".into()) - }, - }); - } - - let prev = state.exports.insert(name, Export { span, item }); - assert!(prev.is_none()); - Ok(()) - } - - fn inline_interface( - &mut self, - state: &mut State<'a>, - iface: &'a ast::InlineInterface<'a>, - ) -> ResolutionResult { - log::debug!("resolving inline interface"); - - state.push_scope(); - - let mut ty = Interface { - id: None, - remapped_types: Default::default(), - uses: Default::default(), - exports: Default::default(), - }; - - self.interface_items(state, None, &iface.items, &mut ty)?; - - state.pop_scope(); - - Ok(ItemKind::Type(Type::Interface( - self.definitions.interfaces.alloc(ty), - ))) - } - - fn id(&self, name: &str) -> String { - format!( - "{pkg}/{name}{version}", - pkg = self.document.directive.package.name, - version = if let Some(version) = &self.document.directive.package.version { - format!("@{version}") - } else { - String::new() - } - ) - } - - fn interface_decl( - &mut self, - state: &mut State<'a>, - decl: &'a ast::InterfaceDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving interface declaration for id `{id}`", - id = decl.id.string - ); - state.push_scope(); - - let mut ty = Interface { - id: Some(self.id(decl.id.string)), - remapped_types: Default::default(), - uses: Default::default(), - exports: Default::default(), - }; - - self.interface_items(state, Some(decl.id.string), &decl.items, &mut ty)?; - - state.pop_scope(); - - let ty = self.definitions.interfaces.alloc(ty); - - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind: ItemKind::Type(Type::Interface(ty)), - })); - - state.register_name(decl.id, id)?; - Ok(id) - } - - fn world_decl( - &mut self, - state: &mut State<'a>, - decl: &'a ast::WorldDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving world declaration for id `{id}`", - id = decl.id.string - ); - state.push_scope(); - - let mut ty = World { - id: Some(self.id(decl.id.string)), - uses: Default::default(), - imports: Default::default(), - exports: Default::default(), - }; - - self.world_items(state, decl.id.string, &decl.items, &mut ty)?; - - state.pop_scope(); - - let ty = self.definitions.worlds.alloc(ty); - - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind: ItemKind::Type(Type::World(ty)), - })); - - state.register_name(decl.id, id)?; - Ok(id) - } - - fn interface_items( - &mut self, - state: &mut State<'a>, - name: Option<&'a str>, - items: &'a [ast::InterfaceItem<'a>], - ty: &mut Interface, - ) -> ResolutionResult<()> { - for item in items { - match item { - ast::InterfaceItem::Use(u) => { - self.use_type(state, u, &mut ty.uses, &mut ty.exports, false)? - } - ast::InterfaceItem::Type(decl) => { - self.item_type_decl(state, decl, &mut ty.exports)?; - } - ast::InterfaceItem::Export(e) => { - let kind = ItemKind::Func(self.func_type_ref(state, &e.ty, FuncKind::Free)?); - if ty.exports.insert(e.id.string.into(), kind).is_some() { - return Err(Error::DuplicateInterfaceExport { - name: e.id.string.to_owned(), - interface_name: name.map(ToOwned::to_owned), - span: e.id.span, - }); - } - } - } - } - - Ok(()) - } - - fn world_items( - &mut self, - state: &mut State<'a>, - world: &'a str, - items: &'a [ast::WorldItem<'a>], - ty: &mut World, - ) -> ResolutionResult<()> { - let mut includes = Vec::new(); - for item in items { - match item { - ast::WorldItem::Use(u) => { - self.use_type(state, u, &mut ty.uses, &mut ty.imports, true)? - } - ast::WorldItem::Type(decl) => { - self.item_type_decl(state, decl, &mut ty.imports)?; - } - ast::WorldItem::Import(i) => { - self.world_item_path(state, &i.path, ExternKind::Import, world, ty)? - } - ast::WorldItem::Export(e) => { - self.world_item_path(state, &e.path, ExternKind::Export, world, ty)? - } - ast::WorldItem::Include(i) => { - // We delay processing includes until after all other items have been processed - includes.push(i); - } - } - } - - // Process the includes now that all imports and exports have been processed. - // This allows us to detect conflicts only in explicitly defined items. - for i in includes { - self.world_include(state, i, world, ty)?; - } - - Ok(()) - } - - fn world_item_path( - &mut self, - state: &mut State<'a>, - path: &'a ast::WorldItemPath<'a>, - kind: ExternKind, - world: &'a str, - ty: &mut World, - ) -> ResolutionResult<()> { - let (k, v) = match path { - ast::WorldItemPath::Named(named) => { - check_name(named.id.string, named.id.span, ty, world, kind)?; - - ( - named.id.string.into(), - match &named.ty { - ast::ExternType::Ident(id) => { - let item = state.local_or_root_item(id)?.1; - match item.kind() { - ItemKind::Type(Type::Interface(id)) => ItemKind::Instance(id), - ItemKind::Type(Type::Func(id)) => ItemKind::Func(id), - kind => { - return Err(Error::NotFuncOrInterface { - name: id.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: id.span, - }); - } - } - } - ast::ExternType::Func(f) => ItemKind::Func(self.func_type( - state, - &f.params, - &f.results, - FuncKind::Free, - None, - )?), - ast::ExternType::Interface(i) => self.inline_interface(state, i)?, - }, - ) - } - ast::WorldItemPath::Ident(id) => { - let item = state.root_item(id)?.1; - match item.kind() { - ItemKind::Type(Type::Interface(iface_ty_id)) => { - let iface_id = self.definitions.interfaces[iface_ty_id] - .id - .as_ref() - .expect("expected an interface id"); - check_name(iface_id, id.span, ty, world, kind)?; - (iface_id.clone(), ItemKind::Instance(iface_ty_id)) - } - kind => { - return Err(Error::NotInterface { - name: id.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: id.span, - }); - } - } - } - - ast::WorldItemPath::Package(p) => match self.resolve_package_export(state, p)? { - ItemKind::Type(Type::Interface(id)) => { - let name = self.definitions.interfaces[id] - .id - .as_ref() - .expect("expected an interface id"); - check_name(name, p.span, ty, world, kind)?; - (name.clone(), ItemKind::Instance(id)) - } - kind => { - return Err(Error::NotInterface { - name: p.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: p.span, - }); - } - }, - }; - - if kind == ExternKind::Import { - ty.imports.insert(k, v); - } else { - ty.exports.insert(k, v); - } - - return Ok(()); - - fn check_name( - name: &str, - span: SourceSpan, - ty: &World, - world: &str, - kind: ExternKind, - ) -> ResolutionResult<()> { - let exists: bool = if kind == ExternKind::Import { - ty.imports.contains_key(name) - } else { - ty.exports.contains_key(name) - }; - - if exists { - return Err(Error::DuplicateWorldItem { - kind, - name: name.to_owned(), - world: world.to_owned(), - span, - }); - } - - Ok(()) - } - } - - fn world_include( - &mut self, - state: &mut State<'a>, - include: &'a ast::WorldInclude<'a>, - world: &'a str, - ty: &mut World, - ) -> ResolutionResult<()> { - log::debug!("resolving include of world `{world}`"); - let mut replacements = HashMap::new(); - for item in &include.with { - let prev = replacements.insert(item.from.string, item); - if prev.is_some() { - return Err(Error::DuplicateWorldIncludeName { - name: item.from.string.to_owned(), - span: item.from.span, - }); - } - } - - let id = match &include.world { - ast::WorldRef::Ident(id) => { - let item = state.root_item(id)?.1; - match item.kind() { - ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, - kind => { - return Err(Error::NotWorld { - name: id.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: id.span, - }); - } - } - } - ast::WorldRef::Package(path) => match self.resolve_package_export(state, path)? { - ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, - kind => { - return Err(Error::NotWorld { - name: path.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: path.span, - }); - } - }, - }; - - let other = &self.definitions.worlds[id]; - for (name, item) in &other.imports { - let name = replace_name( - include, - world, - ty, - name, - ExternKind::Import, - &mut replacements, - )?; - ty.imports.entry(name).or_insert(*item); - } - - for (name, item) in &other.exports { - let name = replace_name( - include, - world, - ty, - name, - ExternKind::Export, - &mut replacements, - )?; - ty.exports.entry(name).or_insert(*item); - } - - if let Some(missing) = replacements.values().next() { - return Err(Error::MissingWorldInclude { - world: include.world.name().to_owned(), - name: missing.from.string.to_owned(), - span: missing.from.span, - }); - } - - return Ok(()); - - fn replace_name<'a>( - include: &ast::WorldInclude<'a>, - world: &'a str, - ty: &mut World, - name: &str, - kind: ExternKind, - replacements: &mut HashMap<&str, &ast::WorldIncludeItem<'a>>, - ) -> ResolutionResult { - // Check for a id, which doesn't get replaced. - if name.contains(':') { - return Ok(name.to_owned()); - } - - let (name, span) = replacements - .remove(name) - .map(|i| (i.to.string, i.to.span)) - .unwrap_or_else(|| (name, include.world.span())); - - let exists = if kind == ExternKind::Import { - ty.imports.contains_key(name) - } else { - ty.exports.contains_key(name) - }; - - if exists { - return Err(Error::WorldIncludeConflict { - kind, - name: name.to_owned(), - from: include.world.name().to_owned(), - to: world.to_owned(), - span, - help: if !include.with.is_empty() { - None - } else { - Some("consider using a `with` clause to use a different name".into()) - }, - }); - } - - Ok(name.to_owned()) - } - } - - fn use_type( - &mut self, - state: &mut State<'a>, - use_type: &'a ast::Use<'a>, - uses: &mut IndexMap, - externs: &mut IndexMap, - in_world: bool, - ) -> ResolutionResult<()> { - let (interface, name) = match &use_type.path { - ast::UsePath::Package(path) => match self.resolve_package_export(state, path)? { - ItemKind::Type(Type::Interface(id)) => (id, path.string), - kind => { - return Err(Error::NotInterface { - name: path.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: path.span, - }); - } - }, - ast::UsePath::Ident(id) => { - let item = state.root_item(id)?.1; - match item.kind() { - ItemKind::Type(Type::Interface(iface_ty_id)) => (iface_ty_id, id.string), - kind => { - return Err(Error::NotInterface { - name: id.string.to_owned(), - kind: kind.as_str(&self.definitions).to_owned(), - span: id.span, - }); - } - } - } - }; - - for item in &use_type.items { - let ident = item.as_id.unwrap_or(item.id); - let kind = self.definitions.interfaces[interface] - .exports - .get(item.id.string) - .ok_or(Error::UndefinedInterfaceType { - name: item.id.string.to_string(), - interface_name: name.to_string(), - span: item.id.span, - })?; - - match kind { - ItemKind::Resource(_) | ItemKind::Type(Type::Value(_)) => { - if externs.contains_key(ident.string) { - return Err(Error::UseConflict { - name: ident.string.to_string(), - kind: if in_world { - ExternKind::Import - } else { - ExternKind::Export - }, - span: ident.span, - help: if item.as_id.is_some() { - None - } else { - Some("consider using an `as` clause to use a different name".into()) - }, - }); - } - - uses.insert( - ident.string.into(), - UsedType { - interface, - name: item.as_id.map(|_| item.id.string.to_string()), - }, - ); - externs.insert(ident.string.into(), *kind); - - let id = state.current.items.alloc(Item::Use(*kind)); - state.register_name(ident, id)?; - } - _ => { - return Err(Error::NotInterfaceValueType { - name: item.id.string.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - interface_name: name.to_string(), - span: item.id.span, - }); - } - } - } - - Ok(()) - } - - fn type_decl( - &mut self, - state: &mut State<'a>, - decl: &'a ast::TypeDecl, - ) -> ResolutionResult { - match decl { - ast::TypeDecl::Variant(v) => self.variant_decl(state, v), - ast::TypeDecl::Record(r) => self.record_decl(state, r), - ast::TypeDecl::Flags(f) => self.flags_decl(state, f), - ast::TypeDecl::Enum(e) => self.enum_decl(state, e), - ast::TypeDecl::Alias(a) => self.type_alias(state, a), - } - } - - fn item_type_decl( - &mut self, - state: &mut State<'a>, - decl: &'a ast::ItemTypeDecl, - externs: &mut IndexMap, - ) -> ResolutionResult { - let (insert, item) = match decl { - ast::ItemTypeDecl::Resource(r) => (false, self.resource_decl(state, r, externs)?), - ast::ItemTypeDecl::Variant(v) => (true, self.variant_decl(state, v)?), - ast::ItemTypeDecl::Record(r) => (true, self.record_decl(state, r)?), - ast::ItemTypeDecl::Flags(f) => (true, self.flags_decl(state, f)?), - ast::ItemTypeDecl::Enum(e) => (true, self.enum_decl(state, e)?), - ast::ItemTypeDecl::Alias(a) => (true, self.type_alias(state, a)?), - }; - - if insert { - let prev = externs.insert(decl.id().string.into(), state.current.items[item].kind()); - assert!(prev.is_none(), "duplicate type in scope"); - } - - Ok(item) - } - - fn resource_decl( - &mut self, - state: &mut State<'a>, - decl: &ast::ResourceDecl<'a>, - externs: &mut IndexMap, - ) -> ResolutionResult { - log::debug!( - "resolving resource declaration for id `{id}`", - id = decl.id.string - ); - - // Define the resource before resolving the methods - let id = self.definitions.resources.alloc(Resource { - name: decl.id.string.to_owned(), - alias_of: None, - }); - - let kind = ItemKind::Resource(id); - let item_id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind, - })); - - state.register_name(decl.id, item_id)?; - - // We must add the resource to the externs before any methods - let prev = externs.insert(decl.id.string.into(), kind); - assert!(prev.is_none()); - - let mut names = HashSet::new(); - for method in &decl.methods { - let (name, ty) = match method { - ast::ResourceMethod::Constructor(ast::Constructor { span, params, .. }) => { - if !names.insert("") { - return Err(Error::DuplicateResourceConstructor { - resource: decl.id.string.to_string(), - span: *span, - }); - } - - ( - method_extern_name(decl.id.string, "", FuncKind::Constructor), - self.func_type( - state, - params, - &ast::ResultList::Empty, - FuncKind::Constructor, - Some(id), - )?, - ) - } - ast::ResourceMethod::Method(ast::Method { - id: method_id, - is_static, - ty, - .. - }) => { - let kind = if *is_static { - FuncKind::Static - } else { - FuncKind::Method - }; - - if !names.insert(method_id.string) { - return Err(Error::DuplicateResourceMethod { - name: method_id.string.to_string(), - resource: decl.id.string.to_string(), - span: method_id.span, - }); - } - - ( - method_extern_name(decl.id.string, method_id.string, kind), - self.func_type(state, &ty.params, &ty.results, kind, Some(id))?, - ) - } - }; - - let prev = externs.insert(name, ItemKind::Func(ty)); - assert!(prev.is_none()); - } - - Ok(item_id) - } - - fn variant_decl( - &mut self, - state: &mut State<'a>, - decl: &ast::VariantDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving variant declaration for id `{id}`", - id = decl.id.string - ); - - let mut cases = IndexMap::new(); - let mut contains_borrow = false; - for case in &decl.cases { - let ty = case.ty.as_ref().map(|ty| self.ty(state, ty)).transpose()?; - contains_borrow |= ty.as_ref().map_or(false, |ty| ty.contains_borrow()); - if cases.insert(case.id.string.into(), ty).is_some() { - return Err(Error::DuplicateVariantCase { - case: case.id.string.to_string(), - name: decl.id.string.to_string(), - span: case.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value(ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Variant(Variant { cases })), - contains_borrow, - })); - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind, - })); - state.register_name(decl.id, id)?; - Ok(id) - } - - fn record_decl( - &mut self, - state: &mut State<'a>, - decl: &ast::RecordDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving record declaration for id `{id}`", - id = decl.id.string - ); - - let mut fields = IndexMap::new(); - let mut contains_borrow = false; - for field in &decl.fields { - let ty = self.ty(state, &field.ty)?; - contains_borrow |= ty.contains_borrow(); - if fields.insert(field.id.string.into(), ty).is_some() { - return Err(Error::DuplicateRecordField { - field: field.id.string.to_string(), - name: decl.id.string.to_string(), - span: field.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value(ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Record(Record { fields })), - contains_borrow, - })); - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind, - })); - state.register_name(decl.id, id)?; - Ok(id) - } - - fn flags_decl( - &mut self, - state: &mut State<'a>, - decl: &ast::FlagsDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving flags declaration for id `{id}`", - id = decl.id.string - ); - - let mut flags = IndexSet::new(); - for flag in &decl.flags { - if !flags.insert(flag.id.string.into()) { - return Err(Error::DuplicateFlag { - flag: flag.id.string.to_string(), - name: decl.id.string.to_string(), - span: flag.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value(ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Flags(Flags(flags))), - contains_borrow: false, - })); - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind, - })); - state.register_name(decl.id, id)?; - Ok(id) - } - - fn enum_decl( - &mut self, - state: &mut State<'a>, - decl: &ast::EnumDecl<'a>, - ) -> ResolutionResult { - log::debug!( - "resolving enum declaration for id `{id}`", - id = decl.id.string - ); - - let mut cases = IndexSet::new(); - for case in &decl.cases { - if !cases.insert(case.id.string.to_owned()) { - return Err(Error::DuplicateEnumCase { - case: case.id.string.to_string(), - name: decl.id.string.to_string(), - span: case.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Enum(Enum(cases))), - contains_borrow: false, - })); - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: decl.id.string.to_owned(), - kind, - })); - - state.register_name(decl.id, id)?; - Ok(id) - } - - fn type_alias( - &mut self, - state: &mut State<'a>, - alias: &ast::TypeAlias<'a>, - ) -> ResolutionResult { - log::debug!("resolving type alias for id `{id}`", id = alias.id.string); - - let kind = match &alias.kind { - ast::TypeAliasKind::Func(f) => ItemKind::Type(Type::Func(self.func_type( - state, - &f.params, - &f.results, - FuncKind::Free, - None, - )?)), - ast::TypeAliasKind::Type(ty) => match ty { - ast::Type::Ident(id) => { - let item = state.local_item(id)?.1; - match item.kind() { - ItemKind::Resource(id) => { - ItemKind::Resource(self.definitions.resources.alloc(Resource { - name: alias.id.string.to_owned(), - alias_of: Some(id), - })) - } - ItemKind::Type(Type::Value(ty)) => { - ItemKind::Type(Type::Value(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Alias(ty)), - contains_borrow: ty.contains_borrow(), - })) - } - ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => { - ItemKind::Type(Type::Func(id)) - } - kind => { - return Err(Error::InvalidAliasType { - name: id.string.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - span: id.span, - }); - } - } - } - _ => { - let ty = self.ty(state, ty)?; - ItemKind::Type(Type::Value(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Alias(ty)), - contains_borrow: ty.contains_borrow(), - })) - } - }, - }; - - let id = state - .current - .items - .alloc(Item::Definition(super::Definition { - name: alias.id.string.to_owned(), - kind, - })); - state.register_name(alias.id, id)?; - Ok(id) - } - - fn func_type_ref( - &mut self, - state: &State<'a>, - r: &ast::FuncTypeRef<'a>, - kind: FuncKind, - ) -> ResolutionResult { - match r { - ast::FuncTypeRef::Func(ty) => { - self.func_type(state, &ty.params, &ty.results, kind, None) - } - ast::FuncTypeRef::Ident(id) => { - let item = state.local_item(id)?.1; - match item.kind() { - ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => Ok(id), - kind => Err(Error::NotFuncType { - name: id.string.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - span: id.span, - }), - } - } - } - } - - fn ty(&mut self, state: &State<'a>, ty: &ast::Type<'a>) -> ResolutionResult { - match ty { - ast::Type::U8(_) => Ok(ValueType::Primitive(PrimitiveType::U8)), - ast::Type::S8(_) => Ok(ValueType::Primitive(PrimitiveType::S8)), - ast::Type::U16(_) => Ok(ValueType::Primitive(PrimitiveType::U16)), - ast::Type::S16(_) => Ok(ValueType::Primitive(PrimitiveType::S16)), - ast::Type::U32(_) => Ok(ValueType::Primitive(PrimitiveType::U32)), - ast::Type::S32(_) => Ok(ValueType::Primitive(PrimitiveType::S32)), - ast::Type::U64(_) => Ok(ValueType::Primitive(PrimitiveType::U64)), - ast::Type::S64(_) => Ok(ValueType::Primitive(PrimitiveType::S64)), - ast::Type::F32(_) => Ok(ValueType::Primitive(PrimitiveType::F32)), - ast::Type::F64(_) => Ok(ValueType::Primitive(PrimitiveType::F64)), - ast::Type::Char(_) => Ok(ValueType::Primitive(PrimitiveType::Char)), - ast::Type::Bool(_) => Ok(ValueType::Primitive(PrimitiveType::Bool)), - ast::Type::String(_) => Ok(ValueType::Primitive(PrimitiveType::String)), - ast::Type::Tuple(v, _) => { - let mut contains_borrow = false; - let types = v - .iter() - .map(|ty| { - let ty = self.ty(state, ty)?; - contains_borrow |= ty.contains_borrow(); - Ok(ty) - }) - .collect::>()?; - - Ok(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Tuple(types)), - contains_borrow, - }) - } - ast::Type::List(ty, _) => { - let ty = self.ty(state, ty)?; - Ok(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::List(ty)), - contains_borrow: ty.contains_borrow(), - }) - } - ast::Type::Option(ty, _) => { - let ty = self.ty(state, ty)?; - Ok(ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Option(ty)), - contains_borrow: ty.contains_borrow(), - }) - } - ast::Type::Result { ok, err, .. } => { - let ok = ok.as_ref().map(|t| self.ty(state, t)).transpose()?; - let err = err.as_ref().map(|t| self.ty(state, t)).transpose()?; - Ok(ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Result { ok, err }), - contains_borrow: ok.as_ref().map_or(false, |t| t.contains_borrow()) - || err.as_ref().map_or(false, |t| t.contains_borrow()), - }) - } - ast::Type::Borrow(id, _) => { - let item = state.local_item(id)?.1; - let kind = item.kind(); - if let ItemKind::Resource(id) = kind { - return Ok(ValueType::Borrow(id)); - } - - Err(Error::NotResourceType { - name: id.string.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - span: id.span, - }) - } - ast::Type::Ident(id) => { - let item = state.local_item(id)?.1; - match item.kind() { - ItemKind::Resource(id) => Ok(ValueType::Own(id)), - ItemKind::Type(Type::Value(ty)) => Ok(ty), - kind => Err(Error::NotValueType { - name: id.string.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - span: id.span, - }), - } - } - } - } - - fn func_type( - &mut self, - state: &State<'a>, - func_params: &[ast::NamedType<'a>], - func_results: &ast::ResultList<'a>, - kind: FuncKind, - resource: Option, - ) -> ResolutionResult { - let mut params = IndexMap::new(); - - if kind == FuncKind::Method { - params.insert("self".into(), ValueType::Borrow(resource.unwrap())); - } - - for param in func_params { - if params - .insert(param.id.string.into(), self.ty(state, ¶m.ty)?) - .is_some() - { - return Err(Error::DuplicateParameter { - name: param.id.string.to_string(), - kind, - span: param.id.span, - }); - } - } - - let results = match func_results { - ast::ResultList::Empty => { - if kind == FuncKind::Constructor { - Some(FuncResult::Scalar(ValueType::Own(resource.unwrap()))) - } else { - None - } - } - ast::ResultList::Named(results) => { - let mut list = IndexMap::new(); - for result in results { - let value_type = self.ty(state, &result.ty)?; - if value_type.contains_borrow() { - return Err(Error::BorrowInResult { - span: result.ty.span(), - }); - } - - if list - .insert(result.id.string.to_owned(), value_type) - .is_some() - { - return Err(Error::DuplicateResult { - name: result.id.string.to_string(), - kind, - span: result.id.span, - }); - } - } - Some(FuncResult::List(list)) - } - ast::ResultList::Scalar(ty) => { - let value_type = self.ty(state, ty)?; - if value_type.contains_borrow() { - return Err(Error::BorrowInResult { span: ty.span() }); - } - Some(FuncResult::Scalar(value_type)) - } - }; - - Ok(self.definitions.funcs.alloc(Func { params, results })) - } - - fn resolve_package( - &mut self, - state: &mut State<'a>, - name: &'a str, - version: Option<&'a Version>, - span: SourceSpan, - ) -> ResolutionResult { - match state.package_map.entry(PackageKey { name, version }) { - hash_map::Entry::Occupied(e) => Ok(*e.get()), - hash_map::Entry::Vacant(e) => { - log::debug!("resolving package `{name}`"); - let bytes = match self.packages.get(&PackageKey { name, version }) { - Some(bytes) => bytes.clone(), - None => { - return Err(Error::UnknownPackage { - name: name.to_string(), - span, - }) - } - }; - - let id = state.packages.alloc( - Package::parse(&mut self.definitions, name, version, bytes).map_err(|e| { - Error::PackageParseFailure { - name: name.to_string(), - span, - source: e, - } - })?, - ); - Ok(*e.insert(id)) - } - } - } - - fn resolve_package_export( - &mut self, - state: &mut State<'a>, - path: &'a ast::PackagePath<'a>, - ) -> ResolutionResult { - log::debug!("resolving package export `{}`", path.string); - // Check for reference to local item - if path.name == self.document.directive.package.name { - return self.resolve_local_export(state, path); - } - - let pkg = self.resolve_package( - state, - path.name, - path.version.as_ref(), - path.package_name_span(), - )?; - - let mut current = 0; - let mut parent_ty = None; - let mut found = None; - for (i, (segment, _)) in path.segment_spans().enumerate() { - current = i; - - // Look up the first segment in the package definitions - if i == 0 { - found = state.packages[pkg].definitions.get(segment).copied(); - continue; - } - - // Otherwise, project into the parent based on the current segment - let export = match found.unwrap() { - // The parent is an interface or instance - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { - self.definitions.interfaces[id] - .exports - .get(segment) - .copied() - } - // The parent is a world or component or component instantiation - ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => { - self.definitions.worlds[id].exports.get(segment).copied() - } - _ => None, - }; - - parent_ty = found.map(|kind| kind.as_str(&self.definitions)); - found = export; - if found.is_none() { - break; - } - } - - found.ok_or_else(|| { - let segments = path.segment_spans().enumerate(); - let mut prev_path = String::new(); - for (i, (segment, span)) in segments { - if i == current { - return Error::PackageMissingExport { - name: path.string.to_string(), - export: segment.to_string(), - kind: parent_ty.map(ToOwned::to_owned), - path: prev_path, - span, - }; - } - - if !prev_path.is_empty() { - prev_path.push('/'); - } - - prev_path.push_str(segment); - } - - unreachable!("path segments should never be empty") - }) - } - - fn resolve_local_export( - &self, - state: &State<'a>, - path: &ast::PackagePath<'a>, - ) -> ResolutionResult { - log::debug!("resolving local path `{path}`", path = path.string); - - let mut segments = path.segment_spans(); - let (segment, span) = segments.next().unwrap(); - let item = state - .root_item(&ast::Ident { - string: segment, - span, - })? - .1; - - let mut current = segment; - let mut kind = item.kind(); - for (segment, span) in segments { - let exports = match kind { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { - &self.definitions.interfaces[id].exports - } - ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => { - &self.definitions.worlds[id].exports - } - _ => { - return Err(Error::PackagePathMissingExport { - name: current.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - export: segment.to_string(), - span, - }); - } - }; - - kind = - exports - .get(segment) - .copied() - .ok_or_else(|| Error::PackagePathMissingExport { - name: current.to_string(), - kind: kind.as_str(&self.definitions).to_string(), - export: segment.to_string(), - span, - })?; - - current = segment; - } - - Ok(kind) - } - - fn expr(&mut self, state: &mut State<'a>, expr: &'a ast::Expr<'a>) -> ResolutionResult { - let mut item = self.primary_expr(state, &expr.primary)?; - - let mut parent_span = expr.primary.span(); - for expr in &expr.postfix { - item = self.postfix_expr(state, item, expr, parent_span)?; - parent_span = expr.span(); - } - - Ok(item) - } - - fn primary_expr( - &mut self, - state: &mut State<'a>, - expr: &'a ast::PrimaryExpr<'a>, - ) -> ResolutionResult { - match expr { - ast::PrimaryExpr::New(e) => self.new_expr(state, e), - ast::PrimaryExpr::Nested(e) => self.expr(state, &e.inner), - ast::PrimaryExpr::Ident(i) => Ok(state.local_item(i)?.0), - } - } - - fn new_expr( - &mut self, - state: &mut State<'a>, - expr: &'a ast::NewExpr<'a>, - ) -> ResolutionResult { - if expr.package.name == self.document.directive.package.name { - return Err(Error::UnknownPackage { - name: expr.package.name.to_string(), - span: expr.package.span, - }); - } - - let pkg = self.resolve_package( - state, - expr.package.name, - expr.package.version.as_ref(), - expr.package.span, - )?; - let ty = state.packages[pkg].world; - let mut require_all = true; - - let mut arguments: IndexMap = Default::default(); - for (i, arg) in expr.arguments.iter().enumerate() { - let (name, item, span) = match arg { - ast::InstantiationArgument::Inferred(id) => { - self.inferred_instantiation_arg(state, id, ty)? - } - ast::InstantiationArgument::Spread(_) => { - // Spread arguments will be processed after all other arguments - continue; - } - ast::InstantiationArgument::Named(arg) => { - self.named_instantiation_arg(state, arg, ty)? - } - ast::InstantiationArgument::Fill(span) => { - if i != (expr.arguments.len() - 1) { - return Err(Error::FillArgumentNotLast { span: *span }); - } - - require_all = false; - continue; - } - }; - - let prev = arguments.insert(name.clone(), (item, span)); - if prev.is_some() { - return Err(Error::DuplicateInstantiationArg { name, span }); - } - } - - // Process the spread arguments - for arg in &expr.arguments { - if let ast::InstantiationArgument::Spread(id) = arg { - self.spread_instantiation_arg(state, id, ty, &mut arguments)?; - } - } - - // Type check the arguments - for (name, (item, span)) in &arguments { - let world = &self.definitions.worlds[ty]; - let expected = - world - .imports - .get(name) - .ok_or_else(|| Error::MissingComponentImport { - package: expr.package.string.to_string(), - import: name.clone(), - span: *span, - })?; - - log::debug!( - "performing subtype check for argument `{name}` (item {item})", - item = item.index() - ); - - SubtypeChecker::new(&self.definitions, &state.packages) - .is_subtype(state.current.items[*item].kind(), *expected) - .map_err(|e| Error::MismatchedInstantiationArg { - name: name.clone(), - span: *span, - source: e, - })?; - } - - // Add implicit imports (i.e. `...` was present) or error in - // case of missing imports - let world = &self.definitions.worlds[ty]; - let missing = world - .imports - .iter() - .filter(|(n, _)| !arguments.contains_key(n.as_str())) - .map(|(n, k)| (n.clone(), *k)) - .collect::>(); - for (name, argument) in missing { - if require_all { - return Err(Error::MissingInstantiationArg { - name, - package: expr.package.string.to_string(), - span: expr.package.span, - }); - } - - // Implicitly import the argument - let item = self.implicit_import( - state, - name.clone(), - argument, - expr.package.name, - expr.package.span, - )?; - - arguments.insert(name, (item, expr.package.span)); - } - - Ok(state - .current - .items - .alloc(Item::Instantiation(super::Instantiation { - package: pkg, - arguments: arguments.into_iter().map(|(n, (i, _))| (n, i)).collect(), - }))) - } - - fn implicit_import( - &mut self, - state: &mut State<'a>, - name: String, - mut kind: ItemKind, - package: &'a str, - span: SourceSpan, - ) -> ResolutionResult { - assert!(state.scopes.is_empty()); - - if let Some(import) = state.imports.get(&name) { - // Check if the implicit import would conflict with an explicit import - if import.package.is_none() { - return Err(Error::InstantiationArgConflict { - name, - kind: kind.as_str(&self.definitions).to_string(), - span, - import: import.span, - }); - }; - - // Merge the existing import item with the given one - return match (kind, state.current.items[import.item].kind()) { - (ItemKind::Instance(id), ItemKind::Instance(_)) => { - log::debug!( - "merging implicit interface import `{name}` ({id})", - id = id.index(), - ); - - let item = import.item; - self.merge_instance_import(state, &name, id, span)?; - Ok(item) - } - (ItemKind::Component(_), ItemKind::Component(_)) => { - todo!("merge component imports") - } - (ItemKind::Func(_), ItemKind::Func(_)) => { - todo!("merge func imports") - } - (ItemKind::Module(_), ItemKind::Module(_)) => { - todo!("merge module imports") - } - (ItemKind::Resource(_), ItemKind::Resource(_)) => { - todo!("merge resource imports") - } - (ItemKind::Type(_), ItemKind::Type(_)) => { - todo!("merge type imports") - } - (ItemKind::Value(_), ItemKind::Value(_)) => { - todo!("merge value imports") - } - (_, kind) => { - return Err(Error::UnmergeableInstantiationArg { - name, - package: import.package.unwrap().to_string(), - kind: kind.as_str(&self.definitions).to_string(), - span, - instantiation: import.span, - }); - } - }; - } - - log::debug!( - "adding implicit import `{name}` ({kind})", - kind = kind.as_str(&self.definitions) - ); - - // If the item is an instance, we need to clone the interface as it - // might be merged in the future with other interface definitions. - if let ItemKind::Instance(id) = kind { - let mut target = self.definitions.interfaces[id].clone(); - self.remap_uses(state, &mut target.uses); - let id = self.definitions.interfaces.alloc(target); - log::debug!( - "creating new interface definition ({id}) for implicit import `{name}`", - id = id.index() - ); - kind = ItemKind::Instance(id); - } - - let id = state.current.items.alloc(Item::Import(super::Import { - name: name.clone(), - kind, - })); - - state.imports.insert( - name, - Import { - package: Some(package), - span, - item: id, - }, - ); - - Ok(id) - } - - fn merge_instance_import( - &mut self, - state: &mut State<'a>, - name: &str, - source_id: InterfaceId, - span: SourceSpan, - ) -> ResolutionResult<()> { - let import = state.imports.get(name).unwrap(); - let import_span = import.span; - let target_id = match state.current.items[import.item].kind() { - ItemKind::Instance(id) => id, - _ => unreachable!(), - }; - - // Merge the used types of the two interfaces - self.merge_used_types(state, target_id, source_id); - - // Perform the merge of the interfaces - let mut target = std::mem::take(&mut self.definitions.interfaces[target_id]); - let source = &self.definitions.interfaces[source_id]; - let mut checker = SubtypeChecker::new(&self.definitions, &state.packages); - - for (name, source_kind) in &source.exports { - match target.exports.get(name) { - Some(target_kind) => { - log::debug!( - "export `{name}` already exists in target interface {target}", - target = target_id.index(), - ); - - match ( - checker - .is_subtype(*source_kind, *target_kind) - .with_context(|| format!("mismatched type for export `{name}`")), - checker - .is_subtype(*target_kind, *source_kind) - .with_context(|| format!("mismatched type for export `{name}`")), - ) { - (Ok(_), Ok(_)) => { - // The two are compatible, check for remapped type - match (*target_kind, *source_kind) { - (ItemKind::Type(new), ItemKind::Type(old)) if new != old => { - target.remapped_types.entry(new).or_default().insert(old); - } - _ => {} - } - } - (Err(e), _) | (_, Err(e)) => { - // Neither is a subtype of the other, so error - return Err(Error::InstantiationArgMergeFailure { - name: name.to_owned(), - package: import.package.unwrap().to_string(), - kind: "instance".to_string(), - span, - instantiation: import_span, - source: e, - }); - } - } - } - None => { - log::debug!( - "adding export `{name}` ({kind}) to interface {target}", - kind = source_kind.as_str(&self.definitions), - target = target_id.index() - ); - - target.exports.insert(name.clone(), *source_kind); - } - } - } - - self.definitions.interfaces[target_id] = target; - Ok(()) - } - - fn merge_used_types(&mut self, state: &State, target_id: InterfaceId, source_id: InterfaceId) { - // Temporarily take ownership of the target - let mut target = std::mem::take(&mut self.definitions.interfaces[target_id]); - let source = &self.definitions.interfaces[source_id]; - - // Merge the source and target usings - for (name, used) in &source.uses { - if target.uses.contains_key(name) { - continue; - } - - target.uses.insert(name.clone(), used.clone()); - } - - // Remap the usings to point at imported interfaces - self.remap_uses(state, &mut target.uses); - self.definitions.interfaces[target_id] = target; - } - - fn remap_uses(&self, state: &State, uses: &mut IndexMap) { - // Now update all the interface ids in the usings - for used in uses.values_mut() { - let old = &self.definitions.interfaces[used.interface]; - let import = &state.imports[old.id.as_deref().unwrap()]; - match &state.current.items[import.item] { - super::Item::Import(super::Import { - kind: ItemKind::Instance(new_id), - .. - }) => { - used.interface = *new_id; - } - _ => unreachable!(), - } - } - } - - fn named_instantiation_arg( - &mut self, - state: &mut State<'a>, - arg: &'a ast::NamedInstantiationArgument<'a>, - world: WorldId, - ) -> ResolutionResult<(String, ItemId, SourceSpan)> { - let item = self.expr(state, &arg.expr)?; - - let name = match &arg.name { - ast::InstantiationArgumentName::Ident(ident) => Self::find_matching_interface_name( - ident.string, - &self.definitions.worlds[world].imports, - ) - .unwrap_or(ident.string), - ast::InstantiationArgumentName::String(name) => name.value, - }; - - Ok((name.to_owned(), item, arg.name.span())) - } - - fn inferred_instantiation_arg( - &mut self, - state: &mut State<'a>, - ident: &ast::Ident<'a>, - world: WorldId, - ) -> ResolutionResult<(String, ItemId, SourceSpan)> { - let (item_id, item) = state.local_item(ident)?; - let world = &self.definitions.worlds[world]; - - // If the item is an instance with an id, try the id. - if let ItemKind::Instance(id) = item.kind() { - if let Some(id) = &self.definitions.interfaces[id].id { - if world.imports.contains_key(id.as_str()) { - return Ok((id.clone(), item_id, ident.span)); - } - } - } - - // If the item comes from an import or an alias, try the name associated with it - match item { - Item::Import(super::Import { name, .. }) - | Item::Alias(super::Alias { export: name, .. }) => { - if world.imports.contains_key(name) { - return Ok((name.clone(), item_id, ident.span)); - } - } - _ => {} - } - - // Fall back to searching for a matching interface name, provided it is not ambiguous - // For example, match `foo:bar/baz` if `baz` is the identifier and the only match - if let Some(name) = Self::find_matching_interface_name(ident.string, &world.imports) { - return Ok((name.to_owned(), item_id, ident.span)); - } - - // Finally default to the id itself - Ok((ident.string.to_owned(), item_id, ident.span)) - } - - fn spread_instantiation_arg( - &mut self, - state: &mut State<'a>, - id: &ast::Ident, - world: WorldId, - arguments: &mut IndexMap, - ) -> ResolutionResult<()> { - let item = state.local_item(id)?.0; - let world = &self.definitions.worlds[world]; - - let exports = self.instance_exports(state, item, id.span, InstanceOperation::Spread)?; - - let mut spread = false; - for name in world.imports.keys() { - // Check if the argument was already provided - if arguments.contains_key(name) { - continue; - } - - // Alias a matching export of the instance - if let Some(aliased) = self.alias_export(state, item, exports, name)? { - spread = true; - arguments.insert(name.clone(), (aliased, id.span)); - } - } - - if !spread { - return Err(Error::SpreadInstantiationNoMatch { span: id.span }); - } - - Ok(()) - } - - fn find_matching_interface_name<'b>( - name: &str, - externs: &'b IndexMap, - ) -> Option<&'b str> { - // If the given name exists directly, don't treat it as an interface name - if externs.contains_key(name) { - return None; - } - - // Fall back to searching for a matching interface name, provided it is not ambiguous - // For example, match `foo:bar/baz` if `baz` is the identifier and the only match - let mut matches = externs.iter().filter(|(n, _)| match n.rfind('/') { - Some(start) => { - let mut n = &n[start + 1..]; - if let Some(index) = n.find('@') { - n = &n[..index]; - } - n == name - } - None => false, - }); - - let (name, _) = matches.next()?; - if matches.next().is_some() { - // More than one match, the name is ambiguous - return None; - } - - Some(name) - } - - fn infer_export_name(&self, state: &'a State, item_id: ItemId) -> Option<&str> { - let item = &state.current.items[item_id]; - - // If the item is an instance with an id, try the id. - if let ItemKind::Instance(id) = item.kind() { - if let Some(id) = &self.definitions.interfaces[id].id { - return Some(id); - } - } - - // If the item comes from an import or an alias, try the name associated with it - match item { - Item::Import(super::Import { name, .. }) - | Item::Alias(super::Alias { export: name, .. }) => Some(name), - _ => None, - } - } - - fn postfix_expr( - &mut self, - state: &mut State<'a>, - item: ItemId, - expr: &ast::PostfixExpr<'a>, - parent_span: SourceSpan, - ) -> ResolutionResult { - let exports = self.instance_exports(state, item, parent_span, InstanceOperation::Access)?; - - match expr { - ast::PostfixExpr::Access(expr) => { - let name = Self::find_matching_interface_name(expr.id.string, exports) - .unwrap_or(expr.id.string); - - self.alias_export(state, item, exports, name)? - .ok_or_else(|| Error::MissingInstanceExport { - name: name.to_owned(), - span: expr.span, - }) - } - ast::PostfixExpr::NamedAccess(expr) => self - .alias_export(state, item, exports, expr.string.value)? - .ok_or_else(|| Error::MissingInstanceExport { - name: expr.string.value.to_owned(), - span: expr.span, - }), - } - } - - fn instance_exports( - &self, - state: &State, - item: ItemId, - span: SourceSpan, - operation: InstanceOperation, - ) -> ResolutionResult<&IndexMap> { - match state.current.items[item].kind() { - ItemKind::Instance(id) => Ok(&self.definitions.interfaces[id].exports), - ItemKind::Instantiation(id) => { - Ok(&self.definitions.worlds[state.packages[id].world].exports) - } - kind => Err(Error::NotAnInstance { - kind: kind.as_str(&self.definitions).to_string(), - operation, - span, - }), - } - } - - fn alias_export( - &self, - state: &mut State<'a>, - item: ItemId, - exports: &IndexMap, - name: &str, - ) -> ResolutionResult> { - let kind = match exports.get(name) { - Some(kind) => *kind, - None => return Ok(None), - }; - - let aliases = state.aliases.entry(item).or_default(); - if let Some(id) = aliases.get(name) { - return Ok(Some(*id)); - } - - let id = state.current.items.alloc(Item::Alias(super::Alias { - item, - export: name.to_owned(), - kind, - })); - - aliases.insert(name.to_owned(), id); - Ok(Some(id)) - } - - fn validate_target( - &self, - state: &State, - path: &ast::PackagePath, - world: WorldId, - ) -> ResolutionResult<()> { - let world = &self.definitions.worlds[world]; - - let mut checker = SubtypeChecker::new(&self.definitions, &state.packages); - - // The output is allowed to import a subset of the world's imports - checker.invert(); - for (name, import) in &state.imports { - let expected = world - .imports - .get(name) - .ok_or_else(|| Error::ImportNotInTarget { - name: name.clone(), - world: path.string.to_owned(), - span: import.span, - })?; - - checker - .is_subtype( - expected.promote(), - state.root_scope().items[import.item].kind(), - ) - .map_err(|e| Error::TargetMismatch { - kind: ExternKind::Import, - name: name.clone(), - world: path.string.to_owned(), - span: import.span, - source: e, - })?; - } - - checker.revert(); - - // The output must export every export in the world - for (name, expected) in &world.exports { - let export = state - .exports - .get(name) - .ok_or_else(|| Error::MissingTargetExport { - name: name.clone(), - world: path.string.to_owned(), - kind: expected.as_str(&self.definitions).to_string(), - span: path.span, - })?; - - checker - .is_subtype( - state.root_scope().items[export.item].kind(), - expected.promote(), - ) - .map_err(|e| Error::TargetMismatch { - kind: ExternKind::Export, - name: name.clone(), - world: path.string.to_owned(), - span: export.span, - source: e, - })?; - } - - Ok(()) - } -} diff --git a/crates/wac-parser/src/resolution/encoding.rs b/crates/wac-parser/src/resolution/encoding.rs deleted file mode 100644 index f937048..0000000 --- a/crates/wac-parser/src/resolution/encoding.rs +++ /dev/null @@ -1,1163 +0,0 @@ -//! Module for encoding WebAssembly compositions. - -use super::{ - package::Package, Composition, DefinedType, DefinedTypeId, Definitions, Enum, Flags, FuncId, - InterfaceId, ItemKind, ModuleId, PrimitiveType, Record, ResourceId, Type, ValueType, Variant, - WorldId, -}; -use crate::{ - resolution::FuncResult, CoreExtern, Definition, Import, Instantiation, Item, ItemId, PackageId, - UsedType, -}; -use anyhow::Result; -use indexmap::{map::Entry, IndexMap, IndexSet}; -use std::fmt::Write; -use wasm_encoder::{ - Alias, ComponentBuilder, ComponentExportKind, ComponentOuterAliasKind, ComponentType, - ComponentTypeEncoder, ComponentTypeRef, ComponentValType, CoreTypeEncoder, EntityType, - GlobalType, InstanceType, MemoryType, ModuleType, PrimitiveValType, TableType, TagKind, - TagType, TypeBounds, -}; - -fn package_import_name(package: &Package) -> String { - let mut name = String::new(); - write!(&mut name, "unlocked-dep=<{name}", name = package.name).unwrap(); - if let Some(version) = &package.version { - write!(&mut name, "@{{>={version}}}", version = version).unwrap(); - } - write!(&mut name, ">").unwrap(); - name -} - -/// The options for encoding a composition. -#[derive(Default, Debug, Copy, Clone)] -pub struct EncodingOptions { - /// Whether or not to define packages. - /// - /// If `false`, packages are imported rather than defined. - pub define_packages: bool, -} - -pub struct Encoder<'a> { - composition: &'a Composition, - options: EncodingOptions, - packages: IndexMap, - exports: IndexSet<&'a str>, -} - -impl<'a> Encoder<'a> { - pub fn new(composition: &'a Composition, options: EncodingOptions) -> Self { - Self { - composition, - options, - packages: Default::default(), - exports: Default::default(), - } - } - - pub fn encode(mut self) -> Result> { - log::debug!( - "encoding composition `{name}`", - name = self.composition.package - ); - - let mut state = State::new(); - - // Encode all the items in the composition - for (id, item) in &self.composition.items { - self.item(&mut state, id, item)?; - } - - // Now encode any additional exports - // Note that defined types have already been exported - for (name, id) in &self.composition.exports { - if self.exports.contains(name.as_str()) { - continue; - } - - let index = state.item_indexes[id]; - let item = &self.composition.items[*id]; - state - .builder() - .export(name, item.kind().into(), index, None); - } - - assert!(state.scopes.is_empty()); - match state.current.encodable { - Encodable::Builder(mut builder) => { - let mut producer = wasm_metadata::Producers::empty(); - producer.add( - "processed-by", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION"), - ); - builder.raw_custom_section(&producer.raw_custom_section()); - - Ok(builder.finish()) - } - _ => unimplemented!("expected a builder"), - } - } - - fn item(&mut self, state: &mut State<'a>, id: ItemId, item: &'a Item) -> Result { - if let Some(index) = state.item_indexes.get(&id) { - return Ok(*index); - } - - let mut encode = || match item { - Item::Use(_) => unreachable!(), - Item::Definition(definition) => self.definition(state, definition), - Item::Import(import) => self.import(state, import), - Item::Alias(alias) => self.alias(state, alias), - Item::Instantiation(instantiation) => self.instantiation(state, instantiation), - }; - - let index = encode()?; - let prev = state.item_indexes.insert(id, index); - assert!(prev.is_none()); - Ok(index) - } - - fn definition(&mut self, state: &mut State<'a>, definition: &'a Definition) -> Result { - log::debug!( - "encoding definition `{name}` ({kind})", - name = definition.name, - kind = definition.kind.as_str(&self.composition.definitions) - ); - - let encoder = TypeEncoder::new(&self.composition.definitions); - let (ty, index) = match definition.kind { - ItemKind::Type(ty) => match ty { - Type::Func(id) => (ty, encoder.ty(state, Type::Func(id), None)?), - Type::Value(id) => (ty, encoder.ty(state, Type::Value(id), None)?), - Type::Interface(id) => (ty, encoder.interface(state, id)?), - Type::World(id) => (ty, encoder.world(state, id)?), - Type::Module(id) => (ty, encoder.ty(state, Type::Module(id), None)?), - }, - _ => unreachable!("only types can be defined"), - }; - - let index = - state - .builder() - .export(&definition.name, ComponentExportKind::Type, index, None); - - let inserted = self.exports.insert(&definition.name); - assert!(inserted); - - // Remap to the exported index - state.current.type_indexes.insert(ty, index); - - log::debug!( - "definition `{name}` encoded to type index {index}", - name = definition.name - ); - Ok(index) - } - - fn import(&self, state: &mut State<'a>, import: &Import) -> Result { - log::debug!( - "encoding import `{name}` ({kind})", - name = import.name, - kind = import.kind.as_str(&self.composition.definitions) - ); - - let encoder = TypeEncoder::new(&self.composition.definitions); - - let ty = match import.kind { - ItemKind::Type(ty) => { - ComponentTypeRef::Type(TypeBounds::Eq(encoder.ty(state, ty, None)?)) - } - ItemKind::Func(id) => { - ComponentTypeRef::Func(encoder.ty(state, Type::Func(id), None)?) - } - ItemKind::Instance(id) => { - // Check to see if the instance has already been imported - // This may occur when an interface uses another - if let Some(index) = state.current.instances.get(&id) { - log::debug!("skipping import of interface {id} as it was already imported with instance index {index}", id = id.index()); - return Ok(*index); - } - - ComponentTypeRef::Instance(encoder.ty(state, Type::Interface(id), None)?) - } - ItemKind::Component(id) => { - ComponentTypeRef::Component(encoder.ty(state, Type::World(id), None)?) - } - ItemKind::Module(id) => { - ComponentTypeRef::Module(encoder.ty(state, Type::Module(id), None)?) - } - ItemKind::Value(ty) => ComponentTypeRef::Value(encoder.value_type(state, ty)?), - ItemKind::Resource(_) => unreachable!("resources cannot be imported at the top-level"), - ItemKind::Instantiation(_) => unreachable!("instantiations cannot be imported"), - }; - - let index = state.builder().import(&import.name, ty); - log::debug!("import `{name}` encoded to {ty:?}", name = import.name); - - match import.kind { - ItemKind::Type(ty) => { - // Remap to the imported index - state.current.type_indexes.insert(ty, index); - } - ItemKind::Instance(id) => { - log::debug!( - "interface {id} is available for aliasing as instance index {index}", - id = id.index() - ); - - state.current.instances.insert(id, index); - } - _ => {} - } - - Ok(index) - } - - fn alias(&mut self, state: &mut State<'a>, alias: &crate::Alias) -> Result { - log::debug!( - "encoding alias for export `{export}` ({kind}) of instance {instance}", - export = alias.export, - kind = alias.kind.as_str(&self.composition.definitions), - instance = alias.item.index() - ); - - let instance = state.item_indexes[&alias.item]; - let kind = alias.kind.into(); - let index = state.builder().alias(Alias::InstanceExport { - instance, - kind, - name: &alias.export, - }); - log::debug!( - "alias of export `{export}` encoded to index {index} ({kind:?})", - export = alias.export - ); - Ok(index) - } - - fn instantiation( - &mut self, - state: &mut State<'a>, - instantiation: &Instantiation, - ) -> Result { - log::debug!( - "encoding instantiation of package `{package}`", - package = self.composition.packages[instantiation.package].name, - ); - - let component_index = match self.packages.entry(instantiation.package) { - Entry::Occupied(e) => *e.get(), - Entry::Vacant(e) => { - let package = &self.composition.packages[instantiation.package]; - let index = if self.options.define_packages { - state.builder().component_raw(&package.bytes) - } else { - let encoder = TypeEncoder::new(&self.composition.definitions); - let ty = encoder.component(state, package.world)?; - state.builder().import( - &package_import_name(package), - ComponentTypeRef::Component(ty), - ) - }; - *e.insert(index) - } - }; - - let arguments = instantiation - .arguments - .iter() - .map(|(n, id)| { - let item = &self.composition.items[*id]; - (n, item.kind().into(), state.item_indexes[id]) - }) - .collect::>(); - - let index = state - .builder() - .instantiate(component_index, arguments.iter().copied()); - - log::debug!( - "instantiation of package `{package}` ({arguments:?}) encoded to instance index {index}", - package = self.composition.packages[instantiation.package].name, - ); - - Ok(index) - } -} - -enum Encodable { - Builder(ComponentBuilder), - Instance(InstanceType), - Component(ComponentType), -} - -impl Encodable { - fn type_count(&self) -> u32 { - match self { - Encodable::Builder(t) => t.type_count(), - Encodable::Component(t) => t.type_count(), - Encodable::Instance(t) => t.type_count(), - } - } - - fn instance_count(&self) -> u32 { - match self { - Encodable::Builder(t) => t.instance_count(), - Encodable::Component(t) => t.instance_count(), - Encodable::Instance(t) => t.instance_count(), - } - } - - fn core_type_count(&self) -> u32 { - match self { - Encodable::Builder(t) => t.core_type_count(), - Encodable::Component(t) => t.core_type_count(), - Encodable::Instance(t) => t.core_type_count(), - } - } - - fn ty(&mut self) -> ComponentTypeEncoder { - match self { - Encodable::Builder(t) => t.ty().1, - Encodable::Instance(t) => t.ty(), - Encodable::Component(t) => t.ty(), - } - } - - fn core_type(&mut self) -> CoreTypeEncoder { - match self { - Encodable::Builder(t) => t.core_type().1, - Encodable::Instance(t) => t.core_type(), - Encodable::Component(t) => t.core_type(), - } - } - - fn import_type(&mut self, name: &str, ty: ComponentTypeRef) { - match self { - Encodable::Component(t) => { - t.import(name, ty); - } - Encodable::Builder(b) => { - b.import(name, ty); - } - _ => unreachable!("expected a component type"), - } - } - - fn alias(&mut self, alias: Alias) { - match self { - Encodable::Builder(t) => { - t.alias(alias); - } - Encodable::Instance(t) => { - t.alias(alias); - } - Encodable::Component(t) => { - t.alias(alias); - } - } - } -} - -impl Default for Encodable { - fn default() -> Self { - Self::Builder(Default::default()) - } -} - -impl From for PrimitiveValType { - fn from(value: PrimitiveType) -> Self { - match value { - PrimitiveType::U8 => Self::U8, - PrimitiveType::S8 => Self::S8, - PrimitiveType::U16 => Self::U16, - PrimitiveType::S16 => Self::S16, - PrimitiveType::U32 => Self::U32, - PrimitiveType::S32 => Self::S32, - PrimitiveType::U64 => Self::U64, - PrimitiveType::S64 => Self::S64, - PrimitiveType::F32 => Self::F32, - PrimitiveType::F64 => Self::F64, - PrimitiveType::Char => Self::Char, - PrimitiveType::Bool => Self::Bool, - PrimitiveType::String => Self::String, - } - } -} - -impl From for ComponentExportKind { - fn from(value: ItemKind) -> Self { - match value { - ItemKind::Resource(_) | ItemKind::Type(_) => Self::Type, - ItemKind::Func(_) => Self::Func, - ItemKind::Instance(_) | ItemKind::Instantiation(_) => Self::Instance, - ItemKind::Component(_) => Self::Component, - ItemKind::Module(_) => Self::Module, - ItemKind::Value(_) => Self::Value, - } - } -} - -#[derive(Default)] -struct Scope<'a> { - /// The map of types to their encoded indexes. - type_indexes: IndexMap, - /// The map of imported instances in this scope. - instances: IndexMap, - /// The map of import/export name to their alias indexes. - type_aliases: IndexMap<&'a str, u32>, - /// The map of resource names to their encoded indexes. - resources: IndexMap<&'a str, u32>, - encodable: Encodable, -} - -#[derive(Default)] -struct State<'a> { - scopes: Vec>, - current: Scope<'a>, - item_indexes: IndexMap, -} - -impl<'a> State<'a> { - fn new() -> Self { - Self::default() - } - - fn builder(&mut self) -> &mut ComponentBuilder { - assert!(self.scopes.is_empty()); - match &mut self.current.encodable { - Encodable::Builder(builder) => builder, - _ => unreachable!("expected a builder"), - } - } - - fn push(&mut self, encodable: Encodable) { - log::debug!("pushing new type scope"); - let prev = std::mem::replace( - &mut self.current, - Scope { - encodable, - ..Default::default() - }, - ); - - self.scopes.push(prev); - } - - fn pop(&mut self) -> Encodable { - log::debug!("popping type scope"); - let prev = std::mem::replace(&mut self.current, self.scopes.pop().unwrap()); - prev.encodable - } - - fn used_type_index(&mut self, name: &str) -> Option { - if let Some(index) = self.current.type_aliases.get(name) { - return Some(*index); - } - - if let Some(parent) = self.scopes.last() { - if let Some(outer) = parent.type_aliases.get(name) { - let index = self.current.encodable.type_count(); - log::debug!("encoding outer alias for `{name}` to type index {index}"); - self.current.encodable.alias(Alias::Outer { - kind: ComponentOuterAliasKind::Type, - count: 1, - index: *outer, - }); - return Some(index); - } - } - - None - } -} - -struct TypeEncoder<'a>(&'a Definitions); - -impl<'a> TypeEncoder<'a> { - fn new(defs: &'a Definitions) -> Self { - Self(defs) - } - - fn ty(&self, state: &mut State<'a>, ty: Type, name: Option<&str>) -> Result { - if let Some(index) = state.current.type_indexes.get(&ty) { - return Ok(*index); - } - - if let Some(name) = name { - if let Some(index) = state.used_type_index(name) { - state.current.type_indexes.insert(ty, index); - return Ok(index); - } - } - - let index = match ty { - Type::Func(id) => self.func_type(state, id)?, - Type::Value(ValueType::Primitive(ty)) => Self::primitive(state, ty), - Type::Value(ValueType::Borrow(id)) => self.borrow(state, id), - Type::Value(ValueType::Own(id)) => self.own(state, id), - Type::Value(ValueType::Defined { id, .. }) => self.defined(state, id)?, - Type::Interface(id) => self.instance(state, id, false)?, - Type::World(id) => self.component(state, id)?, - Type::Module(id) => self.module(state, id), - }; - - state.current.type_indexes.insert(ty, index); - Ok(index) - } - - fn func_type(&self, state: &mut State<'a>, id: FuncId) -> Result { - log::debug!("encoding function {id}", id = id.index()); - let ty = &self.0.funcs[id]; - - let params = ty - .params - .iter() - .map(|(n, ty)| Ok((n.as_str(), self.value_type(state, *ty)?))) - .collect::>>()?; - - let results = match &ty.results { - Some(FuncResult::Scalar(ty)) => vec![("", self.value_type(state, *ty)?)], - Some(FuncResult::List(results)) => results - .iter() - .map(|(n, ty)| Ok((n.as_str(), self.value_type(state, *ty)?))) - .collect::>()?, - None => Vec::new(), - }; - - let index = state.current.encodable.type_count(); - let mut encoder = state.current.encodable.ty().function(); - encoder.params(params); - - match &ty.results { - Some(FuncResult::Scalar(_)) => { - encoder.result(results[0].1); - } - _ => { - encoder.results(results); - } - } - - log::debug!( - "function {id} encoded to type index {index}", - id = id.index() - ); - Ok(index) - } - - fn defined(&self, state: &mut State<'a>, id: DefinedTypeId) -> Result { - log::debug!("encoding defined type {id}", id = id.index()); - let ty = &self.0.types[id]; - let index = match ty { - DefinedType::Tuple(types) => self.tuple(state, types)?, - DefinedType::List(ty) => self.list(state, *ty)?, - DefinedType::Option(ty) => self.option(state, *ty)?, - DefinedType::Result { ok, err } => self.result(state, *ok, *err)?, - DefinedType::Variant(v) => self.variant(state, v)?, - DefinedType::Record(r) => self.record(state, r)?, - DefinedType::Flags(f) => self.flags(state, f), - DefinedType::Enum(e) => self.enum_type(state, e), - DefinedType::Alias(ValueType::Primitive(ty)) => Self::primitive(state, *ty), - DefinedType::Alias(ValueType::Borrow(id)) => self.borrow(state, *id), - DefinedType::Alias(ValueType::Own(id)) => self.own(state, *id), - DefinedType::Alias(ValueType::Defined { id, .. }) => self.defined(state, *id)?, - }; - - log::debug!( - "defined type {id} encoded to type index {index}", - id = id.index() - ); - Ok(index) - } - - fn use_aliases(&self, state: &mut State<'a>, uses: &'a IndexMap) { - state.current.type_aliases.clear(); - - for (name, used) in uses { - let interface = &self.0.interfaces[used.interface]; - let instance = state.current.instances[&used.interface]; - let index = state.current.encodable.type_count(); - let export: &String = used.name.as_ref().unwrap_or(name); - let kind = interface.exports.get(export).unwrap(); - state.current.encodable.alias(Alias::InstanceExport { - instance, - kind: ComponentExportKind::Type, - name: export, - }); - - log::debug!( - "aliased export `{export}` ({kind:?}) of instance {instance} to type index {index}" - ); - - state.current.type_aliases.insert(name, index); - } - } - - fn instance(&self, state: &mut State<'a>, id: InterfaceId, types_only: bool) -> Result { - log::debug!("encoding instance type for interface {id}", id = id.index()); - let interface = &self.0.interfaces[id]; - for used in interface.uses.values() { - self.import_deps(state, used.interface)?; - } - - // Encode any required aliases - self.use_aliases(state, &interface.uses); - state.push(Encodable::Instance(InstanceType::default())); - - // Otherwise, export all exports - for (name, kind) in &interface.exports { - match kind { - ItemKind::Type(ty) => { - let index = self.export(state, name, *kind)?; - - // Map the encoded type index to any remapped types. - if let Some(prev) = interface.remapped_types.get(ty) { - for ty in prev { - state.current.type_indexes.insert(*ty, index); - } - } - } - ItemKind::Resource(_) => { - self.export(state, name, *kind)?; - } - _ => { - if !types_only { - self.export(state, name, *kind)?; - } - } - } - } - - match state.pop() { - Encodable::Instance(ty) => { - let index = state.current.encodable.type_count(); - state.current.encodable.ty().instance(&ty); - log::debug!( - "instance {id} encoded to type index {index}", - id = id.index() - ); - Ok(index) - } - _ => unreachable!(), - } - } - - fn component(&self, state: &mut State<'a>, id: WorldId) -> Result { - log::debug!("encoding component type for world {id}", id = id.index()); - let world = &self.0.worlds[id]; - - state.push(Encodable::Component(ComponentType::default())); - - for used in world.uses.values() { - self.import_deps(state, used.interface)?; - } - - self.use_aliases(state, &world.uses); - - for (name, kind) in &world.imports { - self.import(state, name, *kind)?; - } - - for (name, kind) in &world.exports { - self.export(state, name, *kind)?; - } - - match state.pop() { - Encodable::Component(ty) => { - let index = state.current.encodable.type_count(); - state.current.encodable.ty().component(&ty); - log::debug!("world {id} encoded to type index {index}", id = id.index()); - Ok(index) - } - _ => unreachable!(), - } - } - - fn import_deps(&self, state: &mut State<'a>, id: InterfaceId) -> anyhow::Result<()> { - if state.current.instances.contains_key(&id) { - return Ok(()); - } - - let interface = &self.0.interfaces[id]; - - // Depth-first recurse on the dependencies of this interface - for used in interface.uses.values() { - self.import_deps(state, used.interface)?; - } - - let name = self.0.interfaces[id] - .id - .as_deref() - .expect("interface should have an id"); - - log::debug!("encoding dependency on interface {id}", id = id.index()); - - let index = self.instance(state, id, !state.scopes.is_empty())?; - let import_index = state.current.encodable.instance_count(); - - state - .current - .encodable - .import_type(name, ComponentTypeRef::Instance(index)); - - log::debug!( - "interface {id} is available for aliasing as instance {import_index}", - id = id.index() - ); - - state.current.instances.insert(id, import_index); - Ok(()) - } - - fn interface(&self, state: &mut State<'a>, id: InterfaceId) -> Result { - let interface = &self.0.interfaces[id]; - log::debug!( - "encoding interface definition of `{name}` ({id})", - name = interface.id.as_deref().unwrap_or(""), - id = id.index(), - ); - assert!(state.scopes.is_empty()); - state.push(Encodable::Component(ComponentType::default())); - - for used in interface.uses.values() { - self.import_deps(state, used.interface)?; - } - - let index = self.instance(state, id, false)?; - - Self::export_type( - state, - interface.id.as_deref().expect("interface must have an id"), - ComponentTypeRef::Instance(index), - ); - - match state.pop() { - Encodable::Component(ty) => { - let (index, encoder) = state.builder().ty(); - encoder.component(&ty); - log::debug!( - "encoded interface definition of `{id}` to type index {index}", - id = interface.id.as_deref().unwrap_or("") - ); - Ok(index) - } - _ => unreachable!(), - } - } - - fn world(&self, state: &mut State<'a>, id: WorldId) -> Result { - let world = &self.0.worlds[id]; - let world_id = world.id.as_deref().expect("world must have an id"); - - log::debug!("encoding world definition of `{world_id}`"); - - assert!(state.scopes.is_empty()); - state.push(Encodable::Component(ComponentType::default())); - let index = self.component(state, id)?; - Self::export_type(state, world_id, ComponentTypeRef::Component(index)); - - match state.pop() { - Encodable::Component(ty) => { - let (index, encoder) = state.builder().ty(); - encoder.component(&ty); - log::debug!("encoded world definition of `{world_id}` to type index {index}"); - Ok(index) - } - _ => unreachable!(), - } - } - - fn module(&self, state: &mut State<'a>, id: ModuleId) -> u32 { - log::debug!("encoding module definition"); - let ty = &self.0.modules[id]; - let mut encoded = ModuleType::new(); - - for ((module, name), ext) in &ty.imports { - let ty = self.entity_type(&mut encoded, ext); - encoded.import(module, name, ty); - } - - for (name, ext) in &ty.exports { - let ty = self.entity_type(&mut encoded, ext); - encoded.export(name, ty); - } - - let index = state.current.encodable.core_type_count(); - state.current.encodable.core_type().module(&encoded); - log::debug!("encoded module definition to type index {index}"); - index - } - - fn entity_type(&self, encodable: &mut ModuleType, ext: &CoreExtern) -> EntityType { - match ext { - CoreExtern::Func(func) => { - let index = encodable.type_count(); - encodable.ty().function( - func.params.iter().copied().map(Into::into), - func.results.iter().copied().map(Into::into), - ); - EntityType::Function(index) - } - CoreExtern::Table { - element_type, - initial, - maximum, - } => EntityType::Table(TableType { - element_type: (*element_type).into(), - minimum: *initial, - maximum: *maximum, - }), - CoreExtern::Memory { - memory64, - shared, - initial, - maximum, - } => EntityType::Memory(MemoryType { - minimum: *initial, - maximum: *maximum, - memory64: *memory64, - shared: *shared, - }), - CoreExtern::Global { val_type, mutable } => EntityType::Global(GlobalType { - val_type: (*val_type).into(), - mutable: *mutable, - }), - CoreExtern::Tag(func) => { - let index = encodable.type_count(); - encodable.ty().function( - func.params.iter().copied().map(Into::into), - func.results.iter().copied().map(Into::into), - ); - EntityType::Tag(TagType { - kind: TagKind::Exception, - func_type_idx: index, - }) - } - } - } - - fn value_type(&self, state: &mut State<'a>, ty: ValueType) -> Result { - if let Some(index) = state.current.type_indexes.get(&Type::Value(ty)) { - return Ok(ComponentValType::Type(*index)); - } - - let index = match ty { - ValueType::Primitive(ty) => return Ok(ComponentValType::Primitive(ty.into())), - ValueType::Borrow(id) => self.borrow(state, id), - ValueType::Own(id) => self.own(state, id), - ValueType::Defined { id, .. } => self.defined(state, id)?, - }; - - state.current.type_indexes.insert(Type::Value(ty), index); - Ok(ComponentValType::Type(index)) - } - - fn primitive(state: &mut State<'a>, ty: PrimitiveType) -> u32 { - let index = state.current.encodable.type_count(); - state - .current - .encodable - .ty() - .defined_type() - .primitive(ty.into()); - index - } - - fn tuple(&self, state: &mut State<'a>, types: &[ValueType]) -> Result { - let types = types - .iter() - .map(|ty| self.value_type(state, *ty)) - .collect::>>()?; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().tuple(types); - Ok(index) - } - - fn list(&self, state: &mut State<'a>, ty: ValueType) -> Result { - let ty = self.value_type(state, ty)?; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().list(ty); - Ok(index) - } - - fn option(&self, state: &mut State<'a>, ty: ValueType) -> Result { - let ty = self.value_type(state, ty)?; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().option(ty); - Ok(index) - } - - fn result( - &self, - state: &mut State<'a>, - ok: Option, - err: Option, - ) -> Result { - let ok = ok.map(|ty| self.value_type(state, ty)).transpose()?; - let err = err.map(|ty| self.value_type(state, ty)).transpose()?; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().result(ok, err); - Ok(index) - } - - fn borrow(&self, state: &mut State<'a>, res: ResourceId) -> u32 { - assert!(!state.scopes.is_empty()); - let res = state.current.resources[self.0.resources[res].name.as_str()]; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().borrow(res); - index - } - - fn own(&self, state: &mut State<'a>, res: ResourceId) -> u32 { - assert!(!state.scopes.is_empty()); - let res = state.current.resources[self.0.resources[res].name.as_str()]; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().own(res); - index - } - - fn variant(&self, state: &mut State<'a>, variant: &Variant) -> Result { - let cases = variant - .cases - .iter() - .map(|(n, ty)| { - Ok(( - n.as_str(), - ty.map(|ty| self.value_type(state, ty)).transpose()?, - None, - )) - }) - .collect::>>()?; - - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().variant(cases); - Ok(index) - } - - fn record(&self, state: &mut State<'a>, record: &Record) -> Result { - let fields = record - .fields - .iter() - .map(|(n, ty)| Ok((n.as_str(), self.value_type(state, *ty)?))) - .collect::>>()?; - let index = state.current.encodable.type_count(); - state.current.encodable.ty().defined_type().record(fields); - Ok(index) - } - - fn flags(&self, state: &mut State<'a>, flags: &Flags) -> u32 { - let index = state.current.encodable.type_count(); - state - .current - .encodable - .ty() - .defined_type() - .flags(flags.0.iter().map(String::as_str)); - index - } - - fn enum_type(&self, state: &mut State<'a>, e: &Enum) -> u32 { - let index = state.current.encodable.type_count(); - state - .current - .encodable - .ty() - .defined_type() - .enum_type(e.0.iter().map(String::as_str)); - index - } - - fn import(&self, state: &mut State<'a>, name: &str, kind: ItemKind) -> Result<()> { - if let ItemKind::Resource(id) = kind { - return self.import_resource(state, name, id); - } - - let ty = kind.ty().expect("item should have an associated type"); - log::debug!( - "encoding import of `{name}` ({kind})", - name = name, - kind = kind.as_str(self.0) - ); - - let index = self.ty(state, ty, Some(name))?; - - match kind { - ItemKind::Type(_) => { - let import_index = state.current.encodable.type_count(); - state - .current - .encodable - .import_type(name, ComponentTypeRef::Type(TypeBounds::Eq(index))); - - // Remap the type to the index of the imported item - state.current.type_indexes.insert(ty, import_index); - } - ItemKind::Func(_) => { - state - .current - .encodable - .import_type(name, ComponentTypeRef::Func(index)); - } - ItemKind::Instance(id) => { - let import_index = state.current.encodable.instance_count(); - state - .current - .encodable - .import_type(name, ComponentTypeRef::Instance(index)); - log::debug!( - "instance {import_index} is available for aliasing as interface {id}", - id = id.index() - ); - state.current.instances.insert(id, import_index); - } - _ => unreachable!("expected only types, functions, and instance types"), - } - - Ok(()) - } - - fn import_resource(&self, state: &mut State<'a>, name: &str, id: ResourceId) -> Result<()> { - if state.current.resources.contains_key(name) { - return Ok(()); - } - - log::debug!( - "encoding import of resource `{name}` ({id})", - id = id.index() - ); - - let resource = &self.0.resources[id]; - let index = if let Some(outer) = state.used_type_index(name) { - // This is an alias to an outer resource type - let index = state.current.encodable.type_count(); - state - .current - .encodable - .import_type(name, ComponentTypeRef::Type(TypeBounds::Eq(outer))); - - log::debug!( - "encoded outer alias for resource `{name}` ({id}) to type index {index}", - id = id.index() - ); - index - } else if let Some(alias_of) = resource.alias_of { - // This is an alias to another resource at the same scope - let orig = state.current.resources[self.0.resolve_resource(alias_of).name.as_str()]; - let index = state.current.encodable.type_count(); - state - .current - .encodable - .import_type(name, ComponentTypeRef::Type(TypeBounds::Eq(orig))); - - log::debug!("encoded import for resource `{name}` as type index {index} (alias of type index {orig})"); - index - } else { - // Otherwise, this is a new resource type, import with a subtype bounds - let index = state.current.encodable.type_count(); - state - .current - .encodable - .import_type(name, ComponentTypeRef::Type(TypeBounds::SubResource)); - - log::debug!("encoded import for resource `{name}` to type index {index}"); - index - }; - - state.current.resources.insert(&resource.name, index); - Ok(()) - } - - fn export(&self, state: &mut State<'a>, name: &str, kind: ItemKind) -> Result { - if let ItemKind::Resource(id) = kind { - return self.export_resource(state, name, id); - } - - let ty = kind.ty().expect("item should have an associated type"); - log::debug!( - "encoding export of `{name}` ({kind})", - name = name, - kind = kind.as_str(self.0) - ); - - let index = self.ty(state, ty, Some(name))?; - let index = Self::export_type( - state, - name, - match kind { - ItemKind::Type(_) => ComponentTypeRef::Type(TypeBounds::Eq(index)), - ItemKind::Func(_) => ComponentTypeRef::Func(index), - ItemKind::Instance(_) => ComponentTypeRef::Instance(index), - _ => unreachable!("expected only types, functions, and instance types"), - }, - ); - - // For types, remap to the index of the exported item - if let ItemKind::Type(ty) = kind { - state.current.type_indexes.insert(ty, index); - } - - Ok(index) - } - - fn export_resource(&self, state: &mut State<'a>, name: &str, id: ResourceId) -> Result { - log::debug!( - "encoding export of resource `{name}` ({id})", - id = id.index() - ); - - if let Some(existing) = state.current.resources.get(name) { - return Ok(*existing); - } - - let resource = &self.0.resources[id]; - let index = if let Some(outer) = state.used_type_index(name) { - // This is an alias to an outer resource type - let index = - Self::export_type(state, name, ComponentTypeRef::Type(TypeBounds::Eq(outer))); - log::debug!( - "encoded outer alias for resource `{name}` ({id}) as type index {index}", - id = id.index(), - ); - index - } else if let Some(alias_of) = resource.alias_of { - // This is an alias to another resource at the same scope - let index = state.current.resources[self.0.resolve_resource(alias_of).name.as_str()]; - let index = - Self::export_type(state, name, ComponentTypeRef::Type(TypeBounds::Eq(index))); - log::debug!( - "encoded alias for resource `{name}` ({id}) as type index {index}", - id = id.index(), - ); - index - } else { - // Otherwise, this is a new resource type, export with a subtype bounds - let index = - Self::export_type(state, name, ComponentTypeRef::Type(TypeBounds::SubResource)); - log::debug!( - "encoded export of resource `{name}` ({id}) as type index {index}", - id = id.index() - ); - index - }; - - state.current.resources.insert(&resource.name, index); - Ok(index) - } - - fn export_type(state: &mut State<'a>, name: &str, ty: ComponentTypeRef) -> u32 { - match &mut state.current.encodable { - Encodable::Component(t) => { - let index = t.type_count(); - t.export(name, ty); - index - } - Encodable::Instance(t) => { - let index = t.type_count(); - t.export(name, ty); - index - } - Encodable::Builder(_) => unreachable!("expected a component or instance type"), - } - } -} diff --git a/crates/wac-parser/src/resolution/package.rs b/crates/wac-parser/src/resolution/package.rs deleted file mode 100644 index aab1e53..0000000 --- a/crates/wac-parser/src/resolution/package.rs +++ /dev/null @@ -1,700 +0,0 @@ -use super::{ - serialize_id, CoreExtern, CoreFunc, DefinedType, Definitions, Enum, Flags, Func, FuncId, - FuncResult, Interface, InterfaceId, ItemKind, Module, ModuleId, Record, Type, ValueType, - Variant, World, WorldId, -}; -use crate::{Resource, ResourceId, UsedType}; -use anyhow::{bail, Result}; -use indexmap::IndexMap; -use semver::Version; -use serde::Serialize; -use std::{collections::HashMap, fmt, rc::Rc, sync::Arc}; -use wasmparser::{ - names::{ComponentName, ComponentNameKind}, - types::{self as wasm, ComponentAnyTypeId}, - Chunk, Encoding, Parser, Payload, ValidPayload, Validator, WasmFeatures, -}; - -/// Represents a package key that can be used in associative containers. -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct PackageKey<'a> { - /// The name of the package, - pub name: &'a str, - /// The version of the package. - pub version: Option<&'a Version>, -} - -impl fmt::Display for PackageKey<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{name}", name = self.name)?; - if let Some(version) = self.version { - write!(f, "@{version}")?; - } - Ok(()) - } -} - -/// Represents information about a package. -/// -/// A package is expected to be a valid WebAssembly component. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Package { - /// The name of the package. - pub name: String, - /// The version of the package. - pub version: Option, - /// The bytes of the package. - #[serde(skip)] - pub bytes: Arc>, - /// The world (component type) of the package. - #[serde(serialize_with = "serialize_id")] - pub world: WorldId, - /// Defined interfaces and worlds from a WIT package. - pub definitions: IndexMap, -} - -impl Package { - /// Parses the given bytes into a package. - pub(crate) fn parse( - definitions: &mut Definitions, - name: &str, - version: Option<&Version>, - bytes: Arc>, - ) -> Result { - if !has_magic_header(&bytes) { - bail!("package `{name}` is expected to be a binary WebAssembly component binary but is not"); - } - let mut parser = Parser::new(0); - let mut parsers = Vec::new(); - let mut validator = Validator::new_with_features(WasmFeatures { - component_model: true, - ..Default::default() - }); - let mut imports = Vec::new(); - let mut exports = Vec::new(); - - let mut cur = bytes.as_ref().as_ref(); - loop { - match parser.parse(cur, true)? { - Chunk::Parsed { payload, consumed } => { - cur = &cur[consumed..]; - - match validator.payload(&payload)? { - ValidPayload::Ok => { - // Don't parse any sub-components or sub-modules - if !parsers.is_empty() { - continue; - } - - match payload { - Payload::Version { encoding, .. } => { - if encoding != Encoding::Component { - bail!("input is not a WebAssembly component"); - } - } - Payload::ComponentImportSection(s) => { - imports.reserve(s.count() as usize); - for import in s { - imports.push(import?.name.0); - } - } - Payload::ComponentExportSection(s) => { - exports.reserve(s.count() as usize); - for export in s { - exports.push(export?.name.0); - } - } - _ => {} - } - } - ValidPayload::Func(_, _) => {} - ValidPayload::Parser(next) => { - parsers.push(parser); - parser = next; - } - ValidPayload::End(types) => match parsers.pop() { - Some(parent) => parser = parent, - None => { - let mut converter = TypeConverter::new(definitions, types); - - let imports = imports - .into_iter() - .map(|i| Ok((i.to_string(), converter.import(i)?))) - .collect::>()?; - - let exports: IndexMap = exports - .into_iter() - .map(|i| Ok((i.to_string(), converter.export(i)?))) - .collect::>()?; - - let world = definitions.worlds.alloc(World { - id: None, - uses: Default::default(), - imports, - exports: exports.clone(), - }); - - return Ok(Self { - name: name.to_owned(), - version: version.map(ToOwned::to_owned), - bytes, - world, - definitions: Self::find_definitions(definitions, world), - }); - } - }, - } - } - Chunk::NeedMoreData(_) => unreachable!(), - } - } - } - - fn find_definitions(definitions: &Definitions, world: WorldId) -> IndexMap { - // Look for any component type exports that export a component type or instance type - let exports = &definitions.worlds[world].exports; - let mut defs = IndexMap::new(); - for (name, kind) in exports { - if let ItemKind::Type(Type::World(id)) = kind { - let world = &definitions.worlds[*id]; - if world.exports.len() != 1 { - continue; - } - - // Check if the export name is an interface name - let (export_name, kind) = world.exports.get_index(0).unwrap(); - match ComponentName::new(export_name, 0).unwrap().kind() { - ComponentNameKind::Interface(_) => {} - _ => continue, - } - - match kind { - ItemKind::Instance(id) => { - defs.insert(name.clone(), ItemKind::Type(Type::Interface(*id))); - } - ItemKind::Component(id) => { - defs.insert(name.clone(), ItemKind::Type(Type::World(*id))); - } - _ => continue, - } - } - } - - defs - } -} - -/// Whether the given bytes have a magic header indicating a WebAssembly binary. -fn has_magic_header(bytes: &[u8]) -> bool { - bytes.starts_with(b"\0asm") -} - -impl fmt::Debug for Package { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Package") - .field("name", &self.name) - .field("version", &self.version) - .field("bytes", &"...") - .field("world", &self.world) - .field("definitions", &self.definitions) - .finish() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Owner { - /// The owner is an interface. - Interface(InterfaceId), - /// The owner is a world. - World(WorldId), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum Entity { - /// The entity is a type. - Type(Type), - /// The entity is a resource. - Resource(ResourceId), -} - -/// Responsible for converting between wasmparser and wac-parser type -/// representations. -struct TypeConverter<'a> { - definitions: &'a mut Definitions, - types: Rc, - cache: HashMap, - resource_map: HashMap, - owners: HashMap, -} - -impl<'a> TypeConverter<'a> { - fn new(definitions: &'a mut Definitions, types: wasm::Types) -> Self { - let types = Rc::new(types); - Self { - definitions, - types, - cache: Default::default(), - resource_map: Default::default(), - owners: Default::default(), - } - } - - fn import(&mut self, name: &str) -> Result { - let import = self.types.component_entity_type_of_import(name).unwrap(); - self.entity(name, import) - } - - fn export(&mut self, name: &str) -> Result { - let export = self.types.component_entity_type_of_export(name).unwrap(); - self.entity(name, export) - } - - fn component_func_type(&mut self, id: wasm::ComponentFuncTypeId) -> Result { - let key = wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Func(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Type(Type::Func(id)) => return Ok(*id), - _ => unreachable!("invalid cached type"), - } - } - - let types = self.types.clone(); - let func_ty = &types[id]; - let params = func_ty - .params - .iter() - .map(|(name, ty)| Ok((name.to_string(), self.component_val_type(*ty)?))) - .collect::>()?; - - let results = if func_ty.results.len() == 0 { - None - } else if func_ty.results.len() == 1 && func_ty.results[0].0.is_none() { - Some(FuncResult::Scalar( - self.component_val_type(func_ty.results[0].1)?, - )) - } else { - Some(FuncResult::List( - func_ty - .results - .iter() - .map(|(name, ty)| { - Ok(( - name.as_ref().unwrap().to_string(), - self.component_val_type(*ty)?, - )) - }) - .collect::>()?, - )) - }; - - let id = self.definitions.funcs.alloc(Func { params, results }); - self.cache.insert(key, Entity::Type(Type::Func(id))); - Ok(id) - } - - fn module_type(&mut self, id: wasm::ComponentCoreModuleTypeId) -> Result { - let key = wasm::AnyTypeId::Core(wasm::ComponentCoreTypeId::Module(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Type(Type::Module(id)) => return Ok(*id), - _ => unreachable!("invalid cached type"), - } - } - - let module_ty = &self.types[id]; - - let imports = module_ty - .imports - .iter() - .map(|((module, name), ty)| ((module.clone(), name.clone()), self.entity_type(*ty))) - .collect(); - - let exports = module_ty - .exports - .iter() - .map(|(name, ty)| (name.clone(), self.entity_type(*ty))) - .collect(); - - let module_id = self.definitions.modules.alloc(Module { imports, exports }); - self.cache - .insert(key, Entity::Type(Type::Module(module_id))); - Ok(module_id) - } - - fn ty(&mut self, id: wasm::ComponentAnyTypeId) -> Result { - match id { - wasm::ComponentAnyTypeId::Defined(id) => { - Ok(Type::Value(self.component_defined_type(id)?)) - } - wasm::ComponentAnyTypeId::Func(id) => Ok(Type::Func(self.component_func_type(id)?)), - wasm::ComponentAnyTypeId::Component(id) => { - Ok(Type::World(self.component_type(None, id)?)) - } - wasm::ComponentAnyTypeId::Instance(id) => { - Ok(Type::Interface(self.component_instance_type(None, id)?)) - } - wasm::ComponentAnyTypeId::Resource(_) => { - bail!("unexpected resource encountered") - } - } - } - - fn component_val_type(&mut self, ty: wasm::ComponentValType) -> Result { - match ty { - wasm::ComponentValType::Primitive(ty) => Ok(ValueType::Primitive(ty.into())), - wasm::ComponentValType::Type(id) => Ok(self.component_defined_type(id)?), - } - } - - fn component_instance_type( - &mut self, - name: Option<&str>, - id: wasm::ComponentInstanceTypeId, - ) -> Result { - let key = wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Instance(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Type(Type::Interface(id)) => { - return Ok(*id); - } - _ => unreachable!("invalid cached type"), - } - } - - let types = self.types.clone(); - let instance_ty = &types[id]; - let id = self.definitions.interfaces.alloc(Interface { - id: name.and_then(|n| n.contains(':').then(|| n.to_owned())), - remapped_types: Default::default(), - uses: Default::default(), - exports: IndexMap::with_capacity(instance_ty.exports.len()), - }); - - for (name, ty) in &instance_ty.exports { - let export = self.entity(name, *ty)?; - - if let wasm::ComponentEntityType::Type { - referenced, - created, - } = ty - { - self.use_or_own(Owner::Interface(id), name, *referenced, *created); - } - - let prev = self.definitions.interfaces[id] - .exports - .insert(name.clone(), export); - assert!(prev.is_none()); - } - - self.cache.insert(key, Entity::Type(Type::Interface(id))); - Ok(id) - } - - fn entity(&mut self, name: &str, ty: wasm::ComponentEntityType) -> Result { - match ty { - wasm::ComponentEntityType::Module(id) => Ok(ItemKind::Module(self.module_type(id)?)), - wasm::ComponentEntityType::Value(ty) => { - Ok(ItemKind::Value(self.component_val_type(ty)?)) - } - wasm::ComponentEntityType::Type { - created: wasm::ComponentAnyTypeId::Resource(id), - .. - } => Ok(ItemKind::Resource(self.resource(name, id))), - wasm::ComponentEntityType::Type { created, .. } => { - Ok(ItemKind::Type(self.ty(created)?)) - } - wasm::ComponentEntityType::Func(id) => { - Ok(ItemKind::Func(self.component_func_type(id)?)) - } - wasm::ComponentEntityType::Instance(id) => Ok(ItemKind::Instance( - self.component_instance_type(Some(name), id)?, - )), - wasm::ComponentEntityType::Component(id) => { - Ok(ItemKind::Component(self.component_type(Some(name), id)?)) - } - } - } - - fn use_or_own( - &mut self, - owner: Owner, - name: &str, - referenced: ComponentAnyTypeId, - created: ComponentAnyTypeId, - ) { - if let Some((other, orig)) = self.find_owner(referenced) { - match *other { - Owner::Interface(interface) if owner != *other => { - let used = UsedType { - interface, - name: if name != orig { - Some(orig.to_string()) - } else { - None - }, - }; - - // Owner is a different interface, so add a using reference - let uses = match owner { - Owner::Interface(id) => &mut self.definitions.interfaces[id].uses, - Owner::World(id) => &mut self.definitions.worlds[id].uses, - }; - - uses.insert(name.to_string(), used); - } - _ => {} - } - return; - } - - // Take ownership of the entity - let prev = self.owners.insert(created, (owner, name.to_string())); - assert!(prev.is_none()); - } - - fn component_type(&mut self, name: Option<&str>, id: wasm::ComponentTypeId) -> Result { - let key = wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Component(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Type(Type::World(id)) => return Ok(*id), - _ => unreachable!("invalid cached type"), - } - } - - let types = self.types.clone(); - let component_ty = &types[id]; - let id = self.definitions.worlds.alloc(World { - id: name.and_then(|n| n.contains(':').then(|| n.to_owned())), - uses: Default::default(), - imports: IndexMap::with_capacity(component_ty.imports.len()), - exports: IndexMap::with_capacity(component_ty.exports.len()), - }); - - for (name, ty) in &component_ty.imports { - let import = self.entity(name, *ty)?; - - if let wasm::ComponentEntityType::Type { - referenced, - created, - } = ty - { - self.use_or_own(Owner::World(id), name, *referenced, *created); - } - - let prev = self.definitions.worlds[id] - .imports - .insert(name.clone(), import); - assert!(prev.is_none()); - } - - for (name, ty) in &component_ty.exports { - let ty = self.entity(name, *ty)?; - let prev = self.definitions.worlds[id].exports.insert(name.clone(), ty); - assert!(prev.is_none()); - } - - self.cache.insert(key, Entity::Type(Type::World(id))); - Ok(id) - } - - fn component_defined_type(&mut self, id: wasm::ComponentDefinedTypeId) -> Result { - let key = wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Defined(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Type(Type::Value(ty)) => return Ok(*ty), - _ => unreachable!("invalid cached type"), - } - } - - let types = self.types.clone(); - let ty = match &types[id] { - wasm::ComponentDefinedType::Primitive(ty) => ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Alias(ValueType::Primitive((*ty).into()))), - contains_borrow: false, - }, - wasm::ComponentDefinedType::Record(ty) => { - let mut contains_borrow = false; - let fields = ty - .fields - .iter() - .map(|(name, ty)| { - let ty = self.component_val_type(*ty)?; - contains_borrow |= ty.contains_borrow(); - Ok((name.as_str().to_owned(), ty)) - }) - .collect::>()?; - - ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Record(Record { fields })), - contains_borrow, - } - } - wasm::ComponentDefinedType::Variant(ty) => { - let mut contains_borrow = false; - let cases = ty - .cases - .iter() - .map(|(name, case)| { - let ty = case.ty.map(|ty| self.component_val_type(ty)).transpose()?; - contains_borrow |= ty.as_ref().map_or(false, ValueType::contains_borrow); - Ok((name.as_str().to_owned(), ty)) - }) - .collect::>()?; - - ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Variant(Variant { cases })), - contains_borrow, - } - } - wasm::ComponentDefinedType::List(ty) => { - let ty = self.component_val_type(*ty)?; - ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::List(ty)), - contains_borrow: ty.contains_borrow(), - } - } - wasm::ComponentDefinedType::Tuple(ty) => { - let mut contains_borrow = false; - let types = ty - .types - .iter() - .map(|ty| { - let ty = self.component_val_type(*ty)?; - contains_borrow |= ty.contains_borrow(); - Ok(ty) - }) - .collect::>()?; - ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Tuple(types)), - contains_borrow, - } - } - wasm::ComponentDefinedType::Flags(flags) => { - let flags = flags.iter().map(|flag| flag.as_str().to_owned()).collect(); - ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Flags(Flags(flags))), - contains_borrow: false, - } - } - wasm::ComponentDefinedType::Enum(cases) => { - let cases = cases.iter().map(|case| case.as_str().to_owned()).collect(); - ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Enum(Enum(cases))), - contains_borrow: false, - } - } - wasm::ComponentDefinedType::Option(ty) => { - let ty = self.component_val_type(*ty)?; - ValueType::Defined { - id: self.definitions.types.alloc(DefinedType::Option(ty)), - contains_borrow: ty.contains_borrow(), - } - } - wasm::ComponentDefinedType::Result { ok, err } => { - let ok = ok.map(|ty| self.component_val_type(ty)).transpose()?; - let err = err.map(|ty| self.component_val_type(ty)).transpose()?; - ValueType::Defined { - id: self - .definitions - .types - .alloc(DefinedType::Result { ok, err }), - contains_borrow: ok.as_ref().map_or(false, ValueType::contains_borrow) - || err.as_ref().map_or(false, ValueType::contains_borrow), - } - } - wasm::ComponentDefinedType::Borrow(id) => ValueType::Borrow( - match self.cache.get(&wasm::AnyTypeId::Component( - wasm::ComponentAnyTypeId::Resource(*id), - )) { - Some(Entity::Resource(id)) => *id, - _ => unreachable!("expected a resource"), - }, - ), - wasm::ComponentDefinedType::Own(id) => ValueType::Own( - match self.cache.get(&wasm::AnyTypeId::Component( - wasm::ComponentAnyTypeId::Resource(*id), - )) { - Some(Entity::Resource(id)) => *id, - _ => unreachable!("expected a resource"), - }, - ), - }; - - self.cache.insert(key, Entity::Type(Type::Value(ty))); - Ok(ty) - } - - fn resource(&mut self, name: &str, id: wasm::AliasableResourceId) -> ResourceId { - let key = wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Resource(id)); - if let Some(ty) = self.cache.get(&key) { - match ty { - Entity::Resource(id) => return *id, - _ => unreachable!("invalid cached type"), - } - } - - // Check if this is an alias of another resource - if let Some(resource_id) = self.resource_map.get(&id.resource()) { - let alias_id = self.definitions.resources.alloc(Resource { - name: name.to_owned(), - alias_of: Some(*resource_id), - }); - self.cache.insert(key, Entity::Resource(alias_id)); - return alias_id; - } - - // Otherwise, this is a new resource - let resource_id = self.definitions.resources.alloc(Resource { - name: name.to_owned(), - alias_of: None, - }); - - self.resource_map.insert(id.resource(), resource_id); - self.cache.insert(key, Entity::Resource(resource_id)); - resource_id - } - - fn entity_type(&self, ty: wasm::EntityType) -> CoreExtern { - match ty { - wasm::EntityType::Func(ty) => CoreExtern::Func(self.func_type(ty)), - wasm::EntityType::Table(ty) => ty.into(), - wasm::EntityType::Memory(ty) => ty.into(), - wasm::EntityType::Global(ty) => ty.into(), - wasm::EntityType::Tag(ty) => CoreExtern::Tag(self.func_type(ty)), - } - } - - fn func_type(&self, ty: wasm::CoreTypeId) -> CoreFunc { - let func_ty = self.types[ty].unwrap_func(); - CoreFunc { - params: func_ty.params().iter().copied().map(Into::into).collect(), - results: func_ty.results().iter().copied().map(Into::into).collect(), - } - } - - fn find_owner(&self, mut id: wasm::ComponentAnyTypeId) -> Option<&(Owner, String)> { - let mut prev = None; - while prev.is_none() { - prev = self.owners.get(&id); - id = match self.types.peel_alias(id) { - Some(next) => next, - None => break, - }; - } - prev - } -} diff --git a/crates/wac-parser/src/resolution/types.rs b/crates/wac-parser/src/resolution/types.rs deleted file mode 100644 index 9063cb8..0000000 --- a/crates/wac-parser/src/resolution/types.rs +++ /dev/null @@ -1,1494 +0,0 @@ -use super::{package::Package, serialize_arena, serialize_id, serialize_optional_id, ItemKind}; -use anyhow::{bail, Context, Result}; -use id_arena::{Arena, Id}; -use indexmap::{IndexMap, IndexSet}; -use serde::Serialize; -use std::{collections::HashSet, fmt}; - -/// An identifier for defined value types. -pub type DefinedTypeId = Id; - -/// An identifier for resource types. -pub type ResourceId = Id; - -/// An identifier for function types. -pub type FuncId = Id; - -/// An identifier for interface types. -pub type InterfaceId = Id; - -/// An identifier for world types. -pub type WorldId = Id; - -/// An identifier for module types. -pub type ModuleId = Id; - -/// Represents a collection of type definitions. -#[derive(Default, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Definitions { - /// The defined value types. - #[serde(serialize_with = "serialize_arena")] - pub types: Arena, - /// The defined resources. - #[serde(serialize_with = "serialize_arena")] - pub resources: Arena, - /// The defined function types. - #[serde(serialize_with = "serialize_arena")] - pub funcs: Arena, - /// The defined interfaces (i.e. instance types). - #[serde(serialize_with = "serialize_arena")] - pub interfaces: Arena, - /// The defined worlds (i.e. component types). - #[serde(serialize_with = "serialize_arena")] - pub worlds: Arena, - /// The defined module types. - #[serde(serialize_with = "serialize_arena")] - pub modules: Arena, -} - -impl Definitions { - /// Resolves a value type to a un-aliased value type. - pub fn resolve_type(&self, mut ty: ValueType) -> ValueType { - loop { - match ty { - ValueType::Defined { id, .. } => match &self.types[id] { - DefinedType::Alias(aliased) => ty = *aliased, - _ => return ty, - }, - _ => return ty, - } - } - } - - /// Resolves any aliased resource id to the underlying defined resource id. - pub fn resolve_resource_id(&self, mut id: ResourceId) -> ResourceId { - while let Some(alias_of) = &self.resources[id].alias_of { - id = *alias_of; - } - - id - } - - /// Resolves any aliased resource id to the underlying defined resource. - pub fn resolve_resource(&self, id: ResourceId) -> &Resource { - let resolved = self.resolve_resource_id(id); - &self.resources[resolved] - } - - /// Resolves any aliased resource to the mutable underlying defined resource. - pub fn resolve_resource_mut(&mut self, id: ResourceId) -> &mut Resource { - let resolved = self.resolve_resource_id(id); - &mut self.resources[resolved] - } -} - -/// Represent a component model type. -#[derive(Debug, Clone, Copy, Serialize, Hash, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum Type { - /// The type is a function type. - Func(#[serde(serialize_with = "serialize_id")] FuncId), - /// The type is a value type. - Value(ValueType), - /// The type is an interface (i.e. instance type). - Interface(#[serde(serialize_with = "serialize_id")] InterfaceId), - /// The type is a world (i.e. component type). - World(#[serde(serialize_with = "serialize_id")] WorldId), - /// The type is a core module type. - Module(#[serde(serialize_with = "serialize_id")] ModuleId), -} - -impl Type { - pub(crate) fn as_str(&self, definitions: &Definitions) -> &'static str { - match self { - Type::Func(_) => "function type", - Type::Value(ty) => ty.as_str(definitions), - Type::Interface(_) => "interface", - Type::World(_) => "world", - Type::Module(_) => "module type", - } - } -} - -/// Represents a primitive type. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum PrimitiveType { - /// A `u8` type. - U8, - /// A `s8` type. - S8, - /// A `u16` type. - U16, - /// A `s16` type. - S16, - /// A `u32` type. - U32, - /// A `s32` type. - S32, - /// A `u64` type. - U64, - /// A `s64` type. - S64, - /// A `f32` type. - F32, - /// A `f64` type. - F64, - /// A `char` type. - Char, - /// A `bool` type. - Bool, - /// A `string` type. - String, -} - -impl PrimitiveType { - fn as_str(&self) -> &'static str { - match self { - Self::U8 => "u8", - Self::S8 => "s8", - Self::U16 => "u16", - Self::S16 => "s16", - Self::U32 => "u32", - Self::S32 => "s32", - Self::U64 => "u64", - Self::S64 => "s64", - Self::F32 => "f32", - Self::F64 => "f64", - Self::Char => "char", - Self::Bool => "bool", - Self::String => "string", - } - } -} - -impl From for PrimitiveType { - fn from(value: wasmparser::PrimitiveValType) -> Self { - match value { - wasmparser::PrimitiveValType::Bool => Self::Bool, - wasmparser::PrimitiveValType::S8 => Self::S8, - wasmparser::PrimitiveValType::U8 => Self::U8, - wasmparser::PrimitiveValType::S16 => Self::S16, - wasmparser::PrimitiveValType::U16 => Self::U16, - wasmparser::PrimitiveValType::S32 => Self::S32, - wasmparser::PrimitiveValType::U32 => Self::U32, - wasmparser::PrimitiveValType::S64 => Self::S64, - wasmparser::PrimitiveValType::U64 => Self::U64, - wasmparser::PrimitiveValType::F32 => Self::F32, - wasmparser::PrimitiveValType::F64 => Self::F64, - wasmparser::PrimitiveValType::Char => Self::Char, - wasmparser::PrimitiveValType::String => Self::String, - } - } -} - -/// Represents a value type. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum ValueType { - /// A primitive value type. - Primitive(PrimitiveType), - /// The type is a borrow of a resource type. - Borrow(ResourceId), - /// The type is an owned resource type. - Own(ResourceId), - /// A defined value type. - Defined { - /// The id of the defined value type. - id: DefinedTypeId, - /// Whether or not the defined value type recursively contains a borrow. - contains_borrow: bool, - }, -} - -impl ValueType { - /// Checks if the type contains a borrow. - /// - /// Function results may not return a type containing a borrow. - pub(crate) fn contains_borrow(&self) -> bool { - match self { - ValueType::Primitive(_) | ValueType::Own(_) => false, - ValueType::Borrow(_) => true, - ValueType::Defined { - contains_borrow, .. - } => *contains_borrow, - } - } - - fn as_str(&self, definitions: &Definitions) -> &'static str { - match self { - Self::Primitive(ty) => ty.as_str(), - Self::Borrow(_) => "borrow", - Self::Own(_) => "own", - Self::Defined { id, .. } => definitions.types[*id].as_str(definitions), - } - } -} - -impl Serialize for ValueType { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Primitive(ty) => ty.serialize(serializer), - Self::Borrow(id) => format!("borrow<{id}>", id = id.index()).serialize(serializer), - Self::Own(id) => format!("own<{id}>", id = id.index()).serialize(serializer), - Self::Defined { id, .. } => serialize_id(id, serializer), - } - } -} - -/// Represents a defined value type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum DefinedType { - /// A tuple type. - Tuple(Vec), - /// A list type. - List(ValueType), - /// An option type. - Option(ValueType), - /// A result type. - Result { - /// The result's `ok` type. - ok: Option, - /// The result's `err` type. - err: Option, - }, - /// The type is a variant type. - Variant(Variant), - /// The type is a record type. - Record(Record), - /// The type is a flags type. - Flags(Flags), - /// The type is an enum. - Enum(Enum), - /// The type is an alias to another value type. - Alias(ValueType), -} - -impl DefinedType { - fn as_str(&self, definitions: &Definitions) -> &'static str { - match self { - DefinedType::Tuple(_) => "tuple", - DefinedType::List(_) => "list", - DefinedType::Option(_) => "option", - DefinedType::Result { .. } => "result", - DefinedType::Variant(_) => "variant", - DefinedType::Record(_) => "record", - DefinedType::Flags(_) => "flags", - DefinedType::Enum(_) => "enum", - DefinedType::Alias(ty) => ty.as_str(definitions), - } - } -} - -/// Represents a kind of function in the component model. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum FuncKind { - /// The function is a "free" function (i.e. not associated with a resource). - Free, - /// The function is a method on a resource. - Method, - /// The function is a static method on a resource. - Static, - /// The function is a resource constructor. - Constructor, -} - -impl fmt::Display for FuncKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - FuncKind::Free => write!(f, "function"), - FuncKind::Method => write!(f, "method"), - FuncKind::Static => write!(f, "static method"), - FuncKind::Constructor => write!(f, "constructor"), - } - } -} - -pub(crate) fn method_extern_name(resource: &str, name: &str, kind: FuncKind) -> String { - match kind { - FuncKind::Free => unreachable!("a resource method cannot be a free function"), - FuncKind::Method => format!("[method]{resource}.{name}"), - FuncKind::Static => format!("[static]{resource}.{name}"), - FuncKind::Constructor => format!("[constructor]{resource}"), - } -} - -/// Represents a resource type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Resource { - /// The name of the resource. - pub name: String, - - /// The id of the resource that was aliased. - #[serde( - serialize_with = "serialize_optional_id", - skip_serializing_if = "Option::is_none" - )] - pub alias_of: Option, -} - -/// Represents a variant. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Variant { - /// The variant cases. - pub cases: IndexMap>, -} - -/// Represents a record type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Record { - /// The record fields. - pub fields: IndexMap, -} - -/// Represents a flags type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Flags(pub IndexSet); - -/// Represents an enum type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Enum(pub IndexSet); - -/// Represents a function type. -#[derive(Debug, Clone, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Func { - /// The parameters of the function. - pub params: IndexMap, - /// The results of the function. - pub results: Option, -} - -/// Represents a function result. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum FuncResult { - /// A scalar result. - Scalar(ValueType), - /// A list of named results. - List(IndexMap), -} - -/// Represents a used type. -#[derive(Debug, Clone, Serialize)] -pub struct UsedType { - /// The interface the type was used from. - #[serde(serialize_with = "serialize_id")] - pub interface: InterfaceId, - /// The original export name. - /// - /// This is `None` when the type was not renamed with an `as` clause. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -/// Represents an interface (i.e. instance type). -#[derive(Debug, Clone, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Interface { - /// The identifier of the interface. - /// - /// This may be `None` for inline interfaces. - pub id: Option, - /// Represents a remapping of types that may occur when an interface is merged. - /// - /// The map is from the type present in this interface to a set of types - /// originating from the merged interfaces. - /// - /// Encoding uses this map to populate the encoded type index map for the - /// original types. - #[serde(skip)] - pub remapped_types: IndexMap>, - /// A map of exported name to information about the used type. - pub uses: IndexMap, - /// The exported items of the interface. - pub exports: IndexMap, -} - -/// Represents a world. -#[derive(Debug, Clone, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct World { - /// The identifier of the world. - /// - /// This may be `None` for worlds representing component types. - pub id: Option, - /// A map of imported name to information about the used type. - pub uses: IndexMap, - /// The imported items of the world. - pub imports: IndexMap, - /// The exported items of the world. - pub exports: IndexMap, -} - -/// Represents a core module type. -#[derive(Debug, Clone, Serialize, Default)] -pub struct Module { - /// The imports of the module type. - pub imports: IndexMap<(String, String), CoreExtern>, - /// The exports of the module type. - pub exports: IndexMap, -} - -/// Represents a core extern imported or exported from a core module. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum CoreExtern { - /// The item is a function. - Func(CoreFunc), - /// The item is a table. - #[serde(rename_all = "camelCase")] - Table { - /// The table's element type. - element_type: CoreRefType, - /// Initial size of this table, in elements. - initial: u32, - /// Optional maximum size of the table, in elements. - maximum: Option, - }, - /// The item is a memory. - #[serde(rename_all = "camelCase")] - Memory { - /// Whether or not this is a 64-bit memory, using i64 as an index. If this - /// is false it's a 32-bit memory using i32 as an index. - /// - /// This is part of the memory64 proposal in WebAssembly. - memory64: bool, - - /// Whether or not this is a "shared" memory, indicating that it should be - /// send-able across threads and the `maximum` field is always present for - /// valid types. - /// - /// This is part of the threads proposal in WebAssembly. - shared: bool, - - /// Initial size of this memory, in wasm pages. - /// - /// For 32-bit memories (when `memory64` is `false`) this is guaranteed to - /// be at most `u32::MAX` for valid types. - initial: u64, - - /// Optional maximum size of this memory, in wasm pages. - /// - /// For 32-bit memories (when `memory64` is `false`) this is guaranteed to - /// be at most `u32::MAX` for valid types. This field is always present for - /// valid wasm memories when `shared` is `true`. - maximum: Option, - }, - /// The item is a global. - #[serde(rename_all = "camelCase")] - Global { - /// The global's type. - val_type: CoreType, - /// Whether or not the global is mutable. - mutable: bool, - }, - /// The item is a tag. - Tag(CoreFunc), -} - -impl fmt::Display for CoreExtern { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Func(_) => write!(f, "function"), - Self::Table { .. } => write!(f, "table"), - Self::Memory { .. } => write!(f, "memory"), - Self::Global { .. } => write!(f, "global"), - Self::Tag(_) => write!(f, "tag"), - } - } -} - -impl From for CoreExtern { - fn from(ty: wasmparser::TableType) -> Self { - Self::Table { - element_type: ty.element_type.into(), - initial: ty.initial, - maximum: ty.maximum, - } - } -} - -impl From for CoreExtern { - fn from(ty: wasmparser::MemoryType) -> Self { - Self::Memory { - memory64: ty.memory64, - shared: ty.shared, - initial: ty.initial, - maximum: ty.maximum, - } - } -} - -impl From for CoreExtern { - fn from(ty: wasmparser::GlobalType) -> Self { - Self::Global { - val_type: ty.content_type.into(), - mutable: ty.mutable, - } - } -} - -/// Represents the value types in a WebAssembly module. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum CoreType { - /// The value type is i32. - I32, - /// The value type is i64. - I64, - /// The value type is f32. - F32, - /// The value type is f64. - F64, - /// The value type is v128. - V128, - /// The value type is a reference. - Ref(CoreRefType), -} - -impl fmt::Display for CoreType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::I32 => write!(f, "i32"), - Self::I64 => write!(f, "i64"), - Self::F32 => write!(f, "f32"), - Self::F64 => write!(f, "f64"), - Self::V128 => write!(f, "v128"), - Self::Ref(r) => r.fmt(f), - } - } -} - -impl From for CoreType { - fn from(ty: wasmparser::ValType) -> Self { - match ty { - wasmparser::ValType::I32 => Self::I32, - wasmparser::ValType::I64 => Self::I64, - wasmparser::ValType::F32 => Self::F32, - wasmparser::ValType::F64 => Self::F64, - wasmparser::ValType::V128 => Self::V128, - wasmparser::ValType::Ref(ty) => Self::Ref(ty.into()), - } - } -} - -impl From for wasm_encoder::ValType { - fn from(value: CoreType) -> Self { - match value { - CoreType::I32 => Self::I32, - CoreType::I64 => Self::I64, - CoreType::F32 => Self::F32, - CoreType::F64 => Self::F64, - CoreType::V128 => Self::V128, - CoreType::Ref(r) => Self::Ref(r.into()), - } - } -} - -/// Represents the type of a reference in a WebAssembly module. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CoreRefType { - /// Whether or not the ref type is nullable. - pub nullable: bool, - /// The heap type of the ref type. - pub heap_type: HeapType, -} - -impl fmt::Display for CoreRefType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match (self.nullable, self.heap_type) { - (true, HeapType::Func) => "funcref", - (true, HeapType::Extern) => "externref", - (true, HeapType::Concrete(i)) => return write!(f, "(ref null {i})"), - (true, HeapType::Any) => "anyref", - (true, HeapType::None) => "nullref", - (true, HeapType::NoExtern) => "nullexternref", - (true, HeapType::NoFunc) => "nullfuncref", - (true, HeapType::Eq) => "eqref", - (true, HeapType::Struct) => "structref", - (true, HeapType::Array) => "arrayref", - (true, HeapType::I31) => "i31ref", - (true, HeapType::Exn) => "exnref", - (false, HeapType::Func) => "(ref func)", - (false, HeapType::Extern) => "(ref extern)", - (false, HeapType::Concrete(i)) => return write!(f, "(ref {i})"), - (false, HeapType::Any) => "(ref any)", - (false, HeapType::None) => "(ref none)", - (false, HeapType::NoExtern) => "(ref noextern)", - (false, HeapType::NoFunc) => "(ref nofunc)", - (false, HeapType::Eq) => "(ref eq)", - (false, HeapType::Struct) => "(ref struct)", - (false, HeapType::Array) => "(ref array)", - (false, HeapType::I31) => "(ref i31)", - (false, HeapType::Exn) => "(ref exn)", - }; - - f.write_str(s) - } -} - -impl From for CoreRefType { - fn from(ty: wasmparser::RefType) -> Self { - Self { - nullable: ty.is_nullable(), - heap_type: ty.heap_type().into(), - } - } -} - -impl From for wasm_encoder::RefType { - fn from(value: CoreRefType) -> Self { - wasm_encoder::RefType { - nullable: value.nullable, - heap_type: value.heap_type.into(), - } - } -} - -/// A heap type of a reference type. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)] -pub enum HeapType { - /// User defined type at the given index. - Concrete(u32), - /// Untyped (any) function. - Func, - /// External heap type. - Extern, - /// The `any` heap type. - Any, - /// The `none` heap type. - None, - /// The `noextern` heap type. - NoExtern, - /// The `nofunc` heap type. - NoFunc, - /// The `eq` heap type. - Eq, - /// The `struct` heap type. The common supertype of all struct types. - Struct, - /// The `array` heap type. The common supertype of all array types. - Array, - /// The i31 heap type. - I31, - /// The abstraction `exception` heap type. - /// - /// Introduced in the exception-handling proposal. - Exn, -} - -impl From for HeapType { - fn from(ty: wasmparser::HeapType) -> Self { - match ty { - wasmparser::HeapType::Any => Self::Any, - wasmparser::HeapType::Func => Self::Func, - wasmparser::HeapType::Extern => Self::Extern, - wasmparser::HeapType::Eq => Self::Eq, - wasmparser::HeapType::I31 => Self::I31, - wasmparser::HeapType::None => Self::None, - wasmparser::HeapType::NoExtern => Self::NoExtern, - wasmparser::HeapType::NoFunc => Self::NoFunc, - wasmparser::HeapType::Struct => Self::Struct, - wasmparser::HeapType::Array => Self::Array, - wasmparser::HeapType::Exn => Self::Exn, - wasmparser::HeapType::Concrete(index) => { - Self::Concrete(index.as_module_index().unwrap()) - } - } - } -} - -impl From for wasm_encoder::HeapType { - fn from(value: HeapType) -> Self { - match value { - HeapType::Concrete(index) => Self::Concrete(index), - HeapType::Func => Self::Func, - HeapType::Extern => Self::Extern, - HeapType::Any => Self::Any, - HeapType::None => Self::None, - HeapType::NoExtern => Self::NoExtern, - HeapType::NoFunc => Self::NoFunc, - HeapType::Eq => Self::Eq, - HeapType::Struct => Self::Struct, - HeapType::Array => Self::Array, - HeapType::I31 => Self::I31, - HeapType::Exn => Self::Exn, - } - } -} - -/// Represents a core function type in a WebAssembly module. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CoreFunc { - /// The parameters of the function. - pub params: Vec, - /// The results of the function. - pub results: Vec, -} - -impl fmt::Display for CoreFunc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[")?; - - for (i, ty) in self.params.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - write!(f, "{}", ty)?; - } - - write!(f, "] -> [")?; - - for (i, ty) in self.results.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - write!(f, "{}", ty)?; - } - - write!(f, "]") - } -} - -/// Represents the kind of subtyping check to perform. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) enum SubtypeCheckKind { - /// The type is a covariant check. - Covariant, - /// The type is a contravariant check. - Contravariant, -} - -/// Implements a subtype checker. -/// -/// Subtype checking is used to type check instantiation arguments. -pub struct SubtypeChecker<'a> { - kinds: Vec, - definitions: &'a Definitions, - packages: &'a Arena, - cache: HashSet<(ItemKind, ItemKind)>, -} - -impl<'a> SubtypeChecker<'a> { - /// Creates a new subtype checker. - pub fn new(definitions: &'a Definitions, packages: &'a Arena) -> Self { - Self { - kinds: Default::default(), - definitions, - packages, - cache: Default::default(), - } - } - - /// Checks if `a` is a subtype of `b`. - pub fn is_subtype(&mut self, a: ItemKind, b: ItemKind) -> Result<()> { - if self.cache.contains(&(a, b)) { - return Ok(()); - } - - let mut is_subtype = |a, b| match (a, b) { - (ItemKind::Resource(a), ItemKind::Resource(b)) => self.resource(a, b), - (ItemKind::Type(a), ItemKind::Type(b)) => self.ty(a, b), - (ItemKind::Func(a), ItemKind::Func(b)) => self.func(a, b), - (ItemKind::Instance(a), ItemKind::Instance(b)) => self.interface(a, b), - (ItemKind::Instantiation(a), ItemKind::Instantiation(b)) => { - let a = &self.definitions.worlds[self.packages[a].world]; - let b = &self.definitions.worlds[self.packages[b].world]; - self.instance_exports(&a.exports, &b.exports) - } - (ItemKind::Instantiation(a), ItemKind::Instance(b)) => { - let a = &self.definitions.worlds[self.packages[a].world]; - let b = &self.definitions.interfaces[b]; - self.instance_exports(&a.exports, &b.exports) - } - (ItemKind::Instance(a), ItemKind::Instantiation(b)) => { - let a = &self.definitions.interfaces[a]; - let b = &self.definitions.worlds[self.packages[b].world]; - self.instance_exports(&a.exports, &b.exports) - } - (ItemKind::Component(a), ItemKind::Component(b)) => self.world(a, b), - (ItemKind::Module(a), ItemKind::Module(b)) => self.module(a, b), - (ItemKind::Value(a), ItemKind::Value(b)) => self.value_type(a, b), - _ => { - let (expected, found) = self.expected_found(&a, &b); - bail!( - "expected {expected}, found {found}", - expected = expected.as_str(self.definitions), - found = found.as_str(self.definitions) - ) - } - }; - - let result = is_subtype(a, b); - if result.is_ok() { - self.cache.insert((a, b)); - } - - result - } - - fn expected_found<'b, T>(&self, a: &'b T, b: &'b T) -> (&'b T, &'b T) { - match self.kind() { - // For covariant checks, the supertype is the expected type - SubtypeCheckKind::Covariant => (b, a), - // For contravariant checks, the subtype is the expected type - SubtypeCheckKind::Contravariant => (a, b), - } - } - - /// Gets the current check kind. - fn kind(&self) -> SubtypeCheckKind { - self.kinds - .last() - .copied() - .unwrap_or(SubtypeCheckKind::Covariant) - } - - /// Inverts the current check kind. - pub(crate) fn invert(&mut self) -> SubtypeCheckKind { - let prev = self.kind(); - self.kinds.push(match prev { - SubtypeCheckKind::Covariant => SubtypeCheckKind::Contravariant, - SubtypeCheckKind::Contravariant => SubtypeCheckKind::Covariant, - }); - prev - } - - /// Reverts to the previous check kind. - pub(crate) fn revert(&mut self) { - self.kinds.pop().expect("mismatched stack"); - } - - fn resource(&self, a: ResourceId, b: ResourceId) -> Result<()> { - if a == b { - return Ok(()); - } - - // Currently, just check that the resources have the same name - let a = self.definitions.resolve_resource(a); - let b = self.definitions.resolve_resource(b); - if a.name != b.name { - let (expected, found) = self.expected_found(a, b); - - bail!( - "expected resource `{expected}`, found resource `{found}`", - expected = expected.name, - found = found.name - ); - } - - Ok(()) - } - - fn ty(&mut self, a: Type, b: Type) -> Result<()> { - match (a, b) { - (Type::Func(a), Type::Func(b)) => return self.func(a, b), - (Type::Value(a), Type::Value(b)) => return self.value_type(a, b), - (Type::Interface(a), Type::Interface(b)) => return self.interface(a, b), - (Type::World(a), Type::World(b)) => return self.world(a, b), - (Type::Module(a), Type::Module(b)) => return self.module(a, b), - _ => {} - } - - let (expected, found) = self.expected_found(&a, &b); - - bail!( - "expected {expected}, found {found}", - expected = expected.as_str(self.definitions), - found = found.as_str(self.definitions) - ) - } - - fn func(&self, a: FuncId, b: FuncId) -> Result<()> { - if a == b { - return Ok(()); - } - - let a = &self.definitions.funcs[a]; - let b = &self.definitions.funcs[b]; - - // Note: currently subtyping for functions is done in terms of equality - // rather than actual subtyping; the reason for this is that implementing - // runtimes don't yet support more complex subtyping rules. - - if a.params.len() != b.params.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected function with parameter count {expected}, found parameter count {found}", - expected = expected.params.len(), - found = found.params.len(), - ); - } - - for (i, ((an, a), (bn, b))) in a.params.iter().zip(b.params.iter()).enumerate() { - if an != bn { - let (expected, found) = self.expected_found(an, bn); - bail!("expected function parameter {i} to be named `{expected}`, found name `{found}`"); - } - - self.value_type(*a, *b) - .with_context(|| format!("mismatched type for function parameter `{bn}`"))?; - } - - match (&a.results, &b.results) { - (None, None) => return Ok(()), - (Some(FuncResult::Scalar(a)), Some(FuncResult::Scalar(b))) => { - return self - .value_type(*a, *b) - .context("mismatched type for function result"); - } - (Some(FuncResult::List(a)), Some(FuncResult::List(b))) => { - for (i, ((an, a), (bn, b))) in a.iter().zip(b.iter()).enumerate() { - if an != bn { - let (expected, found) = self.expected_found(an, bn); - bail!("expected function result {i} to be named `{expected}`, found name `{found}`"); - } - - self.value_type(*a, *b) - .with_context(|| format!("mismatched type for function result `{bn}`"))?; - } - - return Ok(()); - } - (Some(FuncResult::List(_)), Some(FuncResult::Scalar(_))) - | (Some(FuncResult::Scalar(_)), Some(FuncResult::List(_))) - | (Some(_), None) - | (None, Some(_)) => { - // Handle the mismatch below - } - } - - let (expected, found) = self.expected_found(a, b); - match (&expected.results, &found.results) { - (Some(FuncResult::List(_)), Some(FuncResult::Scalar(_))) => { - bail!("expected function that returns a named result, found function with a single result type") - } - (Some(FuncResult::Scalar(_)), Some(FuncResult::List(_))) => { - bail!("expected function that returns a single result type, found function that returns a named result") - } - (Some(_), None) => { - bail!("expected function with a result, found function without a result") - } - (None, Some(_)) => { - bail!("expected function without a result, found function with a result") - } - (Some(FuncResult::Scalar(_)), Some(FuncResult::Scalar(_))) - | (Some(FuncResult::List(_)), Some(FuncResult::List(_))) - | (None, None) => unreachable!(), - } - } - - fn instance_exports( - &mut self, - a: &IndexMap, - b: &IndexMap, - ) -> Result<()> { - // For instance type subtyping, all exports in the other - // instance type must be present in this instance type's - // exports (i.e. it can export *more* than what this instance - // type needs). - for (k, b) in b.iter() { - match a.get(k) { - Some(a) => { - self.is_subtype(*a, *b) - .with_context(|| format!("mismatched type for export `{k}`"))?; - } - None => match self.kind() { - SubtypeCheckKind::Covariant => { - bail!( - "instance is missing expected {kind} export `{k}`", - kind = b.as_str(self.definitions) - ) - } - SubtypeCheckKind::Contravariant => { - bail!( - "instance has unexpected {kind} export `{k}`", - kind = b.as_str(self.definitions) - ) - } - }, - } - } - - Ok(()) - } - - fn interface(&mut self, a: InterfaceId, b: InterfaceId) -> Result<()> { - if a == b { - return Ok(()); - } - - let a = &self.definitions.interfaces[a]; - let b = &self.definitions.interfaces[b]; - self.instance_exports(&a.exports, &b.exports) - } - - fn world(&mut self, a: WorldId, b: WorldId) -> Result<()> { - let a = &self.definitions.worlds[a]; - let b = &self.definitions.worlds[b]; - - // For component type subtyping, all exports in the other component - // type must be present in this component type's exports (i.e. it - // can export *more* than what this component type needs). - // However, for imports, the check is reversed (i.e. it is okay - // to import *less* than what this component type needs). - let prev = self.invert(); - for (k, a) in a.imports.iter() { - match b.imports.get(k) { - Some(b) => { - self.is_subtype(*b, *a) - .with_context(|| format!("mismatched type for import `{k}`"))?; - } - None => match prev { - SubtypeCheckKind::Covariant => { - bail!( - "component is missing expected {kind} import `{k}`", - kind = a.as_str(self.definitions) - ) - } - SubtypeCheckKind::Contravariant => { - bail!( - "component has unexpected import {kind} `{k}`", - kind = a.as_str(self.definitions) - ) - } - }, - } - } - - self.revert(); - - for (k, b) in b.exports.iter() { - match a.exports.get(k) { - Some(a) => { - self.is_subtype(*a, *b) - .with_context(|| format!("mismatched type for export `{k}`"))?; - } - None => match prev { - SubtypeCheckKind::Covariant => { - bail!( - "component is missing expected {kind} export `{k}`", - kind = b.as_str(self.definitions) - ) - } - SubtypeCheckKind::Contravariant => { - bail!( - "component has unexpected {kind} export `{k}`", - kind = b.as_str(self.definitions) - ) - } - }, - } - } - - Ok(()) - } - - fn module(&mut self, a: ModuleId, b: ModuleId) -> Result<()> { - if a == b { - return Ok(()); - } - - let a = &self.definitions.modules[a]; - let b = &self.definitions.modules[b]; - - let prev = self.invert(); - - // For module type subtyping, all exports in the other module - // type must be present in expected module type's exports (i.e. it - // can export *more* than what is expected module type needs). - // However, for imports, the check is reversed (i.e. it is okay - // to import *less* than what this module type needs). - for (k, a) in a.imports.iter() { - match b.imports.get(k) { - Some(b) => { - self.core_extern(b, a).with_context(|| { - format!("mismatched type for import `{m}::{n}`", m = k.0, n = k.1) - })?; - } - None => match prev { - SubtypeCheckKind::Covariant => bail!( - "module is missing expected {a} import `{m}::{n}`", - m = k.0, - n = k.1 - ), - SubtypeCheckKind::Contravariant => { - bail!( - "module has unexpected {a} import `{m}::{n}`", - m = k.0, - n = k.1 - ) - } - }, - } - } - - for (k, b) in b.exports.iter() { - match a.exports.get(k) { - Some(a) => { - self.core_extern(a, b) - .with_context(|| format!("mismatched type for export `{k}`"))?; - } - None => match prev { - SubtypeCheckKind::Covariant => { - bail!("module is missing expected {b} export `{k}`") - } - SubtypeCheckKind::Contravariant => { - bail!("module has unexpected {b} export `{k}`") - } - }, - } - } - - Ok(()) - } - - fn core_extern(&self, a: &CoreExtern, b: &CoreExtern) -> Result<()> { - macro_rules! limits_match { - ($ai:expr, $am:expr, $bi:expr, $bm:expr) => {{ - $ai >= $bi - && match ($am, $bm) { - (Some(am), Some(bm)) => am <= bm, - (None, Some(_)) => false, - _ => true, - } - }}; - } - - match (a, b) { - (CoreExtern::Func(a), CoreExtern::Func(b)) => return self.core_func(a, b), - ( - CoreExtern::Table { - element_type: ae, - initial: ai, - maximum: am, - }, - CoreExtern::Table { - element_type: be, - initial: bi, - maximum: bm, - }, - ) => { - if ae != be { - let (expected, found) = self.expected_found(ae, be); - bail!("expected table element type {expected}, found {found}"); - } - - if !limits_match!(ai, am, bi, bm) { - bail!("mismatched table limits"); - } - - return Ok(()); - } - ( - CoreExtern::Memory { - memory64: a64, - shared: ashared, - initial: ai, - maximum: am, - }, - CoreExtern::Memory { - memory64: b64, - shared: bshared, - initial: bi, - maximum: bm, - }, - ) => { - if ashared != bshared { - bail!("mismatched shared flag for memories"); - } - - if a64 != b64 { - bail!("mismatched memory64 flag for memories"); - } - - if !limits_match!(ai, am, bi, bm) { - bail!("mismatched memory limits"); - } - - return Ok(()); - } - ( - CoreExtern::Global { - val_type: at, - mutable: am, - }, - CoreExtern::Global { - val_type: bt, - mutable: bm, - }, - ) => { - if am != bm { - bail!("mismatched mutable flag for globals"); - } - - if at != bt { - let (expected, found) = self.expected_found(at, bt); - bail!("expected global type {expected}, found {found}"); - } - - return Ok(()); - } - (CoreExtern::Tag(a), CoreExtern::Tag(b)) => return self.core_func(a, b), - _ => {} - } - - let (expected, found) = self.expected_found(a, b); - bail!("expected {expected}, found {found}"); - } - - fn core_func(&self, a: &CoreFunc, b: &CoreFunc) -> Result<()> { - if a != b { - let (expected, found) = self.expected_found(a, b); - bail!("expected {expected}, found {found}"); - } - - Ok(()) - } - - fn value_type(&self, a: ValueType, b: ValueType) -> Result<()> { - let a = self.definitions.resolve_type(a); - let b = self.definitions.resolve_type(b); - - match (a, b) { - (ValueType::Primitive(a), ValueType::Primitive(b)) => self.primitive(a, b), - (ValueType::Defined { id: a, .. }, ValueType::Defined { id: b, .. }) => { - self.defined_type(a, b) - } - (ValueType::Borrow(a), ValueType::Borrow(b)) - | (ValueType::Own(a), ValueType::Own(b)) => self.resource(a, b), - _ => { - let (expected, found) = self.expected_found(&a, &b); - bail!( - "expected {expected}, found {found}", - expected = expected.as_str(self.definitions), - found = found.as_str(self.definitions) - ) - } - } - } - - fn defined_type( - &self, - a: DefinedTypeId, - b: DefinedTypeId, - ) -> std::result::Result<(), anyhow::Error> { - if a == b { - return Ok(()); - } - - let a = &self.definitions.types[a]; - let b = &self.definitions.types[b]; - match (a, b) { - (DefinedType::Tuple(a), DefinedType::Tuple(b)) => self.tuple(a, b), - (DefinedType::List(a), DefinedType::List(b)) => self - .value_type(*a, *b) - .context("mismatched type for list element"), - (DefinedType::Option(a), DefinedType::Option(b)) => self - .value_type(*a, *b) - .context("mismatched type for option"), - ( - DefinedType::Result { - ok: a_ok, - err: a_err, - }, - DefinedType::Result { - ok: b_ok, - err: b_err, - }, - ) => { - self.result("ok", a_ok, b_ok)?; - self.result("err", a_err, b_err) - } - (DefinedType::Variant(a), DefinedType::Variant(b)) => self.variant(a, b), - (DefinedType::Record(a), DefinedType::Record(b)) => self.record(a, b), - (DefinedType::Flags(a), DefinedType::Flags(b)) => self.flags(a, b), - (DefinedType::Enum(a), DefinedType::Enum(b)) => self.enum_type(a, b), - (DefinedType::Alias(_), _) | (_, DefinedType::Alias(_)) => { - unreachable!("aliases should have been resolved") - } - _ => { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected {expected}, found {found}", - expected = expected.as_str(self.definitions), - found = found.as_str(self.definitions) - ) - } - } - } - - fn result(&self, desc: &str, a: &Option, b: &Option) -> Result<()> { - match (a, b) { - (None, None) => return Ok(()), - (Some(a), Some(b)) => { - return self - .value_type(*a, *b) - .with_context(|| format!("mismatched type for result `{desc}`")) - } - (Some(_), None) | (None, Some(_)) => { - // Handle mismatch below - } - } - - let (expected, found) = self.expected_found(a, b); - match (expected, found) { - (None, None) | (Some(_), Some(_)) => unreachable!(), - (Some(_), None) => bail!("expected an `{desc}` for result type"), - (None, Some(_)) => bail!("expected no `{desc}` for result type"), - } - } - - fn enum_type(&self, a: &Enum, b: &Enum) -> Result<()> { - if a.0.len() != b.0.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected an enum type case count of {expected}, found a count of {found}", - expected = expected.0.len(), - found = found.0.len() - ); - } - - if let Some((index, (a, b))) = - a.0.iter() - .zip(b.0.iter()) - .enumerate() - .find(|(_, (a, b))| a != b) - { - let (expected, found) = self.expected_found(a, b); - bail!("expected enum case {index} to be named `{expected}`, found an enum case named `{found}`"); - } - - Ok(()) - } - - fn flags(&self, a: &Flags, b: &Flags) -> Result<()> { - if a.0.len() != b.0.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected a flags type flag count of {expected}, found a count of {found}", - expected = expected.0.len(), - found = found.0.len() - ); - } - - if let Some((index, (a, b))) = - a.0.iter() - .zip(b.0.iter()) - .enumerate() - .find(|(_, (a, b))| a != b) - { - let (expected, found) = self.expected_found(a, b); - bail!("expected flag {index} to be named `{expected}`, found a flag named `{found}`"); - } - - Ok(()) - } - - fn record(&self, a: &Record, b: &Record) -> Result<()> { - if a.fields.len() != b.fields.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected a record field count of {expected}, found a count of {found}", - expected = expected.fields.len(), - found = found.fields.len() - ); - } - - for (i, ((an, a), (bn, b))) in a.fields.iter().zip(b.fields.iter()).enumerate() { - if an != bn { - let (expected, found) = self.expected_found(an, bn); - bail!("expected record field {i} to be named `{expected}`, found a field named `{found}`"); - } - - self.value_type(*a, *b) - .with_context(|| format!("mismatched type for record field `{bn}`"))?; - } - - Ok(()) - } - - fn variant(&self, a: &Variant, b: &Variant) -> Result<()> { - if a.cases.len() != b.cases.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected a variant case count of {expected}, found a count of {found}", - expected = expected.cases.len(), - found = found.cases.len() - ); - } - - for (i, ((an, a), (bn, b))) in a.cases.iter().zip(b.cases.iter()).enumerate() { - if an != bn { - let (expected, found) = self.expected_found(an, bn); - bail!("expected variant case {i} to be named `{expected}`, found a case named `{found}`"); - } - - match (a, b) { - (None, None) => {} - (Some(a), Some(b)) => self - .value_type(*a, *b) - .with_context(|| format!("mismatched type for variant case `{bn}`"))?, - _ => { - let (expected, found) = self.expected_found(a, b); - match (expected, found) { - (None, None) | (Some(_), Some(_)) => unreachable!(), - (None, Some(_)) => { - bail!("expected variant case `{bn}` to be untyped, found a typed case") - } - (Some(_), None) => { - bail!("expected variant case `{bn}` to be typed, found an untyped case") - } - } - } - } - } - - Ok(()) - } - - fn tuple(&self, a: &Vec, b: &Vec) -> Result<()> { - if a.len() != b.len() { - let (expected, found) = self.expected_found(a, b); - bail!( - "expected a tuple of size {expected}, found a tuple of size {found}", - expected = expected.len(), - found = found.len() - ); - } - - for (i, (a, b)) in a.iter().zip(b.iter()).enumerate() { - self.value_type(*a, *b) - .with_context(|| format!("mismatched type for tuple item {i}"))?; - } - - Ok(()) - } - - fn primitive(&self, a: PrimitiveType, b: PrimitiveType) -> Result<()> { - // Note: currently subtyping for primitive types is done in terms of equality - // rather than actual subtyping; the reason for this is that implementing - // runtimes don't yet support more complex subtyping rules. - if a != b { - let (expected, found) = self.expected_found(&a, &b); - bail!( - "expected {expected}, found {found}", - expected = expected.as_str(), - found = found.as_str() - ); - } - - Ok(()) - } -} diff --git a/crates/wac-parser/tests/encoding.rs b/crates/wac-parser/tests/encoding.rs index ca0f1b7..46a1fdf 100644 --- a/crates/wac-parser/tests/encoding.rs +++ b/crates/wac-parser/tests/encoding.rs @@ -11,7 +11,8 @@ use std::{ sync::atomic::{AtomicUsize, Ordering}, }; use support::fmt_err; -use wac_parser::{ast::Document, Composition, EncodingOptions}; +use wac_graph::EncodeOptions; +use wac_parser::Document; use wac_resolver::{packages, FileSystemPackageResolver}; mod support; @@ -19,6 +20,7 @@ mod support; fn find_tests() -> Vec { let mut tests = Vec::new(); find_tests("tests/encoding", &mut tests); + find_tests("tests/encoding/fail", &mut tests); tests.sort(); return tests; @@ -39,15 +41,20 @@ fn find_tests() -> Vec { } } -fn normalize(s: &str) -> String { - // Normalize line endings +fn normalize(s: &str, should_fail: bool) -> String { + if should_fail { + // Normalize paths in any error messages + return s.replace('\\', "/").replace("\r\n", "\n"); + } + + // Otherwise, just normalize line endings s.replace("\r\n", "\n") } -fn compare_result(test: &Path, result: &str) -> Result<()> { - let path = test.with_extension("wat.result"); +fn compare_result(test: &Path, result: &str, should_fail: bool) -> Result<()> { + let path = test.with_extension("wac.result"); - let result = normalize(result); + let result = normalize(result, should_fail); if env::var_os("BLESS").is_some() { fs::write(&path, &result).with_context(|| { format!( @@ -73,48 +80,58 @@ fn compare_result(test: &Path, result: &str) -> Result<()> { } fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { + let should_fail = test.parent().map(|p| p.ends_with("fail")).unwrap_or(false); let source = std::fs::read_to_string(test)?.replace("\r\n", "\n"); let document = Document::parse(&source).map_err(|e| anyhow!(fmt_err(e, test, &source)))?; - let resolver = FileSystemPackageResolver::new( - test.parent().unwrap().join(test.file_stem().unwrap()), - Default::default(), - true, - ); + let encode = || { + let resolver = FileSystemPackageResolver::new( + test.parent().unwrap().join(test.file_stem().unwrap()), + Default::default(), + true, + ); - let packages = resolver.resolve(&packages(&document)?)?; + let packages = resolver + .resolve(&packages(&document).map_err(|e| fmt_err(e, test, &source))?) + .map_err(|e| fmt_err(e, test, &source))?; + + let resolution = document + .resolve(packages) + .map_err(|e| fmt_err(e, test, &source))?; + + resolution + .encode(EncodeOptions { + define_components: false, + ..Default::default() + }) + .map_err(|e| fmt_err(e, test, &source)) + }; + + let result = match encode() { + Ok(bytes) => { + if should_fail { + bail!("the encoding was successful but it was expected to fail"); + } - let bytes = Composition::from_ast(&document, packages) - .map_err(|e| anyhow!(fmt_err(e, test, &source)))? - .encode(EncodingOptions::default()) - .with_context(|| { - format!( - "failed to encode the composition `{path}`", - path = test.display() - ) - })?; + wasmprinter::print_bytes(bytes).with_context(|| { + format!( + "failed to convert binary wasm output to text `{path}`", + path = test.display() + ) + })? + } + Err(e) => { + if !should_fail { + return Err(anyhow!(e)) + .context("the resolution failed but it was expected to succeed"); + } + + e + } + }; - wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { - component_model: true, - ..Default::default() - }) - .validate_all(&bytes) - .with_context(|| { - format!( - "failed to validate the encoded composition `{path}`", - path = test.display() - ) - })?; - - let result = wasmprinter::print_bytes(bytes).with_context(|| { - format!( - "failed to convert binary wasm output to text `{path}`", - path = test.display() - ) - })?; - - compare_result(test, &result)?; + compare_result(test, &result, should_fail)?; ntests.fetch_add(1, Ordering::SeqCst); Ok(()) diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac b/crates/wac-parser/tests/encoding/fail/arg-merge-failure.wac similarity index 100% rename from crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac rename to crates/wac-parser/tests/encoding/fail/arg-merge-failure.wac diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result b/crates/wac-parser/tests/encoding/fail/arg-merge-failure.wac.result similarity index 61% rename from crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result rename to crates/wac-parser/tests/encoding/fail/arg-merge-failure.wac.result index 96b89e5..2fa0441 100644 --- a/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result +++ b/crates/wac-parser/tests/encoding/fail/arg-merge-failure.wac.result @@ -1,7 +1,8 @@ failed to resolve document - × implicit instantiation argument `foo` (instance) conflicts with an implicitly imported argument from the instantiation of `foo:bar` - ╭─[tests/resolution/fail/arg-merge-failure.wac:4:13] + × failed to merge the type definition for implicit import `foo` due to conflicting types + ╰─▶ instance cannot be merged with function + ╭─[tests/encoding/fail/arg-merge-failure.wac:4:13] 2 │ 3 │ let a = new foo:bar { ... }; · ───┬─── diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure/bar/baz.wat b/crates/wac-parser/tests/encoding/fail/arg-merge-failure/bar/baz.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/arg-merge-failure/bar/baz.wat rename to crates/wac-parser/tests/encoding/fail/arg-merge-failure/bar/baz.wat diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure/foo/bar.wat b/crates/wac-parser/tests/encoding/fail/arg-merge-failure/foo/bar.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/arg-merge-failure/foo/bar.wat rename to crates/wac-parser/tests/encoding/fail/arg-merge-failure/foo/bar.wat diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac b/crates/wac-parser/tests/encoding/fail/implicit-arg-conflict.wac similarity index 100% rename from crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac rename to crates/wac-parser/tests/encoding/fail/implicit-arg-conflict.wac diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result b/crates/wac-parser/tests/encoding/fail/implicit-arg-conflict.wac.result similarity index 64% rename from crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result rename to crates/wac-parser/tests/encoding/fail/implicit-arg-conflict.wac.result index 57f0f4f..2bdd2b8 100644 --- a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result +++ b/crates/wac-parser/tests/encoding/fail/implicit-arg-conflict.wac.result @@ -1,7 +1,7 @@ failed to resolve document - × implicit instantiation argument `foo` (function) conflicts with an explicit import - ╭─[tests/resolution/fail/implicit-arg-conflict.wac:5:13] + × import `foo` conflicts with an item that was implicitly imported by an instantiation of `foo:bar` + ╭─[tests/encoding/fail/implicit-arg-conflict.wac:3:8] 2 │ 3 │ import foo: func(); · ─┬─ diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict/foo/bar.wat b/crates/wac-parser/tests/encoding/fail/implicit-arg-conflict/foo/bar.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/implicit-arg-conflict/foo/bar.wat rename to crates/wac-parser/tests/encoding/fail/implicit-arg-conflict/foo/bar.wat diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict.wac b/crates/wac-parser/tests/encoding/fail/import-conflict.wac similarity index 100% rename from crates/wac-parser/tests/resolution/fail/import-conflict.wac rename to crates/wac-parser/tests/encoding/fail/import-conflict.wac diff --git a/crates/wac-parser/tests/encoding/fail/import-conflict.wac.result b/crates/wac-parser/tests/encoding/fail/import-conflict.wac.result new file mode 100644 index 0000000..0ba0647 --- /dev/null +++ b/crates/wac-parser/tests/encoding/fail/import-conflict.wac.result @@ -0,0 +1,13 @@ +failed to resolve document + + × import `foo` conflicts with an item that was implicitly imported by an instantiation of `foo:bar` + ╭─[tests/encoding/fail/import-conflict.wac:5:8] + 2 │ + 3 │ let x = new foo:bar { ... }; + · ───┬─── + · ╰── conflicting instantiation here + 4 │ + 5 │ import foo: func(); + · ─┬─ + · ╰── explicit import here + ╰──── diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict/foo/bar.wat b/crates/wac-parser/tests/encoding/fail/import-conflict/foo/bar.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/import-conflict/foo/bar.wat rename to crates/wac-parser/tests/encoding/fail/import-conflict/foo/bar.wat diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac b/crates/wac-parser/tests/encoding/fail/unmergeable-args.wac similarity index 100% rename from crates/wac-parser/tests/resolution/fail/unmergeable-args.wac rename to crates/wac-parser/tests/encoding/fail/unmergeable-args.wac diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result b/crates/wac-parser/tests/encoding/fail/unmergeable-args.wac.result similarity index 62% rename from crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result rename to crates/wac-parser/tests/encoding/fail/unmergeable-args.wac.result index 4d47767..1ed84dc 100644 --- a/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result +++ b/crates/wac-parser/tests/encoding/fail/unmergeable-args.wac.result @@ -1,7 +1,8 @@ failed to resolve document - × implicit instantiation argument `foo` (function) conflicts with an implicitly imported argument from the instantiation of `bar:baz` - ╭─[tests/resolution/fail/unmergeable-args.wac:4:13] + × failed to merge the type definition for implicit import `foo` due to conflicting types + ╰─▶ function cannot be merged with instance + ╭─[tests/encoding/fail/unmergeable-args.wac:4:13] 2 │ 3 │ let a = new bar:baz { ... }; · ───┬─── diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args/bar/baz.wat b/crates/wac-parser/tests/encoding/fail/unmergeable-args/bar/baz.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/unmergeable-args/bar/baz.wat rename to crates/wac-parser/tests/encoding/fail/unmergeable-args/bar/baz.wat diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args/foo/bar.wat b/crates/wac-parser/tests/encoding/fail/unmergeable-args/foo/bar.wat similarity index 100% rename from crates/wac-parser/tests/resolution/fail/unmergeable-args/foo/bar.wat rename to crates/wac-parser/tests/encoding/fail/unmergeable-args/foo/bar.wat diff --git a/crates/wac-parser/tests/encoding/include-resource.wac b/crates/wac-parser/tests/encoding/include-resource.wac new file mode 100644 index 0000000..9905fdd --- /dev/null +++ b/crates/wac-parser/tests/encoding/include-resource.wac @@ -0,0 +1,3 @@ +package test:comp; + +let x = new foo:bar { ... }; diff --git a/crates/wac-parser/tests/encoding/include-resource.wac.result b/crates/wac-parser/tests/encoding/include-resource.wac.result new file mode 100644 index 0000000..a0822bb --- /dev/null +++ b/crates/wac-parser/tests/encoding/include-resource.wac.result @@ -0,0 +1,51 @@ +(component + (type (;0;) + (instance + (export (;0;) "fields" (type (sub resource))) + (export (;1;) "headers" (type (eq 0))) + ) + ) + (import "test:comp/types" (instance (;0;) (type 0))) + (alias export 0 "fields" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "headers" (type (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (export (;0;) "get-headers" (func (type 3))) + ) + ) + (import "test:comp/incoming-request" (instance (;1;) (type 2))) + (type (;3;) + (component + (type (;0;) + (instance + (export (;0;) "fields" (type (sub resource))) + (export (;1;) "headers" (type (eq 0))) + ) + ) + (import "test:comp/types" (instance (;0;) (type 0))) + (alias export 0 "fields" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "headers" (type (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (export (;0;) "get-headers" (func (type 3))) + ) + ) + (import "test:comp/incoming-request" (instance (;1;) (type 2))) + ) + ) + (import "unlocked-dep=" (component (;0;) (type 3))) + (instance $x (;2;) (instantiate 0 + (with "test:comp/types" (instance 0)) + (with "test:comp/incoming-request" (instance 1)) + ) + ) + (@producers + (processed-by "wac-parser" "0.1.0") + ) +) diff --git a/crates/wac-parser/tests/encoding/include-resource/foo/bar.wat b/crates/wac-parser/tests/encoding/include-resource/foo/bar.wat new file mode 100644 index 0000000..279a03d --- /dev/null +++ b/crates/wac-parser/tests/encoding/include-resource/foo/bar.wat @@ -0,0 +1,56 @@ +(component + (type (;0;) + (instance + (export (;0;) "fields" (type (sub resource))) + (export (;1;) "headers" (type (eq 0))) + ) + ) + (import "test:comp/types" (instance (;0;) (type 0))) + (alias export 0 "headers" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "headers" (type (eq 0))) + (type (;2;) (own 1)) + (type (;3;) (func (result 2))) + (export (;0;) "get-headers" (func (type 3))) + ) + ) + (import "test:comp/incoming-request" (instance (;1;) (type 2))) + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (import "test:comp/types" "[resource-drop]fields" (func (;0;) (type 0))) + (import "test:comp/incoming-request" "get-headers" (func (;1;) (type 1))) + (func (;2;) (type 2) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (memory (;0;) 0) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 2)) + (@producers + (processed-by "wit-component" "0.203.0") + ) + ) + (alias export 0 "fields" (type (;3;))) + (core func (;0;) (canon resource.drop 3)) + (core instance (;0;) + (export "[resource-drop]fields" (func 0)) + ) + (alias export 1 "get-headers" (func (;0;))) + (core func (;1;) (canon lower (func 0))) + (core instance (;1;) + (export "get-headers" (func 1)) + ) + (core instance (;2;) (instantiate 0 + (with "test:comp/types" (instance 0)) + (with "test:comp/incoming-request" (instance 1)) + ) + ) + (alias core export 2 "memory" (core memory (;0;))) + (alias core export 2 "cabi_realloc" (core func (;2;))) + (@producers + (processed-by "wit-component" "0.203.0") + ) +) diff --git a/crates/wac-parser/tests/encoding/instantiation.wat.result b/crates/wac-parser/tests/encoding/instantiation.wac.result similarity index 51% rename from crates/wac-parser/tests/encoding/instantiation.wat.result rename to crates/wac-parser/tests/encoding/instantiation.wac.result index 0314cb9..fd8fac6 100644 --- a/crates/wac-parser/tests/encoding/instantiation.wat.result +++ b/crates/wac-parser/tests/encoding/instantiation.wac.result @@ -1,54 +1,54 @@ (component - (type (;0;) + (type (;0;) (func)) + (import "baz" (func (;0;) (type 0))) + (type (;1;) (instance (type (;0;) (func)) (export (;0;) "foo" (func (type 0))) ) ) - (import "i" (instance (;0;) (type 0))) - (type (;1;) + (import "foo" (instance (;0;) (type 1))) + (type (;2;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "i" (instance $i (;1;) (type 2))) + (type (;3;) (instance (type (;0;) (func)) (export (;0;) "baz" (func (type 0))) ) ) - (import "i2" (instance (;1;) (type 1))) - (type (;2;) (func)) - (import "f" (func (;0;) (type 2))) - (type (;3;) (func)) - (import "baz" (func (;1;) (type 3))) - (type (;4;) + (import "i2" (instance $i2 (;2;) (type 3))) + (type (;4;) (func)) + (import "f" (func $f (;1;) (type 4))) + (type (;5;) (component (type (;0;) (func)) (import "baz" (func (;0;) (type 0))) (export (;1;) "foo" (func (type 0))) ) ) - (import "unlocked-dep=" (component (;0;) (type 4))) - (instance (;2;) (instantiate 0 - (with "baz" (func 1)) - ) - ) - (instance (;3;) (instantiate 0 + (import "unlocked-dep=" (component (;0;) (type 5))) + (instance $x1 (;3;) (instantiate 0 (with "baz" (func 0)) ) ) - (instance (;4;) (instantiate 0 - (with "baz" (func 0)) + (instance $x2 (;4;) (instantiate 0 + (with "baz" (func $f)) ) ) - (alias export 1 "baz" (func (;2;))) - (instance (;5;) (instantiate 0 - (with "baz" (func 2)) + (instance $x3 (;5;) (instantiate 0 + (with "baz" (func $f)) ) ) - (type (;5;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) + (alias export $i2 "baz" (func (;2;))) + (instance $x4 (;6;) (instantiate 0 + (with "baz" (func 2)) ) ) - (import "foo" (instance (;6;) (type 5))) (type (;6;) (component (type (;0;) @@ -61,27 +61,27 @@ ) ) (import "unlocked-dep=" (component (;1;) (type 6))) - (instance (;7;) (instantiate 1 - (with "foo" (instance 6)) + (instance $y1 (;7;) (instantiate 1 + (with "foo" (instance 0)) ) ) - (instance (;8;) (instantiate 1 - (with "foo" (instance 0)) + (instance $y2 (;8;) (instantiate 1 + (with "foo" (instance $i)) ) ) - (instance (;9;) (instantiate 1 - (with "foo" (instance 2)) + (instance $y3 (;9;) (instantiate 1 + (with "foo" (instance $x1)) ) ) - (instance (;10;) (instantiate 1 - (with "foo" (instance 3)) + (instance $y4 (;10;) (instantiate 1 + (with "foo" (instance $x2)) ) ) - (instance (;11;) (instantiate 1 - (with "foo" (instance 4)) + (instance $y5 (;11;) (instantiate 1 + (with "foo" (instance $x3)) ) ) - (alias export 5 "foo" (func (;3;))) + (alias export $x4 "foo" (func (;3;))) (export (;4;) "foo" (func 3)) (@producers (processed-by "wac-parser" "0.1.0") diff --git a/crates/wac-parser/tests/encoding/merged-functions.wat.result b/crates/wac-parser/tests/encoding/merged-functions.wac.result similarity index 94% rename from crates/wac-parser/tests/encoding/merged-functions.wat.result rename to crates/wac-parser/tests/encoding/merged-functions.wac.result index a28dd08..5869250 100644 --- a/crates/wac-parser/tests/encoding/merged-functions.wat.result +++ b/crates/wac-parser/tests/encoding/merged-functions.wac.result @@ -24,7 +24,7 @@ ) ) (import "unlocked-dep=" (component (;0;) (type 1))) - (instance (;1;) (instantiate 0 + (instance $a (;1;) (instantiate 0 (with "foo:bar/baz" (instance 0)) ) ) @@ -42,7 +42,7 @@ ) ) (import "unlocked-dep=" (component (;1;) (type 2))) - (instance (;2;) (instantiate 1 + (instance $b (;2;) (instantiate 1 (with "foo:bar/baz" (instance 0)) ) ) diff --git a/crates/wac-parser/tests/encoding/resources.wat.result b/crates/wac-parser/tests/encoding/resources.wac.result similarity index 90% rename from crates/wac-parser/tests/encoding/resources.wat.result rename to crates/wac-parser/tests/encoding/resources.wac.result index 8076b7d..0ee8940 100644 --- a/crates/wac-parser/tests/encoding/resources.wat.result +++ b/crates/wac-parser/tests/encoding/resources.wac.result @@ -20,7 +20,7 @@ (export (;4;) "baz" (func (type 11))) ) ) - (import "foo" (instance (;0;) (type 0))) + (import "foo" (instance $foo (;0;) (type 0))) (type (;1;) (component (type (;0;) @@ -43,11 +43,11 @@ ) ) (import "unlocked-dep=" (component (;0;) (type 1))) - (instance (;1;) (instantiate 0 - (with "foo" (instance 0)) + (instance $x (;1;) (instantiate 0 + (with "foo" (instance $foo)) ) ) - (alias export 1 "foo" (instance (;2;))) + (alias export $x "foo" (instance (;2;))) (export (;3;) "foo" (instance 2)) (@producers (processed-by "wac-parser" "0.1.0") diff --git a/crates/wac-parser/tests/encoding/types.wac b/crates/wac-parser/tests/encoding/types.wac index 4299326..5a67bce 100644 --- a/crates/wac-parser/tests/encoding/types.wac +++ b/crates/wac-parser/tests/encoding/types.wac @@ -99,3 +99,22 @@ world t { } export f: func(a: a, b: b, c: c) -> tuple; } + +// Test encoding of aliases +type a2 = a; +type b2 = b; +type c2 = c; +type d2 = d; +type e2 = e; +type f2 = f; +type g2 = g; +type h2 = h; +type i2 = i; +type j2 = j; +type k2 = k; +type l2 = l; +type n2 = n; +type m2 = m; +type o2 = o; +type p2 = p; +type q2 = q; diff --git a/crates/wac-parser/tests/encoding/types.wat.result b/crates/wac-parser/tests/encoding/types.wac.result similarity index 77% rename from crates/wac-parser/tests/encoding/types.wat.result rename to crates/wac-parser/tests/encoding/types.wac.result index 177a521..32edf2e 100644 --- a/crates/wac-parser/tests/encoding/types.wat.result +++ b/crates/wac-parser/tests/encoding/types.wac.result @@ -1,42 +1,42 @@ (component (type (;0;) u8) - (export (;1;) "a" (type 0)) + (export $a (;1;) "a" (type 0)) (type (;2;) s8) - (export (;3;) "b" (type 2)) + (export $b (;3;) "b" (type 2)) (type (;4;) u16) - (export (;5;) "c" (type 4)) + (export $c (;5;) "c" (type 4)) (type (;6;) s16) - (export (;7;) "d" (type 6)) + (export $d (;7;) "d" (type 6)) (type (;8;) u32) - (export (;9;) "e" (type 8)) + (export $e (;9;) "e" (type 8)) (type (;10;) s32) - (export (;11;) "f" (type 10)) + (export $f (;11;) "f" (type 10)) (type (;12;) u64) - (export (;13;) "g" (type 12)) + (export $g (;13;) "g" (type 12)) (type (;14;) s64) - (export (;15;) "h" (type 14)) + (export $h (;15;) "h" (type 14)) (type (;16;) f32) - (export (;17;) "i" (type 16)) + (export $i (;17;) "i" (type 16)) (type (;18;) f64) - (export (;19;) "j" (type 18)) + (export $j (;19;) "j" (type 18)) (type (;20;) bool) - (export (;21;) "k" (type 20)) + (export $k (;21;) "k" (type 20)) (type (;22;) char) - (export (;23;) "l" (type 22)) + (export $l (;23;) "l" (type 22)) (type (;24;) string) - (export (;25;) "m" (type 24)) + (export $m (;25;) "m" (type 24)) (type (;26;) (list u8)) (type (;27;) (tuple s8 u16 s16)) (type (;28;) (option u32)) (type (;29;) (record (field "a" 26) (field "b" 27) (field "c" 28))) - (export (;30;) "n" (type 29)) - (type (;31;) (tuple 30 30 30)) - (type (;32;) (variant (case "foo") (case "bar" 30) (case "baz" 31))) - (export (;33;) "o" (type 32)) + (export $n (;30;) "n" (type 29)) + (type (;31;) (tuple $n $n $n)) + (type (;32;) (variant (case "foo") (case "bar" $n) (case "baz" 31))) + (export $o (;33;) "o" (type 32)) (type (;34;) (flags "foo" "bar" "baz")) - (export (;35;) "p" (type 34)) + (export $p (;35;) "p" (type 34)) (type (;36;) (enum "a" "b" "c")) - (export (;37;) "q" (type 36)) + (export $q (;37;) "q" (type 36)) (type (;38;) (component (type (;0;) @@ -64,7 +64,7 @@ (export (;0;) "test:pkg/r" (instance (type 0))) ) ) - (export (;39;) "r" (type 38)) + (export $r (;39;) "r" (type 38)) (type (;40;) (component (type (;0;) @@ -108,7 +108,7 @@ (export (;1;) "test:pkg/s" (instance (type 4))) ) ) - (export (;41;) "s" (type 40)) + (export $s (;41;) "s" (type 40)) (type (;42;) (component (type (;0;) @@ -152,7 +152,24 @@ (export (;0;) "test:pkg/t" (component (type 0))) ) ) - (export (;43;) "t" (type 42)) + (export $t (;43;) "t" (type 42)) + (export $a2 (;44;) "a2" (type $a)) + (export $b2 (;45;) "b2" (type $b)) + (export $c2 (;46;) "c2" (type $c)) + (export $d2 (;47;) "d2" (type $d)) + (export $e2 (;48;) "e2" (type $e)) + (export $f2 (;49;) "f2" (type $f)) + (export $g2 (;50;) "g2" (type $g)) + (export $h2 (;51;) "h2" (type $h)) + (export $i2 (;52;) "i2" (type $i)) + (export $j2 (;53;) "j2" (type $j)) + (export $k2 (;54;) "k2" (type $k)) + (export $l2 (;55;) "l2" (type $l)) + (export $n2 (;56;) "n2" (type $n)) + (export $m2 (;57;) "m2" (type $m)) + (export $o2 (;58;) "o2" (type $o)) + (export $p2 (;59;) "p2" (type $p)) + (export $q2 (;60;) "q2" (type $q)) (@producers (processed-by "wac-parser" "0.1.0") ) diff --git a/crates/wac-parser/tests/parser.rs b/crates/wac-parser/tests/parser.rs index 28878a1..9e9e463 100644 --- a/crates/wac-parser/tests/parser.rs +++ b/crates/wac-parser/tests/parser.rs @@ -11,7 +11,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering}, }; use support::fmt_err; -use wac_parser::ast::Document; +use wac_parser::Document; mod support; diff --git a/crates/wac-parser/tests/resolution.rs b/crates/wac-parser/tests/resolution.rs index c0d742e..8d11e82 100644 --- a/crates/wac-parser/tests/resolution.rs +++ b/crates/wac-parser/tests/resolution.rs @@ -11,7 +11,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering}, }; use support::fmt_err; -use wac_parser::{ast::Document, Composition}; +use wac_parser::Document; use wac_resolver::{packages, FileSystemPackageResolver}; mod support; @@ -95,16 +95,18 @@ fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { .resolve(&packages(&document).map_err(|e| fmt_err(e, test, &source))?) .map_err(|e| fmt_err(e, test, &source))?; - Composition::from_ast(&document, packages).map_err(|e| fmt_err(e, test, &source)) + document + .resolve(packages) + .map_err(|e| fmt_err(e, test, &source)) }; let result = match resolve() { - Ok(doc) => { + Ok(resolution) => { if should_fail { bail!("the resolution was successful but it was expected to fail"); } - serde_json::to_string_pretty(&doc)? + format!("{:?}", resolution.into_graph()) } Err(e) => { if !should_fail { diff --git a/crates/wac-parser/tests/resolution/alias.wac.result b/crates/wac-parser/tests/resolution/alias.wac.result index 74d0241..9ab7f4a 100644 --- a/crates/wac-parser/tests/resolution/alias.wac.result +++ b/crates/wac-parser/tests/resolution/alias.wac.result @@ -1,66 +1,5 @@ -{ - "package": "test:comp", - "version": "1.0.0", - "definitions": { - "types": [ - { - "alias": "u32" - }, - { - "alias": "string" - } - ], - "resources": [], - "funcs": [ - { - "params": {}, - "results": null - } - ], - "interfaces": [], - "worlds": [], - "modules": [] - }, - "packages": [], - "items": [ - { - "definition": { - "name": "a", - "kind": { - "type": { - "value": 0 - } - } - } - }, - { - "definition": { - "name": "b", - "kind": { - "type": { - "value": 1 - } - } - } - }, - { - "definition": { - "name": "c", - "kind": { - "type": { - "func": 0 - } - } - } - } - ], - "imports": {}, - "exports": { - "a": 0, - "b": 1, - "c": 2, - "a2": 0, - "b2": 1, - "c2": 2 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"a2\""; kind = "u32"; export = "a2"] + 1 [ label = "type definition \"b2\""; kind = "string"; export = "b2"] + 2 [ label = "type definition \"c2\""; kind = "function type"; export = "c2"] +} diff --git a/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result b/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result index f56dbdd..3ee992b 100644 --- a/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result +++ b/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result @@ -1,53 +1,3 @@ -{ - "package": "test:comp", - "version": "2.0.0", - "definitions": { - "types": [], - "resources": [], - "funcs": [ - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [], - "worlds": [ - { - "id": "test:comp/w@2.0.0", - "uses": {}, - "imports": { - "x": { - "func": 0 - } - }, - "exports": { - "x": { - "func": 1 - } - } - } - ], - "modules": [] - }, - "packages": [], - "items": [ - { - "definition": { - "name": "w", - "kind": { - "type": { - "world": 0 - } - } - } - } - ], - "imports": {}, - "exports": { - "w": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"w\""; kind = "world"; export = "w"] +} diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result deleted file mode 100644 index b8f654c..0000000 --- a/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result +++ /dev/null @@ -1,13 +0,0 @@ -failed to resolve document - - × import name `foo` conflicts with an instance that was implicitly imported by an instantiation of `foo:bar` - ╭─[tests/resolution/fail/import-conflict.wac:5:8] - 2 │ - 3 │ let x = new foo:bar { ... }; - · ───┬─── - · ╰── previous instantiation here - 4 │ - 5 │ import foo: func(); - · ─┬─ - · ╰── conflicting import here - ╰──── diff --git a/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result b/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result index 704f57a..8f8b25b 100644 --- a/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result +++ b/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result @@ -1,7 +1,7 @@ failed to resolve document × failed to parse package `foo:bar` - ╰─▶ input is not a WebAssembly component + ╰─▶ package `foo:bar` is not a binary-encoded WebAssembly component ╭─[tests/resolution/fail/package-not-component.wac:3:13] 2 │ 3 │ import foo: foo:bar/qux; diff --git a/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result b/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result index ce94950..a34529d 100644 --- a/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result +++ b/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result @@ -1,7 +1,7 @@ failed to resolve document × failed to parse package `foo:bar` - ╰─▶ package `foo:bar` is expected to be a binary WebAssembly component binary but is not + ╰─▶ package `foo:bar` is not a binary-encoded WebAssembly component ╭─[tests/resolution/fail/package-not-wasm.wac:3:13] 2 │ 3 │ import foo: foo:bar/qux; diff --git a/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result b/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result index 15e7778..c68ad41 100644 --- a/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result @@ -1,12 +1,12 @@ failed to resolve document - × `x` is already defined + × type declaration `x` conflicts with a previous export of the same name ╭─[tests/resolution/fail/redefined-name.wac:4:6] 2 │ 3 │ type x = u32; · ┬ - · ╰── `x` previously defined here + · ╰── previous export is here 4 │ type x = string; · ┬ - · ╰── `x` redefined here + · ╰── conflicting type declaration `x` ╰──── diff --git a/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac b/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac new file mode 100644 index 0000000..52ff3a8 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac @@ -0,0 +1,8 @@ +package test:comp; + +import foo: func(); +export foo as i; + +interface i { + +} diff --git a/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac.result new file mode 100644 index 0000000..ad5a3be --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac.result @@ -0,0 +1,14 @@ +failed to resolve document + + × type declaration `i` conflicts with a previous export of the same name + ╭─[tests/resolution/fail/type-decl-conflict.wac:6:11] + 3 │ import foo: func(); + 4 │ export foo as i; + · ┬ + · ╰── previous export is here + 5 │ + 6 │ interface i { + · ┬ + · ╰── conflicting type declaration `i` + 7 │ + ╰──── diff --git a/crates/wac-parser/tests/resolution/import.wac.result b/crates/wac-parser/tests/resolution/import.wac.result index 7c33fe2..ce0ee10 100644 --- a/crates/wac-parser/tests/resolution/import.wac.result +++ b/crates/wac-parser/tests/resolution/import.wac.result @@ -1,148 +1,8 @@ -{ - "package": "test:comp", - "version": "0.0.1-beta", - "definitions": { - "types": [], - "resources": [], - "funcs": [ - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": { - "name": "string" - }, - "results": null - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [ - { - "id": null, - "uses": {}, - "exports": { - "x": { - "func": 3 - } - } - }, - { - "id": "foo:bar/baz", - "uses": {}, - "exports": {} - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/baz": { - "instance": 1 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "baz": { - "type": { - "world": 0 - } - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 1, - "definitions": { - "baz": { - "type": { - "interface": 1 - } - } - } - } - ], - "items": [ - { - "import": { - "name": "a", - "kind": { - "func": 0 - } - } - }, - { - "definition": { - "name": "x", - "kind": { - "type": { - "func": 1 - } - } - } - }, - { - "import": { - "name": "b", - "kind": { - "func": 1 - } - } - }, - { - "import": { - "name": "hello-world", - "kind": { - "func": 2 - } - } - }, - { - "import": { - "name": "e", - "kind": { - "instance": 0 - } - } - }, - { - "import": { - "name": "foo:bar/baz", - "kind": { - "instance": 1 - } - } - } - ], - "imports": { - "a": 0, - "b": 2, - "hello-world": 3, - "e": 4, - "foo:bar/baz": 5 - }, - "exports": { - "x": 1, - "hello-world": 3, - "e": 4, - "foo:bar/baz": 5 - } -} \ No newline at end of file +digraph { + 0 [ label = "import \"a\""; kind = "function"] + 1 [ label = "type definition \"x\""; kind = "function type"; export = "x"] + 2 [ label = "import \"b\""; kind = "function"] + 3 [ label = "import \"hello-world\""; kind = "function"; export = "hello-world"] + 4 [ label = "import \"e\""; kind = "instance"; export = "e"] + 5 [ label = "import \"foo:bar/baz\""; kind = "instance"; export = "foo:bar/baz"] +} diff --git a/crates/wac-parser/tests/resolution/let-statements.wac.result b/crates/wac-parser/tests/resolution/let-statements.wac.result index 9eeb840..f0af678 100644 --- a/crates/wac-parser/tests/resolution/let-statements.wac.result +++ b/crates/wac-parser/tests/resolution/let-statements.wac.result @@ -1,461 +1,12 @@ -{ - "package": "test:comp", - "version": "1.0.0", - "definitions": { - "types": [ - { - "enum": [ - "open", - "ended" - ] - }, - { - "enum": [ - "last-operation-failed", - "closed" - ] - }, - { - "list": "u8" - }, - { - "tuple": [ - 2, - 0 - ] - }, - { - "result": { - "ok": 3, - "err": null - } - }, - { - "tuple": [ - "u64", - 0 - ] - }, - { - "result": { - "ok": 5, - "err": null - } - }, - { - "result": { - "ok": "u64", - "err": 1 - } - }, - { - "result": { - "ok": null, - "err": 1 - } - }, - { - "enum": [ - "last-operation-failed", - "closed" - ] - }, - { - "result": { - "ok": "u64", - "err": 9 - } - }, - { - "list": "u8" - }, - { - "result": { - "ok": null, - "err": 9 - } - } - ], - "resources": [ - { - "name": "pollable" - }, - { - "name": "input-stream" - }, - { - "name": "output-stream" - }, - { - "name": "output-stream" - }, - { - "name": "input-stream" - } - ], - "funcs": [ - { - "params": { - "self": "borrow<1>", - "len": "u64" - }, - "results": { - "scalar": 4 - } - }, - { - "params": { - "self": "borrow<1>", - "len": "u64" - }, - "results": { - "scalar": 6 - } - }, - { - "params": { - "self": "borrow<1>" - }, - "results": { - "scalar": "own<0>" - } - }, - { - "params": { - "self": "borrow<2>" - }, - "results": { - "scalar": 7 - } - }, - { - "params": { - "self": "borrow<2>", - "contents": 2 - }, - "results": { - "scalar": 8 - } - }, - { - "params": { - "self": "borrow<2>" - }, - "results": { - "scalar": 8 - } - }, - { - "params": { - "self": "borrow<2>" - }, - "results": { - "scalar": "own<0>" - } - }, - { - "params": { - "self": "borrow<2>", - "len": "u64" - }, - "results": { - "scalar": 8 - } - }, - { - "params": { - "self": "borrow<2>", - "src": "own<1>", - "len": "u64" - }, - "results": { - "scalar": 6 - } - }, - { - "params": { - "self": "borrow<2>", - "src": "own<1>" - }, - "results": { - "scalar": 6 - } - }, - { - "params": { - "self": "borrow<3>" - }, - "results": { - "scalar": 10 - } - }, - { - "params": { - "self": "borrow<3>", - "contents": 11 - }, - "results": { - "scalar": 12 - } - }, - { - "params": { - "self": "borrow<3>" - }, - "results": { - "scalar": 12 - } - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [ - { - "id": "wasi:io/streams@0.2.0-rc-2023-10-18", - "uses": {}, - "exports": { - "pollable": { - "resource": 0 - }, - "stream-status": { - "type": { - "value": 0 - } - }, - "input-stream": { - "resource": 1 - }, - "write-error": { - "type": { - "value": 1 - } - }, - "output-stream": { - "resource": 2 - }, - "[method]input-stream.read": { - "func": 0 - }, - "[method]input-stream.blocking-read": { - "func": 0 - }, - "[method]input-stream.skip": { - "func": 1 - }, - "[method]input-stream.blocking-skip": { - "func": 1 - }, - "[method]input-stream.subscribe": { - "func": 2 - }, - "[method]output-stream.check-write": { - "func": 3 - }, - "[method]output-stream.write": { - "func": 4 - }, - "[method]output-stream.blocking-write-and-flush": { - "func": 4 - }, - "[method]output-stream.flush": { - "func": 5 - }, - "[method]output-stream.blocking-flush": { - "func": 5 - }, - "[method]output-stream.subscribe": { - "func": 6 - }, - "[method]output-stream.write-zeroes": { - "func": 7 - }, - "[method]output-stream.blocking-write-zeroes-and-flush": { - "func": 7 - }, - "[method]output-stream.splice": { - "func": 8 - }, - "[method]output-stream.blocking-splice": { - "func": 8 - }, - "[method]output-stream.forward": { - "func": 9 - } - } - }, - { - "id": "wasi:io/streams@0.2.0-rc-2023-10-18", - "uses": {}, - "exports": { - "output-stream": { - "resource": 3 - }, - "write-error": { - "type": { - "value": 9 - } - }, - "input-stream": { - "resource": 4 - }, - "[method]output-stream.check-write": { - "func": 10 - }, - "[method]output-stream.write": { - "func": 11 - }, - "[method]output-stream.blocking-write-and-flush": { - "func": 11 - }, - "[method]output-stream.blocking-flush": { - "func": 12 - } - } - }, - { - "id": "foo:bar/baz@0.1.0", - "uses": {}, - "exports": { - "f": { - "func": 13 - } - } - }, - { - "id": "foo:bar/baz@0.1.0", - "uses": {}, - "exports": { - "f": { - "func": 13 - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "wasi:io/streams@0.2.0-rc-2023-10-18": { - "instance": 0 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "streams": { - "type": { - "world": 0 - } - } - } - }, - { - "id": null, - "uses": {}, - "imports": { - "wasi:io/streams@0.2.0-rc-2023-10-18": { - "instance": 1 - }, - "foo:bar/baz@0.1.0": { - "instance": 2 - } - }, - "exports": { - "foo:bar/baz@0.1.0": { - "instance": 2 - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "wasi:io", - "version": null, - "world": 1, - "definitions": { - "streams": { - "type": { - "interface": 0 - } - } - } - }, - { - "name": "foo:bar", - "version": null, - "world": 2, - "definitions": {} - } - ], - "items": [ - { - "import": { - "name": "wasi:io/streams@0.2.0-rc-2023-10-18", - "kind": { - "instance": 0 - } - } - }, - { - "import": { - "name": "foo:bar/baz@0.1.0", - "kind": { - "instance": 3 - } - } - }, - { - "instantiation": { - "package": 1, - "arguments": { - "wasi:io/streams@0.2.0-rc-2023-10-18": 0, - "foo:bar/baz@0.1.0": 1 - } - } - }, - { - "alias": { - "item": 2, - "export": "foo:bar/baz@0.1.0", - "kind": { - "instance": 2 - } - } - }, - { - "instantiation": { - "package": 1, - "arguments": { - "wasi:io/streams@0.2.0-rc-2023-10-18": 0, - "foo:bar/baz@0.1.0": 3 - } - } - }, - { - "alias": { - "item": 4, - "export": "foo:bar/baz@0.1.0", - "kind": { - "instance": 2 - } - } - } - ], - "imports": { - "wasi:io/streams@0.2.0-rc-2023-10-18": 0, - "foo:bar/baz@0.1.0": 1 - }, - "exports": { - "foo:bar/baz@0.1.0": 5, - "i": 4 - } -} \ No newline at end of file +digraph { + 0 [ label = "import \"wasi:io/streams\""; kind = "instance"] + 1 [ label = "instantiation of package \"foo:bar\""; kind = "instance"] + 2 [ label = "alias of export \"foo:bar/baz@0.1.0\""; kind = "instance"] + 3 [ label = "instantiation of package \"foo:bar\""; kind = "instance"; export = "i"] + 4 [ label = "alias of export \"foo:bar/baz@0.1.0\""; kind = "instance"; export = "foo:bar/baz@0.1.0"] + 0 -> 1 [ label = "argument to" ] + 1 -> 2 [ label = "aliased export" ] + 0 -> 3 [ label = "argument to" ] + 2 -> 3 [ label = "argument to" ] + 3 -> 4 [ label = "aliased export" ] +} diff --git a/crates/wac-parser/tests/resolution/no-imports.wac.result b/crates/wac-parser/tests/resolution/no-imports.wac.result index 3bcc4fb..0cb07c7 100644 --- a/crates/wac-parser/tests/resolution/no-imports.wac.result +++ b/crates/wac-parser/tests/resolution/no-imports.wac.result @@ -1,39 +1,3 @@ -{ - "package": "test:comp", - "version": "0.3.0", - "definitions": { - "types": [], - "resources": [], - "funcs": [], - "interfaces": [], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": {} - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 0, - "definitions": {} - } - ], - "items": [ - { - "instantiation": { - "package": 0, - "arguments": {} - } - } - ], - "imports": {}, - "exports": { - "i": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "instantiation of package \"foo:bar\""; kind = "instance"; export = "i"] +} diff --git a/crates/wac-parser/tests/resolution/package-import.wac.result b/crates/wac-parser/tests/resolution/package-import.wac.result index ff5b47d..909424f 100644 --- a/crates/wac-parser/tests/resolution/package-import.wac.result +++ b/crates/wac-parser/tests/resolution/package-import.wac.result @@ -1,108 +1,3 @@ -{ - "package": "test:comp", - "version": "1.0.0", - "definitions": { - "types": [ - { - "alias": "string" - } - ], - "resources": [ - { - "name": "r" - } - ], - "funcs": [ - { - "params": { - "self": "borrow<0>", - "r": "own<0>" - }, - "results": null - }, - { - "params": { - "r": "borrow<0>" - }, - "results": null - } - ], - "interfaces": [ - { - "id": "foo:bar/i", - "uses": {}, - "exports": { - "x": { - "type": { - "value": 0 - } - }, - "r": { - "resource": 0 - }, - "[method]r.x": { - "func": 0 - }, - "y": { - "func": 1 - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/i": { - "instance": 0 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "i": { - "type": { - "world": 0 - } - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 1, - "definitions": { - "i": { - "type": { - "interface": 0 - } - } - } - } - ], - "items": [ - { - "import": { - "name": "foo:bar/i", - "kind": { - "instance": 0 - } - } - } - ], - "imports": { - "foo:bar/i": 0 - }, - "exports": { - "foo:bar/i": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "import \"foo:bar/i\""; kind = "instance"; export = "foo:bar/i"] +} diff --git a/crates/wac-parser/tests/resolution/package-use-item.wac.result b/crates/wac-parser/tests/resolution/package-use-item.wac.result index 1caa174..fa03453 100644 --- a/crates/wac-parser/tests/resolution/package-use-item.wac.result +++ b/crates/wac-parser/tests/resolution/package-use-item.wac.result @@ -1,254 +1,4 @@ -{ - "package": "test:comp", - "version": "0.0.1", - "definitions": { - "types": [ - { - "alias": "u8" - }, - { - "alias": "s64" - }, - { - "alias": "string" - }, - { - "alias": "u8" - }, - { - "alias": "s64" - }, - { - "alias": "string" - }, - { - "alias": "string" - } - ], - "resources": [ - { - "name": "x" - } - ], - "funcs": [], - "interfaces": [ - { - "id": "foo:bar/baz", - "uses": {}, - "exports": { - "a": { - "type": { - "value": 0 - } - }, - "b": { - "type": { - "value": 1 - } - }, - "c": { - "type": { - "value": 2 - } - } - } - }, - { - "id": "foo:bar/baz", - "uses": {}, - "exports": { - "a": { - "type": { - "value": 3 - } - }, - "b": { - "type": { - "value": 4 - } - }, - "c": { - "type": { - "value": 5 - } - } - } - }, - { - "id": "foo:bar/qux", - "uses": { - "z": { - "interface": 1, - "name": "c" - } - }, - "exports": { - "z": { - "type": { - "value": 6 - } - }, - "x": { - "resource": 0 - }, - "y": { - "type": { - "value": "borrow<0>" - } - } - } - }, - { - "id": "test:comp/i@0.0.1", - "uses": { - "a": { - "interface": 0 - }, - "b": { - "interface": 0 - }, - "c": { - "interface": 0 - } - }, - "exports": { - "a": { - "type": { - "value": 0 - } - }, - "b": { - "type": { - "value": 1 - } - }, - "c": { - "type": { - "value": 2 - } - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/baz": { - "instance": 0 - } - } - }, - { - "id": null, - "uses": {}, - "imports": { - "foo:bar/baz": { - "instance": 1 - } - }, - "exports": { - "foo:bar/qux": { - "instance": 2 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "baz": { - "type": { - "world": 0 - } - }, - "qux": { - "type": { - "world": 1 - } - } - } - }, - { - "id": "test:comp/w@0.0.1", - "uses": { - "x": { - "interface": 2 - }, - "y": { - "interface": 2 - }, - "z": { - "interface": 2 - } - }, - "imports": { - "x": { - "resource": 0 - }, - "y": { - "type": { - "value": "borrow<0>" - } - }, - "z": { - "type": { - "value": 6 - } - } - }, - "exports": {} - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 2, - "definitions": { - "baz": { - "type": { - "interface": 0 - } - }, - "qux": { - "type": { - "interface": 2 - } - } - } - } - ], - "items": [ - { - "definition": { - "name": "i", - "kind": { - "type": { - "interface": 3 - } - } - } - }, - { - "definition": { - "name": "w", - "kind": { - "type": { - "world": 3 - } - } - } - } - ], - "imports": {}, - "exports": { - "i": 0, - "w": 1 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"i\""; kind = "interface"; export = "i"] + 1 [ label = "type definition \"w\""; kind = "world"; export = "w"] +} diff --git a/crates/wac-parser/tests/resolution/package-world-include.wac.result b/crates/wac-parser/tests/resolution/package-world-include.wac.result index a8fba6c..3ee992b 100644 --- a/crates/wac-parser/tests/resolution/package-world-include.wac.result +++ b/crates/wac-parser/tests/resolution/package-world-include.wac.result @@ -1,241 +1,3 @@ -{ - "package": "test:comp", - "version": "1.2.3-prerelease", - "definitions": { - "types": [], - "resources": [ - { - "name": "x" - }, - { - "name": "x" - }, - { - "name": "x", - "aliasOf": 1 - }, - { - "name": "x" - } - ], - "funcs": [ - { - "params": {}, - "results": { - "scalar": "own<0>" - } - }, - { - "params": { - "self": "borrow<0>" - }, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": { - "scalar": "own<1>" - } - }, - { - "params": { - "self": "borrow<1>" - }, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": { - "scalar": "own<3>" - } - }, - { - "params": { - "self": "borrow<3>" - }, - "results": null - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [ - { - "id": "foo:bar/i", - "uses": {}, - "exports": { - "x": { - "resource": 0 - }, - "[constructor]x": { - "func": 0 - }, - "[method]x.a": { - "func": 1 - }, - "[static]x.b": { - "func": 2 - } - } - }, - { - "id": "foo:bar/i", - "uses": {}, - "exports": { - "x": { - "resource": 1 - }, - "[constructor]x": { - "func": 3 - }, - "[method]x.a": { - "func": 4 - }, - "[static]x.b": { - "func": 5 - } - } - }, - { - "id": "foo:bar/i", - "uses": {}, - "exports": { - "x": { - "resource": 3 - }, - "[constructor]x": { - "func": 6 - }, - "[method]x.a": { - "func": 7 - }, - "[static]x.b": { - "func": 8 - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/i": { - "instance": 0 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/baz": { - "component": 2 - } - } - }, - { - "id": "foo:bar/baz", - "uses": { - "x": { - "interface": 1 - } - }, - "imports": { - "foo:bar/i": { - "instance": 1 - }, - "x": { - "resource": 2 - } - }, - "exports": { - "foo:bar/i": { - "instance": 2 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "i": { - "type": { - "world": 0 - } - }, - "baz": { - "type": { - "world": 1 - } - } - } - }, - { - "id": "test:comp/w@1.2.3-prerelease", - "uses": {}, - "imports": { - "foo:bar/i": { - "instance": 1 - }, - "x": { - "resource": 2 - } - }, - "exports": { - "foo:bar/i": { - "instance": 2 - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 3, - "definitions": { - "i": { - "type": { - "interface": 0 - } - }, - "baz": { - "type": { - "world": 2 - } - } - } - } - ], - "items": [ - { - "definition": { - "name": "w", - "kind": { - "type": { - "world": 4 - } - } - } - } - ], - "imports": {}, - "exports": { - "w": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"w\""; kind = "world"; export = "w"] +} diff --git a/crates/wac-parser/tests/resolution/package-world-item.wac.result b/crates/wac-parser/tests/resolution/package-world-item.wac.result index 7bc9023..3ee992b 100644 --- a/crates/wac-parser/tests/resolution/package-world-item.wac.result +++ b/crates/wac-parser/tests/resolution/package-world-item.wac.result @@ -1,159 +1,3 @@ -{ - "package": "test:comp", - "version": null, - "definitions": { - "types": [ - { - "alias": "string" - } - ], - "resources": [ - { - "name": "z" - } - ], - "funcs": [ - { - "params": {}, - "results": { - "scalar": "own<0>" - } - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [ - { - "id": "foo:bar/baz", - "uses": {}, - "exports": { - "z": { - "resource": 0 - }, - "[constructor]z": { - "func": 0 - }, - "x": { - "func": 1 - } - } - }, - { - "id": "bar:baz/qux", - "uses": {}, - "exports": { - "x": { - "type": { - "value": 0 - } - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/baz": { - "instance": 0 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "baz": { - "type": { - "world": 0 - } - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "bar:baz/qux": { - "instance": 1 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "qux": { - "type": { - "world": 2 - } - } - } - }, - { - "id": "test:comp/w", - "uses": {}, - "imports": { - "foo:bar/baz": { - "instance": 0 - } - }, - "exports": { - "bar:baz/qux": { - "instance": 1 - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 1, - "definitions": { - "baz": { - "type": { - "interface": 0 - } - } - } - }, - { - "name": "bar:baz", - "version": null, - "world": 3, - "definitions": { - "qux": { - "type": { - "interface": 1 - } - } - } - } - ], - "items": [ - { - "definition": { - "name": "w", - "kind": { - "type": { - "world": 4 - } - } - } - } - ], - "imports": {}, - "exports": { - "w": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"w\""; kind = "world"; export = "w"] +} diff --git a/crates/wac-parser/tests/resolution/resource.wac.result b/crates/wac-parser/tests/resolution/resource.wac.result index 23c0ec3..f36a7ec 100644 --- a/crates/wac-parser/tests/resolution/resource.wac.result +++ b/crates/wac-parser/tests/resolution/resource.wac.result @@ -1,77 +1,3 @@ -{ - "package": "test:comp", - "version": null, - "definitions": { - "types": [], - "resources": [ - { - "name": "x" - }, - { - "name": "x2", - "aliasOf": 0 - } - ], - "funcs": [ - { - "params": { - "x": "borrow<0>" - }, - "results": null - }, - { - "params": { - "x": "own<0>", - "x2": "own<1>", - "x3": "borrow<0>", - "x4": "borrow<1>" - }, - "results": null - } - ], - "interfaces": [ - { - "id": "test:comp/foo", - "uses": {}, - "exports": { - "x": { - "resource": 0 - }, - "f": { - "type": { - "func": 0 - } - }, - "f2": { - "func": 0 - }, - "x2": { - "resource": 1 - }, - "f3": { - "func": 1 - } - } - } - ], - "worlds": [], - "modules": [] - }, - "packages": [], - "items": [ - { - "definition": { - "name": "foo", - "kind": { - "type": { - "interface": 0 - } - } - } - } - ], - "imports": {}, - "exports": { - "foo": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"foo\""; kind = "interface"; export = "foo"] +} diff --git a/crates/wac-parser/tests/resolution/targets-empty-world.wac.result b/crates/wac-parser/tests/resolution/targets-empty-world.wac.result index 10215e2..95a330a 100644 --- a/crates/wac-parser/tests/resolution/targets-empty-world.wac.result +++ b/crates/wac-parser/tests/resolution/targets-empty-world.wac.result @@ -1,36 +1,3 @@ -{ - "package": "test:comp", - "version": null, - "definitions": { - "types": [], - "resources": [], - "funcs": [], - "interfaces": [], - "worlds": [ - { - "id": "test:comp/foo", - "uses": {}, - "imports": {}, - "exports": {} - } - ], - "modules": [] - }, - "packages": [], - "items": [ - { - "definition": { - "name": "foo", - "kind": { - "type": { - "world": 0 - } - } - } - } - ], - "imports": {}, - "exports": { - "foo": 0 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"foo\""; kind = "world"; export = "foo"] +} diff --git a/crates/wac-parser/tests/resolution/targets-world.wac.result b/crates/wac-parser/tests/resolution/targets-world.wac.result index a7b282a..c1f5996 100644 --- a/crates/wac-parser/tests/resolution/targets-world.wac.result +++ b/crates/wac-parser/tests/resolution/targets-world.wac.result @@ -1,113 +1,6 @@ -{ - "package": "test:comp", - "version": null, - "definitions": { - "types": [], - "resources": [], - "funcs": [ - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - } - ], - "interfaces": [ - { - "id": null, - "uses": {}, - "exports": { - "f": { - "func": 1 - } - } - } - ], - "worlds": [ - { - "id": "test:comp/world", - "uses": {}, - "imports": { - "f": { - "func": 0 - } - }, - "exports": { - "i": { - "type": { - "interface": 0 - } - } - } - }, - { - "id": null, - "uses": {}, - "imports": { - "f": { - "func": 3 - } - }, - "exports": { - "f": { - "func": 3 - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 1, - "definitions": {} - } - ], - "items": [ - { - "definition": { - "name": "world", - "kind": { - "type": { - "world": 0 - } - } - } - }, - { - "import": { - "name": "f", - "kind": { - "func": 2 - } - } - }, - { - "instantiation": { - "package": 0, - "arguments": { - "f": 1 - } - } - } - ], - "imports": { - "f": 1 - }, - "exports": { - "world": 0, - "i": 2 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"world\""; kind = "world"; export = "world"] + 1 [ label = "import \"f\""; kind = "function"] + 2 [ label = "instantiation of package \"foo:bar\""; kind = "instance"; export = "i"] + 1 -> 2 [ label = "argument to" ] +} diff --git a/crates/wac-parser/tests/resolution/types.wac.result b/crates/wac-parser/tests/resolution/types.wac.result index 235c5e1..4790325 100644 --- a/crates/wac-parser/tests/resolution/types.wac.result +++ b/crates/wac-parser/tests/resolution/types.wac.result @@ -1,484 +1,20 @@ -{ - "package": "test:comp", - "version": null, - "definitions": { - "types": [ - { - "record": { - "fields": { - "x": "u32" - } - } - }, - { - "alias": "u32" - }, - { - "variant": { - "cases": { - "a": 1, - "b": "string", - "c": "u32", - "d": null - } - } - }, - { - "record": { - "fields": { - "x": "u32", - "y": "string", - "z": 2 - } - } - }, - { - "flags": [ - "a", - "b", - "c" - ] - }, - { - "enum": [ - "a", - "b", - "c" - ] - }, - { - "alias": 5 - }, - { - "alias": "string" - } - ], - "resources": [ - { - "name": "r" - }, - { - "name": "res" - } - ], - "funcs": [ - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": null - }, - { - "params": {}, - "results": { - "scalar": "own<1>" - } - }, - { - "params": { - "a": "u32", - "b": 3 - }, - "results": { - "scalar": "u32" - } - }, - { - "params": {}, - "results": { - "list": { - "a": "u32", - "b": "string" - } - } - } - ], - "interfaces": [ - { - "id": "test:comp/i", - "uses": {}, - "exports": { - "a": { - "type": { - "func": 0 - } - }, - "r": { - "type": { - "value": 0 - } - }, - "x": { - "func": 1 - }, - "y": { - "func": 0 - } - } - }, - { - "id": "test:comp/i2", - "uses": { - "r": { - "interface": 0 - }, - "z": { - "interface": 0, - "name": "r" - } - }, - "exports": { - "r": { - "type": { - "value": 0 - } - }, - "z": { - "type": { - "value": 0 - } - } - } - }, - { - "id": "foo:bar/i", - "uses": {}, - "exports": { - "r": { - "resource": 0 - } - } - }, - { - "id": null, - "uses": {}, - "exports": { - "x": { - "func": 4 - } - } - } - ], - "worlds": [ - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/i": { - "instance": 2 - } - } - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "foo:bar/baz": { - "component": 2 - } - } - }, - { - "id": "foo:bar/baz", - "uses": {}, - "imports": {}, - "exports": {} - }, - { - "id": null, - "uses": {}, - "imports": {}, - "exports": { - "i": { - "type": { - "world": 0 - } - }, - "baz": { - "type": { - "world": 1 - } - } - } - }, - { - "id": "test:comp/w1", - "uses": { - "r": { - "interface": 2 - } - }, - "imports": { - "r": { - "resource": 0 - }, - "a": { - "func": 3 - }, - "test:comp/i": { - "instance": 0 - }, - "c": { - "func": 2 - } - }, - "exports": { - "d": { - "type": { - "interface": 3 - } - }, - "test:comp/i2": { - "instance": 1 - }, - "f": { - "func": 2 - } - } - }, - { - "id": "test:comp/w2", - "uses": {}, - "imports": { - "res": { - "resource": 1 - }, - "[constructor]res": { - "func": 5 - }, - "r": { - "resource": 0 - }, - "a": { - "func": 3 - }, - "test:comp/i": { - "instance": 0 - }, - "c": { - "func": 2 - } - }, - "exports": { - "d": { - "type": { - "interface": 3 - } - }, - "test:comp/i2": { - "instance": 1 - }, - "f": { - "func": 2 - } - } - } - ], - "modules": [] - }, - "packages": [ - { - "name": "foo:bar", - "version": null, - "world": 3, - "definitions": { - "i": { - "type": { - "interface": 2 - } - }, - "baz": { - "type": { - "world": 2 - } - } - } - } - ], - "items": [ - { - "definition": { - "name": "i", - "kind": { - "type": { - "interface": 0 - } - } - } - }, - { - "definition": { - "name": "i2", - "kind": { - "type": { - "interface": 1 - } - } - } - }, - { - "definition": { - "name": "c", - "kind": { - "type": { - "func": 2 - } - } - } - }, - { - "definition": { - "name": "f", - "kind": { - "type": { - "func": 2 - } - } - } - }, - { - "definition": { - "name": "w1", - "kind": { - "type": { - "world": 4 - } - } - } - }, - { - "definition": { - "name": "w2", - "kind": { - "type": { - "world": 5 - } - } - } - }, - { - "definition": { - "name": "x", - "kind": { - "type": { - "value": 1 - } - } - } - }, - { - "definition": { - "name": "v", - "kind": { - "type": { - "value": 2 - } - } - } - }, - { - "definition": { - "name": "r", - "kind": { - "type": { - "value": 3 - } - } - } - }, - { - "definition": { - "name": "flags", - "kind": { - "type": { - "value": 4 - } - } - } - }, - { - "definition": { - "name": "e", - "kind": { - "type": { - "value": 5 - } - } - } - }, - { - "definition": { - "name": "t", - "kind": { - "type": { - "value": 6 - } - } - } - }, - { - "definition": { - "name": "t2", - "kind": { - "type": { - "value": 7 - } - } - } - }, - { - "definition": { - "name": "t3", - "kind": { - "type": { - "func": 6 - } - } - } - }, - { - "definition": { - "name": "t4", - "kind": { - "type": { - "func": 7 - } - } - } - } - ], - "imports": {}, - "exports": { - "i": 0, - "i2": 1, - "c": 2, - "f": 3, - "w1": 4, - "w2": 5, - "x": 6, - "v": 7, - "r": 8, - "flags": 9, - "e": 10, - "t": 11, - "t2": 12, - "t3": 13, - "t4": 14 - } -} \ No newline at end of file +digraph { + 0 [ label = "type definition \"i\""; kind = "interface"; export = "i"] + 1 [ label = "type definition \"i2\""; kind = "interface"; export = "i2"] + 2 [ label = "type definition \"c\""; kind = "function type"; export = "c"] + 3 [ label = "type definition \"f\""; kind = "function type"; export = "f"] + 4 [ label = "type definition \"w1\""; kind = "world"; export = "w1"] + 5 [ label = "type definition \"w2\""; kind = "world"; export = "w2"] + 6 [ label = "type definition \"x\""; kind = "u32"; export = "x"] + 7 [ label = "type definition \"v\""; kind = "variant"; export = "v"] + 8 [ label = "type definition \"r\""; kind = "record"; export = "r"] + 9 [ label = "type definition \"flags\""; kind = "flags"; export = "flags"] + 10 [ label = "type definition \"e\""; kind = "enum"; export = "e"] + 11 [ label = "type definition \"t\""; kind = "enum"; export = "t"] + 12 [ label = "type definition \"t2\""; kind = "string"; export = "t2"] + 13 [ label = "type definition \"t3\""; kind = "function type"; export = "t3"] + 14 [ label = "type definition \"t4\""; kind = "function type"; export = "t4"] + 6 -> 7 [ label = "dependency of" ] + 7 -> 8 [ label = "dependency of" ] + 8 -> 13 [ label = "dependency of" ] +} diff --git a/crates/wac-resolver/Cargo.toml b/crates/wac-resolver/Cargo.toml index ac1cc26..7ff8349 100644 --- a/crates/wac-resolver/Cargo.toml +++ b/crates/wac-resolver/Cargo.toml @@ -30,6 +30,7 @@ tokio = { workspace = true, optional = true } futures = { workspace = true, optional = true } [dev-dependencies] +wac-graph = { workspace = true } wasmprinter = { workspace = true } warg-server = { workspace = true } pretty_assertions = { workspace = true } diff --git a/crates/wac-resolver/src/fs.rs b/crates/wac-resolver/src/fs.rs index 1a92289..d9ad1b3 100644 --- a/crates/wac-resolver/src/fs.rs +++ b/crates/wac-resolver/src/fs.rs @@ -2,8 +2,8 @@ use super::Error; use anyhow::{anyhow, Context, Result}; use indexmap::IndexMap; use miette::SourceSpan; -use std::{collections::HashMap, fs, path::PathBuf, sync::Arc}; -use wac_parser::PackageKey; +use std::{collections::HashMap, fs, path::PathBuf}; +use wac_types::BorrowedPackageKey; /// Used to resolve packages from the file system. pub struct FileSystemPackageResolver { @@ -29,8 +29,8 @@ impl FileSystemPackageResolver { /// Resolves the provided package keys to packages. pub fn resolve<'a>( &self, - keys: &IndexMap, SourceSpan>, - ) -> Result, Arc>>, Error> { + keys: &IndexMap, SourceSpan>, + ) -> Result, Vec>, Error> { let mut packages = IndexMap::new(); for (key, span) in keys.iter() { let path = match self.overrides.get(key.name) { @@ -105,16 +105,14 @@ impl FileSystemPackageResolver { if let Some(pkg) = pkg { packages.insert( *key, - Arc::new( - wit_component::encode(Some(true), &resolve, pkg) - .with_context(|| { - format!( - "failed to encode WIT package from `{path}`", - path = path.display() - ) - }) - .map_err(pkg_res_failure)?, - ), + wit_component::encode(Some(true), &resolve, pkg) + .with_context(|| { + format!( + "failed to encode WIT package from `{path}`", + path = path.display() + ) + }) + .map_err(pkg_res_failure)?, ); continue; @@ -162,11 +160,11 @@ impl FileSystemPackageResolver { } }; - packages.insert(*key, Arc::new(bytes)); + packages.insert(*key, bytes); continue; } - packages.insert(*key, Arc::new(bytes)); + packages.insert(*key, bytes); } Ok(packages) diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index e588ba3..9476351 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use miette::{Diagnostic, SourceSpan}; -use wac_parser::{ast::Document, PackageKey}; +use wac_parser::Document; mod fs; #[cfg(feature = "registry")] @@ -13,6 +13,7 @@ pub use fs::*; #[cfg(feature = "registry")] pub use registry::*; pub use visitor::*; +use wac_types::BorrowedPackageKey; /// Represents a package resolution error. #[derive(thiserror::Error, Diagnostic, Debug)] @@ -121,14 +122,20 @@ pub enum Error { /// which references the package. pub fn packages<'a>( document: &'a Document<'a>, -) -> Result, SourceSpan>, Error> { +) -> Result, SourceSpan>, Error> { let mut keys = IndexMap::new(); let mut visitor = PackageVisitor::new(|name, version, span| { if name == document.directive.package.name { return true; } - if keys.insert(PackageKey { name, version }, span).is_none() { + if keys + .insert( + BorrowedPackageKey::from_name_and_version(name, version), + span, + ) + .is_none() + { if let Some(version) = version { log::debug!("discovered reference to package `{name}` ({version})"); } else { diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index c3355ad..6bb20d8 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -6,7 +6,7 @@ use miette::SourceSpan; use secrecy::Secret; use semver::{Comparator, Op, Version, VersionReq}; use std::{fs, path::Path, sync::Arc}; -use wac_parser::PackageKey; +use wac_types::BorrowedPackageKey; use warg_client::{ storage::{ContentStorage, RegistryStorage}, Client, ClientError, Config, FileSystemClient, RegistryUrl, @@ -80,8 +80,8 @@ impl RegistryPackageResolver { /// If the package isn't found, an error is returned. pub async fn resolve<'a>( &self, - keys: &IndexMap, SourceSpan>, - ) -> Result, Arc>>, Error> { + keys: &IndexMap, SourceSpan>, + ) -> Result, Vec>, Error> { // Start by fetching any required package logs self.fetch(keys).await?; @@ -142,7 +142,10 @@ impl RegistryPackageResolver { Ok(packages) } - async fn fetch(&self, keys: &IndexMap, SourceSpan>) -> Result<(), Error> { + async fn fetch<'a>( + &self, + keys: &IndexMap, SourceSpan>, + ) -> Result<(), Error> { // First check if we already have the packages in client storage. // If not, we'll fetch the logs from the registry. let mut fetch = IndexMap::new(); @@ -231,10 +234,11 @@ impl RegistryPackageResolver { async fn find_missing_content<'a>( &self, - keys: &IndexMap, SourceSpan>, - packages: &mut IndexMap, Arc>>, - ) -> Result>)>, Error> { - let mut downloads: IndexMap>)> = IndexMap::new(); + keys: &IndexMap, SourceSpan>, + packages: &mut IndexMap, Vec>, + ) -> Result>)>, Error> { + let mut downloads: IndexMap)> = + IndexMap::new(); for (key, span) in keys { let id = key.name @@ -316,13 +320,11 @@ impl RegistryPackageResolver { Ok(downloads) } - fn read_contents(path: &Path) -> Result>, Error> { - Ok(Arc::new(fs::read(path).map_err(|e| { - Error::RegistryContentFailure { - path: path.to_path_buf(), - source: e.into(), - } - })?)) + fn read_contents(path: &Path) -> Result, Error> { + fs::read(path).map_err(|e| Error::RegistryContentFailure { + path: path.to_path_buf(), + source: e.into(), + }) } pub fn auth_token(config: &Config, url: Option<&str>) -> Result>> { diff --git a/crates/wac-resolver/src/visitor.rs b/crates/wac-resolver/src/visitor.rs index d94ab71..d2e39ca 100644 --- a/crates/wac-resolver/src/visitor.rs +++ b/crates/wac-resolver/src/visitor.rs @@ -1,6 +1,6 @@ use miette::SourceSpan; use semver::Version; -use wac_parser::ast::{ +use wac_parser::{ Document, Expr, ExternType, ImportStatement, ImportType, InstantiationArgument, InterfaceItem, PrimaryExpr, Statement, TypeStatement, UsePath, WorldItem, WorldItemPath, WorldRef, }; diff --git a/crates/wac-resolver/tests/registry.rs b/crates/wac-resolver/tests/registry.rs index b1c80b0..2b124a3 100644 --- a/crates/wac-resolver/tests/registry.rs +++ b/crates/wac-resolver/tests/registry.rs @@ -2,7 +2,8 @@ use crate::support::{publish_component, publish_wit, spawn_server}; use anyhow::Result; use pretty_assertions::assert_eq; use tempdir::TempDir; -use wac_parser::{ast::Document, Composition, EncodingOptions}; +use wac_graph::EncodeOptions; +use wac_parser::Document; use wac_resolver::{packages, RegistryPackageResolver}; mod support; @@ -63,8 +64,11 @@ export i2.foo as "bar"; let resolver = RegistryPackageResolver::new_with_config(None, &config, None)?; let packages = resolver.resolve(&packages(&document)?).await?; - let composition = Composition::from_ast(&document, packages)?; - let bytes = composition.encode(EncodingOptions::default())?; + let resolution = document.resolve(packages)?; + let bytes = resolution.encode(EncodeOptions { + define_components: false, + ..Default::default() + })?; assert_eq!( wasmprinter::print_bytes(bytes)?, @@ -75,14 +79,14 @@ export i2.foo as "bar"; (export (;0;) "bar" (func (type 0))) ) ) - (import "test:wit/foo" (instance (;0;) (type 0))) + (import "test:wit/foo" (instance $x (;0;) (type 0))) (type (;1;) (instance (type (;0;) (func)) (export (;0;) "bar" (func (type 0))) ) ) - (import "y" (instance (;1;) (type 1))) + (import "y" (instance $y (;1;) (type 1))) (type (;2;) (component (type (;0;) @@ -96,16 +100,16 @@ export i2.foo as "bar"; ) ) (import "unlocked-dep=" (component (;0;) (type 2))) - (instance (;2;) (instantiate 0 - (with "test:wit/foo" (instance 0)) + (instance $i1 (;2;) (instantiate 0 + (with "test:wit/foo" (instance $x)) ) ) - (instance (;3;) (instantiate 0 - (with "test:wit/foo" (instance 1)) + (instance $i2 (;3;) (instantiate 0 + (with "test:wit/foo" (instance $y)) ) ) - (alias export 2 "test:wit/foo" (instance (;4;))) - (alias export 3 "test:wit/foo" (instance (;5;))) + (alias export $i1 "test:wit/foo" (instance (;4;))) + (alias export $i2 "test:wit/foo" (instance (;5;))) (export (;6;) "test:wit/foo" (instance 4)) (export (;7;) "bar" (instance 5)) (@producers diff --git a/crates/wac-types/src/aggregator.rs b/crates/wac-types/src/aggregator.rs index a46508f..ab4a16d 100644 --- a/crates/wac-types/src/aggregator.rs +++ b/crates/wac-types/src/aggregator.rs @@ -1,7 +1,7 @@ use crate::{ DefinedType, DefinedTypeId, FuncResult, FuncType, FuncTypeId, Interface, InterfaceId, ItemKind, - ModuleTypeId, Record, Resource, ResourceAlias, ResourceId, SubtypeCheck, SubtypeChecker, Type, - Types, UsedType, ValueType, Variant, World, WorldId, + ModuleTypeId, Record, Resource, ResourceAlias, ResourceId, SubtypeChecker, Type, Types, + UsedType, ValueType, Variant, World, WorldId, }; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; @@ -122,13 +122,7 @@ impl TypeAggregator { if let Some(target_kind) = self.types[existing].exports.get(name).copied() { // If the source kind is already a subtype of the target, do nothing if checker - .is_subtype( - *source_kind, - types, - target_kind, - &self.types, - SubtypeCheck::Covariant, - ) + .is_subtype(*source_kind, types, target_kind, &self.types) .is_ok() { // Keep track that the source type should be replaced with the @@ -140,13 +134,7 @@ impl TypeAggregator { // Otherwise, the target *must* be a subtype of the source // We'll remap the source below and replace checker - .is_subtype( - target_kind, - &self.types, - *source_kind, - types, - SubtypeCheck::Covariant, - ) + .is_subtype(target_kind, &self.types, *source_kind, types) .with_context(|| format!("mismatched type for export `{name}`"))?; } @@ -224,17 +212,12 @@ impl TypeAggregator { self.merge_world_used_types(existing, types, id, checker)?; // Merge the worlds's imports + checker.invert(); for (name, source_kind) in &types[id].imports { if let Some(target_kind) = self.types[existing].imports.get(name).copied() { // If the target kind is already a subtype of the source, do nothing if checker - .is_subtype( - target_kind, - &self.types, - *source_kind, - types, - SubtypeCheck::Contravariant, - ) + .is_subtype(target_kind, &self.types, *source_kind, types) .is_ok() { continue; @@ -243,13 +226,7 @@ impl TypeAggregator { // Otherwise, the source *must* be a subtype of the target // We'll remap the source below and replace checker - .is_subtype( - *source_kind, - types, - target_kind, - &self.types, - SubtypeCheck::Contravariant, - ) + .is_subtype(*source_kind, types, target_kind, &self.types) .with_context(|| format!("mismatched type for export `{name}`"))?; } @@ -257,18 +234,14 @@ impl TypeAggregator { self.types[existing].imports.insert(name.clone(), remapped); } + checker.revert(); + // Merge the worlds's exports for (name, source_kind) in &types[id].exports { if let Some(target_kind) = self.types[existing].exports.get(name).copied() { // If the source kind is already a subtype of the target, do nothing if checker - .is_subtype( - *source_kind, - types, - target_kind, - &self.types, - SubtypeCheck::Covariant, - ) + .is_subtype(*source_kind, types, target_kind, &self.types) .is_ok() { continue; @@ -277,13 +250,7 @@ impl TypeAggregator { // Otherwise, the target *must* be a subtype of the source // We'll remap the source below and replace checker - .is_subtype( - target_kind, - &self.types, - *source_kind, - types, - SubtypeCheck::Covariant, - ) + .is_subtype(target_kind, &self.types, *source_kind, types) .with_context(|| format!("mismatched type for export `{name}`"))?; } @@ -365,14 +332,12 @@ impl TypeAggregator { types, ItemKind::Func(existing), &self.types, - SubtypeCheck::Covariant, )?; checker.is_subtype( ItemKind::Func(existing), &self.types, ItemKind::Func(id), types, - SubtypeCheck::Covariant, )?; Ok(()) @@ -386,20 +351,20 @@ impl TypeAggregator { checker: &mut SubtypeChecker, ) -> Result<()> { // Merge the module type's imports + checker.invert(); for (name, source_extern) in &types[id].imports { if let Some(target_extern) = self.types[existing].imports.get(name) { // If the target extern is already a subtype of the source, do nothing - checker.kinds.push(SubtypeCheck::Contravariant); - let r = checker.core_extern(target_extern, &self.types, source_extern, types); - checker.kinds.pop(); - if r.is_ok() { + if checker + .core_extern(target_extern, &self.types, source_extern, types) + .is_ok() + { continue; } // Otherwise, the source *must* be a subtype of the target // We'll remap the source below and replace - checker.kinds.push(SubtypeCheck::Contravariant); - let r = checker + checker .core_extern(source_extern, types, target_extern, &self.types) .with_context(|| { format!( @@ -407,9 +372,7 @@ impl TypeAggregator { m = name.0, n = name.1 ) - }); - checker.kinds.pop(); - r?; + })?; } self.types[existing] @@ -417,26 +380,25 @@ impl TypeAggregator { .insert(name.clone(), source_extern.clone()); } + checker.revert(); + // Merge the module type's exports for (name, source_extern) in &types[id].exports { if let Some(target_extern) = self.types[existing].exports.get(name) { // If the source kind is already a subtype of the target, do nothing // If the target extern is already a subtype of the source, do nothing - checker.kinds.push(SubtypeCheck::Contravariant); - let r = checker.core_extern(source_extern, types, target_extern, &self.types); - checker.kinds.pop(); - if r.is_ok() { + if checker + .core_extern(source_extern, types, target_extern, &self.types) + .is_ok() + { continue; } // Otherwise, the target *must* be a subtype of the source // We'll remap the source below and replace - checker.kinds.push(SubtypeCheck::Contravariant); - let r = checker + checker .core_extern(target_extern, &self.types, source_extern, types) - .with_context(|| format!("mismatched type for export `{name}`")); - checker.kinds.pop(); - r?; + .with_context(|| format!("mismatched type for export `{name}`"))?; } self.types[existing] @@ -495,7 +457,6 @@ impl TypeAggregator { types, ItemKind::Type(Type::Resource(existing)), &self.types, - SubtypeCheck::Covariant, )?; checker.is_subtype( @@ -503,7 +464,6 @@ impl TypeAggregator { &self.types, ItemKind::Type(Type::Resource(id)), types, - SubtypeCheck::Covariant, )?; Ok(()) @@ -522,7 +482,6 @@ impl TypeAggregator { types, ItemKind::Value(existing), &self.types, - SubtypeCheck::Covariant, )?; checker.is_subtype( @@ -530,7 +489,6 @@ impl TypeAggregator { &self.types, ItemKind::Value(ty), types, - SubtypeCheck::Covariant, )?; Ok(()) diff --git a/crates/wac-types/src/checker.rs b/crates/wac-types/src/checker.rs index 9dae8c9..5d87bf6 100644 --- a/crates/wac-types/src/checker.rs +++ b/crates/wac-types/src/checker.rs @@ -20,7 +20,7 @@ pub enum SubtypeCheck { /// /// Subtype checking is used to type check instantiation arguments. pub struct SubtypeChecker<'a> { - pub(crate) kinds: Vec, + kinds: Vec, cache: &'a mut HashSet<(ItemKind, ItemKind)>, } @@ -33,23 +33,20 @@ impl<'a> SubtypeChecker<'a> { } } + fn kind(&self) -> SubtypeCheck { + self.kinds + .last() + .copied() + .unwrap_or(SubtypeCheck::Covariant) + } + /// Checks if `a` is a subtype of `b`. - pub fn is_subtype( - &mut self, - a: ItemKind, - at: &Types, - b: ItemKind, - bt: &Types, - kind: SubtypeCheck, - ) -> Result<()> { + pub fn is_subtype(&mut self, a: ItemKind, at: &Types, b: ItemKind, bt: &Types) -> Result<()> { if self.cache.contains(&(a, b)) { return Ok(()); } - self.kinds.push(kind); let result = self.is_subtype_(a, at, b, bt); - self.kinds.pop(); - if result.is_ok() { self.cache.insert((a, b)); } @@ -57,6 +54,23 @@ impl<'a> SubtypeChecker<'a> { result } + /// Inverts the current subtype check being performed. + /// + /// Returns the previous subtype check. + pub fn invert(&mut self) -> SubtypeCheck { + let prev = self.kind(); + self.kinds.push(match prev { + SubtypeCheck::Covariant => SubtypeCheck::Contravariant, + SubtypeCheck::Contravariant => SubtypeCheck::Covariant, + }); + prev + } + + /// Reverts to the previous check kind. + pub fn revert(&mut self) { + self.kinds.pop().expect("mismatched stack"); + } + fn is_subtype_(&mut self, a: ItemKind, at: &Types, b: ItemKind, bt: &Types) -> Result<()> { match (a, b) { (ItemKind::Type(a), ItemKind::Type(b)) => self.ty(a, at, b, bt), @@ -92,11 +106,6 @@ impl<'a> SubtypeChecker<'a> { } } - /// Gets the current check kind. - fn kind(&self) -> SubtypeCheck { - self.kinds.last().copied().unwrap() - } - fn resource(&self, a: ResourceId, at: &Types, b: ResourceId, bt: &Types) -> Result<()> { if a == b { return Ok(()); @@ -230,7 +239,7 @@ impl<'a> SubtypeChecker<'a> { for (k, b) in b.iter() { match a.get(k) { Some(a) => { - self.is_subtype(*a, at, *b, bt, SubtypeCheck::Covariant) + self.is_subtype(*a, at, *b, bt) .with_context(|| format!("mismatched type for export `{k}`"))?; } None => match self.kind() { @@ -272,13 +281,14 @@ impl<'a> SubtypeChecker<'a> { // can export *more* than what this component type needs). // However, for imports, the check is reversed (i.e. it is okay // to import *less* than what this component type needs). + let prev = self.invert(); for (k, a) in a.imports.iter() { match b.imports.get(k) { Some(b) => { - self.is_subtype(*b, bt, *a, at, SubtypeCheck::Contravariant) + self.is_subtype(*b, bt, *a, at) .with_context(|| format!("mismatched type for import `{k}`"))?; } - None => match self.kind() { + None => match prev { SubtypeCheck::Covariant => { bail!( "component is missing expected {kind} import `{k}`", @@ -295,10 +305,12 @@ impl<'a> SubtypeChecker<'a> { } } + self.revert(); + for (k, b) in b.exports.iter() { match a.exports.get(k) { Some(a) => { - self.is_subtype(*a, at, *b, bt, SubtypeCheck::Covariant) + self.is_subtype(*a, at, *b, bt) .with_context(|| format!("mismatched type for export `{k}`"))?; } None => match self.kind() { @@ -334,17 +346,15 @@ impl<'a> SubtypeChecker<'a> { // can export *more* than what is expected module type needs). // However, for imports, the check is reversed (i.e. it is okay // to import *less* than what this module type needs). + let prev = self.invert(); for (k, a) in a.imports.iter() { match b.imports.get(k) { Some(b) => { - self.kinds.push(SubtypeCheck::Contravariant); - let r = self.core_extern(b, bt, a, at).with_context(|| { + self.core_extern(b, bt, a, at).with_context(|| { format!("mismatched type for import `{m}::{n}`", m = k.0, n = k.1) - }); - self.kinds.pop(); - r?; + })?; } - None => match self.kind() { + None => match prev { SubtypeCheck::Covariant => bail!( "module is missing expected {a} import `{m}::{n}`", m = k.0, @@ -361,6 +371,8 @@ impl<'a> SubtypeChecker<'a> { } } + self.revert(); + for (k, b) in b.exports.iter() { match a.exports.get(k) { Some(a) => { diff --git a/crates/wac-types/src/component.rs b/crates/wac-types/src/component.rs index c26d722..6e2b2ef 100644 --- a/crates/wac-types/src/component.rs +++ b/crates/wac-types/src/component.rs @@ -395,6 +395,21 @@ impl ItemKind { kind => kind, } } + + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + match self { + ItemKind::Type(ty) => ty._visit_defined_types(types, visitor, false), + ItemKind::Func(id) => types[*id]._visit_defined_types(types, visitor), + ItemKind::Instance(id) => types[*id]._visit_defined_types(types, visitor), + ItemKind::Component(id) => types[*id]._visit_defined_types(types, visitor), + ItemKind::Module(_) => Ok(()), + ItemKind::Value(ty) => ty._visit_defined_types(types, visitor, false), + } + } } impl From for wasm_encoder::ComponentExportKind { @@ -441,6 +456,32 @@ impl Type { Type::Module(_) => "module type", } } + + /// Visits each defined type referenced by this type. + /// + /// If the visitor returns `Err`, the visiting stops and the error is returned. + pub fn visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + self._visit_defined_types(types, visitor, true) + } + + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + recurse: bool, + ) -> Result<(), E> { + match self { + Type::Module(_) | Type::Resource(_) => Ok(()), + Type::Func(id) => types[*id]._visit_defined_types(types, visitor), + Type::Value(ty) => ty._visit_defined_types(types, visitor, recurse), + Type::Interface(id) => types[*id]._visit_defined_types(types, visitor), + Type::World(id) => types[*id]._visit_defined_types(types, visitor), + } + } } /// Represents a primitive type. @@ -562,6 +603,25 @@ impl ValueType { } } + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + recurse: bool, + ) -> Result<(), E> { + match self { + ValueType::Primitive(_) | ValueType::Borrow(_) | ValueType::Own(_) => Ok(()), + ValueType::Defined(id) => { + visitor(types, *id)?; + if recurse { + types[*id]._visit_defined_types(types, visitor)?; + } + + Ok(()) + } + } + } + /// Gets a description of the value type. pub fn desc(&self, types: &Types) -> &'static str { match self { @@ -637,6 +697,51 @@ impl DefinedType { } } + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + match self { + DefinedType::Tuple(tys) => { + for ty in tys { + ty._visit_defined_types(types, visitor, false)?; + } + + Ok(()) + } + DefinedType::List(ty) | DefinedType::Option(ty) => { + ty._visit_defined_types(types, visitor, false) + } + DefinedType::Result { ok, err } => { + if let Some(ty) = ok.as_ref() { + ty._visit_defined_types(types, visitor, false)?; + } + + if let Some(ty) = err.as_ref() { + ty._visit_defined_types(types, visitor, false)?; + } + + Ok(()) + } + DefinedType::Variant(v) => { + for ty in v.cases.values().filter_map(Option::as_ref) { + ty._visit_defined_types(types, visitor, false)?; + } + + Ok(()) + } + DefinedType::Record(r) => { + for (_, ty) in &r.fields { + ty._visit_defined_types(types, visitor, false)? + } + + Ok(()) + } + DefinedType::Flags(_) | DefinedType::Enum(_) | DefinedType::Alias(_) => Ok(()), + } + } + /// Gets a description of the defined type. pub fn desc(&self, types: &Types) -> &'static str { match self { @@ -684,9 +789,10 @@ impl fmt::Display for FuncKind { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ResourceAlias { - /// The owning interface for the resource. + /// The foreign owning interface for the resource. /// - /// This may be `None` if the resource is owned by a world. + /// This may be `None` if the resource does not have a foreign interface owner + /// such as in a world or when aliasing within the same interface. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub owner: Option, /// The id of the resource that was aliased. @@ -746,6 +852,24 @@ pub struct FuncType { pub results: Option, } +impl FuncType { + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + for ty in self.params.values() { + ty._visit_defined_types(types, visitor, false)?; + } + + if let Some(results) = self.results.as_ref() { + results._visit_defined_types(types, visitor)?; + } + + Ok(()) + } +} + /// Represents a function result. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -757,6 +881,25 @@ pub enum FuncResult { List(IndexMap), } +impl FuncResult { + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + match self { + FuncResult::Scalar(ty) => ty._visit_defined_types(types, visitor, false), + FuncResult::List(tys) => { + for ty in tys.values() { + ty._visit_defined_types(types, visitor, false)?; + } + + Ok(()) + } + } + } +} + /// Represents a used type. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -789,6 +932,20 @@ pub struct Interface { pub exports: IndexMap, } +impl Interface { + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + for kind in self.exports.values() { + kind._visit_defined_types(types, visitor)?; + } + + Ok(()) + } +} + /// Represents a world. #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -810,6 +967,24 @@ pub struct World { pub exports: IndexMap, } +impl World { + fn _visit_defined_types<'a, E>( + &self, + types: &'a Types, + visitor: &mut impl FnMut(&'a Types, DefinedTypeId) -> Result<(), E>, + ) -> Result<(), E> { + for kind in self.imports.values() { + kind._visit_defined_types(types, visitor)?; + } + + for kind in self.exports.values() { + kind._visit_defined_types(types, visitor)?; + } + + Ok(()) + } +} + /// Represents a kind of an extern item. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum ExternKind { diff --git a/crates/wac-types/src/package.rs b/crates/wac-types/src/package.rs index 8811fea..e8bb601 100644 --- a/crates/wac-types/src/package.rs +++ b/crates/wac-types/src/package.rs @@ -8,7 +8,7 @@ use indexmap::IndexMap; use semver::Version; use std::borrow::Borrow; use std::fmt; -use std::{collections::HashMap, path::Path, rc::Rc, sync::Arc}; +use std::{collections::HashMap, path::Path, rc::Rc}; use wasmparser::{ names::{ComponentName, ComponentNameKind}, types::{self as wasm, ComponentAnyTypeId}, @@ -32,6 +32,16 @@ impl PackageKey { version: package.version.clone(), } } + + /// Gets the name of the package. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the version of the package. + pub fn version(&self) -> Option<&Version> { + self.version.as_ref() + } } impl fmt::Display for PackageKey { @@ -48,8 +58,10 @@ impl fmt::Display for PackageKey { /// A borrowed package key. #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub struct BorrowedPackageKey<'a> { - name: &'a str, - version: Option<&'a Version>, + /// The package name. + pub name: &'a str, + /// The optional package version. + pub version: Option<&'a Version>, } impl<'a> BorrowedPackageKey<'a> { @@ -142,9 +154,7 @@ pub struct Package { /// /// The bytes are a binary-encoded WebAssembly component. #[cfg_attr(feature = "serde", serde(skip))] - bytes: Arc>, - /// The types of the package. - types: Types, + bytes: Vec, /// The type of the represented component. ty: WorldId, /// The type resulting from instantiating the component. @@ -154,23 +164,34 @@ pub struct Package { } impl Package { + /// Gets the package key for the package. + pub fn key(&self) -> BorrowedPackageKey { + BorrowedPackageKey::new(self) + } + /// Creates a new package from the given file path. + /// + /// The package will populate its types into the provided type collection. pub fn from_file( name: &str, version: Option<&Version>, path: impl AsRef, + types: &mut Types, ) -> Result { let path = path.as_ref(); let bytes = std::fs::read(path) .with_context(|| format!("failed to read `{path}`", path = path.display()))?; - Self::from_bytes(name, version, Arc::new(bytes)) + Self::from_bytes(name, version, bytes, types) } /// Creates a new package from the given bytes. + /// + /// The package will populate its types into the provided type collection. pub fn from_bytes( name: &str, version: Option<&Version>, - bytes: impl Into>>, + bytes: impl Into>, + types: &mut Types, ) -> Result { let bytes = bytes.into(); if !Parser::is_component(&bytes) { @@ -183,11 +204,10 @@ impl Package { component_model: true, ..Default::default() }); - let mut types = Types::default(); let mut imports = Vec::new(); let mut exports = Vec::new(); - let mut cur = bytes.as_ref().as_ref(); + let mut cur = bytes.as_ref(); loop { match parser.parse(cur, true)? { Chunk::Parsed { payload, consumed } => { @@ -227,7 +247,7 @@ impl Package { ValidPayload::End(wasm_types) => match parsers.pop() { Some(parent) => parser = parent, None => { - let mut converter = TypeConverter::new(&mut types, wasm_types); + let mut converter = TypeConverter::new(types, wasm_types); let imports = imports .into_iter() @@ -252,13 +272,12 @@ impl Package { exports, }); - let definitions = Self::find_definitions(&types, ty); + let definitions = Self::find_definitions(types, ty); return Ok(Self { name: name.to_owned(), version: version.map(ToOwned::to_owned), bytes, - types, ty, instance_type, definitions, @@ -285,15 +304,10 @@ impl Package { /// Gets the bytes of the package. /// /// The bytes are a binary-encoded WebAssembly component. - pub fn bytes(&self) -> &Arc> { + pub fn bytes(&self) -> &[u8] { &self.bytes } - /// Gets the types in the package. - pub fn types(&self) -> &Types { - &self.types - } - /// Gets the id of the world (i.e. component type) of the package. pub fn ty(&self) -> WorldId { self.ty @@ -310,11 +324,6 @@ impl Package { &self.definitions } - /// Gets an export of the given name from the package's component type. - pub fn export(&self, name: &str) -> Option { - self.types[self.ty].exports.get(name).copied() - } - fn find_definitions(types: &Types, world: WorldId) -> IndexMap { // Look for any component type exports that export a component type or instance type let exports = &types[world].exports; @@ -542,6 +551,17 @@ impl<'a> TypeConverter<'a> { } = ty { self.use_or_own(Owner::Interface(id), name, *referenced, *created); + + // Prevent self-referential ownership of any aliased resources in this interface + if let ItemKind::Type(Type::Resource(res)) = export { + if let Some(ResourceAlias { owner, .. }) = &mut self.types[res].alias { + if let Some(owner_id) = owner { + if *owner_id == id { + *owner = None; + } + } + } + } } let prev = self.types[id].exports.insert(name.clone(), export); diff --git a/src/commands/encode.rs b/src/commands/encode.rs index e66b742..8a157a5 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -6,8 +6,8 @@ use std::{ io::{IsTerminal, Write}, path::PathBuf, }; -use wac_parser::{ast::Document, Composition, EncodingOptions}; -use wasmparser::{Validator, WasmFeatures}; +use wac_graph::EncodeOptions; +use wac_parser::Document; use wasmprinter::print_bytes; fn parse(s: &str) -> Result<(T, U)> @@ -25,7 +25,7 @@ where )) } -/// Encodes a composition into a WebAssembly component. +/// Encodes a WAC source file into a WebAssembly component. #[derive(Args)] #[clap(disable_version_flag = true)] pub struct EncodeCommand { @@ -64,7 +64,7 @@ pub struct EncodeCommand { #[clap(long, value_name = "URL")] pub registry: Option, - /// The path to the composition file. + /// The path to the source WAC file. #[clap(value_name = "PATH")] pub path: PathBuf, } @@ -91,25 +91,19 @@ impl EncodeCommand { .await .map_err(|e| fmt_err(e, &self.path, &contents))?; - let resolved = Composition::from_ast(&document, packages) + let resolution = document + .resolve(packages) .map_err(|e| fmt_err(e, &self.path, &contents))?; if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() { bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead"); } - let mut bytes = resolved.encode(EncodingOptions { - define_packages: !self.import_dependencies, + let mut bytes = resolution.encode(EncodeOptions { + define_components: !self.import_dependencies, + validate: !self.no_validate, + ..Default::default() })?; - if !self.no_validate { - Validator::new_with_features(WasmFeatures { - component_model: true, - ..Default::default() - }) - .validate_all(&bytes) - .context("failed to validate the encoded composition")?; - } - if self.wat { bytes = print_bytes(&bytes) .context("failed to convert binary wasm output to text")? diff --git a/src/commands/parse.rs b/src/commands/parse.rs index fa8690a..f68ad10 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -2,13 +2,13 @@ use crate::fmt_err; use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; -use wac_parser::ast::Document; +use wac_parser::Document; -/// Parses a composition into a JSON AST representation. +/// Parses a WAC source file into a JSON AST representation. #[derive(Args)] #[clap(disable_version_flag = true)] pub struct ParseCommand { - /// The path to the composition file. + /// The path to the source WAC file. #[clap(value_name = "PATH")] pub path: PathBuf, } diff --git a/src/commands/resolve.rs b/src/commands/resolve.rs index ec19fdb..92b9e13 100644 --- a/src/commands/resolve.rs +++ b/src/commands/resolve.rs @@ -2,7 +2,7 @@ use crate::{fmt_err, PackageResolver}; use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; -use wac_parser::{ast::Document, Composition}; +use wac_parser::Document; fn parse(s: &str) -> Result<(T, U)> where @@ -19,7 +19,7 @@ where )) } -/// Resolves a composition into a JSON representation. +/// Resolves a WAC source file into a DOT representation. #[derive(Args)] #[clap(disable_version_flag = true)] pub struct ResolveCommand { @@ -36,7 +36,7 @@ pub struct ResolveCommand { #[clap(long, value_name = "URL")] pub registry: Option, - /// The path to the composition file. + /// The path to the source WAC file. #[clap(value_name = "PATH")] pub path: PathBuf, } @@ -63,11 +63,11 @@ impl ResolveCommand { .await .map_err(|e| fmt_err(e, &self.path, &contents))?; - let resolved = Composition::from_ast(&document, packages) + let resolution = document + .resolve(packages) .map_err(|e| fmt_err(e, &self.path, &contents))?; - serde_json::to_writer_pretty(std::io::stdout(), &resolved)?; - println!(); + print!("{:?}", resolution.into_graph()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index f4daa49..e94271c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,10 @@ use std::{ collections::HashMap, io::IsTerminal, path::{Path, PathBuf}, - sync::Arc, }; -use wac_parser::{ast::Document, PackageKey}; +use wac_parser::Document; use wac_resolver::{packages, Error, FileSystemPackageResolver}; +use wac_types::BorrowedPackageKey; pub mod commands; @@ -71,7 +71,7 @@ impl PackageResolver { pub async fn resolve<'a>( &self, document: &'a Document<'a>, - ) -> Result, Arc>>, Error> { + ) -> Result, Vec>, Error> { let mut keys = packages(document)?; // Next, we resolve as many of the packages from the file system as possible