From ff6cf2bf9c200597fe2185494df57436b311f49e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 3 Apr 2024 00:28:34 -0700 Subject: [PATCH 01/15] Reimplement `wac-parser` on `wac-graph`. This commit is a complete reimplementation of `wac-parser`'s resolution to be based on `wac-graph`. It also contains an overhaul of the `wac-graph` API to facilitate its use from `wac-parser`: * Reimplements `CompositionGraph` on top of `petgraph::StableDiGraph`, which eliminates needing to keep track of nodes internally to `CompositionGraph`. * Packages now populate their type information into the graph's types collection rather than having their own collections. * Splits the `Error` enum into discrete error types for each graph operation; this allows the AST resolver to properly attach span information to errors. * Add type dependency edges for defined types, ensuring topological ordering. * Fix encoding of defined type aliases so that each encoded alias refers to the export index of the previously encoded type. * Added a debug formatter for `CompositionGraph` that outputs a DOT representation (used in resolution tests as well). * Graph encoding now includes an optional producers custom section. * Fixed toposort to be in ascending node index order for independent nodes. The subtype checker implementation was reverted to the previous implementation, where `invert` and `revert` are called at specific points to change the type of check being performed; the newer implementation did not give the correct "expected, found" error messages for AST resolution. `wac resolve` now outputs a DOT representation of the resolved composition graph instead of JSON. Most of the deleted code comes from code that has already moved into `wac-types` and `wac-graph`. Closes #79. Fixes #78. Fixes #76. --- Cargo.lock | 5 +- Cargo.toml | 3 +- crates/wac-graph/Cargo.toml | 1 + crates/wac-graph/src/encoding.rs | 7 +- crates/wac-graph/src/graph.rs | 1573 +++++------ crates/wac-graph/src/lib.rs | 14 +- crates/wac-graph/tests/encoding.rs | 59 +- .../argument-already-satisfied/graph.json | 6 +- .../graphs/argument-type-mismatch/graph.json | 3 +- .../implicit-import-conflict/graph.json | 3 +- .../graphs/import-already-exists/graph.json | 6 +- .../graphs/invalid-import-name/graph.json | 5 +- .../graphs/invalid-package-version/error.txt | 5 - .../graphs/invalid-package-version/graph.json | 9 - .../graphs/merged-func-results/encoded.wat | 32 +- .../tests/graphs/not-an-instance/graph.json | 3 +- .../tests/graphs/not-instantiation/graph.json | 6 +- .../graphs/package-missing-export/error.txt | 4 - .../graphs/package-missing-export/foo.wat | 1 - .../graphs/package-missing-export/graph.json | 15 - .../wac-graph/tests/graphs/simple/graph.json | 3 +- .../graphs/type-aggregation-error/error.txt | 2 +- .../tests/graphs/unknown-package/error.txt | 4 - .../tests/graphs/unknown-package/graph.json | 9 - .../graphs/unqualified-package-path/error.txt | 4 - .../unqualified-package-path/graph.json | 9 - crates/wac-parser/src/ast.rs | 17 +- crates/wac-parser/src/lexer.rs | 2 +- crates/wac-parser/src/lib.rs | 6 +- crates/wac-parser/src/resolution.rs | 2441 +++++++++++++++-- crates/wac-parser/src/resolution/ast.rs | 2335 ---------------- crates/wac-parser/src/resolution/encoding.rs | 1163 -------- crates/wac-parser/src/resolution/package.rs | 700 ----- crates/wac-parser/src/resolution/types.rs | 1494 ---------- crates/wac-parser/tests/encoding.rs | 99 +- .../fail/arg-merge-failure.wac | 0 .../fail/arg-merge-failure.wac.result | 5 +- .../fail/arg-merge-failure/bar/baz.wat | 0 .../fail/arg-merge-failure/foo/bar.wat | 0 .../fail/implicit-arg-conflict.wac | 0 .../fail/implicit-arg-conflict.wac.result | 4 +- .../fail/implicit-arg-conflict/foo/bar.wat | 0 .../fail/import-conflict.wac | 0 .../encoding/fail/import-conflict.wac.result | 13 + .../fail/import-conflict/foo/bar.wat | 0 .../fail/unmergeable-args.wac | 0 .../fail/unmergeable-args.wac.result | 5 +- .../fail/unmergeable-args/bar/baz.wat | 0 .../fail/unmergeable-args/foo/bar.wat | 0 ...on.wat.result => instantiation.wac.result} | 56 +- ...wat.result => merged-functions.wac.result} | 0 ...ources.wat.result => resources.wac.result} | 0 crates/wac-parser/tests/encoding/types.wac | 19 + .../{types.wat.result => types.wac.result} | 17 + crates/wac-parser/tests/parser.rs | 2 +- crates/wac-parser/tests/resolution.rs | 10 +- .../tests/resolution/alias.wac.result | 71 +- .../duplicate-world-item.wac.result | 56 +- .../fail/import-conflict.wac.result | 13 - .../fail/package-not-component.wac.result | 2 +- .../fail/package-not-wasm.wac.result | 2 +- .../resolution/fail/redefined-name.wac.result | 6 +- .../resolution/fail/type-decl-conflict.wac | 8 + .../fail/type-decl-conflict.wac.result | 14 + .../tests/resolution/import.wac.result | 156 +- .../resolution/let-statements.wac.result | 473 +--- .../tests/resolution/no-imports.wac.result | 42 +- .../resolution/package-import.wac.result | 111 +- .../resolution/package-use-item.wac.result | 258 +- .../package-world-include.wac.result | 244 +- .../resolution/package-world-item.wac.result | 162 +- .../tests/resolution/resource.wac.result | 80 +- .../resolution/targets-empty-world.wac.result | 39 +- .../tests/resolution/targets-world.wac.result | 119 +- .../tests/resolution/types.wac.result | 504 +--- crates/wac-resolver/Cargo.toml | 1 + crates/wac-resolver/src/fs.rs | 30 +- crates/wac-resolver/src/lib.rs | 13 +- crates/wac-resolver/src/registry.rs | 32 +- crates/wac-resolver/src/visitor.rs | 2 +- crates/wac-resolver/tests/registry.rs | 10 +- crates/wac-types/src/aggregator.rs | 94 +- crates/wac-types/src/checker.rs | 66 +- crates/wac-types/src/component.rs | 174 ++ crates/wac-types/src/package.rs | 57 +- src/commands/encode.rs | 26 +- src/commands/parse.rs | 6 +- src/commands/resolve.rs | 12 +- src/lib.rs | 6 +- 89 files changed, 3678 insertions(+), 9390 deletions(-) delete mode 100644 crates/wac-graph/tests/graphs/invalid-package-version/error.txt delete mode 100644 crates/wac-graph/tests/graphs/invalid-package-version/graph.json delete mode 100644 crates/wac-graph/tests/graphs/package-missing-export/error.txt delete mode 100644 crates/wac-graph/tests/graphs/package-missing-export/foo.wat delete mode 100644 crates/wac-graph/tests/graphs/package-missing-export/graph.json delete mode 100644 crates/wac-graph/tests/graphs/unknown-package/error.txt delete mode 100644 crates/wac-graph/tests/graphs/unknown-package/graph.json delete mode 100644 crates/wac-graph/tests/graphs/unqualified-package-path/error.txt delete mode 100644 crates/wac-graph/tests/graphs/unqualified-package-path/graph.json delete mode 100644 crates/wac-parser/src/resolution/ast.rs delete mode 100644 crates/wac-parser/src/resolution/encoding.rs delete mode 100644 crates/wac-parser/src/resolution/package.rs delete mode 100644 crates/wac-parser/src/resolution/types.rs rename crates/wac-parser/tests/{resolution => encoding}/fail/arg-merge-failure.wac (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/arg-merge-failure.wac.result (61%) rename crates/wac-parser/tests/{resolution => encoding}/fail/arg-merge-failure/bar/baz.wat (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/arg-merge-failure/foo/bar.wat (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/implicit-arg-conflict.wac (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/implicit-arg-conflict.wac.result (64%) rename crates/wac-parser/tests/{resolution => encoding}/fail/implicit-arg-conflict/foo/bar.wat (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/import-conflict.wac (100%) create mode 100644 crates/wac-parser/tests/encoding/fail/import-conflict.wac.result rename crates/wac-parser/tests/{resolution => encoding}/fail/import-conflict/foo/bar.wat (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/unmergeable-args.wac (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/unmergeable-args.wac.result (62%) rename crates/wac-parser/tests/{resolution => encoding}/fail/unmergeable-args/bar/baz.wat (100%) rename crates/wac-parser/tests/{resolution => encoding}/fail/unmergeable-args/foo/bar.wat (100%) rename crates/wac-parser/tests/encoding/{instantiation.wat.result => instantiation.wac.result} (71%) rename crates/wac-parser/tests/encoding/{merged-functions.wat.result => merged-functions.wac.result} (100%) rename crates/wac-parser/tests/encoding/{resources.wat.result => resources.wac.result} (100%) rename crates/wac-parser/tests/encoding/{types.wat.result => types.wac.result} (90%) delete mode 100644 crates/wac-parser/tests/resolution/fail/import-conflict.wac.result create mode 100644 crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac create mode 100644 crates/wac-parser/tests/resolution/fail/type-decl-conflict.wac.result 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..5ae2253 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 a valid extern name")] + 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 a valid extern name")] + 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("export name `{name}` already exists in the graph")] + 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 a valid extern name")] + 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("the node cannot be unexported as it is 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,22 +181,24 @@ pub enum Error { /// The name of the argument. name: String, }, - /// 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, - }, +} + +/// 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("the encoding of the graph failed validation")] - EncodingValidationFailure { + ValidationFailure { /// 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, + /// 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, + }, /// 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")] ImplicitImportConflict { @@ -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,31 +285,29 @@ 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, + pub kind: NodeKind, /// The package associated with the node, if any. - package: Option, + pub package: Option, /// The item kind of the node. - item_kind: ItemKind, + pub item_kind: ItemKind, /// The optional name to associate with the node. /// /// When the graph is encoded, node names are recorded in a `names` custom section. - name: Option, + pub name: Option, /// The name to use for exporting the node. - export: Option, - /// The aliased nodes from this node. - aliases: HashMap, + pub export: Option, } -impl NodeData { +impl Node { fn new(kind: NodeKind, item_kind: ItemKind, package: Option) -> Self { Self { kind, @@ -306,7 +315,6 @@ impl NodeData { package, name: None, export: None, - aliases: Default::default(), } } @@ -345,30 +353,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 +362,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 +409,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 +424,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 ids. + 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 +461,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 +509,40 @@ 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()); - 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 +557,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 +603,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() + ); - let id = self.alloc_node(data); - self.graph.add_node(id); + // 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(()) + })?; - let prev = self.exports.insert(name, id); + // 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); + } + + Ok(()) + })?; + } + + 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 +730,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 +758,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 +784,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 +843,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 +908,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 +957,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); } - self.free_node(node, false); - true + // 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()); + } + + // 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 +1029,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 +1097,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 +1138,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 +1198,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 +1207,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 +1236,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 +1328,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 +1351,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 +1470,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 +1529,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 +1586,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 +1638,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 +1652,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 +1668,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 +1704,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 +1732,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 +1758,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 +1770,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 +1810,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 +1832,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 +1847,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/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-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..160a70b 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,2139 @@ 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, -} + Ok(()) + } -/// 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), -} + /// 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, + }) + } -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 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) + } + + fn push_scope(&mut self) { + log::debug!("pushing new name scope"); + self.scopes.push(std::mem::take(&mut self.current)); } -} -/// 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 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)) + } + + 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))) } - /// Encode the composition into a WebAssembly component. - pub fn encode(&self, options: EncodingOptions) -> anyhow::Result> { - Encoder::new(self, options).encode() + 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/instantiation.wat.result b/crates/wac-parser/tests/encoding/instantiation.wac.result similarity index 71% rename from crates/wac-parser/tests/encoding/instantiation.wat.result rename to crates/wac-parser/tests/encoding/instantiation.wac.result index 0314cb9..9846333 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 (;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 (;2;) (type 3))) + (type (;4;) (func)) + (import "f" (func (;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)) - ) - ) + (import "unlocked-dep=" (component (;0;) (type 5))) (instance (;3;) (instantiate 0 (with "baz" (func 0)) ) ) (instance (;4;) (instantiate 0 - (with "baz" (func 0)) + (with "baz" (func 1)) ) ) - (alias export 1 "baz" (func (;2;))) (instance (;5;) (instantiate 0 - (with "baz" (func 2)) + (with "baz" (func 1)) ) ) - (type (;5;) - (instance - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) + (alias export 2 "baz" (func (;2;))) + (instance (;6;) (instantiate 0 + (with "baz" (func 2)) ) ) - (import "foo" (instance (;6;) (type 5))) (type (;6;) (component (type (;0;) @@ -62,26 +62,26 @@ ) (import "unlocked-dep=" (component (;1;) (type 6))) (instance (;7;) (instantiate 1 - (with "foo" (instance 6)) + (with "foo" (instance 0)) ) ) (instance (;8;) (instantiate 1 - (with "foo" (instance 0)) + (with "foo" (instance 1)) ) ) (instance (;9;) (instantiate 1 - (with "foo" (instance 2)) + (with "foo" (instance 3)) ) ) (instance (;10;) (instantiate 1 - (with "foo" (instance 3)) + (with "foo" (instance 4)) ) ) (instance (;11;) (instantiate 1 - (with "foo" (instance 4)) + (with "foo" (instance 5)) ) ) - (alias export 5 "foo" (func (;3;))) + (alias export 6 "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 100% rename from crates/wac-parser/tests/encoding/merged-functions.wat.result rename to crates/wac-parser/tests/encoding/merged-functions.wac.result diff --git a/crates/wac-parser/tests/encoding/resources.wat.result b/crates/wac-parser/tests/encoding/resources.wac.result similarity index 100% rename from crates/wac-parser/tests/encoding/resources.wat.result rename to crates/wac-parser/tests/encoding/resources.wac.result 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 90% rename from crates/wac-parser/tests/encoding/types.wat.result rename to crates/wac-parser/tests/encoding/types.wac.result index 177a521..c735e36 100644 --- a/crates/wac-parser/tests/encoding/types.wat.result +++ b/crates/wac-parser/tests/encoding/types.wac.result @@ -153,6 +153,23 @@ ) ) (export (;43;) "t" (type 42)) + (export (;44;) "a2" (type 1)) + (export (;45;) "b2" (type 3)) + (export (;46;) "c2" (type 5)) + (export (;47;) "d2" (type 7)) + (export (;48;) "e2" (type 9)) + (export (;49;) "f2" (type 11)) + (export (;50;) "g2" (type 13)) + (export (;51;) "h2" (type 15)) + (export (;52;) "i2" (type 17)) + (export (;53;) "j2" (type 19)) + (export (;54;) "k2" (type 21)) + (export (;55;) "l2" (type 23)) + (export (;56;) "n2" (type 30)) + (export (;57;) "m2" (type 25)) + (export (;58;) "o2" (type 33)) + (export (;59;) "p2" (type 35)) + (export (;60;) "q2" (type 37)) (@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..f2e3be2 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)?, 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..a26310b 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 { @@ -746,6 +851,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 +880,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 +931,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 +966,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..f0bafd3 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; diff --git a/src/commands/encode.rs b/src/commands/encode.rs index e66b742..547f31e 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 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..f0c87fb 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 file. #[clap(value_name = "PATH")] pub path: PathBuf, } diff --git a/src/commands/resolve.rs b/src/commands/resolve.rs index ec19fdb..aca31a0 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 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 From 0970c6dc809316fddcbb05d07d03092f810d24a5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 14 Apr 2024 10:56:49 -0700 Subject: [PATCH 02/15] Add names section to encoded WAC documents. Implements #37. --- crates/wac-parser/src/resolution.rs | 8 ++ .../tests/encoding/instantiation.wac.result | 40 +++++----- .../encoding/merged-functions.wac.result | 4 +- .../tests/encoding/resources.wac.result | 8 +- .../tests/encoding/types.wac.result | 78 +++++++++---------- crates/wac-resolver/tests/registry.rs | 16 ++-- 6 files changed, 81 insertions(+), 73 deletions(-) diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index 160a70b..fb03f4d 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -785,6 +785,14 @@ impl State { }); } + 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()); + } + } + Ok(()) } diff --git a/crates/wac-parser/tests/encoding/instantiation.wac.result b/crates/wac-parser/tests/encoding/instantiation.wac.result index 9846333..fd8fac6 100644 --- a/crates/wac-parser/tests/encoding/instantiation.wac.result +++ b/crates/wac-parser/tests/encoding/instantiation.wac.result @@ -14,16 +14,16 @@ (export (;0;) "foo" (func (type 0))) ) ) - (import "i" (instance (;1;) (type 2))) + (import "i" (instance $i (;1;) (type 2))) (type (;3;) (instance (type (;0;) (func)) (export (;0;) "baz" (func (type 0))) ) ) - (import "i2" (instance (;2;) (type 3))) + (import "i2" (instance $i2 (;2;) (type 3))) (type (;4;) (func)) - (import "f" (func (;1;) (type 4))) + (import "f" (func $f (;1;) (type 4))) (type (;5;) (component (type (;0;) (func)) @@ -32,20 +32,20 @@ ) ) (import "unlocked-dep=" (component (;0;) (type 5))) - (instance (;3;) (instantiate 0 + (instance $x1 (;3;) (instantiate 0 (with "baz" (func 0)) ) ) - (instance (;4;) (instantiate 0 - (with "baz" (func 1)) + (instance $x2 (;4;) (instantiate 0 + (with "baz" (func $f)) ) ) - (instance (;5;) (instantiate 0 - (with "baz" (func 1)) + (instance $x3 (;5;) (instantiate 0 + (with "baz" (func $f)) ) ) - (alias export 2 "baz" (func (;2;))) - (instance (;6;) (instantiate 0 + (alias export $i2 "baz" (func (;2;))) + (instance $x4 (;6;) (instantiate 0 (with "baz" (func 2)) ) ) @@ -61,27 +61,27 @@ ) ) (import "unlocked-dep=" (component (;1;) (type 6))) - (instance (;7;) (instantiate 1 + (instance $y1 (;7;) (instantiate 1 (with "foo" (instance 0)) ) ) - (instance (;8;) (instantiate 1 - (with "foo" (instance 1)) + (instance $y2 (;8;) (instantiate 1 + (with "foo" (instance $i)) ) ) - (instance (;9;) (instantiate 1 - (with "foo" (instance 3)) + (instance $y3 (;9;) (instantiate 1 + (with "foo" (instance $x1)) ) ) - (instance (;10;) (instantiate 1 - (with "foo" (instance 4)) + (instance $y4 (;10;) (instantiate 1 + (with "foo" (instance $x2)) ) ) - (instance (;11;) (instantiate 1 - (with "foo" (instance 5)) + (instance $y5 (;11;) (instantiate 1 + (with "foo" (instance $x3)) ) ) - (alias export 6 "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.wac.result b/crates/wac-parser/tests/encoding/merged-functions.wac.result index a28dd08..5869250 100644 --- a/crates/wac-parser/tests/encoding/merged-functions.wac.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.wac.result b/crates/wac-parser/tests/encoding/resources.wac.result index 8076b7d..0ee8940 100644 --- a/crates/wac-parser/tests/encoding/resources.wac.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.result b/crates/wac-parser/tests/encoding/types.wac.result index c735e36..32edf2e 100644 --- a/crates/wac-parser/tests/encoding/types.wac.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,24 +152,24 @@ (export (;0;) "test:pkg/t" (component (type 0))) ) ) - (export (;43;) "t" (type 42)) - (export (;44;) "a2" (type 1)) - (export (;45;) "b2" (type 3)) - (export (;46;) "c2" (type 5)) - (export (;47;) "d2" (type 7)) - (export (;48;) "e2" (type 9)) - (export (;49;) "f2" (type 11)) - (export (;50;) "g2" (type 13)) - (export (;51;) "h2" (type 15)) - (export (;52;) "i2" (type 17)) - (export (;53;) "j2" (type 19)) - (export (;54;) "k2" (type 21)) - (export (;55;) "l2" (type 23)) - (export (;56;) "n2" (type 30)) - (export (;57;) "m2" (type 25)) - (export (;58;) "o2" (type 33)) - (export (;59;) "p2" (type 35)) - (export (;60;) "q2" (type 37)) + (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-resolver/tests/registry.rs b/crates/wac-resolver/tests/registry.rs index f2e3be2..2b124a3 100644 --- a/crates/wac-resolver/tests/registry.rs +++ b/crates/wac-resolver/tests/registry.rs @@ -79,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;) @@ -100,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 From e9092b909b050e3484eb8b2f3c9733ce89dce1ba Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:38:51 -0700 Subject: [PATCH 03/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 5ae2253..7c0a827 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -45,7 +45,7 @@ pub enum DefineTypeError { name: String, }, /// The specified type name is not a valid extern name. - #[error("type name `{name}` is not a valid extern name")] + #[error("type name `{name}` is not valid")] InvalidExternName { /// The name of the type. name: String, From fd34bfaba56146c7ecd41bfc5aaf480d46723697 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:39:11 -0700 Subject: [PATCH 04/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 7c0a827..6701ed1 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -68,7 +68,7 @@ pub enum ImportError { node: NodeId, }, /// An invalid import name was given. - #[error("import name `{name}` is not a valid extern name")] + #[error("import name `{name}` is not valid")] InvalidImportName { /// The invalid name. name: String, From a6bed3a133488089cfac101cee1a3ba96b777b57 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:39:23 -0700 Subject: [PATCH 05/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 6701ed1..2ca6833 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -125,7 +125,7 @@ pub enum ExportError { node: NodeId, }, /// An invalid export name was given. - #[error("export name `{name}` is not a valid extern name")] + #[error("export name `{name}` is not valid")] InvalidExportName { /// The invalid name. name: String, From 18a314da4b95fb85b9858ad581ddf6885a50907e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:39:32 -0700 Subject: [PATCH 06/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 2ca6833..7f10eaa 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -117,7 +117,7 @@ pub enum AliasError { #[derive(Debug, Error)] pub enum ExportError { /// An export name already exists in the graph. - #[error("export name `{name}` already exists in the graph")] + #[error("an export with the name `{name}` already exists")] ExportAlreadyExists { /// The name of the existing export. name: String, From 182414ca6701e726c9c486c9bbf1997591b7cf2a Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:39:39 -0700 Subject: [PATCH 07/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 7f10eaa..dc527ee 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -140,7 +140,7 @@ pub enum ExportError { #[derive(Debug, Error)] pub enum UnexportError { /// The node cannot be unexported as it is a type definition. - #[error("the node cannot be unexported as it is a type definition")] + #[error("cannot unexport a type definition")] MustExportDefinition, } From 49983920381f55e5e9a794aec9db7cb1fca40433 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:39:50 -0700 Subject: [PATCH 08/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index dc527ee..c99157b 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -187,7 +187,7 @@ pub enum InstantiationArgumentError { #[derive(Debug, Error)] pub enum EncodeError { /// The encoding of the graph failed validation. - #[error("the encoding of the graph failed validation")] + #[error("encoding produced a component that failed validation")] ValidationFailure { /// The source of the validation error. #[source] From 392a45afa92c3424adde514f357bb907447de212 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:40:25 -0700 Subject: [PATCH 09/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index c99157b..25e4ac4 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -200,7 +200,7 @@ pub enum EncodeError { node: NodeId, }, /// 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, From f6263375d4b7b786dcbe8cefa106e851d4fa17ff Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:40:38 -0700 Subject: [PATCH 10/15] Update crates/wac-graph/src/graph.rs Co-authored-by: Ryan Levick --- crates/wac-graph/src/graph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 25e4ac4..5e218ae 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -425,7 +425,7 @@ pub struct NodeInfo<'a> { pub struct CompositionGraph { /// The underlying graph data structure. graph: StableDiGraph, - /// The map of import names to node ids. + /// The map of import names to node indices. imports: HashMap, /// The map of export names to node ids. exports: IndexMap, From e77ae99bddb304931c7f2ba70ead6b9ca5efa328 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 09:44:54 -0700 Subject: [PATCH 11/15] Update test baselines. --- crates/wac-graph/tests/graphs/export-already-exists/error.txt | 2 +- .../wac-graph/tests/graphs/implicit-import-conflict/error.txt | 2 +- crates/wac-graph/tests/graphs/invalid-export-name/error.txt | 2 +- crates/wac-graph/tests/graphs/invalid-import-name/error.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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/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 From 6d44da5fe18d9cd4e7a7c37579b8ea67e825a463 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 12:33:02 -0700 Subject: [PATCH 12/15] Fix infinite recursion caused by aliased resources. This commit fixes an existing bug exposed by the refactoring (likely due to the changes in the order in which nodes are processed for implicit arguments). Previously, an interface might contain a type alias for another resource it owns; this lead to the alias information of the resource being self-referential for the interface containing the aliased resource. It would then lead to infinite recursion in an attempt to remap such an interface when populating implicit instantiation arguments. The fix is to clear the self-referencing information while converting an interface during package parsing. --- .../tests/encoding/include-resource.wac | 3 + .../encoding/include-resource.wac.result | 51 +++++++++++++++++ .../encoding/include-resource/foo/bar.wat | 56 +++++++++++++++++++ crates/wac-types/src/component.rs | 4 +- crates/wac-types/src/package.rs | 11 ++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 crates/wac-parser/tests/encoding/include-resource.wac create mode 100644 crates/wac-parser/tests/encoding/include-resource.wac.result create mode 100644 crates/wac-parser/tests/encoding/include-resource/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-types/src/component.rs b/crates/wac-types/src/component.rs index a26310b..598ef31 100644 --- a/crates/wac-types/src/component.rs +++ b/crates/wac-types/src/component.rs @@ -789,9 +789,9 @@ 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. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub owner: Option, /// The id of the resource that was aliased. diff --git a/crates/wac-types/src/package.rs b/crates/wac-types/src/package.rs index f0bafd3..e8bb601 100644 --- a/crates/wac-types/src/package.rs +++ b/crates/wac-types/src/package.rs @@ -551,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); From a94f2aef255dcff60e6f9bc2b25fe854766958f8 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 12:55:43 -0700 Subject: [PATCH 13/15] Code review feedback. --- crates/wac-graph/src/graph.rs | 53 +++++++++++++++++++++++++---- crates/wac-parser/src/resolution.rs | 14 ++++---- src/commands/encode.rs | 2 +- src/commands/parse.rs | 2 +- src/commands/resolve.rs | 2 +- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/crates/wac-graph/src/graph.rs b/crates/wac-graph/src/graph.rs index 5e218ae..f7d0b60 100644 --- a/crates/wac-graph/src/graph.rs +++ b/crates/wac-graph/src/graph.rs @@ -294,17 +294,17 @@ impl RegisteredPackage { #[derive(Debug, Clone)] pub struct Node { /// The node kind. - pub kind: NodeKind, + kind: NodeKind, /// The package associated with the node, if any. - pub package: Option, + package: Option, /// The item kind of the node. - pub item_kind: ItemKind, + item_kind: ItemKind, /// The optional name to associate with the node. /// /// When the graph is encoded, node names are recorded in a `names` custom section. - pub name: Option, + name: Option, /// The name to use for exporting the node. - pub export: Option, + export: Option, } impl Node { @@ -318,13 +318,49 @@ impl Node { } } - 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) => { @@ -532,7 +568,10 @@ impl CompositionGraph { 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()); + assert!( + prev.is_some(), + "package should exist in the package map (this is a bug)" + ); // Finally free the package *entry = RegisteredPackage::new(entry.generation.wrapping_add(1)); diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index fb03f4d..a537144 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -737,7 +737,7 @@ enum Item { impl Item { fn kind(&self, graph: &CompositionGraph) -> ItemKind { match self { - Self::Node(id) => graph[*id].item_kind, + Self::Node(id) => graph[*id].item_kind(), Self::Use(ty) => ItemKind::Type(*ty), Self::Type(ty) => ItemKind::Type(*ty), } @@ -788,7 +788,7 @@ impl State { 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() { + if self.graph[node].name().is_none() { self.graph.set_node_name(node, id.string.to_owned()); } } @@ -1133,10 +1133,10 @@ impl<'a> AstResolver<'a> { ) -> 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 { + if let NodeKind::Definition = node.kind() { return Err(Error::ExportConflict { name, - kind: node.item_kind.desc(state.graph.types()).to_string(), + kind: node.item_kind().desc(state.graph.types()).to_string(), span, definition: prev_span, help: if !show_hint { @@ -2720,7 +2720,7 @@ impl<'a> AstResolver<'a> { for (name, import) in state .graph .node_ids() - .filter_map(|n| match &state.graph[n].kind { + .filter_map(|n| match &state.graph[n].kind() { NodeKind::Import(name) => Some((name, n)), _ => None, }) @@ -2738,7 +2738,7 @@ impl<'a> AstResolver<'a> { .is_subtype( expected.promote(), state.graph.types(), - state.graph[import].item_kind, + state.graph[import].item_kind(), state.graph.types(), ) .map_err(|e| Error::TargetMismatch { @@ -2767,7 +2767,7 @@ impl<'a> AstResolver<'a> { checker .is_subtype( - state.graph[export].item_kind, + state.graph[export].item_kind(), state.graph.types(), expected.promote(), state.graph.types(), diff --git a/src/commands/encode.rs b/src/commands/encode.rs index 547f31e..8a157a5 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -64,7 +64,7 @@ pub struct EncodeCommand { #[clap(long, value_name = "URL")] pub registry: Option, - /// The path to the source file. + /// The path to the source WAC file. #[clap(value_name = "PATH")] pub path: PathBuf, } diff --git a/src/commands/parse.rs b/src/commands/parse.rs index f0c87fb..f68ad10 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -8,7 +8,7 @@ use wac_parser::Document; #[derive(Args)] #[clap(disable_version_flag = true)] pub struct ParseCommand { - /// The path to the source 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 aca31a0..92b9e13 100644 --- a/src/commands/resolve.rs +++ b/src/commands/resolve.rs @@ -36,7 +36,7 @@ pub struct ResolveCommand { #[clap(long, value_name = "URL")] pub registry: Option, - /// The path to the source file. + /// The path to the source WAC file. #[clap(value_name = "PATH")] pub path: PathBuf, } From b7d5e89072cccd0e0010e0998c89d07c42ffa8fe Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 13:00:36 -0700 Subject: [PATCH 14/15] Update crates/wac-types/src/component.rs Co-authored-by: Ryan Levick --- crates/wac-types/src/component.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wac-types/src/component.rs b/crates/wac-types/src/component.rs index 598ef31..37c7f33 100644 --- a/crates/wac-types/src/component.rs +++ b/crates/wac-types/src/component.rs @@ -791,7 +791,7 @@ impl fmt::Display for FuncKind { pub struct ResourceAlias { /// The foreign owning interface for the resource. /// - /// This may be `None` if the resource does not have a foreign interface owner. + /// 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. From a38e62982111dadf42fb409a9e687ac34a9eaed9 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 15 Apr 2024 13:01:54 -0700 Subject: [PATCH 15/15] Fix formatting. --- crates/wac-types/src/component.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wac-types/src/component.rs b/crates/wac-types/src/component.rs index 37c7f33..6e2b2ef 100644 --- a/crates/wac-types/src/component.rs +++ b/crates/wac-types/src/component.rs @@ -791,7 +791,8 @@ impl fmt::Display for FuncKind { pub struct ResourceAlias { /// The foreign owning interface for the resource. /// - /// 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. + /// 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.