diff --git a/Cargo.lock b/Cargo.lock index eb7113a..ebd44d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -753,6 +753,9 @@ dependencies = [ "serde_json", "tokio", "wac-parser", + "wasmparser", + "wasmprinter", + "wat", ] [[package]] @@ -772,7 +775,10 @@ dependencies = [ "serde", "serde_json", "thiserror", + "wasm-encoder", + "wasm-metadata", "wasmparser", + "wasmprinter", "wat", "wit-component", "wit-parser", @@ -813,6 +819,16 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmprinter" +version = "0.2.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aff4df0cdf1906ec040e97d78c3fc8fd26d3f8d70adaac81f07f80957b63b54" +dependencies = [ + "anyhow", + "wasmparser", +] + [[package]] name = "wast" version = "67.0.1" diff --git a/Cargo.toml b/Cargo.toml index 7944e8e..4b36e9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ keywords = ["webassembly", "wasm", "components", "component-model"] repository = "https://github.com/bytecodealliance/wac" [dependencies] -wac-parser = { workspace = true } +wac-parser = { workspace = true, default-features = false} anyhow = { workspace = true } clap = { workspace = true } pretty_env_logger = { workspace = true } @@ -28,6 +28,9 @@ tokio = { workspace = true } owo-colors = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +wat = { workspace = true } +wasmparser = { workspace = true } +wasmprinter = { workspace = true } [features] default = [] @@ -38,6 +41,9 @@ wac-parser = { path = "crates/wac-parser", default-features = false } wit-parser = "0.13.0" wasmparser = "0.116.1" wit-component = "0.18.0" +wasm-encoder = "0.36.2" +wasmprinter = "0.2.72" +wasm-metadata = "0.10.11" anyhow = "1.0.75" clap = { version = "4.4.7", features = ["derive"] } semver = { version = "1.0.20", features = ["serde"] } diff --git a/README.md b/README.md index c7f70e8..97fb772 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ item-type-decl ::= resource-decl | type-decl resource-decl ::= 'resource' id '{' resource-item* '}' resource-item ::= constructor | method constructor ::= 'constructor' param-list -method ::= id ':' 'static'? func-type-ref +method ::= id ':' 'static'? func-type variant-decl ::= 'variant' id '{' variant-cases '}' variant-cases ::= variant-case (',' variant-case)* ','? variant-case ::= id ('(' type ')')? diff --git a/crates/wac-parser/Cargo.toml b/crates/wac-parser/Cargo.toml index 21784a7..1ed1e35 100644 --- a/crates/wac-parser/Cargo.toml +++ b/crates/wac-parser/Cargo.toml @@ -21,6 +21,8 @@ serde = { workspace = true } wasmparser = { workspace = true } wit-parser = { workspace = true } wit-component = { workspace = true } +wasm-encoder = { workspace = true } +wasm-metadata = { workspace = true } wat = { workspace = true, optional = true } owo-colors = { workspace = true } @@ -32,6 +34,7 @@ pretty_assertions = "1.4.0" pretty_env_logger = { workspace = true } rayon = "1.8.0" serde_json = { workspace = true } +wasmprinter = { workspace = true } [[test]] name = "parser" @@ -40,3 +43,7 @@ harness = false [[test]] name = "resolution" harness = false + +[[test]] +name = "encoding" +harness = false diff --git a/crates/wac-parser/src/ast.rs b/crates/wac-parser/src/ast.rs index afdfc36..ebdc585 100644 --- a/crates/wac-parser/src/ast.rs +++ b/crates/wac-parser/src/ast.rs @@ -11,11 +11,13 @@ mod export; mod expr; mod import; mod r#let; +mod printer; mod r#type; pub use export::*; pub use expr::*; pub use import::*; +pub use printer::*; pub use r#let::*; pub use r#type::*; @@ -43,7 +45,7 @@ impl fmt::Display for Expected<'_> { write!(f, ", ")?; } - if i == self.expected.len() - 1 && self.count <= self.expected.len() { + if i == self.count - 1 { write!(f, "or ")?; } @@ -306,7 +308,7 @@ macro_rules! display { ($name:ident, $method:ident) => { impl std::fmt::Display for $name<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut printer = crate::printer::DocumentPrinter::new(f, None); + let mut printer = crate::ast::DocumentPrinter::new(f, None); printer.$method(self) } } diff --git a/crates/wac-parser/src/ast/import.rs b/crates/wac-parser/src/ast/import.rs index eb95b74..4761183 100644 --- a/crates/wac-parser/src/ast/import.rs +++ b/crates/wac-parser/src/ast/import.rs @@ -98,7 +98,7 @@ impl<'a> PackagePath<'a> { /// Iterates over the segments of the package path. pub fn segment_spans<'b>(&'b self) -> impl Iterator)> + 'b { self.segments.split('/').map(|s| { - let start = s.as_ptr() as usize - self.name.as_ptr() as usize; + let start = self.span.start + s.as_ptr() as usize - self.name.as_ptr() as usize; let end = start + s.len(); (s, Span::from_span(self.span.source(), &(start..end))) }) diff --git a/crates/wac-parser/src/printer.rs b/crates/wac-parser/src/ast/printer.rs similarity index 95% rename from crates/wac-parser/src/printer.rs rename to crates/wac-parser/src/ast/printer.rs index 7ec7cc0..1996ea9 100644 --- a/crates/wac-parser/src/printer.rs +++ b/crates/wac-parser/src/ast/printer.rs @@ -140,20 +140,20 @@ impl DocumentPrinter { /// Prints the given type. pub fn ty(&mut self, ty: &Type) -> std::fmt::Result { match ty { - Type::U8 => write!(self.writer, "u8"), - Type::S8 => write!(self.writer, "s8"), - Type::U16 => write!(self.writer, "u16"), - Type::S16 => write!(self.writer, "s16"), - Type::U32 => write!(self.writer, "u32"), - Type::S32 => write!(self.writer, "s32"), - Type::U64 => write!(self.writer, "u64"), - Type::S64 => write!(self.writer, "s64"), - Type::Float32 => write!(self.writer, "float32"), - Type::Float64 => write!(self.writer, "float64"), - Type::Char => write!(self.writer, "char"), - Type::Bool => write!(self.writer, "bool"), - Type::String => write!(self.writer, "string"), - Type::Tuple(types) => { + Type::U8(_) => write!(self.writer, "u8"), + Type::S8(_) => write!(self.writer, "s8"), + Type::U16(_) => write!(self.writer, "u16"), + Type::S16(_) => write!(self.writer, "s16"), + Type::U32(_) => write!(self.writer, "u32"), + Type::S32(_) => write!(self.writer, "s32"), + Type::U64(_) => write!(self.writer, "u64"), + Type::S64(_) => write!(self.writer, "s64"), + Type::Float32(_) => write!(self.writer, "float32"), + Type::Float64(_) => write!(self.writer, "float64"), + Type::Char(_) => write!(self.writer, "char"), + Type::Bool(_) => write!(self.writer, "bool"), + Type::String(_) => write!(self.writer, "string"), + Type::Tuple(types, _) => { write!(self.writer, "tuple<")?; for (i, ty) in types.iter().enumerate() { if i > 0 { @@ -165,17 +165,17 @@ impl DocumentPrinter { write!(self.writer, ">") } - Type::List(ty) => { + Type::List(ty, _) => { write!(self.writer, "list<")?; self.ty(ty)?; write!(self.writer, ">") } - Type::Option(ty) => { + Type::Option(ty, _) => { write!(self.writer, "option<")?; self.ty(ty)?; write!(self.writer, ">") } - Type::Result { ok, err } => match (ok, err) { + Type::Result { ok, err, .. } => match (ok, err) { (None, None) => write!(self.writer, "result"), (None, Some(err)) => { write!(self.writer, "result<_, ")?; @@ -195,7 +195,7 @@ impl DocumentPrinter { write!(self.writer, ">") } }, - Type::Borrow(id) => { + Type::Borrow(id, _) => { write!(self.writer, "borrow<{id}>", id = id.span.as_str()) } Type::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), @@ -323,7 +323,7 @@ impl DocumentPrinter { write!(self.writer, "static ")?; } - self.func_type_ref(&method.ty)?; + self.func_type(&method.ty)?; write!(self.writer, ";") } diff --git a/crates/wac-parser/src/ast/type.rs b/crates/wac-parser/src/ast/type.rs index cb83724..ff679fe 100644 --- a/crates/wac-parser/src/ast/type.rs +++ b/crates/wac-parser/src/ast/type.rs @@ -61,6 +61,19 @@ pub enum TypeDecl<'a> { Alias(TypeAlias<'a>), } +impl TypeDecl<'_> { + /// Gets the identifier of the type being declared. + pub fn id(&self) -> &Ident { + match self { + Self::Variant(variant) => &variant.id, + Self::Record(record) => &record.id, + Self::Flags(flags) => &flags.id, + Self::Enum(e) => &e.id, + Self::Alias(alias) => &alias.id, + } + } +} + impl<'a> Parse<'a> for TypeDecl<'a> { fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { let mut lookahead = Lookahead::new(lexer); @@ -428,8 +441,8 @@ pub struct Method<'a> { pub id: Ident<'a>, /// Wether or not the method is static. pub is_static: bool, - /// The function type reference. - pub ty: FuncTypeRef<'a>, + /// The function type of the method. + pub ty: FuncType<'a>, } impl<'a> Parse<'a> for Method<'a> { @@ -614,94 +627,110 @@ impl<'a> Parse<'a> for TypeAliasKind<'a> { #[serde(rename_all = "camelCase")] pub enum Type<'a> { /// A `u8` type. - U8, + U8(Span<'a>), /// A `s8` type. - S8, + S8(Span<'a>), /// A `u16` type. - U16, + U16(Span<'a>), /// A `s16` type. - S16, + S16(Span<'a>), /// A `u32` type. - U32, + U32(Span<'a>), /// A `s32` type. - S32, + S32(Span<'a>), /// A `u64` type. - U64, + U64(Span<'a>), /// A `s64` type. - S64, + S64(Span<'a>), /// A `float32` type. - Float32, + Float32(Span<'a>), /// A `float64` type. - Float64, + Float64(Span<'a>), /// A `char` type. - Char, + Char(Span<'a>), /// A `bool` type. - Bool, + Bool(Span<'a>), /// A `string` type. - String, + String(Span<'a>), /// A tuple type. - Tuple(Vec>), + Tuple(Vec>, Span<'a>), /// A list type. - List(Box>), + List(Box>, Span<'a>), /// An option type. - Option(Box>), + Option(Box>, Span<'a>), /// A result type. Result { /// The `ok` of the result type. ok: Option>>, /// The `err` of the result type. err: Option>>, + /// The span of the result type. + span: Span<'a>, }, /// A borrow type. - Borrow(Ident<'a>), + Borrow(Ident<'a>, Span<'a>), /// An identifier to a value type. Ident(Ident<'a>), } +impl<'a> Type<'a> { + /// Gets the span of the type. + pub fn span(&self) -> Span<'a> { + match self { + Self::U8(span) + | Self::S8(span) + | Self::U16(span) + | Self::S16(span) + | Self::U32(span) + | Self::S32(span) + | Self::U64(span) + | Self::S64(span) + | Self::Float32(span) + | Self::Float64(span) + | Self::Char(span) + | Self::Bool(span) + | Self::String(span) + | Self::Tuple(_, span) + | Self::List(_, span) + | Self::Option(_, span) + | Self::Result { span, .. } + | Self::Borrow(_, span) => *span, + Self::Ident(ident) => ident.span, + } + } +} + impl<'a> Parse<'a> for Type<'a> { fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { let mut lookahead = Lookahead::new(lexer); if lookahead.peek(Token::U8Keyword) { - lexer.next(); - Ok(Self::U8) + Ok(Self::U8(lexer.next().unwrap().1)) } else if lookahead.peek(Token::S8Keyword) { - lexer.next(); - Ok(Self::S8) + Ok(Self::S8(lexer.next().unwrap().1)) } else if lookahead.peek(Token::U16Keyword) { - lexer.next(); - Ok(Self::U16) + Ok(Self::U16(lexer.next().unwrap().1)) } else if lookahead.peek(Token::S16Keyword) { - lexer.next(); - Ok(Self::S16) + Ok(Self::S16(lexer.next().unwrap().1)) } else if lookahead.peek(Token::U32Keyword) { - lexer.next(); - Ok(Self::U32) + Ok(Self::U32(lexer.next().unwrap().1)) } else if lookahead.peek(Token::S32Keyword) { - lexer.next(); - Ok(Self::S32) + Ok(Self::S32(lexer.next().unwrap().1)) } else if lookahead.peek(Token::U64Keyword) { - lexer.next(); - Ok(Self::U64) + Ok(Self::U64(lexer.next().unwrap().1)) } else if lookahead.peek(Token::S64Keyword) { - lexer.next(); - Ok(Self::S64) + Ok(Self::S64(lexer.next().unwrap().1)) } else if lookahead.peek(Token::Float32Keyword) { - lexer.next(); - Ok(Self::Float32) + Ok(Self::Float32(lexer.next().unwrap().1)) } else if lookahead.peek(Token::Float64Keyword) { - lexer.next(); - Ok(Self::Float64) + Ok(Self::Float64(lexer.next().unwrap().1)) } else if lookahead.peek(Token::CharKeyword) { - lexer.next(); - Ok(Self::Char) + Ok(Self::Char(lexer.next().unwrap().1)) } else if lookahead.peek(Token::BoolKeyword) { - lexer.next(); - Ok(Self::Bool) + Ok(Self::Bool(lexer.next().unwrap().1)) } else if lookahead.peek(Token::StringKeyword) { - lexer.next(); - Ok(Self::String) + Ok(Self::String(lexer.next().unwrap().1)) } else if lookahead.peek(Token::TupleKeyword) { - lexer.next(); + let mut span = lexer.next().unwrap().1; parse_token(lexer, Token::OpenAngle)?; // There must be at least one type in the tuple. @@ -712,22 +741,25 @@ impl<'a> Parse<'a> for Type<'a> { let types = parse_delimited(lexer, &[Token::CloseAngle], true)?; assert!(!types.is_empty()); - parse_token(lexer, Token::CloseAngle)?; - Ok(Self::Tuple(types)) + let close = parse_token(lexer, Token::CloseAngle)?; + span.end = close.end; + Ok(Self::Tuple(types, span)) } else if lookahead.peek(Token::ListKeyword) { - lexer.next(); + let mut span = lexer.next().unwrap().1; parse_token(lexer, Token::OpenAngle)?; let ty = Box::new(Parse::parse(lexer)?); - parse_token(lexer, Token::CloseAngle)?; - Ok(Self::List(ty)) + let close = parse_token(lexer, Token::CloseAngle)?; + span.end = close.end; + Ok(Self::List(ty, span)) } else if lookahead.peek(Token::OptionKeyword) { - lexer.next(); + let mut span = lexer.next().unwrap().1; parse_token(lexer, Token::OpenAngle)?; let ty = Box::new(Parse::parse(lexer)?); - parse_token(lexer, Token::CloseAngle)?; - Ok(Self::Option(ty)) + let close = parse_token(lexer, Token::CloseAngle)?; + span.end = close.end; + Ok(Self::Option(ty, span)) } else if lookahead.peek(Token::ResultKeyword) { - lexer.next(); + let mut span = lexer.next().unwrap().1; let (ok, err) = match parse_optional(lexer, Token::OpenAngle, |lexer| { let mut lookahead = Lookahead::new(lexer); let ok = if lookahead.peek(Token::Underscore) { @@ -752,19 +784,21 @@ impl<'a> Parse<'a> for Type<'a> { })? .unwrap_or(None); - parse_token(lexer, Token::CloseAngle)?; + let close = parse_token(lexer, Token::CloseAngle)?; + span.end = close.end; Ok((ok, err)) })? { Some((ok, err)) => (ok, err), None => (None, None), }; - Ok(Self::Result { ok, err }) + Ok(Self::Result { ok, err, span }) } else if lookahead.peek(Token::BorrowKeyword) { - lexer.next(); + let mut span = lexer.next().unwrap().1; parse_token(lexer, Token::OpenAngle)?; let id = Parse::parse(lexer)?; - parse_token(lexer, Token::CloseAngle)?; - Ok(Self::Borrow(id)) + let close = parse_token(lexer, Token::CloseAngle)?; + span.end = close.end; + Ok(Self::Borrow(id, span)) } else if Ident::peek(&mut lookahead) { Ok(Self::Ident(Parse::parse(lexer)?)) } else { @@ -1390,8 +1424,6 @@ mod test { set-foo: func(foo: string); /// A static method id: static func() -> u32; - /// A method - baz: x; }}"#, r#"interface i { resource foo-bar { @@ -1406,9 +1438,6 @@ mod test { /// A static method id: static func() -> u32; - - /// A method - baz: x; } }"#, ) diff --git a/crates/wac-parser/src/lexer.rs b/crates/wac-parser/src/lexer.rs index 195e805..833b216 100644 --- a/crates/wac-parser/src/lexer.rs +++ b/crates/wac-parser/src/lexer.rs @@ -1,4 +1,4 @@ -//! The module for the WAC lexer implementation. +//! Module for the lexer implementation. use logos::{Logos, SpannedIter}; use serde::{Serialize, Serializer}; diff --git a/crates/wac-parser/src/lib.rs b/crates/wac-parser/src/lib.rs index 0ae26ca..39be82e 100644 --- a/crates/wac-parser/src/lib.rs +++ b/crates/wac-parser/src/lib.rs @@ -12,8 +12,9 @@ use std::{ pub mod ast; pub mod lexer; -pub mod printer; -pub mod resolution; +mod resolution; + +pub use resolution::*; /// Gets the 1-based line and column of a position within a source. pub(crate) fn line_column(source: &str, pos: usize) -> (usize, usize) { diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index 171aaea..4fd0ab3 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -1,27 +1,25 @@ //! Module for resolving WAC documents. -use self::package::Package; -use crate::{ - ast::{self, InterfaceItem, WorldItem}, - lexer::Span, - line_column, Spanned, -}; +use self::{encoding::Encoder, package::Package}; +use crate::{lexer::Span, resolution::ast::AstResolver, Spanned}; use anyhow::Context; use id_arena::{Arena, Id}; -use indexmap::{IndexMap, IndexSet}; +use indexmap::IndexMap; use semver::Version; use serde::{Serialize, Serializer}; use std::{ - collections::{hash_map, HashMap}, + collections::HashMap, fmt, fs, path::{Path, PathBuf}, }; -use wasmparser::names::{ComponentName, ComponentNameKind}; use wit_parser::Resolve; +mod ast; +mod encoding; mod package; mod types; +pub use encoding::EncodingOptions; pub use types::*; fn serialize_arena(arena: &Arena, serializer: S) -> std::result::Result @@ -39,12 +37,32 @@ where s.end() } -fn serialize_id_map( - map: &IndexMap>, +fn serialize_id_key_map( + map: &IndexMap, V>, + serializer: S, +) -> std::result::Result +where + S: Serializer, + T: Serialize, + V: Serialize, +{ + use serde::ser::SerializeMap; + + let mut s = serializer.serialize_map(Some(map.len()))?; + for (k, v) in map { + s.serialize_entry(&k.index(), v)?; + } + + 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; @@ -74,25 +92,25 @@ where T: Serialize, { match id { - Some(i) => serializer.serialize_some(&i.index()), + Some(id) => serializer.serialize_some(&id.index()), None => serializer.serialize_none(), } } -/// Represents a kind of item in a world. +/// Represents a kind of an extern item. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum WorldItemKind { +pub enum ExternKind { /// The item is an import. Import, /// The item is an export. Export, } -impl fmt::Display for WorldItemKind { +impl fmt::Display for ExternKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - WorldItemKind::Import => write!(f, "import"), - WorldItemKind::Export => write!(f, "export"), + Self::Import => write!(f, "import"), + Self::Export => write!(f, "export"), } } } @@ -159,8 +177,8 @@ pub enum Error<'a> { /// Duplicate world item. #[error("{kind} `{name}` conflicts with existing {kind} of the same name in world `{world}`")] DuplicateWorldItem { - /// The kind of the item. - kind: WorldItemKind, + /// The extern kind of the item. + kind: ExternKind, /// The name of the item. name: String, /// The name of the world. @@ -219,8 +237,8 @@ pub enum Error<'a> { /// A conflict was encountered in a world include. #[error("{kind} `{name}` from world `{from}` conflicts with {kind} of the same name in world `{to}`{hint}")] WorldIncludeConflict { - /// The kind of the item. - kind: WorldItemKind, + /// The extern kind of the item. + kind: ExternKind, /// The name of the item. name: String, /// The name of the source world. @@ -372,6 +390,12 @@ pub enum Error<'a> { /// The span where the error occurred. span: Span<'a>, }, + /// A borrow type was encountered in a function result. + #[error("function result cannot recursively contain a borrow type")] + BorrowInResult { + /// The span where the error occurred. + span: Span<'a>, + }, /// A package failed to resolve. #[error("failed to resolve package `{name}`")] PackageResolutionFailure { @@ -467,6 +491,77 @@ pub enum Error<'a> { /// The span where the error occurred. span: Span<'a>, }, + /// An instantiation argument conflict was encountered. + #[error("implicit instantiation argument `{name}` ({kind}) conflicts with an explicit import at {path}:{line}:{column}", path = .path.display())] + InstantiationArgConflict { + /// The name of the argument. + name: String, + /// The path of the source file. + path: &'a Path, + /// The kind of the argument. + kind: &'static str, + /// The line of the original instantiation. + line: usize, + /// The column of the original instantiation. + column: usize, + /// The span where the error occurred. + span: Span<'a>, + }, + /// 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 the instantiation of `{package}` at {path}:{line}:{column}", path = .path.display())] + ImportConflict { + /// The name of the argument. + name: String, + /// The package that first introduced the import. + package: &'a str, + /// The path of the source file. + path: &'a Path, + /// The line of the original instantiation. + line: usize, + /// The column of the original instantiation. + column: usize, + /// The span where the error occurred. + span: Span<'a>, + }, + /// 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}` at {path}:{line}:{column}", path = .path.display())] + InstantiationArgMergeFailure { + /// The name of the argument. + name: String, + /// The name of the package that first introduced the import. + package: &'a str, + /// The path of the source file. + path: &'a Path, + /// The kind of the argument. + kind: &'static str, + /// The line of the original instantiation. + line: usize, + /// The column of the original instantiation. + column: usize, + /// The span where the error occurred. + span: Span<'a>, + /// The underlying merge 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}` at {path}:{line}:{column}", path = .path.display())] + UnmergeableInstantiationArg { + /// The name of the argument. + name: String, + /// The name of the package that first introduced the import. + package: &'a str, + /// The path of the source file. + path: &'a Path, + /// The kind of the argument. + kind: &'static str, + /// The line of the original instantiation. + line: usize, + /// The column of the original instantiation. + column: usize, + /// The span where the error occurred. + span: Span<'a>, + }, /// An access expression on an inaccessible value was encountered. #[error("a {kind} cannot be accessed")] Inaccessible { @@ -518,8 +613,8 @@ pub enum Error<'a> { DuplicateExternName { /// The name of the export. name: String, - /// The kind of extern name (e.g. `import` or `export`). - kind: &'static str, + /// The kind of extern name. + kind: ExternKind, /// The span where the error occurred. span: Span<'a>, }, @@ -528,14 +623,32 @@ pub enum Error<'a> { InvalidExternName { /// The name of the export. name: String, - /// The kind of extern name (e.g. `import` or `export`). - kind: &'static str, + /// The kind of extern. + kind: ExternKind, /// The span where the error occurred. span: Span<'a>, /// The underlying validation error. #[source] source: anyhow::Error, }, + /// Cannot instantiate the package being defined. + #[error("cannot instantiate the package being defined")] + CannotInstantiateSelf { + /// The span where the error occurred. + span: Span<'a>, + }, + /// A use of a type conflicts with an extern item. + #[error("use of type `{name}` conflicts with an {kind} of the same name{hint}")] + UseConflict { + /// The name of the used type. + name: &'a str, + /// The extern kind of the conflicting item. + kind: ExternKind, + /// The hint for the error. + hint: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, } impl Spanned for Error<'_> { @@ -565,6 +678,7 @@ impl Spanned for Error<'_> { | Error::NotValueType { span, .. } | Error::DuplicateParameter { span, .. } | Error::DuplicateResult { span, .. } + | Error::BorrowInResult { span } | Error::PackageResolutionFailure { span, .. } | Error::PackageParseFailure { span, .. } | Error::UnknownPackage { span, .. } @@ -574,13 +688,19 @@ impl Spanned for Error<'_> { | Error::MismatchedInstantiationArg { span, .. } | Error::DuplicateInstantiationArg { span, .. } | Error::MissingInstantiationArg { span, .. } + | Error::InstantiationArgConflict { span, .. } + | Error::ImportConflict { span, .. } + | Error::InstantiationArgMergeFailure { span, .. } + | Error::UnmergeableInstantiationArg { span, .. } | Error::Inaccessible { span, .. } | Error::InaccessibleInterface { span, .. } | Error::MissingInstanceExport { span, .. } | Error::ExportRequiresWith { span } | Error::ExportConflict { span, .. } | Error::DuplicateExternName { span, .. } - | Error::InvalidExternName { span, .. } => *span, + | Error::InvalidExternName { span, .. } + | Error::CannotInstantiateSelf { span } + | Error::UseConflict { span, .. } => *span, } } } @@ -621,9 +741,13 @@ impl Default for FileSystemPackageResolver { } impl PackageResolver for FileSystemPackageResolver { - fn resolve(&self, name: &str, _version: Option<&Version>) -> anyhow::Result>> { + fn resolve(&self, name: &str, version: Option<&Version>) -> anyhow::Result>> { + if version.is_some() { + anyhow::bail!("local dependency resolution does not support package versions"); + } + let path = if let Some(path) = self.overrides.get(name) { - if !path.exists() { + if !path.is_file() { anyhow::bail!( "local path `{path}` for package `{name}` does not exist", path = path.display(), @@ -706,25 +830,40 @@ impl PackageResolver for FileSystemPackageResolver { #[derive(Debug, Clone, Copy, Serialize, Hash, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub enum ItemKind { - /// The kind is a type. + /// The item is a type. Type(Type), - /// The kind is a function. + /// 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 kind is a component instance. + /// The item is a component instance. Instance(#[serde(serialize_with = "serialize_id")] InterfaceId), - /// The kind is an instantiation of a component. - Instantiation(#[serde(serialize_with = "serialize_id")] WorldId), - /// The kind is a component. + /// 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 kind is a core module. + /// The item is a core module. Module(#[serde(serialize_with = "serialize_id")] ModuleId), - /// The kind is a value. + /// The item is a value. Value(ValueType), } impl ItemKind { + fn ty(&self) -> Option { + 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, + } + } + fn as_str(&self, definitions: &Definitions) -> &'static str { match self { + ItemKind::Resource(_) => "resource", ItemKind::Func(_) => "function", ItemKind::Type(ty) => ty.as_str(definitions), ItemKind::Instance(_) | ItemKind::Instantiation(_) => "instance", @@ -735,1775 +874,123 @@ impl ItemKind { } } -/// Represents the source of an item. +/// Represents a item defining a type. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub enum ItemSource { - /// The item comes from a local definition. - Definition, - /// The item comes from a use statement. - Use, - /// The item comes from an import, - Import(String), - /// The item comes from an instance alias. - Alias { - /// The instance being aliased. - #[serde(serialize_with = "serialize_id")] - item: ItemId, - /// The export name. - export: String, - }, - /// The item comes from an instantiation. - Instantiation { - /// The arguments of the instantiation. - #[serde(serialize_with = "serialize_id_map")] - arguments: IndexMap, - }, -} - -/// Represents an item in a resolved document. -#[derive(Debug, Clone, Serialize)] -pub struct Item { +pub struct Definition { + /// The name of the type. + pub name: String, /// The kind of the item. pub kind: ItemKind, - /// The source of the item. - pub source: ItemSource, } -/// Represents an identifier for an item. -pub type ItemId = Id; - -/// An identifier for scopes. -pub type ScopeId = Id; - -/// Represents a scope for named items. +/// Represents an import item. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Scope { - #[serde(serialize_with = "serialize_optional_id")] - parent: Option, - #[serde(serialize_with = "serialize_id_map")] - items: IndexMap, +pub struct Import { + /// The import name. + pub name: String, + /// The kind of the import. + pub kind: ItemKind, } -impl Scope { - /// Gets a named item from the scope. - pub fn get(&self, name: &str) -> Option { - self.items.get(name).copied() - } - - /// Iterates all named items in the scope. - pub fn iter(&self) -> impl Iterator + '_ { - self.items.values().copied() - } +/// 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, } -struct ResolutionState<'a> { - document: &'a ast::Document<'a>, - resolver: Option>, - root_scope: ScopeId, - current_scope: ScopeId, - packages: HashMap, - offsets: HashMap, - aliases: HashMap<(ItemId, String), ItemId>, +/// 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, } -/// Represents a kind of function in the component model. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +/// Represents a composition item. +#[derive(Debug, Clone, 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, +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), } -impl fmt::Display for FuncKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Item { + /// Returns the kind of the item. + pub fn kind(&self) -> ItemKind { match self { - FuncKind::Free => write!(f, "function"), - FuncKind::Method => write!(f, "method"), - FuncKind::Static => write!(f, "static method"), - FuncKind::Constructor => write!(f, "constructor"), + 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), } } } -/// Represents a resolved document. +/// 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 resolution may be encoded to a WebAssembly component. -#[derive(Debug, Serialize)] +/// A composition may be encoded into a WebAssembly component. +#[derive(Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] -pub struct ResolvedDocument { - /// The name of the package being resolved. +pub struct Composition { + /// The package name of the composition. pub package: String, - /// The version of the package being resolved. + /// The package version of the composition. pub version: Option, - /// The definitions in the resolution. + /// The definitions in the composition. pub definitions: Definitions, - /// The items in the resolution. + /// 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 imported items from the composition. - #[serde(serialize_with = "serialize_id_map")] + /// The map of import names to items. + #[serde(serialize_with = "serialize_id_value_map")] pub imports: IndexMap, - /// The exported items from the composition. - #[serde(serialize_with = "serialize_id_map")] + /// The map of export names to items. + #[serde(serialize_with = "serialize_id_value_map")] pub exports: IndexMap, - /// The name scopes in the resolution. - #[serde(serialize_with = "serialize_arena")] - pub scopes: Arena, } -impl ResolvedDocument { - /// Creates a new resolved document from the given document. - pub fn new<'a>( - document: &'a ast::Document<'a>, +impl Composition { + /// Creates a new composition from an AST document. + pub fn from_ast<'a>( + document: &'a crate::ast::Document<'a>, resolver: Option>, ) -> ResolutionResult<'a, Self> { - let mut scopes = Arena::new(); - let root_scope = scopes.alloc(Scope { - parent: None, - items: Default::default(), - }); - - let mut resolution = ResolvedDocument { - package: document.package.name.to_owned(), - version: document.package.version.clone(), - definitions: Default::default(), - items: Default::default(), - imports: Default::default(), - exports: Default::default(), - scopes, - }; - - let mut state = ResolutionState { - document, - resolver, - root_scope, - current_scope: root_scope, - packages: Default::default(), - offsets: Default::default(), - aliases: Default::default(), - }; - - for stmt in &document.statements { - match stmt { - ast::Statement::Import(i) => resolution.import_statement(&mut state, i)?, - ast::Statement::Type(t) => resolution.type_statement(&mut state, t)?, - ast::Statement::Let(l) => resolution.let_statement(&mut state, l)?, - ast::Statement::Export(e) => resolution.export_statement(&mut state, e)?, - } - } - - Ok(resolution) - } - - /// Encode the resolved document as a WebAssembly component. - pub fn encode(&self) -> ResolutionResult> { - todo!("implement encoding") - } - - fn get(&self, state: &ResolutionState, id: &ast::Ident) -> Option { - let mut current = &self.scopes[state.current_scope]; - loop { - if let Some(item) = current.get(id.string) { - return Some(item); - } - - current = &self.scopes[current.parent?]; - } - } - - fn require<'a>( - &self, - state: &ResolutionState, - id: &ast::Ident<'a>, - ) -> ResolutionResult<'a, ItemId> { - self.get(state, id).ok_or(Error::UndefinedName { - name: id.string, - span: id.span, - }) - } - - fn push_scope(&mut self, state: &mut ResolutionState) { - state.current_scope = self.scopes.alloc(Scope { - parent: Some(state.current_scope), - items: Default::default(), - }); - } - - fn pop_scope(&mut self, state: &mut ResolutionState) -> ScopeId { - let id = state.current_scope; - state.current_scope = self.scopes[state.current_scope] - .parent - .expect("expected a scope to pop"); - id - } - - fn register_name<'a>( - &mut self, - state: &mut ResolutionState<'a>, - id: ast::Ident<'a>, - item: ItemId, - ) -> ResolutionResult<'a, ()> { - if let Some(prev) = self.scopes[state.current_scope] - .items - .insert(id.string.to_owned(), item) - { - let offset = state.offsets[&prev]; - let (line, column) = line_column(id.span.source(), offset); - return Err(Error::DuplicateName { - name: id.string, - path: state.document.path, - line, - column, - span: id.span, - }); - } - - state.offsets.insert(item, id.span.start); - - Ok(()) - } - - fn import_statement<'a>( - &mut self, - state: &mut ResolutionState<'a>, - stmt: &'a ast::ImportStatement<'a>, - ) -> ResolutionResult<'a, ()> { - let kind = match &stmt.ty { - ast::ImportType::Package(p) => self.resolve_package_export(state, p)?, - ast::ImportType::Func(ty) => { - ItemKind::Func(self.func_type(state, &ty.params, &ty.results, FuncKind::Free)?) - } - ast::ImportType::Interface(i) => self.inline_interface(state, i)?, - ast::ImportType::Ident(id) => self.items[self.require(state, id)?].kind, - }; - - // Promote function types, instance types, and component types to functions, instances, and components - let kind = match kind { - 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, - }; - - let (name, span) = if let Some(with) = stmt.with { - (with.value, with.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(), stmt.id.span) - } else { - (stmt.id.string, stmt.id.span) - } - } else { - (stmt.id.string, stmt.id.span) - } - }; - - // Validate the import name - ComponentName::new(name, 0).map_err(|e| { - let msg = e.to_string(); - Error::InvalidExternName { - name: name.to_string(), - kind: "import", - span, - source: anyhow::anyhow!( - "{msg}", - msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) - ), - } - })?; - - if self.imports.contains_key(name) { - return Err(Error::DuplicateExternName { - name: name.to_owned(), - kind: "import", - span, - }); - } - - let id = self.items.alloc(Item { - kind, - source: ItemSource::Import(name.to_owned()), - }); - - self.imports.insert(name.to_owned(), id); - - self.register_name(state, stmt.id, id) - } - - fn type_statement<'a>( - &mut self, - state: &mut ResolutionState<'a>, - stmt: &'a ast::TypeStatement<'a>, - ) -> ResolutionResult<'a, ()> { - match stmt { - ast::TypeStatement::Interface(i) => self.interface_decl(state, i), - ast::TypeStatement::World(w) => self.world_decl(state, w), - ast::TypeStatement::Type(t) => self.type_decl(state, t).map(|_| ()), - } - } - - fn let_statement<'a>( - &mut self, - state: &mut ResolutionState<'a>, - stmt: &'a ast::LetStatement<'a>, - ) -> ResolutionResult<'a, ()> { - let item = self.expr(state, &stmt.expr)?; - self.register_name(state, stmt.id, item) + AstResolver::new(document).resolve(resolver) } - fn export_statement<'a>( - &mut self, - state: &mut ResolutionState<'a>, - stmt: &'a ast::ExportStatement<'a>, - ) -> ResolutionResult<'a, ()> { - let item = self.expr(state, &stmt.expr)?; - let (name, span) = if let Some(name) = stmt.with { - (name.value, name.span) - } else { - ( - self.infer_export_name(item) - .ok_or(Error::ExportRequiresWith { span: stmt.span })?, - stmt.span, - ) - }; - - // Validate the export name - match ComponentName::new(name, 0) - .map_err(|e| { - let msg = e.to_string(); - Error::InvalidExternName { - name: name.to_string(), - kind: "export", - span, - source: anyhow::anyhow!( - "{msg}", - msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) - ), - } - })? - .kind() - { - ComponentNameKind::Hash(_) - | ComponentNameKind::Url(_) - | ComponentNameKind::Dependency(_) => { - return Err(Error::InvalidExternName { - name: name.to_string(), - kind: "export", - span, - source: anyhow::anyhow!("export name cannot be a hash, url, or dependency"), - }); - } - _ => {} - } - - if self.exports.contains_key(name) { - return Err(Error::DuplicateExternName { - name: name.to_owned(), - kind: "export", - span, - }); - } - - // Ensure the export does not conflict with a defined item as - // they are implicitly exported. - if let Some(item_id) = self.scopes[state.root_scope].get(name) { - let item = &self.items[item_id]; - if let ItemSource::Definition = &item.source { - let offset = state.offsets[&item_id]; - let (line, column) = line_column(stmt.span.source(), offset); - return Err(Error::ExportConflict { - name: name.to_owned(), - path: state.document.path, - kind: item.kind.as_str(&self.definitions), - line, - column, - hint: if stmt.with.is_some() { - "" - } else { - " (consider using a `with` clause to use a different name)" - }, - span, - }); - } - } - - let prev = self.exports.insert(name.to_owned(), item); - assert!(prev.is_none()); - - Ok(()) - } - - fn inline_interface<'a>( - &mut self, - state: &mut ResolutionState<'a>, - iface: &'a ast::InlineInterface<'a>, - ) -> ResolutionResult<'a, ItemKind> { - self.push_scope(state); - - let mut ty = Interface { - id: None, - exports: Default::default(), - scope: Some(state.current_scope), - }; - - self.interface_items(state, None, &iface.items, &mut ty)?; - - self.pop_scope(state); - - Ok(ItemKind::Type(Type::Interface( - self.definitions.interfaces.alloc(ty), - ))) - } - - fn interface_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &'a ast::InterfaceDecl<'a>, - ) -> ResolutionResult<'a, ()> { - self.push_scope(state); - - let mut ty = Interface { - id: Some(format!( - "{pkg}/{iface}", - pkg = self.package, - iface = decl.id.string, - )), - exports: Default::default(), - scope: Some(state.current_scope), - }; - - self.interface_items(state, Some(decl.id.string), &decl.items, &mut ty)?; - - self.pop_scope(state); - - let ty = self.definitions.interfaces.alloc(ty); - - let id = self.items.alloc(Item { - kind: ItemKind::Type(Type::Interface(ty)), - source: ItemSource::Definition, - }); - - self.register_name(state, decl.id, id) - } - - fn world_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &'a ast::WorldDecl<'a>, - ) -> ResolutionResult<'a, ()> { - self.push_scope(state); - - let mut ty = World { - imports: Default::default(), - exports: Default::default(), - scope: Some(state.current_scope), - }; - - self.world_body(state, decl.id.string, &decl.items, &mut ty)?; - - self.pop_scope(state); - - let ty = self.definitions.worlds.alloc(ty); - - let id = self.items.alloc(Item { - kind: ItemKind::Type(Type::World(ty)), - source: ItemSource::Definition, - }); - - self.register_name(state, decl.id, id) - } - - fn interface_items<'a>( - &mut self, - state: &mut ResolutionState<'a>, - name: Option<&'a str>, - items: &'a [InterfaceItem<'a>], - ty: &mut Interface, - ) -> ResolutionResult<'a, ()> { - for item in items { - match item { - ast::InterfaceItem::Use(u) => self.use_type(state, u, &mut ty.exports, false)?, - ast::InterfaceItem::Type(decl) => { - let kind = self.item_type_decl(state, decl)?; - let prev = ty - .exports - .insert(decl.id().string.into(), Extern::Kind(kind)); - assert!(prev.is_none(), "duplicate type in scope"); - } - 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(), Extern::Kind(kind)) - .is_some() - { - return Err(Error::DuplicateInterfaceExport { - name: e.id.string, - interface_name: name, - span: e.id.span, - }); - } - } - } - } - - Ok(()) - } - - fn world_body<'a>( - &mut self, - state: &mut ResolutionState<'a>, - world: &'a str, - items: &'a [WorldItem<'a>], - ty: &mut World, - ) -> ResolutionResult<'a, ()> { - let mut includes = Vec::new(); - for item in items { - match item { - ast::WorldItem::Use(u) => self.use_type(state, u, &mut ty.imports, true)?, - ast::WorldItem::Type(decl) => { - let kind = self.item_type_decl(state, decl)?; - let prev = ty - .exports - .insert(decl.id().string.into(), Extern::Kind(kind)); - assert!(prev.is_none(), "duplicate type in scope"); - } - ast::WorldItem::Import(i) => { - self.world_item_path(state, &i.path, WorldItemKind::Import, world, ty)? - } - ast::WorldItem::Export(e) => { - self.world_item_path(state, &e.path, WorldItemKind::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<'a>( - &mut self, - state: &mut ResolutionState<'a>, - path: &'a ast::WorldItemPath<'a>, - kind: WorldItemKind, - world: &'a str, - ty: &mut World, - ) -> ResolutionResult<'a, ()> { - 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 = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { - ItemKind::Instance(id) - } - ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => { - ItemKind::Func(id) - } - _ => { - return Err(Error::NotFuncOrInterface { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }); - } - } - } - ast::ExternType::Func(f) => ItemKind::Func(self.func_type( - state, - &f.params, - &f.results, - FuncKind::Free, - )?), - ast::ExternType::Interface(i) => self.inline_interface(state, i)?, - }, - ) - } - ast::WorldItemPath::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Interface(iface_ty_id)) - | ItemKind::Instance(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)) - } - _ => { - return Err(Error::NotInterface { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }); - } - } - } - - ast::WorldItemPath::Package(p) => match self.resolve_package_export(state, p)? { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(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.span.as_str(), - kind: kind.as_str(&self.definitions), - span: p.span, - }); - } - }, - }; - - if kind == WorldItemKind::Import { - ty.imports.insert(k, Extern::Kind(v)); - } else { - ty.exports.insert(k, Extern::Kind(v)); - } - - return Ok(()); - - fn check_name<'a>( - name: &str, - span: Span<'a>, - ty: &World, - world: &'a str, - kind: WorldItemKind, - ) -> ResolutionResult<'a, ()> { - let exists: bool = if kind == WorldItemKind::Import { - ty.imports.contains_key(name) - } else { - ty.exports.contains_key(name) - }; - - if exists { - return Err(Error::DuplicateWorldItem { - kind, - name: name.to_owned(), - world, - span, - }); - } - - Ok(()) - } - } - - fn world_include<'a>( - &mut self, - state: &mut ResolutionState<'a>, - include: &ast::WorldInclude<'a>, - world: &'a str, - ty: &mut World, - ) -> ResolutionResult<'a, ()> { - 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, - span: item.from.span, - }); - } - } - - let id = match &include.world { - ast::WorldRef::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, - kind => { - return Err(Error::NotWorld { - name: id.string, - kind: kind.as_str(&self.definitions), - 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.span.as_str(), - kind: kind.as_str(&self.definitions), - span: path.span, - }); - } - }, - }; - - let other = &self.definitions.worlds[id]; - for (name, item) in &other.imports { - let name = replace_name( - include, - world, - ty, - name, - WorldItemKind::Import, - &mut replacements, - )?; - ty.imports.entry(name).or_insert(*item); - } - - for (name, item) in &other.exports { - let name = replace_name( - include, - world, - ty, - name, - WorldItemKind::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(), - name: missing.from.string, - span: missing.from.span, - }); - } - - return Ok(()); - - fn replace_name<'a>( - include: &ast::WorldInclude<'a>, - world: &'a str, - ty: &mut World, - name: &str, - kind: WorldItemKind, - replacements: &mut HashMap<&str, &ast::WorldIncludeItem<'a>>, - ) -> ResolutionResult<'a, String> { - // 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 == WorldItemKind::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: world, - span, - hint: if !include.with.is_empty() { - "" - } else { - " (consider using a `with` clause to use a different name)" - }, - }); - } - - Ok(name.to_owned()) - } - } - - fn use_type<'a>( - &mut self, - state: &mut ResolutionState<'a>, - use_type: &ast::Use<'a>, - externs: &mut ExternMap, - in_world: bool, - ) -> ResolutionResult<'a, ()> { - let (interface, name) = match &use_type.path { - ast::UsePath::Package(path) => match self.resolve_package_export(state, path)? { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { - (id, path.span.as_str()) - } - kind => { - return Err(Error::NotInterface { - name: path.span.as_str(), - kind: kind.as_str(&self.definitions), - span: path.span, - }); - } - }, - ast::UsePath::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Interface(iface_ty_id)) - | ItemKind::Instance(iface_ty_id) => (iface_ty_id, id.string), - kind => { - return Err(Error::NotInterface { - name: id.string, - kind: kind.as_str(&self.definitions), - span: id.span, - }); - } - } - } - }; - - for item in &use_type.items { - let ident = item.as_id.unwrap_or(item.id); - let (index, _, ext) = self.definitions.interfaces[interface] - .exports - .get_full(item.id.string) - .ok_or(Error::UndefinedInterfaceType { - name: item.id.string, - interface_name: name, - span: item.id.span, - })?; - - let kind = ext.kind(); - match kind { - ItemKind::Type(Type::Value(ty)) => { - let new_extern = Extern::Use { - interface, - export_index: index, - ty, - }; - - if in_world { - // A `use` of a type in a world will (transitively) import the interface that defines - // the type being used. This walks up the use chain, adding any necessary imports of - // the interfaces. - let mut cur = new_extern; - while let Extern::Use { - interface, - export_index, - .. - } = cur - { - let iface = &self.definitions.interfaces[interface]; - let id = iface.id.as_deref().expect("interface must have an id"); - - if !externs.contains_key(id) { - externs.insert( - id.to_owned(), - Extern::Kind(ItemKind::Instance(interface)), - ); - } - - cur = iface.exports[export_index]; - } - } - - externs.insert(ident.string.into(), new_extern); - - let id = self.items.alloc(Item { - kind, - source: ItemSource::Use, - }); - self.register_name(state, ident, id)?; - } - _ => { - return Err(Error::NotInterfaceValueType { - name: item.id.string, - kind: kind.as_str(&self.definitions), - interface_name: name, - span: item.id.span, - }); - } - } - } - - Ok(()) - } - - fn type_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &'a ast::TypeDecl, - ) -> ResolutionResult<'a, ItemKind> { - 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<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &'a ast::ItemTypeDecl, - ) -> ResolutionResult<'a, ItemKind> { - match decl { - ast::ItemTypeDecl::Resource(r) => self.resource_decl(state, r), - ast::ItemTypeDecl::Variant(v) => self.variant_decl(state, v), - ast::ItemTypeDecl::Record(r) => self.record_decl(state, r), - ast::ItemTypeDecl::Flags(f) => self.flags_decl(state, f), - ast::ItemTypeDecl::Enum(e) => self.enum_decl(state, e), - ast::ItemTypeDecl::Alias(a) => self.type_alias(state, a), - } - } - - fn resource_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &ast::ResourceDecl<'a>, - ) -> ResolutionResult<'a, ItemKind> { - // Resources are allowed to be self-referential, so we need to allocate the resource - // before we resolve the methods. - let id = self.definitions.resources.alloc(Resource { - methods: Default::default(), - }); - let kind = ItemKind::Type(Type::Value( - self.definitions.types.alloc(DefinedType::Resource(id)), - )); - let item_id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - - self.register_name(state, decl.id, item_id)?; - - let mut methods: IndexMap = Default::default(); - for method in &decl.methods { - let (name, method, span) = match method { - ast::ResourceMethod::Constructor(ast::Constructor { span, params, .. }) => ( - "", - ResourceMethod { - kind: FuncKind::Constructor, - ty: self.func_type( - state, - params, - &ast::ResultList::Empty, - FuncKind::Constructor, - )?, - }, - *span, - ), - ast::ResourceMethod::Method(ast::Method { - id, is_static, ty, .. - }) => ( - id.string, - ResourceMethod { - kind: if *is_static { - FuncKind::Static - } else { - FuncKind::Method - }, - ty: self.func_type_ref(state, ty, FuncKind::Method)?, - }, - id.span, - ), - }; - - let prev = methods.insert(name.to_owned(), method); - if prev.is_some() { - return Err(if !name.is_empty() { - Error::DuplicateResourceMethod { - name, - resource: decl.id.string, - span, - } - } else { - Error::DuplicateResourceConstructor { - resource: decl.id.string, - span, - } - }); - } - } - - self.definitions.resources[id].methods = methods; - - Ok(kind) - } - - fn variant_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &ast::VariantDecl<'a>, - ) -> ResolutionResult<'a, ItemKind> { - let mut cases = IndexMap::new(); - for case in &decl.cases { - if cases - .insert( - case.id.string.into(), - case.ty.as_ref().map(|ty| self.ty(state, ty)).transpose()?, - ) - .is_some() - { - return Err(Error::DuplicateVariantCase { - case: case.id.string, - name: decl.id.string, - span: case.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value( - self.definitions - .types - .alloc(DefinedType::Variant(Variant { cases })), - )); - let id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - self.register_name(state, decl.id, id)?; - Ok(kind) - } - - fn record_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &ast::RecordDecl<'a>, - ) -> ResolutionResult<'a, ItemKind> { - let mut fields = IndexMap::new(); - for field in &decl.fields { - if fields - .insert(field.id.string.into(), self.ty(state, &field.ty)?) - .is_some() - { - return Err(Error::DuplicateRecordField { - field: field.id.string, - name: decl.id.string, - span: field.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value( - self.definitions - .types - .alloc(DefinedType::Record(Record { fields })), - )); - let id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - self.register_name(state, decl.id, id)?; - Ok(kind) - } - - fn flags_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &ast::FlagsDecl<'a>, - ) -> ResolutionResult<'a, ItemKind> { - 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, - name: decl.id.string, - span: flag.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value( - self.definitions - .types - .alloc(DefinedType::Flags(Flags(flags))), - )); - let id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - self.register_name(state, decl.id, id)?; - Ok(kind) - } - - fn enum_decl<'a>( - &mut self, - state: &mut ResolutionState<'a>, - decl: &ast::EnumDecl<'a>, - ) -> ResolutionResult<'a, ItemKind> { - 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, - name: decl.id.string, - span: case.id.span, - }); - } - } - - let kind = ItemKind::Type(Type::Value( - self.definitions.types.alloc(DefinedType::Enum(Enum(cases))), - )); - let id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - - self.register_name(state, decl.id, id)?; - Ok(kind) - } - - fn type_alias<'a>( - &mut self, - state: &mut ResolutionState<'a>, - alias: &ast::TypeAlias<'a>, - ) -> ResolutionResult<'a, ItemKind> { - let kind = match &alias.kind { - ast::TypeAliasKind::Func(f) => ItemKind::Type(Type::Func(self.func_type( - state, - &f.params, - &f.results, - FuncKind::Free, - )?)), - ast::TypeAliasKind::Type(ty) => match ty { - ast::Type::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Value(id)) => ItemKind::Type(Type::Value( - self.definitions - .types - .alloc(DefinedType::Alias(ValueType::Defined(id))), - )), - ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => { - ItemKind::Type(Type::Func(id)) - } - _ => { - return Err(Error::InvalidAliasType { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }); - } - } - } - _ => { - let ty = self.ty(state, ty)?; - ItemKind::Type(Type::Value( - self.definitions.types.alloc(DefinedType::Alias(ty)), - )) - } - }, - }; - - let id = self.items.alloc(Item { - kind, - source: ItemSource::Definition, - }); - - self.register_name(state, alias.id, id)?; - Ok(kind) - } - - fn func_type_ref<'a>( - &mut self, - state: &ResolutionState<'a>, - r: &ast::FuncTypeRef<'a>, - kind: FuncKind, - ) -> ResolutionResult<'a, FuncId> { - match r { - ast::FuncTypeRef::Func(ty) => self.func_type(state, &ty.params, &ty.results, kind), - ast::FuncTypeRef::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => Ok(id), - _ => Err(Error::NotFuncType { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }), - } - } - } - } - - fn ty<'a>( - &mut self, - state: &ResolutionState<'a>, - ty: &ast::Type<'a>, - ) -> ResolutionResult<'a, ValueType> { - 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::Float32 => Ok(ValueType::Primitive(PrimitiveType::Float32)), - ast::Type::Float64 => Ok(ValueType::Primitive(PrimitiveType::Float64)), - 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 types = v - .iter() - .map(|ty| self.ty(state, ty)) - .collect::>()?; - - Ok(ValueType::Defined( - self.definitions.types.alloc(DefinedType::Tuple(types)), - )) - } - ast::Type::List(ty) => { - let ty = self.ty(state, ty)?; - Ok(ValueType::Defined( - self.definitions.types.alloc(DefinedType::List(ty)), - )) - } - ast::Type::Option(ty) => { - let ty = self.ty(state, ty)?; - Ok(ValueType::Defined( - self.definitions.types.alloc(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( - self.definitions - .types - .alloc(DefinedType::Result { ok, err }), - )) - } - ast::Type::Borrow(id) => { - let item = &self.items[self.require(state, id)?]; - if let ItemKind::Type(Type::Value(id)) = item.kind { - if let DefinedType::Resource(id) = &self.definitions.types[id] { - return Ok(ValueType::Defined( - self.definitions.types.alloc(DefinedType::Borrow(*id)), - )); - } - } - - Err(Error::NotResourceType { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }) - } - ast::Type::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Value(id)) => Ok(ValueType::Defined(id)), - _ => Err(Error::NotValueType { - name: id.string, - kind: item.kind.as_str(&self.definitions), - span: id.span, - }), - } - } - } - } - - fn func_type<'a>( - &mut self, - state: &ResolutionState<'a>, - func_params: &[ast::NamedType<'a>], - func_results: &ast::ResultList<'a>, - kind: FuncKind, - ) -> ResolutionResult<'a, FuncId> { - let mut params = IndexMap::new(); - 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, - kind, - span: param.id.span, - }); - } - } - - let results = match func_results { - ast::ResultList::Empty => None, - ast::ResultList::Named(results) => { - let mut list = IndexMap::new(); - for result in results { - if list - .insert(result.id.string.to_owned(), self.ty(state, &result.ty)?) - .is_some() - { - return Err(Error::DuplicateResult { - name: result.id.string, - kind, - span: result.id.span, - }); - } - } - Some(FuncResult::List(list)) - } - ast::ResultList::Scalar(ty) => Some(FuncResult::Scalar(self.ty(state, ty)?)), - }; - - Ok(self.definitions.funcs.alloc(Func { params, results })) - } - - fn resolve_package<'a, 'b>( - &mut self, - state: &'b mut ResolutionState<'a>, - name: &'a str, - version: Option<&Version>, - span: Span<'a>, - ) -> ResolutionResult<'a, &'b Package> { - match state.packages.entry(name.to_owned()) { - hash_map::Entry::Occupied(e) => Ok(e.into_mut()), - hash_map::Entry::Vacant(e) => { - log::debug!("resolving package `{name}`"); - match state - .resolver - .as_deref() - .and_then(|r| r.resolve(name, version).transpose()) - .transpose() - .map_err(|e| Error::PackageResolutionFailure { - name, - span, - source: e, - })? { - Some(bytes) => Ok(e.insert( - Package::parse(&mut self.definitions, bytes).map_err(|e| { - Error::PackageParseFailure { - name, - span, - source: e, - } - })?, - )), - None => Err(Error::UnknownPackage { name, span }), - } - } - } - } - - fn resolve_package_export<'a>( - &mut self, - state: &mut ResolutionState<'a>, - path: &ast::PackagePath<'a>, - ) -> ResolutionResult<'a, ItemKind> { - // Check for reference to local item - if path.name == self.package { - 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 = 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) - .map(Extern::kind) - } - // The parent is a world or component or component instantiation - ItemKind::Type(Type::World(id)) - | ItemKind::Component(id) - | ItemKind::Instantiation(id) => self.definitions.worlds[id] - .exports - .get(segment) - .map(Extern::kind), - _ => 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.span.as_str(), - export: segment, - kind: parent_ty, - 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<'a>( - &self, - state: &ResolutionState, - path: &ast::PackagePath<'a>, - ) -> ResolutionResult<'a, ItemKind> { - log::debug!("resolving local path `{path}`", path = path.span.as_str()); - - let mut segments = path.segment_spans(); - let (segment, span) = segments.next().unwrap(); - let item = &self.items[self.scopes[state.root_scope].get(segment).ok_or({ - Error::UndefinedName { - name: segment, - span, - } - })?]; - - 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, - kind: kind.as_str(&self.definitions), - export: segment, - span, - }); - } - }; - - kind = exports.get(segment).map(Extern::kind).ok_or_else(|| { - Error::PackagePathMissingExport { - name: current, - kind: kind.as_str(&self.definitions), - export: segment, - span, - } - })?; - - current = segment; - } - - Ok(kind) - } - - fn expr<'a>( - &mut self, - state: &mut ResolutionState<'a>, - expr: &'a ast::Expr, - ) -> ResolutionResult<'a, ItemId> { - let mut item = self.primary_expr(state, &expr.primary)?; - - for expr in &expr.postfix { - item = self.postfix_expr(state, item, expr)?; - } - - Ok(item) - } - - fn primary_expr<'a>( - &mut self, - state: &mut ResolutionState<'a>, - expr: &'a ast::PrimaryExpr<'a>, - ) -> ResolutionResult<'a, ItemId> { - match expr { - ast::PrimaryExpr::New(e) => self.new_expr(state, e), - ast::PrimaryExpr::Nested(e) => self.expr(state, &e.0), - ast::PrimaryExpr::Ident(i) => Ok(self.require(state, i)?), - } - } - - fn new_expr<'a>( - &mut self, - state: &mut ResolutionState<'a>, - expr: &'a ast::NewExpr<'a>, - ) -> ResolutionResult<'a, ItemId> { - let pkg = self.resolve_package( - state, - expr.package.name, - expr.package.version.as_ref(), - expr.package.span, - )?; - let ty = pkg.ty; - let require_all = !expr.ellipsis; - - let mut arguments: IndexMap = Default::default(); - for arg in &expr.arguments { - let (name, item, span) = match arg { - ast::InstantiationArgument::Named(arg) => { - self.named_instantiation_arg(state, arg, ty)? - } - ast::InstantiationArgument::Ident(id) => { - self.ident_instantiation_arg(state, id, ty)? - } - }; - - let world = &self.definitions.worlds[ty]; - let expected = - world - .imports - .get(&name) - .ok_or_else(|| Error::MissingComponentImport { - package: expr.package.span.as_str(), - import: name.clone(), - span, - })?; - - SubtypeChecker::new(&self.definitions) - .is_subtype(expected.kind(), self.items[item].kind) - .map_err(|e| Error::MismatchedInstantiationArg { - name: name.clone(), - span, - source: e, - })?; - - let prev = arguments.insert(name.clone(), item); - if prev.is_some() { - return Err(Error::DuplicateInstantiationArg { name, span }); - } - } - - if require_all { - let world = &self.definitions.worlds[ty]; - if let Some((missing, _)) = world - .imports - .iter() - .find(|(n, _)| !arguments.contains_key(*n)) - { - return Err(Error::MissingInstantiationArg { - name: missing.clone(), - package: expr.package.span.as_str(), - span: expr.package.span, - }); - } - } - - Ok(self.items.alloc(Item { - kind: ItemKind::Instantiation(ty), - source: ItemSource::Instantiation { arguments }, - })) - } - - fn named_instantiation_arg<'a>( - &mut self, - state: &mut ResolutionState<'a>, - arg: &'a ast::NamedInstantiationArgument<'a>, - world: WorldId, - ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { - 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 ident_instantiation_arg<'a>( - &mut self, - state: &mut ResolutionState<'a>, - ident: &ast::Ident<'a>, - world: WorldId, - ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { - let item_id = self.require(state, ident)?; - let item = &self.items[item_id]; - 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.source { - ItemSource::Import(name) | ItemSource::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 find_matching_interface_name<'a>(name: &str, externs: &'a ExternMap) -> Option<&'a 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, item_id: ItemId) -> Option<&str> { - let item = &self.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 { - if !self.exports.contains_key(id.as_str()) { - return Some(id); - } - } - } - - // If the item comes from an import or an alias, try the name associated with it - match &item.source { - ItemSource::Import(name) | ItemSource::Alias { export: name, .. } - if !self.exports.contains_key(name) => - { - Some(name) - } - _ => None, - } - } - - fn postfix_expr<'a>( - &mut self, - state: &mut ResolutionState<'a>, - item: ItemId, - expr: &ast::PostfixExpr<'a>, - ) -> ResolutionResult<'a, ItemId> { - match expr { - ast::PostfixExpr::Access(expr) => { - let exports = self.instance_exports(item, expr.id.span)?; - let name = Self::find_matching_interface_name(expr.id.string, exports) - .unwrap_or(expr.id.string) - .to_owned(); - self.access_expr(state, item, name, expr.id.span) - } - ast::PostfixExpr::NamedAccess(expr) => { - self.access_expr(state, item, expr.string.value.to_owned(), expr.string.span) - } - } - } - - fn instance_exports<'a>( - &self, - item: ItemId, - span: Span<'a>, - ) -> ResolutionResult<'a, &ExternMap> { - match self.items[item].kind { - ItemKind::Instance(id) => Ok(&self.definitions.interfaces[id].exports), - ItemKind::Instantiation(id) => Ok(&self.definitions.worlds[id].exports), - ItemKind::Type(Type::Interface(_)) => Err(Error::InaccessibleInterface { span }), - kind => Err(Error::Inaccessible { - kind: kind.as_str(&self.definitions), - span, - }), - } - } - - fn access_expr<'a>( - &mut self, - state: &mut ResolutionState<'a>, - item: ItemId, - name: String, - span: Span<'a>, - ) -> ResolutionResult<'a, ItemId> { - let exports = self.instance_exports(item, span)?; - let kind = - exports - .get(&name) - .map(Extern::kind) - .ok_or_else(|| Error::MissingInstanceExport { - name: name.clone(), - span, - })?; - - match state.aliases.entry((item, name.clone())) { - hash_map::Entry::Occupied(e) => Ok(*e.get()), - hash_map::Entry::Vacant(e) => { - let id = self.items.alloc(Item { - kind, - source: ItemSource::Alias { item, export: name }, - }); - Ok(*e.insert(id)) - } - } + /// Encode the composition into a WebAssembly component. + pub fn encode(&self, options: EncodingOptions) -> anyhow::Result> { + Encoder::new(self, options).encode() } } diff --git a/crates/wac-parser/src/resolution/ast.rs b/crates/wac-parser/src/resolution/ast.rs new file mode 100644 index 0000000..bbda725 --- /dev/null +++ b/crates/wac-parser/src/resolution/ast.rs @@ -0,0 +1,2126 @@ +use super::{ + package::Package, Composition, DefinedType, Definitions, Enum, Error, ExternKind, Flags, Func, + FuncId, FuncKind, FuncResult, Interface, InterfaceId, ItemKind, PackageResolver, PrimitiveType, + Record, ResolutionResult, Resource, ResourceId, SubtypeChecker, Type, ValueType, Variant, + World, WorldId, +}; +use crate::{ast, lexer::Span, line_column, method_extern_name, Item, ItemId, PackageId}; +use anyhow::Context; +use id_arena::Arena; +use indexmap::{IndexMap, IndexSet}; +use semver::Version; +use std::collections::{hash_map, HashMap, HashSet}; +use wasmparser::names::{ComponentName, ComponentNameKind}; + +#[derive(Default)] +struct Scope<'a> { + names: IndexMap)>, + items: Arena, +} + +impl<'a> Scope<'a> { + fn get(&self, name: &str) -> Option<(ItemId, Span<'a>)> { + 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: Span<'a>, + /// The imported item. + item: ItemId, +} + +struct State<'a> { + document: &'a ast::Document<'a>, + resolver: Option>, + scopes: Vec>, + current: Scope<'a>, + packages: Arena, + /// The map of package name to id. + package_map: HashMap, + /// The map of instance items and export names to the aliased item id. + aliases: HashMap<(ItemId, String), ItemId>, + /// The map of imported items. + /// This is used to keep track of implicit imports and merge them together. + imports: IndexMap>, +} + +impl<'a> State<'a> { + fn new(document: &'a ast::Document<'a>, resolver: Option>) -> Self { + Self { + document, + resolver, + scopes: Default::default(), + current: Default::default(), + packages: Default::default(), + package_map: Default::default(), + aliases: Default::default(), + imports: Default::default(), + } + } + + // Gets an item by identifier from the root scope. + fn root_item(&self, id: &ast::Ident<'a>) -> ResolutionResult<'a, (ItemId, &Item)> { + let scope = self.root_scope(); + + let id = scope + .get(id.string) + .ok_or(Error::UndefinedName { + name: id.string, + 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<'a, (ItemId, &Item)> { + let id = self + .current + .get(id.string) + .ok_or(Error::UndefinedName { + name: id.string, + 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<'a, (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<'a> { + self.scopes.first().unwrap_or(&self.current) + } + + fn register_name(&mut self, id: ast::Ident<'a>, item: ItemId) -> ResolutionResult<'a, ()> { + 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)) + { + let (line, column) = line_column(id.span.source(), span.start); + return Err(Error::DuplicateName { + name: id.string, + path: self.document.path, + line, + column, + span: id.span, + }); + } + + Ok(()) + } +} + +pub struct AstResolver<'a> { + document: &'a ast::Document<'a>, + definitions: Definitions, + exports: IndexMap, +} + +impl<'a> AstResolver<'a> { + pub fn new(document: &'a ast::Document<'a>) -> Self { + Self { + document, + definitions: Default::default(), + exports: Default::default(), + } + } + + pub fn resolve( + mut self, + resolver: Option>, + ) -> ResolutionResult<'a, Composition> { + let mut state = State::new(self.document, resolver); + + 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)?, + } + } + + assert!(state.scopes.is_empty()); + + Ok(Composition { + package: self.document.package.name.to_owned(), + version: self.document.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: self.exports, + }) + } + + fn import_statement( + &mut self, + state: &mut State<'a>, + stmt: &'a ast::ImportStatement<'a>, + ) -> ResolutionResult<'a, ()> { + log::debug!( + "resolving import statement for id `{id}`", + id = stmt.id.string + ); + let kind = match &stmt.ty { + ast::ImportType::Package(p) => self.resolve_package_export(state, p)?, + ast::ImportType::Func(ty) => ItemKind::Func(self.func_type( + state, + &ty.params, + &ty.results, + FuncKind::Free, + None, + )?), + ast::ImportType::Interface(i) => self.inline_interface(state, i)?, + ast::ImportType::Ident(id) => state.local_item(id)?.1.kind(), + }; + + // Promote function types, instance types, and component types to functions, instances, and components + let kind = match kind { + 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, + }; + + let (name, span) = if let Some(with) = stmt.with { + (with.value, with.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(), stmt.id.span) + } else { + (stmt.id.string, stmt.id.span) + } + } else { + (stmt.id.string, stmt.id.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) + { + let (line, column) = line_column(span.source(), prev_span.start); + return Err(Error::ImportConflict { + name: name.to_owned(), + package, + path: state.document.path, + line, + column, + span, + }); + } + + // TODO: should use prev_span with this error + return Err(Error::DuplicateExternName { + name: name.to_owned(), + kind: ExternKind::Import, + span, + }); + } + _ => 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<'a, ()> { + log::debug!("resolving type statement"); + + let (name, item) = match stmt { + ast::TypeStatement::Interface(i) => (i.id.string, self.interface_decl(state, i)?), + ast::TypeStatement::World(w) => (w.id.string, self.world_decl(state, w)?), + ast::TypeStatement::Type(t) => (t.id().string, self.type_decl(state, t)?), + }; + + let prev = self.exports.insert(name.to_owned(), item); + assert!(prev.is_none()); + Ok(()) + } + + fn let_statement( + &mut self, + state: &mut State<'a>, + stmt: &'a ast::LetStatement<'a>, + ) -> ResolutionResult<'a, ()> { + 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<'a, ()> { + log::debug!("resolving export statement"); + let item = self.expr(state, &stmt.expr)?; + let (name, span) = if let Some(name) = stmt.with { + (name.value, name.span) + } else { + ( + self.infer_export_name(state, item) + .ok_or(Error::ExportRequiresWith { span: stmt.span })?, + stmt.span, + ) + }; + + // Validate the export name + match ComponentName::new(name, 0) + .map_err(|e| { + let msg = e.to_string(); + Error::InvalidExternName { + name: name.to_string(), + kind: ExternKind::Export, + span, + source: anyhow::anyhow!( + "{msg}", + msg = msg.strip_suffix(" (at offset 0x0)").unwrap_or(&msg) + ), + } + })? + .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"), + }); + } + _ => {} + } + + // Ensure the export does not conflict with a defined item as + // they are implicitly exported. + 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 { + let (line, column) = line_column(stmt.span.source(), prev_span.start); + return Err(Error::ExportConflict { + name: name.to_owned(), + path: state.document.path, + kind: definition.kind.as_str(&self.definitions), + line, + column, + hint: if stmt.with.is_some() { + "" + } else { + " (consider using a `with` clause to use a different name)" + }, + span, + }); + } + } + + if self.exports.contains_key(name) { + return Err(Error::DuplicateExternName { + name: name.to_owned(), + kind: ExternKind::Export, + span, + }); + } + + let prev = self.exports.insert(name.to_owned(), item); + assert!(prev.is_none()); + + Ok(()) + } + + fn inline_interface( + &mut self, + state: &mut State<'a>, + iface: &'a ast::InlineInterface<'a>, + ) -> ResolutionResult<'a, ItemKind> { + 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, &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.package.name, + version = if let Some(version) = &self.document.package.version { + format!("@{version}") + } else { + String::new() + } + ) + } + + fn interface_decl( + &mut self, + state: &mut State<'a>, + decl: &'a ast::InterfaceDecl<'a>, + ) -> ResolutionResult<'a, ItemId> { + 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, &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<'a, ItemId> { + 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<'a, ()> { + 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, + interface_name: name, + 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<'a, ()> { + 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<'a, ()> { + 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, + kind: kind.as_str(&self.definitions), + 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, + kind: kind.as_str(&self.definitions), + 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.span.as_str(), + kind: kind.as_str(&self.definitions), + span: p.span, + }); + } + }, + }; + + if kind == ExternKind::Import { + ty.imports.insert(k, v); + } else { + ty.exports.insert(k, v); + } + + return Ok(()); + + fn check_name<'a>( + name: &str, + span: Span<'a>, + ty: &World, + world: &'a str, + kind: ExternKind, + ) -> ResolutionResult<'a, ()> { + 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, + span, + }); + } + + Ok(()) + } + } + + fn world_include( + &mut self, + state: &mut State<'a>, + include: &ast::WorldInclude<'a>, + world: &'a str, + ty: &mut World, + ) -> ResolutionResult<'a, ()> { + 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, + 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, + kind: kind.as_str(&self.definitions), + 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.span.as_str(), + kind: kind.as_str(&self.definitions), + 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(), + name: missing.from.string, + 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<'a, String> { + // 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: world, + span, + hint: if !include.with.is_empty() { + "" + } else { + " (consider using a `with` clause to use a different name)" + }, + }); + } + + Ok(name.to_owned()) + } + } + + fn use_type( + &mut self, + state: &mut State<'a>, + use_type: &ast::Use<'a>, + uses: &mut IndexMap>, + externs: &mut IndexMap, + in_world: bool, + ) -> ResolutionResult<'a, ()> { + 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.span.as_str()), + kind => { + return Err(Error::NotInterface { + name: path.span.as_str(), + kind: kind.as_str(&self.definitions), + 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, + kind: kind.as_str(&self.definitions), + span: id.span, + }); + } + } + } + }; + + for item in &use_type.items { + let ident = item.as_id.unwrap_or(item.id); + let (index, _, kind) = self.definitions.interfaces[interface] + .exports + .get_full(item.id.string) + .ok_or(Error::UndefinedInterfaceType { + name: item.id.string, + interface_name: name, + 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, + kind: if in_world { + ExternKind::Import + } else { + ExternKind::Export + }, + hint: if item.as_id.is_some() { + "" + } else { + " (consider using an `as` clause to use a different name)" + }, + span: ident.span, + }); + } + + uses.entry(interface).or_default().insert(index); + 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, + kind: kind.as_str(&self.definitions), + interface_name: name, + span: item.id.span, + }); + } + } + } + + Ok(()) + } + + fn type_decl( + &mut self, + state: &mut State<'a>, + decl: &'a ast::TypeDecl, + ) -> ResolutionResult<'a, ItemId> { + 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<'a, ItemId> { + 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<'a, ItemId> { + 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, + 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, + resource: decl.id.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<'a, ItemId> { + 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, + name: decl.id.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<'a, ItemId> { + 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, + name: decl.id.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<'a, ItemId> { + 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, + name: decl.id.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<'a, ItemId> { + 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, + name: decl.id.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<'a, ItemId> { + 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, + kind: kind.as_str(&self.definitions), + 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<'a, FuncId> { + 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, + kind: kind.as_str(&self.definitions), + span: id.span, + }), + } + } + } + } + + fn ty(&mut self, state: &State<'a>, ty: &ast::Type<'a>) -> ResolutionResult<'a, ValueType> { + 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::Float32(_) => Ok(ValueType::Primitive(PrimitiveType::Float32)), + ast::Type::Float64(_) => Ok(ValueType::Primitive(PrimitiveType::Float64)), + 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, + kind: kind.as_str(&self.definitions), + 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, + kind: kind.as_str(&self.definitions), + 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<'a, FuncId> { + 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, + 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, + 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<&Version>, + span: Span<'a>, + ) -> ResolutionResult<'a, PackageId> { + match state.package_map.entry(if let Some(version) = version { + format!("{name}@{version}") + } else { + name.to_owned() + }) { + hash_map::Entry::Occupied(e) => Ok(*e.get()), + hash_map::Entry::Vacant(e) => { + log::debug!("resolving package `{name}`"); + match state + .resolver + .as_deref() + .and_then(|r| r.resolve(name, version).transpose()) + .transpose() + .map_err(|e| Error::PackageResolutionFailure { + name, + span, + source: e, + })? { + Some(bytes) => { + let id = state.packages.alloc( + Package::parse(&mut self.definitions, name, version, bytes).map_err( + |e| Error::PackageParseFailure { + name, + span, + source: e, + }, + )?, + ); + Ok(*e.insert(id)) + } + None => Err(Error::UnknownPackage { name, span }), + } + } + } + } + + fn resolve_package_export( + &mut self, + state: &mut State<'a>, + path: &ast::PackagePath<'a>, + ) -> ResolutionResult<'a, ItemKind> { + // Check for reference to local item + if path.name == self.document.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.span.as_str(), + export: segment, + kind: parent_ty, + 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<'a, ItemKind> { + log::debug!("resolving local path `{path}`", path = path.span.as_str()); + + 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, + kind: kind.as_str(&self.definitions), + export: segment, + span, + }); + } + }; + + kind = + exports + .get(segment) + .copied() + .ok_or_else(|| Error::PackagePathMissingExport { + name: current, + kind: kind.as_str(&self.definitions), + export: segment, + span, + })?; + + current = segment; + } + + Ok(kind) + } + + fn expr(&mut self, state: &mut State<'a>, expr: &'a ast::Expr) -> ResolutionResult<'a, ItemId> { + let mut item = self.primary_expr(state, &expr.primary)?; + + for expr in &expr.postfix { + item = self.postfix_expr(state, item, expr)?; + } + + Ok(item) + } + + fn primary_expr( + &mut self, + state: &mut State<'a>, + expr: &'a ast::PrimaryExpr<'a>, + ) -> ResolutionResult<'a, ItemId> { + match expr { + ast::PrimaryExpr::New(e) => self.new_expr(state, e), + ast::PrimaryExpr::Nested(e) => self.expr(state, &e.0), + 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<'a, ItemId> { + if expr.package.name == self.document.package.name { + return Err(Error::CannotInstantiateSelf { + 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 require_all = !expr.ellipsis; + + let mut arguments: IndexMap = Default::default(); + for arg in &expr.arguments { + let (name, item, span) = match arg { + ast::InstantiationArgument::Named(arg) => { + self.named_instantiation_arg(state, arg, ty)? + } + ast::InstantiationArgument::Ident(id) => { + self.ident_instantiation_arg(state, id, ty)? + } + }; + + let world = &self.definitions.worlds[ty]; + let expected = + world + .imports + .get(&name) + .ok_or_else(|| Error::MissingComponentImport { + package: expr.package.span.as_str(), + import: name.clone(), + span, + })?; + + log::debug!( + "performing subtype check for argument `{name}` (item {item})", + item = item.index() + ); + + SubtypeChecker::new(&self.definitions, &state.packages) + .is_subtype(*expected, state.current.items[item].kind()) + .map_err(|e| Error::MismatchedInstantiationArg { + name: name.clone(), + span, + source: e, + })?; + + let prev = arguments.insert(name.clone(), item); + if prev.is_some() { + return Err(Error::DuplicateInstantiationArg { name, span }); + } + } + + // 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: name.clone(), + package: expr.package.span.as_str(), + 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); + } + + Ok(state + .current + .items + .alloc(Item::Instantiation(super::Instantiation { + package: pkg, + arguments, + }))) + } + + fn implicit_import( + &mut self, + state: &mut State<'a>, + name: String, + mut kind: ItemKind, + package: &'a str, + span: Span<'a>, + ) -> ResolutionResult<'a, ItemId> { + assert!(state.scopes.is_empty()); + + // If the item is an instance, we need to recurse on its dependencies + if let ItemKind::Instance(id) = kind { + let interface = &self.definitions.interfaces[id]; + let deps = interface + .uses + .keys() + .map(|id| { + ( + *id, + self.definitions.interfaces[*id] + .id + .as_ref() + .unwrap() + .clone(), + ) + }) + .collect::>(); + for (dep, name) in deps { + self.implicit_import(state, name, ItemKind::Instance(dep), package, span)?; + } + } + + if let Some(import) = state.imports.get(&name) { + // Check if the implicit import would conflict with an explicit import + if import.package.is_none() { + let (line, column) = line_column(span.source(), import.span.start); + return Err(Error::InstantiationArgConflict { + name: name.to_owned(), + path: state.document.path, + kind: kind.as_str(&self.definitions), + line, + column, + span, + }); + }; + + // Ensure the import and the existing import are instances, otherwise + // we cannot merge them + let id = match (kind, state.current.items[import.item].kind()) { + (ItemKind::Instance(id), ItemKind::Instance(_)) => id, + (_, kind) => { + let (line, column) = line_column(span.source(), import.span.start); + return Err(Error::UnmergeableInstantiationArg { + name: name.to_owned(), + package: import.package.unwrap(), + path: state.document.path, + kind: kind.as_str(&self.definitions), + line, + column, + span, + }); + } + }; + + log::debug!( + "merging implicit interface import `{name}` ({id})", + id = id.index(), + ); + + let item = import.item; + self.merge_instance_import(state, &name, id, span)?; + return Ok(item); + } + + 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(); + target.uses = self.remap_uses(state, 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: Span<'a>, + ) -> ResolutionResult<'a, ()> { + 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(*target_kind, *source_kind) + .with_context(|| format!("mismatched type for export `{name}`")), + checker + .is_subtype(*source_kind, *target_kind) + .with_context(|| format!("mismatched type for export `{name}`")), + ) { + (Ok(_), Ok(_)) => { + // The two are compatible, so do nothing + } + (Err(e), _) | (_, Err(e)) => { + // Neither is a subtype of the other, so error + let (line, column) = line_column(span.source(), import_span.start); + return Err(Error::InstantiationArgMergeFailure { + name: name.to_owned(), + package: import.package.unwrap(), + path: state.document.path, + kind: "instance", + line, + column, + 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 (dep, exports) in &source.uses { + target.uses.entry(*dep).or_default().extend(exports); + } + + // Remap the usings to point at imported interfaces + target.uses = self.remap_uses(state, target.uses); + self.definitions.interfaces[target_id] = target; + } + + fn remap_uses( + &self, + state: &State, + uses: IndexMap>, + ) -> IndexMap> { + // Now update all the interface ids in the usings + uses.into_iter() + .map(|(dep, exports)| { + let import = + &state.imports[self.definitions.interfaces[dep].id.as_deref().unwrap()]; + match &state.current.items[import.item] { + super::Item::Import(super::Import { + kind: ItemKind::Instance(id), + .. + }) => (*id, exports), + _ => unreachable!(), + } + }) + .collect() + } + + fn named_instantiation_arg( + &mut self, + state: &mut State<'a>, + arg: &'a ast::NamedInstantiationArgument<'a>, + world: WorldId, + ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { + 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 ident_instantiation_arg( + &mut self, + state: &mut State<'a>, + ident: &ast::Ident<'a>, + world: WorldId, + ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { + 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 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>, + ) -> ResolutionResult<'a, ItemId> { + match expr { + ast::PostfixExpr::Access(expr) => { + let exports = self.instance_exports(state, item, expr.span)?; + let name = Self::find_matching_interface_name(expr.id.string, exports) + .unwrap_or(expr.id.string) + .to_owned(); + self.access_expr(state, item, name, expr.span) + } + ast::PostfixExpr::NamedAccess(expr) => { + self.access_expr(state, item, expr.string.value.to_owned(), expr.span) + } + } + } + + fn instance_exports( + &self, + state: &State, + item: ItemId, + span: Span<'a>, + ) -> ResolutionResult<'a, &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) + } + ItemKind::Type(Type::Interface(_)) => Err(Error::InaccessibleInterface { span }), + kind => Err(Error::Inaccessible { + kind: kind.as_str(&self.definitions), + span, + }), + } + } + + fn access_expr( + &mut self, + state: &mut State<'a>, + item: ItemId, + name: String, + span: Span<'a>, + ) -> ResolutionResult<'a, ItemId> { + let exports = self.instance_exports(state, item, span)?; + let kind = exports + .get(&name) + .copied() + .ok_or_else(|| Error::MissingInstanceExport { + name: name.clone(), + span, + })?; + + match state.aliases.entry((item, name.clone())) { + hash_map::Entry::Occupied(e) => Ok(*e.get()), + hash_map::Entry::Vacant(e) => { + let id = state.current.items.alloc(Item::Alias(super::Alias { + item, + export: name, + kind, + })); + Ok(*e.insert(id)) + } + } + } +} diff --git a/crates/wac-parser/src/resolution/encoding.rs b/crates/wac-parser/src/resolution/encoding.rs new file mode 100644 index 0000000..a72b685 --- /dev/null +++ b/crates/wac-parser/src/resolution/encoding.rs @@ -0,0 +1,1174 @@ +//! 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, +}; +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))?), + Type::Value(id) => (ty, encoder.ty(state, Type::Value(id))?), + 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))?), + }, + _ => 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)?)), + ItemKind::Func(id) => ComponentTypeRef::Func(encoder.ty(state, Type::Func(id))?), + ItemKind::Instance(id) => { + ComponentTypeRef::Instance(encoder.ty(state, Type::Interface(id))?) + } + ItemKind::Component(id) => { + ComponentTypeRef::Component(encoder.ty(state, Type::World(id))?) + } + ItemKind::Module(id) => ComponentTypeRef::Module(encoder.ty(state, Type::Module(id))?), + 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::Float32 => Self::Float64, + PrimitiveType::Float64 => Self::Float64, + 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(Clone, PartialEq, Eq, Hash, Debug)] +enum AliasKey<'a> { + Resource(&'a str), + Type(Type), +} + +impl AliasKey<'_> { + fn as_str(&self, definitions: &Definitions) -> &str { + match self { + AliasKey::Resource(name) => name, + AliasKey::Type(ty) => ty.as_str(definitions), + } + } +} + +#[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 aliasable types to their encoded indexes. + type_aliases: IndexMap, 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, definitions: &Definitions, key: AliasKey) -> Option { + if let Some(index) = self.current.type_aliases.get(&key) { + return Some(*index); + } + + if let Some(parent) = self.scopes.last() { + if let Some(outer) = parent.type_aliases.get(&key) { + let index = self.current.encodable.type_count(); + log::debug!( + "encoding outer alias for {key} to type index {index}", + key = key.as_str(definitions) + ); + 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) -> Result { + if let Some(index) = state.current.type_indexes.get(&ty) { + return Ok(*index); + } + + let index = if let Some(index) = state.used_type_index(self.0, AliasKey::Type(ty)) { + index + } else { + 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: &IndexMap>) { + for (id, exports) in uses { + let interface = &self.0.interfaces[*id]; + let instance = state.current.instances[id]; + for export in exports { + let index = state.current.encodable.type_count(); + let (export, kind) = interface.exports.get_index(*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}"); + + match kind { + ItemKind::Resource(id) => { + state + .current + .type_aliases + .insert(AliasKey::Resource(&self.0.resources[*id].name), index); + } + ItemKind::Type(ty) => { + state + .current + .type_aliases + .insert(AliasKey::Type(*ty), index); + } + _ => unreachable!("use of non-type"), + } + } + } + } + + 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 owner in interface.uses.keys() { + self.import_deps(state, self.0, *owner)?; + } + + // 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(_) | 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 dep in world.uses.keys() { + self.import_deps(state, self.0, *dep)?; + } + + 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>, + definitions: &Definitions, + id: InterfaceId, + ) -> anyhow::Result<()> { + if state.current.instances.contains_key(&id) { + return Ok(()); + } + + let interface = &definitions.interfaces[id]; + + // Depth-first recurse on the dependencies of this interface + for id in interface.uses.keys() { + self.import_deps(state, definitions, *id)?; + } + + 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, true)?; + 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 dep in interface.uses.keys() { + self.import_deps(state, self.0, *dep)?; + } + + 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)?; + + 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(self.0, AliasKey::Resource(&resource.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)?; + 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(self.0, AliasKey::Resource(&resource.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 index 9a7bcdd..9d8ff93 100644 --- a/crates/wac-parser/src/resolution/package.rs +++ b/crates/wac-parser/src/resolution/package.rs @@ -1,34 +1,48 @@ -use self::cache::{MappedResourceId, ResourceMapper, TypeCache}; use super::{ - CoreExtern, CoreFunc, DefinedType, DefinedTypeId, Definitions, Enum, Extern, Flags, Func, - FuncId, FuncKind, FuncResult, Interface, InterfaceId, ItemKind, Module, ModuleId, Record, - Resource, ResourceMethod, Type, ValueType, Variant, World, WorldId, + 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}; use anyhow::{bail, Result}; use indexmap::IndexMap; +use semver::Version; +use serde::Serialize; use std::{collections::HashMap, fmt, rc::Rc}; use wasmparser::{ - types::{self as wasm}, + names::{ComponentName, ComponentNameKind}, + types::{self as wasm, ComponentAnyTypeId}, Chunk, Encoding, Parser, Payload, ValidPayload, Validator, WasmFeatures, }; -mod cache; - /// 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: Vec, /// The world (component type) of the package. - pub ty: WorldId, + #[serde(serialize_with = "serialize_id")] + pub world: WorldId, /// Defined interfaces and worlds from a WIT package. - pub definitions: HashMap, + pub definitions: IndexMap, } impl Package { /// Parses the given bytes into a package. - pub(crate) fn parse(definitions: &mut Definitions, bytes: Vec) -> Result { + pub(crate) fn parse( + definitions: &mut Definitions, + name: &str, + version: Option<&Version>, + bytes: Vec, + ) -> Result { let mut parser = Parser::new(0); let mut parsers = Vec::new(); let mut validator = Validator::new_with_features(WasmFeatures { @@ -87,21 +101,24 @@ impl Package { .map(|i| Ok((i.to_string(), converter.import(i)?))) .collect::>()?; - let exports = exports + let exports: IndexMap = exports .into_iter() .map(|i| Ok((i.to_string(), converter.export(i)?))) .collect::>()?; - let ty = definitions.worlds.alloc(World { + let world = definitions.worlds.alloc(World { + id: None, + uses: Default::default(), imports, - exports, - scope: None, + exports: exports.clone(), }); return Ok(Self { + name: name.to_owned(), + version: version.map(ToOwned::to_owned), bytes, - ty, - definitions: Self::find_definitions(definitions, ty), + world, + definitions: Self::find_definitions(definitions, world), }); } }, @@ -112,29 +129,30 @@ impl Package { } } - fn find_definitions(definitions: &Definitions, world: WorldId) -> HashMap { + 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 = HashMap::new(); - for (name, ext) in exports { - if let Extern::Kind(ItemKind::Type(Type::World(id))) = ext { + 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 fully qualified - let (qname, ext) = world.exports.get_index(0).unwrap(); - if !qname.contains(':') { - 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 ext.kind() { + match kind { ItemKind::Instance(id) => { - defs.insert(name.clone(), ItemKind::Type(Type::Interface(id))); + defs.insert(name.clone(), ItemKind::Type(Type::Interface(*id))); } ItemKind::Component(id) => { - defs.insert(name.clone(), ItemKind::Type(Type::World(id))); + defs.insert(name.clone(), ItemKind::Type(Type::World(*id))); } _ => continue, } @@ -148,51 +166,61 @@ impl Package { 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("ty", &self.ty) + .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: TypeCache, - mapper: ResourceMapper, - owners: HashMap, - resources: HashMap, + 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); - let mapper = ResourceMapper::new(types.clone()); - let cache = TypeCache::new(types.clone(), mapper.map()); Self { definitions, types, - cache, - mapper, + cache: Default::default(), + resource_map: Default::default(), owners: Default::default(), - resources: Default::default(), } } - fn import(&mut self, name: &str) -> Result { + fn import(&mut self, name: &str) -> Result { let import = self.types.component_entity_type_of_import(name).unwrap(); - // We must map any resources before we can convert the import - self.mapper.component_entity_type(name, import); - Ok(Extern::Kind(self.component_entity_type(name, import)?)) + self.component_entity_type(name, import) } - fn export(&mut self, name: &str) -> Result { + fn export(&mut self, name: &str) -> Result { let export = self.types.component_entity_type_of_export(name).unwrap(); - // We must map any resources before we can convert the export - self.mapper.component_entity_type(name, export); - Ok(Extern::Kind(self.component_entity_type(name, export)?)) + self.component_entity_type(name, export) } fn component_entity_type( @@ -202,36 +230,29 @@ impl<'a> TypeConverter<'a> { ) -> Result { match ty { wasm::ComponentEntityType::Module(id) => Ok(ItemKind::Module(self.module_type(id)?)), - wasm::ComponentEntityType::Func(id) => Ok(ItemKind::Func( - self.component_func_type(id, FuncKind::Free)?, - )), + wasm::ComponentEntityType::Func(id) => { + Ok(ItemKind::Func(self.component_func_type(id)?)) + } wasm::ComponentEntityType::Value(ty) => { Ok(ItemKind::Value(self.component_val_type(ty)?)) } - wasm::ComponentEntityType::Type { referenced, .. } => Ok(ItemKind::Type( - self.ty(wasm::AnyTypeId::Component(referenced))?, - )), + wasm::ComponentEntityType::Type { created, .. } => { + Ok(ItemKind::Type(self.ty(created)?)) + } wasm::ComponentEntityType::Instance(ty) => Ok(ItemKind::Instance( self.component_instance_type(Some(name), ty)?, )), wasm::ComponentEntityType::Component(ty) => { - Ok(ItemKind::Component(self.component_type(ty)?)) + Ok(ItemKind::Component(self.component_type(Some(name), ty)?)) } } } - fn component_func_type( - &mut self, - id: wasm::ComponentFuncTypeId, - kind: FuncKind, - ) -> Result { - let key = self.cache.key( - None, - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Func(id)), - ); + 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 { - Type::Func(id) => return Ok(id), + Entity::Type(Type::Func(id)) => return Ok(*id), _ => unreachable!("invalid cached type"), } } @@ -241,11 +262,10 @@ impl<'a> TypeConverter<'a> { let params = func_ty .params .iter() - .skip(if kind == FuncKind::Method { 1 } else { 0 }) .map(|(name, ty)| Ok((name.to_string(), self.component_val_type(*ty)?))) .collect::>()?; - let results = if kind == FuncKind::Constructor || func_ty.results.len() == 0 { + 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( @@ -267,18 +287,15 @@ impl<'a> TypeConverter<'a> { }; let id = self.definitions.funcs.alloc(Func { params, results }); - self.cache.insert(key, Type::Func(id)); + self.cache.insert(key, Entity::Type(Type::Func(id))); Ok(id) } fn module_type(&mut self, id: wasm::ComponentCoreModuleTypeId) -> Result { - let key = self.cache.key( - None, - wasm::AnyTypeId::Core(wasm::ComponentCoreTypeId::Module(id)), - ); + let key = wasm::AnyTypeId::Core(wasm::ComponentCoreTypeId::Module(id)); if let Some(ty) = self.cache.get(&key) { match ty { - Type::Module(id) => return Ok(id), + Entity::Type(Type::Module(id)) => return Ok(*id), _ => unreachable!("invalid cached type"), } } @@ -297,34 +314,26 @@ impl<'a> TypeConverter<'a> { .map(|(name, ty)| (name.clone(), self.entity_type(*ty))) .collect(); - let id = self.definitions.modules.alloc(Module { imports, exports }); - - self.cache.insert(key, Type::Module(id)); - Ok(id) + 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::AnyTypeId) -> Result { + fn ty(&mut self, id: wasm::ComponentAnyTypeId) -> Result { match id { - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Defined(id)) => { + wasm::ComponentAnyTypeId::Defined(id) => { Ok(Type::Value(self.component_defined_type(id)?)) } - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Resource(id)) => { - Ok(Type::Value(self.resource(id))) - } - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Func(id)) => { - Ok(Type::Func(self.component_func_type(id, FuncKind::Free)?)) - } - wasm::AnyTypeId::Core(wasm::ComponentCoreTypeId::Module(id)) => { - Ok(Type::Module(self.module_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::AnyTypeId::Component(wasm::ComponentAnyTypeId::Component(id)) => { - Ok(Type::World(self.component_type(id)?)) - } - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Instance(id)) => { + wasm::ComponentAnyTypeId::Instance(id) => { Ok(Type::Interface(self.component_instance_type(None, id)?)) } - wasm::AnyTypeId::Core(wasm::ComponentCoreTypeId::Sub(_)) => { - unreachable!("not a valid component extern type") + wasm::ComponentAnyTypeId::Resource(_) => { + bail!("unexpected resource encountered") } } } @@ -332,9 +341,7 @@ impl<'a> TypeConverter<'a> { 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(ValueType::Defined(self.component_defined_type(id)?)) - } + wasm::ComponentValType::Type(id) => Ok(self.component_defined_type(id)?), } } @@ -343,22 +350,11 @@ impl<'a> TypeConverter<'a> { name: Option<&str>, id: wasm::ComponentInstanceTypeId, ) -> Result { - let key = self.cache.key( - name, - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Instance(id)), - ); - if let Some(cached_ty) = self.cache.get(&key) { - match cached_ty { - Type::Interface(iface_id) => { - // We still need to map ownership of any types for this interface - for (index, (_, ty)) in self.types[id].exports.iter().enumerate() { - if let wasm::ComponentEntityType::Type { referenced, .. } = ty { - let ty = self.resolve_alias(*referenced); - self.owners.insert(ty, (iface_id, index)); - } - } - - return Ok(iface_id); + 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"), } @@ -366,37 +362,22 @@ impl<'a> TypeConverter<'a> { 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())), + uses: Default::default(), exports: IndexMap::with_capacity(instance_ty.exports.len()), - scope: None, }); for (index, (name, ty)) in instance_ty.exports.iter().enumerate() { - let export = match ty { - wasm::ComponentEntityType::Type { referenced, .. } => { - let ty = self.resolve_alias(*referenced); - let converted_ty = self.ty(ty)?; - let (interface, index) = *self.owners.entry(ty).or_insert((id, index)); - - match converted_ty { - Type::Value(ty) if interface != id => Extern::Use { - interface, - export_index: index, - ty, - }, - _ => Extern::Kind(ItemKind::Type(converted_ty)), - } - } - wasm::ComponentEntityType::Func(ty) => { - match self.resource_method_or_func(&instance_ty.exports, name, *ty)? { - Some(export) => export, - None => continue, - } - } - _ => Extern::Kind(self.component_entity_type(name, *ty)?), - }; + let export = self.entity(name, *ty)?; + + if let wasm::ComponentEntityType::Type { + referenced, + created, + } = ty + { + self.use_or_own(Owner::Interface(id), index, *referenced, *created); + } let prev = self.definitions.interfaces[id] .exports @@ -404,275 +385,274 @@ impl<'a> TypeConverter<'a> { assert!(prev.is_none()); } - self.cache.insert(key, Type::Interface(id)); + self.cache.insert(key, Entity::Type(Type::Interface(id))); Ok(id) } - fn resource_export( - &mut self, - externs: &IndexMap, - name: &str, - ) -> &mut Resource { - match externs[name] { + 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 { - referenced: wasm::ComponentAnyTypeId::Resource(id), + created: wasm::ComponentAnyTypeId::Resource(id), .. - } => { - let id = self - .mapper - .get(id.resource()) - .expect("resource should be mapped"); - let id = self.resources[&id]; - match self.definitions.types[id] { - DefinedType::Resource(id) => &mut self.definitions.resources[id], - _ => unreachable!("expected type to be a resource type"), - } + } => 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)?)) } - _ => unreachable!("expected a type export"), } } - fn resource_method_or_func( + fn use_or_own( &mut self, - externs: &IndexMap, - name: &str, - id: wasm::ComponentFuncTypeId, - ) -> Result> { - if let Some(res) = name.strip_prefix("[constructor]") { - let id = self.component_func_type(id, FuncKind::Constructor)?; - self.resource_export(externs, res).methods.insert( - String::new(), - ResourceMethod { - kind: FuncKind::Constructor, - ty: id, - }, - ); - Ok(None) - } else if let Some(name) = name.strip_prefix("[method]") { - let (res, name) = name.split_once('.').unwrap(); - let id = self.component_func_type(id, FuncKind::Method)?; - self.resource_export(externs, res).methods.insert( - name.to_owned(), - ResourceMethod { - kind: FuncKind::Method, - ty: id, - }, - ); - Ok(None) - } else if let Some(name) = name.strip_prefix("[static]") { - let (res, name) = name.split_once('.').unwrap(); - let id = self.component_func_type(id, FuncKind::Static)?; - self.resource_export(externs, res).methods.insert( - name.to_owned(), - ResourceMethod { - kind: FuncKind::Static, - ty: id, - }, - ); - Ok(None) - } else { - Ok(Some(Extern::Kind(ItemKind::Func( - self.component_func_type(id, FuncKind::Free)?, - )))) + owner: Owner, + index: usize, + referenced: ComponentAnyTypeId, + created: ComponentAnyTypeId, + ) { + if let Some((other, index)) = self.find_owner(referenced) { + match other { + Owner::Interface(interface) if owner != other => { + // 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.entry(interface).or_default().insert(index); + } + _ => {} + } + return; } + + // Take ownership of the entity + let prev = self.owners.insert(created, (owner, index)); + assert!(prev.is_none()); } - fn component_type(&mut self, id: wasm::ComponentTypeId) -> Result { - let key = self.cache.key( - None, - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Component(id)), - ); + 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 { - Type::World(id) => return Ok(id), + Entity::Type(Type::World(id)) => return Ok(*id), _ => unreachable!("invalid cached type"), } } let types = self.types.clone(); let component_ty = &types[id]; - let mut imports = IndexMap::with_capacity(component_ty.imports.len()); - for (name, ty) in &component_ty.imports { - let export = match ty { - wasm::ComponentEntityType::Type { referenced, .. } => { - let ty = self.resolve_alias(*referenced); - let converted_ty = self.ty(ty)?; - let interface = self.owners.get(&ty).copied(); - match (interface, converted_ty) { - (Some((interface, index)), Type::Value(ty)) => Extern::Use { - interface, - export_index: index, - ty, - }, - _ => Extern::Kind(ItemKind::Type(converted_ty)), - } - } - wasm::ComponentEntityType::Func(ty) => { - match self.resource_method_or_func(&component_ty.imports, name, *ty)? { - Some(export) => export, - None => continue, - } - } - _ => Extern::Kind(self.component_entity_type(name, *ty)?), - }; + 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 (index, (name, ty)) in component_ty.imports.iter().enumerate() { + let import = self.entity(name, *ty)?; - let prev = imports.insert(name.clone(), export); + if let wasm::ComponentEntityType::Type { + referenced, + created, + } = ty + { + self.use_or_own(Owner::World(id), index, *referenced, *created); + } + + let prev = self.definitions.worlds[id] + .imports + .insert(name.clone(), import); assert!(prev.is_none()); } - let exports = component_ty - .exports - .iter() - .map(|(name, ty)| { - Ok(( - name.clone(), - Extern::Kind(self.component_entity_type(name, *ty)?), - )) - }) - .collect::>()?; - - let id = self.definitions.worlds.alloc(World { - imports, - exports, - scope: None, - }); + for (name, ty) in &component_ty.exports { + let ty = self.component_entity_type(name, *ty)?; + let prev = self.definitions.worlds[id].exports.insert(name.clone(), ty); + assert!(prev.is_none()); + } - self.cache.insert(key, Type::World(id)); + self.cache.insert(key, Entity::Type(Type::World(id))); Ok(id) } - fn component_defined_type( - &mut self, - id: wasm::ComponentDefinedTypeId, - ) -> Result { - let key = self.cache.key( - None, - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Defined(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 { - Type::Value(id) => return Ok(id), + Entity::Type(Type::Value(ty)) => return Ok(*ty), _ => unreachable!("invalid cached type"), } } let types = self.types.clone(); - let id = match &types[id] { - wasm::ComponentDefinedType::Primitive(ty) => self - .definitions - .types - .alloc(DefinedType::Primitive((*ty).into())), + 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)| Ok((name.as_str().to_owned(), self.component_val_type(*ty)?))) + .map(|(name, ty)| { + let ty = self.component_val_type(*ty)?; + contains_borrow |= ty.contains_borrow(); + Ok((name.as_str().to_owned(), ty)) + }) .collect::>()?; - self.definitions - .types - .alloc(DefinedType::Record(Record { fields })) + 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)| { - Ok(( - name.as_str().to_owned(), - case.ty.map(|ty| self.component_val_type(ty)).transpose()?, - )) + 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::>()?; - self.definitions - .types - .alloc(DefinedType::Variant(Variant { cases })) + ValueType::Defined { + id: self + .definitions + .types + .alloc(DefinedType::Variant(Variant { cases })), + contains_borrow, + } } wasm::ComponentDefinedType::List(ty) => { let ty = self.component_val_type(*ty)?; - self.definitions.types.alloc(DefinedType::List(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| self.component_val_type(*ty)) + .map(|ty| { + let ty = self.component_val_type(*ty)?; + contains_borrow |= ty.contains_borrow(); + Ok(ty) + }) .collect::>()?; - self.definitions.types.alloc(DefinedType::Tuple(types)) + 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(); - self.definitions - .types - .alloc(DefinedType::Flags(Flags(flags))) + 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(); - self.definitions.types.alloc(DefinedType::Enum(Enum(cases))) + 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)?; - self.definitions.types.alloc(DefinedType::Option(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()?; - self.definitions - .types - .alloc(DefinedType::Result { ok, err }) - } - wasm::ComponentDefinedType::Borrow(id) => { - let id = self.resources[&self - .mapper - .get(id.resource()) - .expect("resource should be mapped")]; - match self.definitions.types[id] { - DefinedType::Resource(id) => { - self.definitions.types.alloc(DefinedType::Borrow(id)) - } - _ => unreachable!("expected type to be a resource type"), + 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::Own(id) => { - self.resources[&self - .mapper - .get(id.resource()) - .expect("resource should be mapped")] - } + 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, Type::Value(id)); - Ok(id) + self.cache.insert(key, Entity::Type(Type::Value(ty))); + Ok(ty) } - fn resource(&mut self, id: wasm::AliasableResourceId) -> DefinedTypeId { - let key = self.cache.key( - None, - wasm::AnyTypeId::Component(wasm::ComponentAnyTypeId::Resource(id)), - ); + 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 { - Type::Value(id) => return id, + Entity::Resource(id) => return *id, _ => unreachable!("invalid cached type"), } } - let res = id.resource(); - let id = - self.definitions - .types - .alloc(DefinedType::Resource(self.definitions.resources.alloc( - Resource { - methods: Default::default(), - }, - ))); - let internal_id = self.mapper.get(res).expect("resource should be mapped"); - let prev = self.resources.insert(internal_id, id); - assert!(prev.is_none(), "duplicate resource"); - - self.cache.insert(key, Type::Value(id)); - id + // 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 { @@ -693,15 +673,15 @@ impl<'a> TypeConverter<'a> { } } - fn resolve_alias(&self, id: wasm::ComponentAnyTypeId) -> wasm::AnyTypeId { - let mut cur = id; - loop { - cur = match self.types.peel_alias(cur) { + fn find_owner(&self, mut id: wasm::ComponentAnyTypeId) -> Option<(Owner, usize)> { + let mut prev = None; + while prev.is_none() { + prev = self.owners.get(&id).copied(); + id = match self.types.peel_alias(id) { Some(next) => next, None => break, }; } - - wasm::AnyTypeId::Component(cur) + prev } } diff --git a/crates/wac-parser/src/resolution/package/cache.rs b/crates/wac-parser/src/resolution/package/cache.rs deleted file mode 100644 index 3f4ff2a..0000000 --- a/crates/wac-parser/src/resolution/package/cache.rs +++ /dev/null @@ -1,813 +0,0 @@ -//! This module contains the (convoluted) logic for caching package type -//! information. -//! -//! The cache serves as a deduplication mechanism for wasmparser types. -//! -//! For example, WIT duplicates interface definitions within any referring -//! world definition. This means that the same interface definition may -//! appear many times in a package. The cache allows us to deduplicate -//! these definitions so that they hash and compare equal based on their -//! structure. -//! -//! There are four main types defined in this module: -//! -//! - `ResourceMapper` - a utility type that scans import and export entities -//! for resource definitions that can be deduplicated. -//! - `TypeHasher` - a utility type that can calculate a 64-bit hash value for -//! a wasmparser type based on its structure. -//! - `TypeComparer` - a utility type that can compare two wasmparser types for -//! structural equality. -//! - `TypeCache` - the main cache type that uses the above utilities to cache -//! converted wasmparser types. -//! -//! The wasmparser type information needs to be shared between the above types. -//! -//! Additionally, some of the shared data structures need interior mutability -//! via `RefCell`. - -use std::{ - cell::RefCell, - collections::{hash_map::DefaultHasher, HashMap}, - fmt, - hash::{Hash, Hasher}, - rc::Rc, -}; -use wasmparser::{ - types::{ - AnyTypeId, ComponentAnyTypeId, ComponentCoreTypeId, ComponentDefinedType, - ComponentEntityType, ComponentFuncType, ComponentInstanceType, ComponentType, - ComponentValType, EntityType, ModuleType, ResourceId, Types, - }, - PrimitiveValType, -}; - -/// Map between two type ids and the result of a comparison check. -type TypeComparisonMap = HashMap<(AnyTypeId, AnyTypeId), bool>; - -/// Map between a type id and its structural hash. -type TypeHashMap = HashMap; - -/// Represents a mapped resource id. -/// -/// This is used to deduplicate wasmparser resource ids so that -/// the same exported resource from an interface hashes and compares -/// equal. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub struct MappedResourceId(usize); - -/// A map from wasmparser resource ids to internal resource ids. -pub type ResourceMap = HashMap; - -/// Represents a mapper of wasmparser resource ids to internal ids. -/// -/// This allows the same exported resource from an interface to hash -/// and compare equal despite different wasmparser resource ids. -pub struct ResourceMapper { - types: Rc, - map: Rc>, - /// Map of qualified resource names (e.g. `foo:bar/baz/resource`) to mapped ids. - resources: HashMap, - next_id: usize, -} - -impl ResourceMapper { - pub fn new(types: Rc) -> Self { - Self { - types, - map: Default::default(), - resources: Default::default(), - next_id: 0, - } - } - - pub fn map(&self) -> Rc> { - self.map.clone() - } - - pub fn get(&self, id: ResourceId) -> Option { - self.map.borrow().get(&id).copied() - } - - pub fn component_entity_type(&mut self, name: &str, ty: ComponentEntityType) { - let types = self.types.clone(); - - match ty { - ComponentEntityType::Module(_) - | ComponentEntityType::Func(_) - | ComponentEntityType::Value(_) => {} - ComponentEntityType::Type { referenced, .. } => match referenced { - ComponentAnyTypeId::Instance(ty) => self.component_instance_type(name, &types[ty]), - ComponentAnyTypeId::Component(ty) => self.component_type(name, &types[ty]), - _ => {} - }, - ComponentEntityType::Instance(ty) => self.component_instance_type(name, &types[ty]), - ComponentEntityType::Component(ty) => self.component_type(name, &types[ty]), - } - } - - fn component_instance_type(&mut self, name: &str, ty: &ComponentInstanceType) { - for (export, ty) in &ty.exports { - if let ComponentEntityType::Type { referenced, .. } = ty { - self.map_resource(name, export, *referenced); - } else { - self.component_entity_type(export, *ty); - } - } - } - - fn component_type(&mut self, name: &str, ty: &ComponentType) { - for (import, ty) in &ty.imports { - if let ComponentEntityType::Type { referenced, .. } = ty { - self.map_resource(name, import, *referenced); - } else { - self.component_entity_type(import, *ty); - } - } - - for (export, ty) in &ty.exports { - self.component_entity_type(export, *ty); - } - } - - fn map_resource(&mut self, pkg: &str, name: &str, ty: ComponentAnyTypeId) { - let ty = self.resolve_alias(ty); - if let ComponentAnyTypeId::Resource(id) = ty { - self.map - .borrow_mut() - .entry(id.resource()) - .or_insert_with(|| { - *self - .resources - .entry(format!("{pkg}/{name}")) - .or_insert_with(|| { - let id = self.next_id; - self.next_id += 1; - MappedResourceId(id) - }) - }); - } - } - - fn resolve_alias(&self, id: ComponentAnyTypeId) -> ComponentAnyTypeId { - let mut cur = id; - loop { - cur = match self.types.peel_alias(cur) { - Some(next) => next, - None => break, - }; - } - cur - } -} - -/// Represents a wasmparser type hasher. -/// -/// This is used to hash a wasmparser `TypeId` based on its structure. -struct TypeHasher<'a, H: Hasher> { - types: &'a Types, - resources: &'a ResourceMap, - state: &'a mut H, -} - -impl<'a, H: Hasher> TypeHasher<'a, H> { - fn type_id(&mut self, id: AnyTypeId) { - match id { - AnyTypeId::Component(ComponentAnyTypeId::Component(id)) => { - 0u8.hash(self.state); - self.component_type(&self.types[id]); - } - AnyTypeId::Component(ComponentAnyTypeId::Instance(id)) => { - 1u8.hash(self.state); - self.component_instance_type(&self.types[id]) - } - AnyTypeId::Component(ComponentAnyTypeId::Func(id)) => { - 2u8.hash(self.state); - self.component_func_type(&self.types[id]); - } - AnyTypeId::Component(ComponentAnyTypeId::Defined(id)) => { - 4u8.hash(self.state); - self.component_defined_type(&self.types[id]); - } - AnyTypeId::Component(ComponentAnyTypeId::Resource(id)) => { - 5u8.hash(self.state); - self.resources[&id.resource()].hash(self.state); - } - AnyTypeId::Core(ComponentCoreTypeId::Module(id)) => { - 6u8.hash(self.state); - self.module_type(&self.types[id]); - } - AnyTypeId::Core(ComponentCoreTypeId::Sub(_)) => { - unreachable!("not a valid component extern type"); - } - } - } - - fn module_type(&mut self, ty: &ModuleType) { - ty.imports.len().hash(self.state); - for (n, ty) in &ty.imports { - n.hash(self.state); - self.entity_type(*ty); - } - - ty.exports.len().hash(self.state); - for (n, ty) in &ty.exports { - n.hash(self.state); - self.entity_type(*ty); - } - } - - fn entity_type(&mut self, ty: EntityType) { - match ty { - EntityType::Func(id) => { - 0u8.hash(self.state); - self.types[id].unwrap_func().hash(self.state); - } - EntityType::Table(ty) => (1u8, ty).hash(self.state), - EntityType::Memory(ty) => (2u8, ty).hash(self.state), - EntityType::Global(ty) => (3u8, ty).hash(self.state), - EntityType::Tag(id) => { - 4u8.hash(self.state); - self.types[id].unwrap_func().hash(self.state); - } - } - } - - fn component_instance_type(&mut self, ty: &ComponentInstanceType) { - ty.exports.len().hash(self.state); - for (n, ty) in &ty.exports { - n.hash(self.state); - self.component_entity_type(*ty); - } - } - - fn component_entity_type(&mut self, ty: ComponentEntityType) { - match ty { - ComponentEntityType::Module(id) => { - 0u8.hash(self.state); - self.module_type(&self.types[id]); - } - ComponentEntityType::Func(id) => { - 1u8.hash(self.state); - self.component_func_type(&self.types[id]); - } - ComponentEntityType::Value(ty) => { - 2u8.hash(self.state); - self.component_val_type(ty); - } - ComponentEntityType::Type { - referenced, - created, - } => { - 3u8.hash(self.state); - self.type_id(AnyTypeId::Component(referenced)); - self.type_id(AnyTypeId::Component(created)); - } - ComponentEntityType::Instance(id) => { - 4u8.hash(self.state); - self.component_instance_type(&self.types[id]); - } - ComponentEntityType::Component(id) => { - 5u8.hash(self.state); - self.component_type(&self.types[id]); - } - } - } - - fn component_val_type(&mut self, ty: ComponentValType) { - match ty { - ComponentValType::Primitive(ty) => { - 0u8.hash(self.state); - self.primitive_val_type(ty); - } - ComponentValType::Type(id) => { - 1u8.hash(self.state); - self.component_defined_type(&self.types[id]); - } - } - } - - fn primitive_val_type(&mut self, ty: PrimitiveValType) { - match ty { - PrimitiveValType::Bool => 0u8, - PrimitiveValType::S8 => 1u8, - PrimitiveValType::U8 => 2u8, - PrimitiveValType::S16 => 3u8, - PrimitiveValType::U16 => 4u8, - PrimitiveValType::S32 => 5u8, - PrimitiveValType::U32 => 6u8, - PrimitiveValType::S64 => 7u8, - PrimitiveValType::U64 => 8u8, - PrimitiveValType::Float32 => 9u8, - PrimitiveValType::Float64 => 10u8, - PrimitiveValType::Char => 11u8, - PrimitiveValType::String => 12u8, - } - .hash(self.state); - } - - fn component_type(&mut self, ty: &ComponentType) { - ty.imports.len().hash(self.state); - for (n, ty) in &ty.imports { - n.hash(self.state); - self.component_entity_type(*ty); - } - - ty.exports.len().hash(self.state); - for (n, ty) in &ty.exports { - n.hash(self.state); - self.component_entity_type(*ty); - } - } - - fn component_func_type(&mut self, ty: &ComponentFuncType) { - ty.params.len().hash(self.state); - for (n, ty) in ty.params.iter() { - n.hash(self.state); - self.component_val_type(*ty); - } - - ty.results.len().hash(self.state); - for (n, ty) in ty.results.iter() { - n.hash(self.state); - self.component_val_type(*ty); - } - } - - fn component_defined_type(&mut self, ty: &ComponentDefinedType) { - match ty { - ComponentDefinedType::Primitive(ty) => { - 0u8.hash(self.state); - self.primitive_val_type(*ty); - } - ComponentDefinedType::Record(r) => { - 1u8.hash(self.state); - r.fields.len().hash(self.state); - for (n, ty) in r.fields.iter() { - n.hash(self.state); - self.component_val_type(*ty); - } - } - ComponentDefinedType::Variant(v) => { - 2u8.hash(self.state); - v.cases.len().hash(self.state); - for (n, case) in v.cases.iter() { - n.hash(self.state); - match case.ty { - Some(ty) => { - 0u8.hash(self.state); - self.component_val_type(ty); - } - None => 1u8.hash(self.state), - } - } - } - ComponentDefinedType::List(ty) => { - 3u8.hash(self.state); - self.component_val_type(*ty); - } - ComponentDefinedType::Tuple(t) => { - 4u8.hash(self.state); - t.types.len().hash(self.state); - for ty in t.types.iter().copied() { - self.component_val_type(ty); - } - } - ComponentDefinedType::Flags(flags) => { - 5u8.hash(self.state); - flags.as_slice().hash(self.state); - } - ComponentDefinedType::Enum(cases) => { - 6u8.hash(self.state); - cases.as_slice().hash(self.state); - } - ComponentDefinedType::Option(ty) => { - 7u8.hash(self.state); - self.component_val_type(*ty); - } - ComponentDefinedType::Result { ok, err } => { - 8u8.hash(self.state); - match ok { - Some(ty) => { - 0u8.hash(self.state); - self.component_val_type(*ty); - } - None => 1u8.hash(self.state), - } - match err { - Some(ty) => { - 0u8.hash(self.state); - self.component_val_type(*ty); - } - None => 1u8.hash(self.state), - } - } - ComponentDefinedType::Own(id) => { - 9u8.hash(self.state); - self.resources[&id.resource()].hash(self.state); - } - ComponentDefinedType::Borrow(id) => { - 10u8.hash(self.state); - self.resources[&id.resource()].hash(self.state); - } - } - } -} - -/// Represents a type equality comparer. -/// -/// It compares two wasmparser `TypeId` for _structural_ equality. -/// -/// The result of any comparison is cached in the `comparisons` map. -struct TypeComparer<'a> { - types: &'a Types, - comparisons: &'a mut TypeComparisonMap, - resources: &'a ResourceMap, -} - -impl<'a> TypeComparer<'a> { - fn type_id(&mut self, a: AnyTypeId, b: AnyTypeId) -> bool { - // Check for id equality first - if a == b { - return true; - } - - // Symmetrically check for a cached result - if let Some(result) = self - .comparisons - .get(&(a, b)) - .or_else(|| self.comparisons.get(&(b, a))) - { - return *result; - } - - // Otherwise, check for type structural equality - let result = match (a, b) { - (AnyTypeId::Core(ComponentCoreTypeId::Sub(_)), _) - | (_, AnyTypeId::Core(ComponentCoreTypeId::Sub(_))) => { - unreachable!("not a valid component extern type") - } - ( - AnyTypeId::Core(ComponentCoreTypeId::Module(a)), - AnyTypeId::Core(ComponentCoreTypeId::Module(b)), - ) => self.module_type(&self.types[a], &self.types[b]), - ( - AnyTypeId::Component(ComponentAnyTypeId::Component(a)), - AnyTypeId::Component(ComponentAnyTypeId::Component(b)), - ) => self.component_type(&self.types[a], &self.types[b]), - ( - AnyTypeId::Component(ComponentAnyTypeId::Instance(a)), - AnyTypeId::Component(ComponentAnyTypeId::Instance(b)), - ) => self.component_instance_type(&self.types[a], &self.types[b]), - ( - AnyTypeId::Component(ComponentAnyTypeId::Func(a)), - AnyTypeId::Component(ComponentAnyTypeId::Func(b)), - ) => self.component_func_type(&self.types[a], &self.types[b]), - ( - AnyTypeId::Component(ComponentAnyTypeId::Defined(a)), - AnyTypeId::Component(ComponentAnyTypeId::Defined(b)), - ) => self.component_defined_type(&self.types[a], &self.types[b]), - ( - AnyTypeId::Component(ComponentAnyTypeId::Resource(a)), - AnyTypeId::Component(ComponentAnyTypeId::Resource(b)), - ) => a == b || self.resources[&a.resource()] == self.resources[&b.resource()], - _ => false, - }; - - self.comparisons.insert((a, b), result); - result - } - - fn module_type(&mut self, a: &ModuleType, b: &ModuleType) -> bool { - if a.imports.len() != b.imports.len() || a.exports.len() != b.exports.len() { - return false; - } - - a.imports - .iter() - .zip(b.imports.iter()) - .all(|((name_a, a), (name_b, b))| name_a == name_b && self.entity_type(*a, *b)) - && a.exports - .iter() - .zip(b.exports.iter()) - .all(|((name_a, a), (name_b, b))| name_a == name_b && self.entity_type(*a, *b)) - } - - fn entity_type(&mut self, a: EntityType, b: EntityType) -> bool { - match (a, b) { - (EntityType::Func(a), EntityType::Func(b)) => { - self.types[a].unwrap_func() == self.types[b].unwrap_func() - } - (EntityType::Table(a), EntityType::Table(b)) => a == b, - (EntityType::Memory(a), EntityType::Memory(b)) => a == b, - (EntityType::Global(a), EntityType::Global(b)) => a == b, - (EntityType::Tag(a), EntityType::Tag(b)) => { - self.types[a].unwrap_func() == self.types[b].unwrap_func() - } - _ => false, - } - } - - fn component_type(&mut self, a: &ComponentType, b: &ComponentType) -> bool { - if a.imports.len() != b.imports.len() || a.exports.len() != b.exports.len() { - return false; - } - - a.imports - .iter() - .zip(b.imports.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b && self.component_entity_type(*a, *b) - }) - && a.exports - .iter() - .zip(b.exports.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b && self.component_entity_type(*a, *b) - }) - } - - fn component_entity_type(&mut self, a: ComponentEntityType, b: ComponentEntityType) -> bool { - match (a, b) { - (ComponentEntityType::Module(a), ComponentEntityType::Module(b)) => { - self.module_type(&self.types[a], &self.types[b]) - } - (ComponentEntityType::Func(a), ComponentEntityType::Func(b)) => { - self.component_func_type(&self.types[a], &self.types[b]) - } - (ComponentEntityType::Value(a), ComponentEntityType::Value(b)) => { - self.component_val_type(a, b) - } - ( - ComponentEntityType::Type { referenced: a, .. }, - ComponentEntityType::Type { referenced: b, .. }, - ) => self.type_id(AnyTypeId::Component(a), AnyTypeId::Component(b)), - (ComponentEntityType::Instance(a), ComponentEntityType::Instance(b)) => { - self.component_instance_type(&self.types[a], &self.types[b]) - } - (ComponentEntityType::Component(a), ComponentEntityType::Component(b)) => { - self.component_type(&self.types[a], &self.types[b]) - } - _ => false, - } - } - - fn component_val_type(&mut self, a: ComponentValType, b: ComponentValType) -> bool { - match (a, b) { - (ComponentValType::Primitive(a), ComponentValType::Primitive(b)) => a == b, - (ComponentValType::Type(a), ComponentValType::Type(b)) => { - self.component_defined_type(&self.types[a], &self.types[b]) - } - _ => false, - } - } - - fn component_instance_type( - &mut self, - a: &ComponentInstanceType, - b: &ComponentInstanceType, - ) -> bool { - if a.exports.len() != b.exports.len() { - return false; - } - - a.exports - .iter() - .zip(b.exports.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b && self.component_entity_type(*a, *b) - }) - } - - fn component_func_type(&mut self, a: &ComponentFuncType, b: &ComponentFuncType) -> bool { - if a.params.len() != b.params.len() || a.results.len() != b.results.len() { - return false; - } - - a.params - .iter() - .zip(b.params.iter()) - .all(|((name_a, a), (name_b, b))| name_a == name_b && self.component_val_type(*a, *b)) - && a.results - .iter() - .zip(b.results.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b && self.component_val_type(*a, *b) - }) - } - - fn component_defined_type( - &mut self, - a: &ComponentDefinedType, - b: &ComponentDefinedType, - ) -> bool { - match (a, b) { - (ComponentDefinedType::Primitive(a), ComponentDefinedType::Primitive(b)) => a == b, - (ComponentDefinedType::Record(a), ComponentDefinedType::Record(b)) => { - if a.fields.len() != b.fields.len() { - return false; - } - - a.fields - .iter() - .zip(b.fields.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b && self.component_val_type(*a, *b) - }) - } - (ComponentDefinedType::Variant(a), ComponentDefinedType::Variant(b)) => { - if a.cases.len() != b.cases.len() { - return false; - } - - a.cases - .iter() - .zip(b.cases.iter()) - .all(|((name_a, a), (name_b, b))| { - name_a == name_b - && match (a.ty, b.ty) { - (Some(a), Some(b)) => self.component_val_type(a, b), - (None, None) => true, - _ => false, - } - }) - } - (ComponentDefinedType::List(a), ComponentDefinedType::List(b)) => { - self.component_val_type(*a, *b) - } - (ComponentDefinedType::Tuple(a), ComponentDefinedType::Tuple(b)) => { - if a.types.len() != b.types.len() { - return false; - } - - a.types - .iter() - .zip(b.types.iter()) - .all(|(a, b)| self.component_val_type(*a, *b)) - } - (ComponentDefinedType::Flags(a), ComponentDefinedType::Flags(b)) => a == b, - (ComponentDefinedType::Enum(a), ComponentDefinedType::Enum(b)) => a == b, - (ComponentDefinedType::Option(a), ComponentDefinedType::Option(b)) => { - self.component_val_type(*a, *b) - } - ( - ComponentDefinedType::Result { - ok: ok_a, - err: err_a, - }, - ComponentDefinedType::Result { - ok: ok_b, - err: err_b, - }, - ) => { - match (ok_a, ok_b) { - (Some(a), Some(b)) => { - if !self.component_val_type(*a, *b) { - return false; - } - } - (None, None) => {} - _ => return false, - } - - match (err_a, err_b) { - (Some(a), Some(b)) => { - if !self.component_val_type(*a, *b) { - return false; - } - } - (None, None) => {} - _ => return false, - } - - true - } - (ComponentDefinedType::Own(a), ComponentDefinedType::Own(b)) - | (ComponentDefinedType::Borrow(a), ComponentDefinedType::Borrow(b)) => { - a == b || self.resources[&a.resource()] == self.resources[&b.resource()] - } - _ => false, - } - } -} - -/// Represents a type cache key. -#[derive(Clone)] -pub struct TypeCacheKey { - types: Rc, - comparisons: Rc>, - resources: Rc>, - name: Option, - ty: AnyTypeId, - hash: u64, -} - -impl Hash for TypeCacheKey { - fn hash(&self, state: &mut H) { - self.hash.hash(state); - } -} - -impl fmt::Debug for TypeCacheKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TypeCacheKey") - .field("types", &"...") - .field("comparisons", &"...") - .field("resources", &"...") - .field("name", &self.name) - .field("ty", &self.ty) - .field("hash", &self.hash) - .finish() - } -} - -impl PartialEq for TypeCacheKey { - fn eq(&self, other: &Self) -> bool { - if self.hash != other.hash || self.name != other.name { - return false; - } - - let mut comparisons = self.comparisons.borrow_mut(); - let resources = self.resources.borrow(); - let mut comparer = TypeComparer { - types: self.types.as_ref(), - comparisons: &mut comparisons, - resources: &resources, - }; - comparer.type_id(self.ty, other.ty) - } -} - -impl Eq for TypeCacheKey {} - -/// Represents a cache of wasmparser types. -/// -/// This is used to deduplicate types as a package is parsed. -pub struct TypeCache { - /// The wasmparser types collection. - types: Rc, - /// A map of a wasmparser type id to its hash. - hashes: TypeHashMap, - /// A map of wasmparser type ids to the comparison result. - comparisons: Rc>, - /// A map of wasmparser resource ids to their mapped ids. - resources: Rc>, - /// A map of type cache keys to the resolved extern type. - cache: HashMap, -} - -impl TypeCache { - /// Creates a new type cache with the given wasmparser types collection. - pub fn new(types: Rc, resources: Rc>) -> Self { - Self { - types, - hashes: Default::default(), - comparisons: Default::default(), - resources, - cache: Default::default(), - } - } - - /// Gets a key for the cache for the given type. - pub fn key(&mut self, name: Option<&str>, ty: AnyTypeId) -> TypeCacheKey { - let hash = match self.hashes.get(&ty) { - Some(hash) => *hash, - None => { - let mut state = DefaultHasher::new(); - name.hash(&mut state); - - let mut hasher = TypeHasher { - types: &self.types, - resources: &self.resources.borrow(), - state: &mut state, - }; - - hasher.type_id(ty); - let hash = state.finish(); - self.hashes.insert(ty, hash); - hash - } - }; - - TypeCacheKey { - types: self.types.clone(), - comparisons: self.comparisons.clone(), - resources: self.resources.clone(), - name: name.map(ToOwned::to_owned), - ty, - hash, - } - } - - /// Gets a type from the cache by key. - pub fn get(&mut self, key: &TypeCacheKey) -> Option { - self.cache.get(key).copied() - } - - /// Inserts a new key into the cache. - /// - /// Panics if the key is already present. - pub fn insert(&mut self, key: TypeCacheKey, ty: super::Type) { - let prev = self.cache.insert(key, ty); - assert!(prev.is_none()); - } -} diff --git a/crates/wac-parser/src/resolution/types.rs b/crates/wac-parser/src/resolution/types.rs index 472b9bb..c5085b0 100644 --- a/crates/wac-parser/src/resolution/types.rs +++ b/crates/wac-parser/src/resolution/types.rs @@ -1,14 +1,14 @@ -use super::{serialize_arena, serialize_id, serialize_optional_id, FuncKind, ItemKind, ScopeId}; +use super::{ + package::Package, serialize_arena, serialize_id, serialize_id_key_map, serialize_optional_id, + ItemKind, +}; use anyhow::{bail, Context, Result}; use id_arena::{Arena, Id}; use indexmap::{IndexMap, IndexSet}; use serde::Serialize; -use std::{ - collections::{HashMap, HashSet}, - fmt, -}; +use std::{collections::HashSet, fmt}; -/// An identifier for value types. +/// An identifier for defined value types. pub type DefinedTypeId = Id; /// An identifier for resource types. @@ -50,14 +50,50 @@ pub struct Definitions { 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 defined value type. - Value(#[serde(serialize_with = "serialize_id")] DefinedTypeId), + /// 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). @@ -70,7 +106,7 @@ impl Type { pub(crate) fn as_str(&self, definitions: &Definitions) -> &'static str { match self { Type::Func(_) => "function type", - Type::Value(ty) => definitions.types[*ty].as_str(definitions), + Type::Value(ty) => ty.as_str(definitions), Type::Interface(_) => "interface", Type::World(_) => "world", Type::Module(_) => "module type", @@ -155,15 +191,50 @@ impl From for PrimitiveType { 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(DefinedTypeId), + 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::Defined(ty) => serialize_id(ty, 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), } } } @@ -172,8 +243,6 @@ impl Serialize for ValueType { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum DefinedType { - /// A primitive type. - Primitive(PrimitiveType), /// A tuple type. Tuple(Vec), /// A list type. @@ -187,10 +256,6 @@ pub enum DefinedType { /// The result's `err` type. err: Option, }, - /// The type is a borrow of a resource type. - Borrow(#[serde(serialize_with = "serialize_id")] ResourceId), - /// The type is a resource type. - Resource(#[serde(serialize_with = "serialize_id")] ResourceId), /// The type is a variant type. Variant(Variant), /// The type is a record type. @@ -199,52 +264,73 @@ pub enum DefinedType { Flags(Flags), /// The type is an enum. Enum(Enum), - /// The type is an alias to another type. + /// The type is an alias to another value type. Alias(ValueType), } impl DefinedType { fn as_str(&self, definitions: &Definitions) -> &'static str { match self { - DefinedType::Primitive(ty) => ty.as_str(), DefinedType::Tuple(_) => "tuple", DefinedType::List(_) => "list", DefinedType::Option(_) => "option", DefinedType::Result { .. } => "result", - DefinedType::Borrow(_) => "borrow", - DefinedType::Resource(_) => "resource", DefinedType::Variant(_) => "variant", DefinedType::Record(_) => "record", DefinedType::Flags(_) => "flags", DefinedType::Enum(_) => "enum", - DefinedType::Alias(ValueType::Primitive(ty)) => ty.as_str(), - DefinedType::Alias(ValueType::Defined(id)) => { - definitions.types[*id].as_str(definitions) - } + DefinedType::Alias(ty) => ty.as_str(definitions), } } } -/// Represents a resource method. -#[derive(Debug, Clone, Serialize)] +/// Represents a kind of function in the component model. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ResourceMethod { - /// The kind of resource method. - pub kind: FuncKind, - /// The method's type. - #[serde(serialize_with = "serialize_id")] - pub ty: FuncId, +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 resource methods. - /// - /// The resource's constructor uses an empty name. - #[serde(skip_serializing_if = "IndexMap::is_empty")] - pub methods: IndexMap, + /// 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. @@ -274,7 +360,7 @@ pub struct Flags(pub IndexSet); pub struct Enum(pub IndexSet); /// Represents a function type. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Default)] #[serde(rename_all = "camelCase")] pub struct Func { /// The parameters of the function. @@ -293,73 +379,40 @@ pub enum FuncResult { List(IndexMap), } -/// Represents an external item (imported or exported) from an interface or world. -#[derive(Debug, Clone, Copy, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum Extern { - /// The item is a type used from an interface. - #[serde(rename_all = "camelCase")] - Use { - /// The interface the type was sourced from. - #[serde(serialize_with = "serialize_id")] - interface: InterfaceId, - /// The export index on the interface for the type. - export_index: usize, - /// The value type. - #[serde(serialize_with = "serialize_id")] - ty: DefinedTypeId, - }, - /// The item is another kind of extern. - Kind(ItemKind), -} - -impl Extern { - /// Gets the extern kind of the item. - pub fn kind(&self) -> ItemKind { - match self { - Self::Use { ty, .. } => ItemKind::Type(Type::Value(*ty)), - Self::Kind(kind) => *kind, - } - } -} - -/// Represents a map of extern items. -pub type ExternMap = IndexMap; - /// Represents an interface (i.e. instance type). -#[derive(Debug, Clone, Serialize)] +#[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, + /// A map from used interface to set of used type export indexes. + #[serde(serialize_with = "serialize_id_key_map")] + pub uses: IndexMap>, /// The exported items of the interface. - pub exports: ExternMap, - /// The scope associated with a defined interface. - /// - /// This is `None` for foreign interfaces. - #[serde(serialize_with = "serialize_optional_id")] - pub scope: Option, + pub exports: IndexMap, } /// Represents a world. -#[derive(Debug, Clone, Serialize)] +#[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 from used interface to set of used type export indexes. + #[serde(serialize_with = "serialize_id_key_map")] + pub uses: IndexMap>, /// The imported items of the world. - pub imports: ExternMap, + pub imports: IndexMap, /// The exported items of the world. - pub exports: ExternMap, - /// The scope associated with a defined world. - /// - /// This is `None` for foreign worlds. - #[serde(serialize_with = "serialize_optional_id")] - pub scope: Option, + pub exports: IndexMap, } /// Represents a core module type. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Default)] pub struct Module { /// The imports of the module type. pub imports: IndexMap<(String, String), CoreExtern>, @@ -416,7 +469,7 @@ pub enum CoreExtern { #[serde(rename_all = "camelCase")] Global { /// The global's type. - content_type: CoreType, + val_type: CoreType, /// Whether or not the global is mutable. mutable: bool, }, @@ -460,7 +513,7 @@ impl From for CoreExtern { impl From for CoreExtern { fn from(ty: wasmparser::GlobalType) -> Self { Self::Global { - content_type: ty.content_type.into(), + val_type: ty.content_type.into(), mutable: ty.mutable, } } @@ -510,6 +563,19 @@ impl From for CoreType { } } +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")] @@ -560,6 +626,15 @@ impl From for CoreRefType { } } +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 { @@ -605,6 +680,24 @@ impl From for HeapType { } } +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, + } + } +} + /// Represents a core function type in a WebAssembly module. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] #[serde(rename_all = "camelCase")] @@ -641,82 +734,22 @@ impl fmt::Display for CoreFunc { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum CheckKind { - Covariant, - Contravariant, -} - -struct ResourceMap { - kind: CheckKind, - map: HashMap, -} - -impl ResourceMap { - fn new(kind: CheckKind) -> Self { - Self { - kind, - map: Default::default(), - } - } - - fn insert(&mut self, a: ResourceId, b: ResourceId) { - self.map.insert(a, b); - } - - fn is_subtype(&self, checker: &SubtypeChecker, a: ResourceId, b: ResourceId) -> Result<()> { - if self.map.get(&a) != Some(&b) && self.map.get(&b) != Some(&a) { - bail!("mismatched resource types"); - } - - let a = &checker.definitions.resources[a]; - let b = &checker.definitions.resources[b]; - - for (k, a) in a.methods.iter() { - match b.methods.get(k) { - Some(b) => { - match self.kind { - CheckKind::Covariant => checker.func(a.ty, b.ty), - CheckKind::Contravariant => checker.func(b.ty, a.ty), - } - .with_context(|| { - if k.is_empty() { - "mismatched type for constructor".to_string() - } else { - format!("mismatched type for method `{k}`") - } - })?; - } - None => { - if k.is_empty() { - bail!("missing expected constructor"); - } else { - bail!("missing expected method `{k}`"); - } - } - } - } - - Ok(()) - } -} - /// Implements a subtype checker. /// /// Subtype checking is used to type check instantiation arguments. pub struct SubtypeChecker<'a> { definitions: &'a Definitions, + packages: &'a Arena, cache: HashSet<(ItemKind, ItemKind)>, - resource_map: Vec, } impl<'a> SubtypeChecker<'a> { /// Creates a new subtype checker. - pub fn new(definitions: &'a Definitions) -> Self { + pub fn new(definitions: &'a Definitions, packages: &'a Arena) -> Self { Self { definitions, + packages, cache: Default::default(), - resource_map: Default::default(), } } @@ -727,22 +760,36 @@ impl<'a> SubtypeChecker<'a> { } let mut check = || match (a, b) { + (ItemKind::Resource(a), ItemKind::Resource(b)) => { + if a != b { + let a = self.definitions.resolve_resource(a); + let b = self.definitions.resolve_resource(b); + if a.name != b.name { + bail!( + "expected resource `{a}`, found resource `{b}`", + a = a.name, + b = b.name + ); + } + } + Ok(()) + } (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[a]; - let b = &self.definitions.worlds[b]; + let a = &self.definitions.worlds[self.packages[a].world]; + let b = &self.definitions.worlds[self.packages[b].world]; self.interface_exports(&a.exports, &b.exports) } (ItemKind::Instantiation(a), ItemKind::Instance(b)) => { - let a = &self.definitions.worlds[a]; + let a = &self.definitions.worlds[self.packages[a].world]; let b = &self.definitions.interfaces[b]; self.interface_exports(&a.exports, &b.exports) } (ItemKind::Instance(a), ItemKind::Instantiation(b)) => { let a = &self.definitions.interfaces[a]; - let b = &self.definitions.worlds[b]; + let b = &self.definitions.worlds[self.packages[b].world]; self.interface_exports(&a.exports, &b.exports) } (ItemKind::Component(a), ItemKind::Component(b)) => self.world(a, b), @@ -766,7 +813,7 @@ impl<'a> SubtypeChecker<'a> { 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.defined_type(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), @@ -842,51 +889,26 @@ impl<'a> SubtypeChecker<'a> { Ok(()) } - fn interface_exports(&mut self, a: &ExternMap, b: &ExternMap) -> Result<()> { - // Before we do anything, we need to populate a resource mapping - let mut map = ResourceMap::new(CheckKind::Covariant); + fn interface_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, a) in a.iter() { match b.get(k) { - Some(b) => match (a.kind(), b.kind()) { - (ItemKind::Type(Type::Value(a)), ItemKind::Type(Type::Value(b))) => { - let a = &self.definitions.types[a]; - let b = &self.definitions.types[b]; - match (a, b) { - (DefinedType::Resource(a), DefinedType::Resource(b)) => { - map.insert(*a, *b); - } - _ => continue, - } - } - _ => continue, - }, - None => continue, - } - } - - self.resource_map.push(map); - - let mut check = || { - // 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, a) in a.iter() { - match b.get(k) { - Some(b) => { - self.is_subtype(a.kind(), b.kind()) - .with_context(|| format!("mismatched type for export `{k}`"))?; - } - None => bail!("missing expected export `{k}`"), + Some(b) => { + self.is_subtype(*a, *b) + .with_context(|| format!("mismatched type for export `{k}`"))?; } + None => bail!("instance is missing expected export `{k}`"), } + } - Ok(()) - }; - - let result = check(); - self.resource_map.pop(); - result + Ok(()) } fn interface(&mut self, a: InterfaceId, b: InterfaceId) -> Result<()> { @@ -900,68 +922,35 @@ impl<'a> SubtypeChecker<'a> { } fn world(&mut self, a: WorldId, b: WorldId) -> Result<()> { - if a == b { - return Ok(()); - } - let a = &self.definitions.worlds[a]; let b = &self.definitions.worlds[b]; - // Before we do anything, we need to populate a resource mapping - let mut map = ResourceMap::new(CheckKind::Contravariant); + // 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). for (k, a) in a.imports.iter() { match b.imports.get(k) { - Some(b) => match (a.kind(), b.kind()) { - (ItemKind::Type(Type::Value(a)), ItemKind::Type(Type::Value(b))) => { - let a = &self.definitions.types[a]; - let b = &self.definitions.types[b]; - match (a, b) { - (DefinedType::Resource(a), DefinedType::Resource(b)) => { - map.insert(*a, *b); - } - _ => continue, - } - } - _ => continue, - }, - None => continue, - } - } - - self.resource_map.push(map); - - let mut check = || { - // 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). - for (k, a) in a.imports.iter() { - match b.imports.get(k) { - Some(b) => { - self.is_subtype(b.kind(), a.kind()) - .with_context(|| format!("mismatched type for import `{k}`"))?; - } - None => bail!("missing expected import `{k}`"), + Some(b) => { + self.is_subtype(*b, *a) + .with_context(|| format!("mismatched type for import `{k}`"))?; } + None => bail!("missing expected import `{k}`"), } + } - for (k, b) in b.exports.iter() { - match a.exports.get(k) { - Some(a) => { - self.is_subtype(a.kind(), b.kind()) - .with_context(|| format!("mismatched type for export `{k}"))?; - } - None => bail!("missing expected export `{k}`"), + 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 => bail!("component is missing expected export `{k}`"), } + } - Ok(()) - }; - - let result = check(); - self.resource_map.pop(); - result + Ok(()) } fn module(&mut self, a: ModuleId, b: ModuleId) -> Result<()> { @@ -994,7 +983,7 @@ impl<'a> SubtypeChecker<'a> { Self::core_extern(a, b) .with_context(|| format!("mismatched type for export `{k}"))?; } - None => bail!("missing expected export `{k}`"), + None => bail!("module is missing expected export `{k}`"), } } @@ -1067,11 +1056,11 @@ impl<'a> SubtypeChecker<'a> { } ( CoreExtern::Global { - content_type: at, + val_type: at, mutable: am, }, CoreExtern::Global { - content_type: bt, + val_type: bt, mutable: bm, }, ) => { @@ -1100,45 +1089,39 @@ impl<'a> SubtypeChecker<'a> { Ok(()) } - fn resolve_alias(&self, mut id: DefinedTypeId) -> DefinedTypeId { - loop { - let ty = &self.definitions.types[id]; - if let DefinedType::Alias(ValueType::Defined(next)) = ty { - id = *next; - } else { - return id; - } - } - } - fn value_type(&self, a: ValueType, b: ValueType) -> Result<()> { - match (a, b) { + match ( + self.definitions.resolve_type(a), + self.definitions.resolve_type(b), + ) { (ValueType::Primitive(a), ValueType::Primitive(b)) => Self::primitive(a, b), - (ValueType::Primitive(a), ValueType::Defined(b)) => { - let b = &self.definitions.types[self.resolve_alias(b)]; - if let DefinedType::Primitive(b) = b { - Self::primitive(a, *b) - } else { - bail!( - "expected {a}, found {b}", - a = a.as_str(), - b = b.as_str(self.definitions) - ); - } + (ValueType::Defined { id: a, .. }, ValueType::Defined { id: b, .. }) => { + self.defined_type(a, b) } - (ValueType::Defined(a), ValueType::Primitive(b)) => { - let a = &self.definitions.types[self.resolve_alias(a)]; - if let DefinedType::Primitive(a) = a { - Self::primitive(*a, b) - } else { + (ValueType::Borrow(a), ValueType::Borrow(b)) + | (ValueType::Own(a), ValueType::Own(b)) => { + if a == b { + return Ok(()); + } + + let a = self.definitions.resolve_resource(a); + let b = self.definitions.resolve_resource(b); + + if a.name != b.name { bail!( - "expected {a}, found {b}", - a = a.as_str(self.definitions), - b = b.as_str() + "expected resource `{a}`, found resource `{b}`", + a = a.name, + b = b.name ); } + + Ok(()) } - (ValueType::Defined(a), ValueType::Defined(b)) => self.defined_type(a, b), + (a, b) => bail!( + "expected {a}, found {b}", + a = a.as_str(self.definitions), + b = b.as_str(self.definitions) + ), } } @@ -1151,10 +1134,9 @@ impl<'a> SubtypeChecker<'a> { return Ok(()); } - let a = &self.definitions.types[self.resolve_alias(a)]; - let b = &self.definitions.types[self.resolve_alias(b)]; + let a = &self.definitions.types[a]; + let b = &self.definitions.types[b]; match (a, b) { - (DefinedType::Primitive(a), DefinedType::Primitive(b)) => Self::primitive(*a, *b), (DefinedType::Tuple(a), DefinedType::Tuple(b)) => self.tuple(a, b), (DefinedType::List(a), DefinedType::List(b)) => self .value_type(*a, *b) @@ -1175,23 +1157,13 @@ impl<'a> SubtypeChecker<'a> { self.result("ok", a_ok, b_ok)?; self.result("err", a_err, b_err) } - (DefinedType::Borrow(a), DefinedType::Borrow(b)) - | (DefinedType::Resource(a), DefinedType::Resource(b)) => { - if a == b { - return Ok(()); - } - - if let Some(map) = self.resource_map.last() { - return map.is_subtype(self, *a, *b); - } - - bail!("mismatched resource types") - } (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(_), _) => unreachable!("alias should have been resolved"), + (DefinedType::Alias(_), _) | (_, DefinedType::Alias(_)) => { + unreachable!("aliases should have been resolved") + } _ => { bail!( "expected {a}, found {b}", diff --git a/crates/wac-parser/tests/encoding.rs b/crates/wac-parser/tests/encoding.rs new file mode 100644 index 0000000..12f10c6 --- /dev/null +++ b/crates/wac-parser/tests/encoding.rs @@ -0,0 +1,192 @@ +use anyhow::{anyhow, bail, Context, Result}; +use owo_colors::OwoColorize; +use pretty_assertions::StrComparison; +use rayon::prelude::*; +use std::{ + env, + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::exit, + sync::atomic::{AtomicUsize, Ordering}, +}; +use wac_parser::{ + ast::Document, Composition, EncodingOptions, ErrorFormatter, FileSystemPackageResolver, +}; + +#[cfg(not(feature = "wat"))] +compile_error!("the `wat` feature must be enabled for this test to run"); + +fn find_tests() -> Vec { + let mut tests = Vec::new(); + find_tests("tests/encoding", &mut tests); + tests.sort(); + return tests; + + fn find_tests(path: impl AsRef, tests: &mut Vec) { + for entry in path.as_ref().read_dir().unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + continue; + } + + match path.extension().and_then(|s| s.to_str()) { + Some("wac") => {} + _ => continue, + } + + tests.push(path); + } + } +} + +fn normalize(s: &str) -> String { + // Normalize line endings + s.replace("\r\n", "\n") +} + +fn compare_result(test: &Path, result: &str) -> Result<()> { + let path = test.with_extension("wat.result"); + + let result = normalize(result); + if env::var_os("BLESS").is_some() { + fs::write(&path, &result).with_context(|| { + format!( + "failed to write result file `{path}`", + path = path.display() + ) + })?; + return Ok(()); + } + + let expected = fs::read_to_string(&path) + .with_context(|| format!("failed to read result file `{path}`", path = path.display()))? + .replace("\r\n", "\n"); + + if expected != result { + bail!( + "result is not as expected:\n{}", + StrComparison::new(&expected, &result), + ); + } + + Ok(()) +} + +fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { + let source = std::fs::read_to_string(test)?.replace("\r\n", "\n"); + let document = Document::parse(&source, test) + .map_err(|e| anyhow!("{e}", e = ErrorFormatter::new(test, e, false)))?; + let bytes = Composition::from_ast( + &document, + Some(Box::new(FileSystemPackageResolver::new( + test.parent().unwrap().join(test.file_stem().unwrap()), + Default::default(), + ))), + ) + .map_err(|e| { + anyhow!( + "the resolution failed but it was expected to succeed: {e}", + e = ErrorFormatter::new(test, e, false) + ) + })? + .encode(EncodingOptions::default()) + .with_context(|| { + format!( + "failed to encode the composition `{path}`", + path = test.display() + ) + })?; + + 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)?; + + ntests.fetch_add(1, Ordering::SeqCst); + Ok(()) +} + +fn main() { + pretty_env_logger::init(); + + let tests = find_tests(); + println!("running {} tests\n", tests.len()); + + let ntests = AtomicUsize::new(0); + let errors = tests + .par_iter() + .filter_map(|test| { + let test_name = test.file_stem().and_then(OsStr::to_str).unwrap(); + match std::panic::catch_unwind(|| { + match run_test(test, &ntests) + .with_context(|| format!("failed to run test `{path}`", path = test.display())) + .err() + { + Some(e) => { + println!("test {test_name} ... {failed}", failed = "failed".red()); + Some((test_name, e)) + } + None => { + println!("test {test_name} ... {ok}", ok = "ok".green()); + None + } + } + }) { + Ok(result) => result, + Err(e) => { + println!( + "test {test_name} ... {panicked}", + panicked = "panicked".red() + ); + Some(( + test_name, + anyhow!( + "test panicked: {e:?}", + e = e + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| e.downcast_ref::<&str>().copied()) + .unwrap_or("no panic message") + ), + )) + } + } + }) + .collect::>(); + + if !errors.is_empty() { + eprintln!( + "\n{count} test(s) {failed}:", + count = errors.len(), + failed = "failed".red() + ); + + for (name, msg) in errors.iter() { + eprintln!("{name}: {msg:?}", msg = msg.red()); + } + + exit(1); + } + + println!( + "\ntest result: ok. {} passed\n", + ntests.load(Ordering::SeqCst) + ); +} diff --git a/crates/wac-parser/tests/encoding/instantiation.wac b/crates/wac-parser/tests/encoding/instantiation.wac new file mode 100644 index 0000000..c8f2ec2 --- /dev/null +++ b/crates/wac-parser/tests/encoding/instantiation.wac @@ -0,0 +1,18 @@ +package test:comp; + +import i: interface { + foo: func(); +}; + +import f: func(); + +let baz = f; +let x1 = new foo:bar { ... }; +let x2 = new foo:bar { baz: f }; +let x3 = new foo:bar { baz }; + +let y1 = new bar:baz { ... }; +let y2 = new bar:baz { foo: i }; +let y3 = new bar:baz { foo: x1 }; +let y4 = new bar:baz { foo: x2 }; +let y5 = new bar:baz { foo: x3 }; diff --git a/crates/wac-parser/tests/encoding/instantiation.wat.result b/crates/wac-parser/tests/encoding/instantiation.wat.result new file mode 100644 index 0000000..877b8a6 --- /dev/null +++ b/crates/wac-parser/tests/encoding/instantiation.wat.result @@ -0,0 +1,75 @@ +(component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "i" (instance (;0;) (type 0))) + (type (;1;) (func)) + (import "f" (func (;0;) (type 1))) + (type (;2;) (func)) + (import "baz" (func (;1;) (type 2))) + (type (;3;) + (component + (type (;0;) (func)) + (import "baz" (func (;0;) (type 0))) + (export (;1;) "foo" (func (type 0))) + ) + ) + (import "unlocked-dep=" (component (;0;) (type 3))) + (instance (;1;) (instantiate 0 + (with "baz" (func 1)) + ) + ) + (instance (;2;) (instantiate 0 + (with "baz" (func 0)) + ) + ) + (instance (;3;) (instantiate 0 + (with "baz" (func 0)) + ) + ) + (type (;4;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "foo" (instance (;4;) (type 4))) + (type (;5;) + (component + (type (;0;) + (instance + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + ) + ) + (import "unlocked-dep=" (component (;1;) (type 5))) + (instance (;5;) (instantiate 1 + (with "foo" (instance 4)) + ) + ) + (instance (;6;) (instantiate 1 + (with "foo" (instance 0)) + ) + ) + (instance (;7;) (instantiate 1 + (with "foo" (instance 1)) + ) + ) + (instance (;8;) (instantiate 1 + (with "foo" (instance 2)) + ) + ) + (instance (;9;) (instantiate 1 + (with "foo" (instance 3)) + ) + ) + (@producers + (processed-by "wac-parser" "0.1.0") + ) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/encoding/instantiation/bar/baz.wat b/crates/wac-parser/tests/encoding/instantiation/bar/baz.wat new file mode 100644 index 0000000..7b76416 --- /dev/null +++ b/crates/wac-parser/tests/encoding/instantiation/bar/baz.wat @@ -0,0 +1,7 @@ +(component + (import "foo" + (instance + (export "foo" (func)) + ) + ) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/encoding/instantiation/foo/bar.wat b/crates/wac-parser/tests/encoding/instantiation/foo/bar.wat new file mode 100644 index 0000000..4a27b1e --- /dev/null +++ b/crates/wac-parser/tests/encoding/instantiation/foo/bar.wat @@ -0,0 +1,4 @@ +(component + (import "baz" (func)) + (export "foo" (func 0)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/encoding/resources.wac b/crates/wac-parser/tests/encoding/resources.wac new file mode 100644 index 0000000..d136d9d --- /dev/null +++ b/crates/wac-parser/tests/encoding/resources.wac @@ -0,0 +1,18 @@ +package test:comp; + +import foo: interface { + resource r { + constructor(); + foo: func(); + bar: static func(); + } + + type r2 = r; + + foo: func(r: borrow, r2: borrow, r3: r, r4: r2) -> tuple; + baz: func(); +}; + +let x = new foo:bar { foo }; + +export x.foo; diff --git a/crates/wac-parser/tests/encoding/resources.wat.result b/crates/wac-parser/tests/encoding/resources.wat.result new file mode 100644 index 0000000..07de5ce --- /dev/null +++ b/crates/wac-parser/tests/encoding/resources.wat.result @@ -0,0 +1,55 @@ +(component + (type (;0;) + (instance + (export (;0;) "r" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (result 1))) + (export (;0;) "[constructor]r" (func (type 2))) + (type (;3;) (borrow 0)) + (type (;4;) (func (param "self" 3))) + (export (;1;) "[method]r.foo" (func (type 4))) + (type (;5;) (func)) + (export (;2;) "[static]r.bar" (func (type 5))) + (export (;6;) "r2" (type (eq 0))) + (type (;7;) (borrow 6)) + (type (;8;) (own 6)) + (type (;9;) (tuple 1 8)) + (type (;10;) (func (param "r" 3) (param "r2" 7) (param "r3" 1) (param "r4" 8) (result 9))) + (export (;3;) "foo" (func (type 10))) + (type (;11;) (func)) + (export (;4;) "baz" (func (type 11))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + (type (;1;) + (component + (type (;0;) + (instance + (export (;0;) "r" (type (sub resource))) + (type (;1;) (func)) + (export (;0;) "[static]r.bar" (func (type 1))) + (export (;2;) "r2" (type (eq 0))) + (type (;3;) (borrow 0)) + (type (;4;) (borrow 2)) + (type (;5;) (own 0)) + (type (;6;) (own 2)) + (type (;7;) (tuple 5 6)) + (type (;8;) (func (param "r" 3) (param "r2" 4) (param "r3" 5) (param "r4" 6) (result 7))) + (export (;1;) "foo" (func (type 8))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + (export (;1;) "foo" (instance (type 0))) + ) + ) + (import "unlocked-dep=" (component (;0;) (type 1))) + (instance (;1;) (instantiate 0 + (with "foo" (instance 0)) + ) + ) + (alias export 1 "foo" (instance (;2;))) + (export (;3;) "foo" (instance 2)) + (@producers + (processed-by "wac-parser" "0.1.0") + ) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/encoding/resources/foo/bar.wat b/crates/wac-parser/tests/encoding/resources/foo/bar.wat new file mode 100644 index 0000000..9fd323d --- /dev/null +++ b/crates/wac-parser/tests/encoding/resources/foo/bar.wat @@ -0,0 +1,22 @@ +(component + (type (;0;) + (instance + (export (;0;) "r" (type (sub resource))) + (type (;1;) (func)) + (export (;0;) "[static]r.bar" (func (type 1))) + (export (;2;) "r2" (type (eq 0))) + (type (;3;) (borrow 0)) + (type (;4;) (borrow 2)) + (type (;5;) (own 0)) + (type (;6;) (own 2)) + (type (;7;) (tuple 5 6)) + (type (;8;) (func (param "r" 3) (param "r2" 4) (param "r3" 5) (param "r4" 6) (result 7))) + (export (;1;) "foo" (func (type 8))) + ) + ) + (import "foo" (instance (;0;) (type 0))) + (export "foo" (instance 0)) + (@producers + (processed-by "wac-parser" "0.1.0") + ) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/encoding/types.wac b/crates/wac-parser/tests/encoding/types.wac new file mode 100644 index 0000000..6edbd00 --- /dev/null +++ b/crates/wac-parser/tests/encoding/types.wac @@ -0,0 +1,101 @@ +package test:pkg; + +type a = u8; +type b = s8; +type c = u16; +type d = s16; +type e = u32; +type f = s32; +type g = u64; +type h = s64; +type i = float32; +type j = float64; +type k = bool; +type l = char; +type m = string; + +record n { + a: list, + b: tuple, + c: option, +} + +variant o { + foo, + bar(n), + baz(tuple), +} + +flags p { + foo, + bar, + baz, +} + +enum q { + a, + b, + c, +} + +interface r { + type a = string; + type b = u16; + type c = b; + resource d { + constructor(a: a); + a: func(b: b); + b: static func(c: c); + } + + f: func(a: a, b: b, c: c, d: d) -> tuple; +} + +interface s { + use r.{a, b, c}; + + record n { + a: list, + b: tuple, + c: option, + } + + variant o { + foo, + bar(n), + baz(tuple), + } + + flags p { + foo, + bar, + baz, + } + + f: func(a: a, b: b, c: c) -> tuple; +} + +world t { + use r.{a, b, c}; + + import f: func(a: a, b: b, c: c) -> tuple; + + record n { + a: list, + b: tuple, + c: option, + } + + variant o { + foo, + bar(n), + baz(tuple), + } + + flags p { + foo, + bar, + baz, + } + export f: func(a: a, b: b, c: c) -> tuple; +} diff --git a/crates/wac-parser/tests/encoding/types.wat.result b/crates/wac-parser/tests/encoding/types.wat.result new file mode 100644 index 0000000..3a7269a --- /dev/null +++ b/crates/wac-parser/tests/encoding/types.wat.result @@ -0,0 +1,159 @@ +(component + (type (;0;) u8) + (export (;1;) "a" (type 0)) + (type (;2;) s8) + (export (;3;) "b" (type 2)) + (type (;4;) u16) + (export (;5;) "c" (type 4)) + (type (;6;) s16) + (export (;7;) "d" (type 6)) + (type (;8;) u32) + (export (;9;) "e" (type 8)) + (type (;10;) s32) + (export (;11;) "f" (type 10)) + (type (;12;) u64) + (export (;13;) "g" (type 12)) + (type (;14;) s64) + (export (;15;) "h" (type 14)) + (type (;16;) float64) + (export (;17;) "i" (type 16)) + (type (;18;) float64) + (export (;19;) "j" (type 18)) + (type (;20;) bool) + (export (;21;) "k" (type 20)) + (type (;22;) char) + (export (;23;) "l" (type 22)) + (type (;24;) string) + (export (;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)) + (type (;34;) (flags "foo" "bar" "baz")) + (export (;35;) "p" (type 34)) + (type (;36;) (enum "a" "b" "c")) + (export (;37;) "q" (type 36)) + (type (;38;) + (component + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "a" (type (eq 0))) + (type (;2;) u16) + (export (;3;) "b" (type (eq 2))) + (type (;4;) u16) + (export (;5;) "c" (type (eq 4))) + (export (;6;) "d" (type (sub resource))) + (type (;7;) (own 6)) + (type (;8;) (func (param "a" 1) (result 7))) + (export (;0;) "[constructor]d" (func (type 8))) + (type (;9;) (borrow 6)) + (type (;10;) (func (param "self" 9) (param "b" 3))) + (export (;1;) "[method]d.a" (func (type 10))) + (type (;11;) (func (param "c" 5))) + (export (;2;) "[static]d.b" (func (type 11))) + (type (;12;) (tuple 5 3 7 1)) + (type (;13;) (func (param "a" 1) (param "b" 3) (param "c" 5) (param "d" 7) (result 12))) + (export (;3;) "f" (func (type 13))) + ) + ) + (export (;0;) "test:pkg/r" (instance (type 0))) + ) + ) + (export (;39;) "r" (type 38)) + (type (;40;) + (component + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "a" (type (eq 0))) + (type (;2;) u16) + (export (;3;) "b" (type (eq 2))) + (type (;4;) u16) + (export (;5;) "c" (type (eq 4))) + (export (;6;) "d" (type (sub resource))) + ) + ) + (import "test:pkg/r" (instance (;0;) (type 0))) + (alias export 0 "a" (type (;1;))) + (alias export 0 "b" (type (;2;))) + (alias export 0 "c" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "a" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "b" (type (eq 2))) + (alias outer 1 3 (type (;4;))) + (export (;5;) "c" (type (eq 4))) + (type (;6;) (list u8)) + (type (;7;) (tuple s8 u16 s16)) + (type (;8;) (option u32)) + (type (;9;) (record (field "a" 6) (field "b" 7) (field "c" 8))) + (export (;10;) "n" (type (eq 9))) + (type (;11;) (tuple 10 10 10)) + (type (;12;) (variant (case "foo") (case "bar" 10) (case "baz" 11))) + (export (;13;) "o" (type (eq 12))) + (type (;14;) (flags "foo" "bar" "baz")) + (export (;15;) "p" (type (eq 14))) + (type (;16;) (tuple 5 3 1)) + (type (;17;) (func (param "a" 1) (param "b" 3) (param "c" 5) (result 16))) + (export (;0;) "f" (func (type 17))) + ) + ) + (export (;1;) "test:pkg/s" (instance (type 4))) + ) + ) + (export (;41;) "s" (type 40)) + (type (;42;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) string) + (export (;1;) "a" (type (eq 0))) + (type (;2;) u16) + (export (;3;) "b" (type (eq 2))) + (type (;4;) u16) + (export (;5;) "c" (type (eq 4))) + (export (;6;) "d" (type (sub resource))) + ) + ) + (import "test:pkg/r" (instance (;0;) (type 0))) + (alias export 0 "a" (type (;1;))) + (alias export 0 "b" (type (;2;))) + (alias export 0 "c" (type (;3;))) + (import "a" (type (;4;) (eq 1))) + (import "b" (type (;5;) (eq 2))) + (import "c" (type (;6;) (eq 3))) + (type (;7;) (tuple 6 5 4)) + (type (;8;) (func (param "a" 4) (param "b" 5) (param "c" 6) (result 7))) + (import "f" (func (;0;) (type 8))) + (type (;9;) (list u8)) + (type (;10;) (tuple s8 u16 s16)) + (type (;11;) (option u32)) + (type (;12;) (record (field "a" 9) (field "b" 10) (field "c" 11))) + (import "n" (type (;13;) (eq 12))) + (type (;14;) (tuple 13 13 13)) + (type (;15;) (variant (case "foo") (case "bar" 13) (case "baz" 14))) + (import "o" (type (;16;) (eq 15))) + (type (;17;) (flags "foo" "bar" "baz")) + (import "p" (type (;18;) (eq 17))) + (type (;19;) (tuple 13 16 18)) + (type (;20;) (func (param "a" 4) (param "b" 5) (param "c" 6) (result 19))) + (export (;1;) "f" (func (type 20))) + ) + ) + (export (;0;) "test:pkg/t" (component (type 0))) + ) + ) + (export (;43;) "t" (type 42)) + (@producers + (processed-by "wac-parser" "0.1.0") + ) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/parser.rs b/crates/wac-parser/tests/parser.rs index 4aacbd1..43642a3 100644 --- a/crates/wac-parser/tests/parser.rs +++ b/crates/wac-parser/tests/parser.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use owo_colors::OwoColorize; use pretty_assertions::StrComparison; use rayon::prelude::*; @@ -114,17 +114,38 @@ fn main() { .par_iter() .filter_map(|test| { let test_name = test.file_stem().and_then(OsStr::to_str).unwrap(); - match run_test(test, &ntests) - .with_context(|| format!("failed to run test `{path}`", path = test.display())) - .err() - { - Some(e) => { - println!("test {test_name} ... {failed}", failed = "failed".red()); - Some((test_name, e)) + match std::panic::catch_unwind(|| { + match run_test(test, &ntests) + .with_context(|| format!("failed to run test `{path}`", path = test.display())) + .err() + { + Some(e) => { + println!("test {test_name} ... {failed}", failed = "failed".red()); + Some((test_name, e)) + } + None => { + println!("test {test_name} ... {ok}", ok = "ok".green()); + None + } } - None => { - println!("test {test_name} ... {ok}", ok = "ok".green()); - None + }) { + Ok(result) => result, + Err(e) => { + println!( + "test {test_name} ... {panicked}", + panicked = "panicked".red() + ); + Some(( + test_name, + anyhow!( + "test panicked: {e:?}", + e = e + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| e.downcast_ref::<&str>().copied()) + .unwrap_or("no panic message") + ), + )) } } }) diff --git a/crates/wac-parser/tests/parser/import.wac.result b/crates/wac-parser/tests/parser/import.wac.result index 2c3e285..aab3390 100644 --- a/crates/wac-parser/tests/parser/import.wac.result +++ b/crates/wac-parser/tests/parser/import.wac.result @@ -147,7 +147,13 @@ "end": 227 } }, - "ty": "string" + "ty": { + "string": { + "str": "string", + "start": 229, + "end": 235 + } + } } ], "results": "empty" diff --git a/crates/wac-parser/tests/parser/resource.wac.result b/crates/wac-parser/tests/parser/resource.wac.result index 84a002b..84be44e 100644 --- a/crates/wac-parser/tests/parser/resource.wac.result +++ b/crates/wac-parser/tests/parser/resource.wac.result @@ -143,7 +143,13 @@ "end": 331 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 333, + "end": 336 + } + } } ] } @@ -170,22 +176,26 @@ }, "isStatic": false, "ty": { - "func": { - "params": [ - { - "id": { - "string": "b", - "span": { - "str": "b", - "start": 374, - "end": 375 - } - }, - "ty": "u32" + "params": [ + { + "id": { + "string": "b", + "span": { + "str": "b", + "start": 374, + "end": 375 + } + }, + "ty": { + "u32": { + "str": "u32", + "start": 377, + "end": 380 + } } - ], - "results": "empty" - } + } + ], + "results": "empty" } } }, @@ -211,22 +221,26 @@ }, "isStatic": true, "ty": { - "func": { - "params": [ - { - "id": { - "string": "c", - "span": { - "str": "c", - "start": 432, - "end": 433 - } - }, - "ty": "u32" + "params": [ + { + "id": { + "string": "c", + "span": { + "str": "c", + "start": 432, + "end": 433 + } + }, + "ty": { + "u32": { + "str": "u32", + "start": 435, + "end": 438 + } } - ], - "results": "empty" - } + } + ], + "results": "empty" } } } @@ -371,7 +385,13 @@ "end": 757 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 759, + "end": 762 + } + } } ] } @@ -398,22 +418,26 @@ }, "isStatic": false, "ty": { - "func": { - "params": [ - { - "id": { - "string": "b", - "span": { - "str": "b", - "start": 800, - "end": 801 - } - }, - "ty": "u32" + "params": [ + { + "id": { + "string": "b", + "span": { + "str": "b", + "start": 800, + "end": 801 + } + }, + "ty": { + "u32": { + "str": "u32", + "start": 803, + "end": 806 + } } - ], - "results": "empty" - } + } + ], + "results": "empty" } } }, @@ -439,22 +463,26 @@ }, "isStatic": true, "ty": { - "func": { - "params": [ - { - "id": { - "string": "c", - "span": { - "str": "c", - "start": 858, - "end": 859 - } - }, - "ty": "u32" + "params": [ + { + "id": { + "string": "c", + "span": { + "str": "c", + "start": 858, + "end": 859 + } + }, + "ty": { + "u32": { + "str": "u32", + "start": 861, + "end": 864 + } } - ], - "results": "empty" - } + } + ], + "results": "empty" } } } diff --git a/crates/wac-parser/tests/parser/type-alias.wac.result b/crates/wac-parser/tests/parser/type-alias.wac.result index 6915d66..ff40f41 100644 --- a/crates/wac-parser/tests/parser/type-alias.wac.result +++ b/crates/wac-parser/tests/parser/type-alias.wac.result @@ -33,7 +33,13 @@ } }, "kind": { - "type": "u8" + "type": { + "u8": { + "str": "u8", + "start": 36, + "end": 38 + } + } } } } @@ -62,7 +68,13 @@ } }, "kind": { - "type": "s8" + "type": { + "s8": { + "str": "s8", + "start": 56, + "end": 58 + } + } } } } @@ -91,7 +103,13 @@ } }, "kind": { - "type": "u16" + "type": { + "u16": { + "str": "u16", + "start": 77, + "end": 80 + } + } } } } @@ -120,7 +138,13 @@ } }, "kind": { - "type": "s16" + "type": { + "s16": { + "str": "s16", + "start": 99, + "end": 102 + } + } } } } @@ -149,7 +173,13 @@ } }, "kind": { - "type": "u32" + "type": { + "u32": { + "str": "u32", + "start": 121, + "end": 124 + } + } } } } @@ -178,7 +208,13 @@ } }, "kind": { - "type": "s32" + "type": { + "s32": { + "str": "s32", + "start": 143, + "end": 146 + } + } } } } @@ -207,7 +243,13 @@ } }, "kind": { - "type": "u64" + "type": { + "u64": { + "str": "u64", + "start": 165, + "end": 168 + } + } } } } @@ -236,7 +278,13 @@ } }, "kind": { - "type": "s64" + "type": { + "s64": { + "str": "s64", + "start": 187, + "end": 190 + } + } } } } @@ -265,7 +313,13 @@ } }, "kind": { - "type": "float32" + "type": { + "float32": { + "str": "float32", + "start": 213, + "end": 220 + } + } } } } @@ -294,7 +348,13 @@ } }, "kind": { - "type": "float64" + "type": { + "float64": { + "str": "float64", + "start": 243, + "end": 250 + } + } } } } @@ -323,7 +383,13 @@ } }, "kind": { - "type": "bool" + "type": { + "bool": { + "str": "bool", + "start": 270, + "end": 274 + } + } } } } @@ -352,7 +418,13 @@ } }, "kind": { - "type": "char" + "type": { + "char": { + "str": "char", + "start": 294, + "end": 298 + } + } } } } @@ -381,7 +453,13 @@ } }, "kind": { - "type": "string" + "type": { + "string": { + "str": "string", + "start": 320, + "end": 326 + } + } } } } @@ -412,8 +490,27 @@ "kind": { "type": { "tuple": [ - "u8", - "s8" + [ + { + "u8": { + "str": "u8", + "start": 361, + "end": 363 + } + }, + { + "s8": { + "str": "s8", + "start": 365, + "end": 367 + } + } + ], + { + "str": "tuple", + "start": 355, + "end": 368 + } ] } } @@ -445,11 +542,31 @@ }, "kind": { "type": { - "list": { - "tuple": [ - "u8" - ] - } + "list": [ + { + "tuple": [ + [ + { + "u8": { + "str": "u8", + "start": 410, + "end": 412 + } + } + ], + { + "str": "tuple", + "start": 404, + "end": 413 + } + ] + }, + { + "str": "list>", + "start": 399, + "end": 414 + } + ] } } } @@ -480,7 +597,20 @@ }, "kind": { "type": { - "option": "string" + "option": [ + { + "string": { + "str": "string", + "start": 451, + "end": 457 + } + }, + { + "str": "option", + "start": 444, + "end": 458 + } + ] } } } @@ -513,7 +643,12 @@ "type": { "result": { "ok": null, - "err": null + "err": null, + "span": { + "str": "result", + "start": 480, + "end": 486 + } } } } @@ -546,8 +681,19 @@ "kind": { "type": { "result": { - "ok": "u8", - "err": null + "ok": { + "u8": { + "str": "u8", + "start": 519, + "end": 521 + } + }, + "err": null, + "span": { + "str": "result", + "start": 512, + "end": 522 + } } } } @@ -581,7 +727,18 @@ "type": { "result": { "ok": null, - "err": "s8" + "err": { + "s8": { + "str": "s8", + "start": 561, + "end": 563 + } + }, + "span": { + "str": "result<_, s8>", + "start": 551, + "end": 564 + } } } } @@ -614,8 +771,25 @@ "kind": { "type": { "result": { - "ok": "u8", - "err": "string" + "ok": { + "u8": { + "str": "u8", + "start": 605, + "end": 607 + } + }, + "err": { + "string": { + "str": "string", + "start": 609, + "end": 615 + } + }, + "span": { + "str": "result", + "start": 598, + "end": 616 + } } } } @@ -647,14 +821,21 @@ }, "kind": { "type": { - "borrow": { - "string": "x", - "span": { - "str": "x", - "start": 648, - "end": 649 + "borrow": [ + { + "string": "x", + "span": { + "str": "x", + "start": 648, + "end": 649 + } + }, + { + "str": "borrow", + "start": 641, + "end": 650 } - } + ] } } } diff --git a/crates/wac-parser/tests/parser/types.wac.result b/crates/wac-parser/tests/parser/types.wac.result index 3be853d..6d2db30 100644 --- a/crates/wac-parser/tests/parser/types.wac.result +++ b/crates/wac-parser/tests/parser/types.wac.result @@ -131,7 +131,13 @@ "end": 223 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 225, + "end": 228 + } + } } ] } @@ -708,7 +714,13 @@ "end": 1084 } }, - "ty": "string" + "ty": { + "string": { + "str": "string", + "start": 1085, + "end": 1091 + } + } }, { "docs": [], @@ -720,7 +732,13 @@ "end": 1099 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 1100, + "end": 1103 + } + } }, { "docs": [], @@ -772,7 +790,13 @@ "end": 1154 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 1156, + "end": 1159 + } + } }, { "docs": [], @@ -784,7 +808,13 @@ "end": 1166 } }, - "ty": "string" + "ty": { + "string": { + "str": "string", + "start": 1168, + "end": 1174 + } + } }, { "docs": [], @@ -986,7 +1016,13 @@ } }, "kind": { - "type": "string" + "type": { + "string": { + "str": "string", + "start": 1336, + "end": 1342 + } + } } } } @@ -1017,7 +1053,13 @@ "end": 1360 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 1362, + "end": 1365 + } + } }, { "id": { @@ -1041,7 +1083,13 @@ } ], "results": { - "scalar": "u32" + "scalar": { + "u32": { + "str": "u32", + "start": 1376, + "end": 1379 + } + } } } } @@ -1076,7 +1124,13 @@ "end": 1403 } }, - "ty": "u32" + "ty": { + "u32": { + "str": "u32", + "start": 1405, + "end": 1408 + } + } }, { "id": { @@ -1087,7 +1141,13 @@ "end": 1411 } }, - "ty": "string" + "ty": { + "string": { + "str": "string", + "start": 1413, + "end": 1419 + } + } } ] } diff --git a/crates/wac-parser/tests/resolution.rs b/crates/wac-parser/tests/resolution.rs index 3fe8d38..aa3f879 100644 --- a/crates/wac-parser/tests/resolution.rs +++ b/crates/wac-parser/tests/resolution.rs @@ -10,11 +10,7 @@ use std::{ process::exit, sync::atomic::{AtomicUsize, Ordering}, }; -use wac_parser::{ - ast::Document, - resolution::{FileSystemPackageResolver, ResolvedDocument}, - ErrorFormatter, -}; +use wac_parser::{ast::Document, Composition, ErrorFormatter, FileSystemPackageResolver}; #[cfg(not(feature = "wat"))] compile_error!("the `wat` feature must be enabled for this test to run"); @@ -86,7 +82,7 @@ fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { let source = std::fs::read_to_string(test)?.replace("\r\n", "\n"); let document = Document::parse(&source, test) .map_err(|e| anyhow!("{e}", e = ErrorFormatter::new(test, e, false)))?; - let result = match ResolvedDocument::new( + let result = match Composition::from_ast( &document, Some(Box::new(FileSystemPackageResolver::new( test.parent().unwrap().join(test.file_stem().unwrap()), @@ -129,17 +125,38 @@ fn main() { .par_iter() .filter_map(|test| { let test_name = test.file_stem().and_then(OsStr::to_str).unwrap(); - match run_test(test, &ntests) - .with_context(|| format!("failed to run test `{path}`", path = test.display())) - .err() - { - Some(e) => { - println!("test {test_name} ... {failed}", failed = "failed".red()); - Some((test_name, e)) + match std::panic::catch_unwind(|| { + match run_test(test, &ntests) + .with_context(|| format!("failed to run test `{path}`", path = test.display())) + .err() + { + Some(e) => { + println!("test {test_name} ... {failed}", failed = "failed".red()); + Some((test_name, e)) + } + None => { + println!("test {test_name} ... {ok}", ok = "ok".green()); + None + } } - None => { - println!("test {test_name} ... {ok}", ok = "ok".green()); - None + }) { + Ok(result) => result, + Err(e) => { + println!( + "test {test_name} ... {panicked}", + panicked = "panicked".red() + ); + Some(( + test_name, + anyhow!( + "test panicked: {e:?}", + e = e + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| e.downcast_ref::<&str>().copied()) + .unwrap_or("no panic message") + ), + )) } } }) diff --git a/crates/wac-parser/tests/resolution/alias.wac.result b/crates/wac-parser/tests/resolution/alias.wac.result index 6d4d683..74d0241 100644 --- a/crates/wac-parser/tests/resolution/alias.wac.result +++ b/crates/wac-parser/tests/resolution/alias.wac.result @@ -21,46 +21,46 @@ "worlds": [], "modules": [] }, + "packages": [], "items": [ { - "kind": { - "type": { - "value": 0 + "definition": { + "name": "a", + "kind": { + "type": { + "value": 0 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 1 + "definition": { + "name": "b", + "kind": { + "type": { + "value": 1 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "func": 0 + "definition": { + "name": "c", + "kind": { + "type": { + "func": 0 + } } - }, - "source": "definition" + } } ], "imports": {}, "exports": { + "a": 0, + "b": 1, + "c": 2, "a2": 0, "b2": 1, "c2": 2 - }, - "scopes": [ - { - "parent": null, - "items": { - "a": 0, - "b": 1, - "c": 2 - } - } - ] + } } \ No newline at end of file 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 8504e5b..f56dbdd 100644 --- a/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result +++ b/crates/wac-parser/tests/resolution/duplicate-world-item.wac.result @@ -17,47 +17,37 @@ "interfaces": [], "worlds": [ { + "id": "test:comp/w@2.0.0", + "uses": {}, "imports": { "x": { - "kind": { - "func": 0 - } + "func": 0 } }, "exports": { "x": { - "kind": { - "func": 1 - } + "func": 1 } - }, - "scope": 1 + } } ], "modules": [] }, + "packages": [], "items": [ { - "kind": { - "type": { - "world": 0 + "definition": { + "name": "w", + "kind": { + "type": { + "world": 0 + } } - }, - "source": "definition" + } } ], "imports": {}, - "exports": {}, - "scopes": [ - { - "parent": null, - "items": { - "w": 0 - } - }, - { - "parent": 0, - "items": {} - } - ] + "exports": { + "w": 0 + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac b/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac new file mode 100644 index 0000000..6b33fe3 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac @@ -0,0 +1,4 @@ +package test:comp; + +let a = new foo:bar { ... }; +let b = new bar:baz { ... }; diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result b/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result new file mode 100644 index 0000000..2155f32 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/arg-merge-failure.wac.result @@ -0,0 +1,5 @@ +implicit instantiation argument `foo` (instance) conflicts with an implicitly imported argument from the instantiation of `foo:bar` at tests/resolution/fail/arg-merge-failure.wac:3:13 + --> tests/resolution/fail/arg-merge-failure.wac:4:13 + | + 4 | let b = new bar:baz { ... }; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure/bar/baz.wat b/crates/wac-parser/tests/resolution/fail/arg-merge-failure/bar/baz.wat new file mode 100644 index 0000000..a6eb7aa --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/arg-merge-failure/bar/baz.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/arg-merge-failure/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/arg-merge-failure/foo/bar.wat new file mode 100644 index 0000000..754453e --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/arg-merge-failure/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (instance)) +) diff --git a/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac b/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac new file mode 100644 index 0000000..267dfdc --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac @@ -0,0 +1,7 @@ +package test:comp; + +interface foo { + resource x; + type a = borrow; + f: func() -> result; +} diff --git a/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac.result b/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac.result new file mode 100644 index 0000000..fd746b4 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/borrow-in-func-result.wac.result @@ -0,0 +1,5 @@ +function result cannot recursively contain a borrow type + --> tests/resolution/fail/borrow-in-func-result.wac:6:18 + | + 6 | f: func() -> result; + | ^-------^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac new file mode 100644 index 0000000..2cd0b85 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac @@ -0,0 +1,5 @@ +package test:comp; + +import bar: func(); + +let x = new foo:bar { foo: bar, foo: bar }; diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac.result new file mode 100644 index 0000000..39d4582 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args.wac.result @@ -0,0 +1,5 @@ +duplicate instantiation argument `foo` + --> tests/resolution/fail/duplicate-inst-args.wac:5:33 + | + 5 | let x = new foo:bar { foo: bar, foo: bar }; + | ^-^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-inst-args/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args/foo/bar.wat new file mode 100644 index 0000000..a6eb7aa --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-inst-args/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac new file mode 100644 index 0000000..c7eab41 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac @@ -0,0 +1,10 @@ +package foo:bar; + +interface i { + type x = u32; +} + +interface i2 { + x: func(); + use i.{x}; +} diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac.result new file mode 100644 index 0000000..20156ce --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-interface.wac.result @@ -0,0 +1,5 @@ +use of type `x` conflicts with an export of the same name (consider using an `as` clause to use a different name) + --> tests/resolution/fail/duplicate-use-in-interface.wac:9:12 + | + 9 | use i.{x}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac new file mode 100644 index 0000000..b92599d --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac @@ -0,0 +1,10 @@ +package foo:bar; + +interface i { + type x = u32; +} + +world w { + import x: func(); + use i.{x}; +} diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac.result new file mode 100644 index 0000000..dbd7336 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/duplicate-use-in-world.wac.result @@ -0,0 +1,5 @@ +use of type `x` conflicts with an import of the same name (consider using an `as` clause to use a different name) + --> tests/resolution/fail/duplicate-use-in-world.wac:9:12 + | + 9 | use i.{x}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac new file mode 100644 index 0000000..26e4b93 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac @@ -0,0 +1,5 @@ +package test:comp; + +import foo: func(); + +let x = new foo:bar { ... }; diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result new file mode 100644 index 0000000..4ba9917 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict.wac.result @@ -0,0 +1,5 @@ +implicit instantiation argument `foo` (function) conflicts with an explicit import at tests/resolution/fail/implicit-arg-conflict.wac:3:8 + --> tests/resolution/fail/implicit-arg-conflict.wac:5:13 + | + 5 | let x = new foo:bar { ... }; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict/foo/bar.wat new file mode 100644 index 0000000..571d460 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/implicit-arg-conflict/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict.wac b/crates/wac-parser/tests/resolution/fail/import-conflict.wac new file mode 100644 index 0000000..da88965 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/import-conflict.wac @@ -0,0 +1,5 @@ +package test:comp; + +let x = new foo:bar { ... }; + +import foo: func(); diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result new file mode 100644 index 0000000..da6060b --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/import-conflict.wac.result @@ -0,0 +1,5 @@ +import name `foo` conflicts with an instance that was implicitly imported by the instantiation of `foo:bar` at tests/resolution/fail/import-conflict.wac:3:13 + --> tests/resolution/fail/import-conflict.wac:5:8 + | + 5 | import foo: func(); + | ^-^ diff --git a/crates/wac-parser/tests/resolution/fail/import-conflict/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/import-conflict/foo/bar.wat new file mode 100644 index 0000000..a6eb7aa --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/import-conflict/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/inaccessible.wac b/crates/wac-parser/tests/resolution/fail/inaccessible.wac new file mode 100644 index 0000000..20e9719 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/inaccessible.wac @@ -0,0 +1,5 @@ +package test:comp; + +import f: func(); + +let x = f.foo; diff --git a/crates/wac-parser/tests/resolution/fail/inaccessible.wac.result b/crates/wac-parser/tests/resolution/fail/inaccessible.wac.result new file mode 100644 index 0000000..5cdb005 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/inaccessible.wac.result @@ -0,0 +1,5 @@ +a function cannot be accessed + --> tests/resolution/fail/inaccessible.wac:5:10 + | + 5 | let x = f.foo; + | ^--^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac b/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac index 79843b3..3f250d1 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac +++ b/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac @@ -1,7 +1,7 @@ package test:comp; -type x = u32; - interface i { + type x = u32; + x: x; } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result index 6d2f1d3..fccb432 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result @@ -1,4 +1,4 @@ -`b` was previously defined at tests/resolution/fail/invalid-use-alias.wac:8:10 +use of type `b` conflicts with an export of the same name --> tests/resolution/fail/invalid-use-alias.wac:9:17 | 9 | use a.{a as b}; diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result index 76627dc..14f0cd2 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result @@ -7,4 +7,4 @@ mismatched instantiation argument `x` Caused by: 0: mismatched type for export `f` 1: mismatched type for function parameter `x` - 2: mismatched resource types \ No newline at end of file + 2: expected resource `x`, found resource `y` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-access-export.wac b/crates/wac-parser/tests/resolution/fail/missing-access-export.wac new file mode 100644 index 0000000..51e9d7b --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/missing-access-export.wac @@ -0,0 +1,7 @@ +package test:comp; + +import i: interface { + +}; + +let x = i.foo; diff --git a/crates/wac-parser/tests/resolution/fail/missing-access-export.wac.result b/crates/wac-parser/tests/resolution/fail/missing-access-export.wac.result new file mode 100644 index 0000000..09b3ebf --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/missing-access-export.wac.result @@ -0,0 +1,5 @@ +the instance has no export named `foo` + --> tests/resolution/fail/missing-access-export.wac:7:10 + | + 7 | let x = i.foo; + | ^--^ diff --git a/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result b/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result index b083d87..68852d1 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result @@ -5,5 +5,4 @@ mismatched instantiation argument `x` | ^ Caused by: - 0: mismatched type for export `r` - 1: missing expected constructor \ No newline at end of file + instance is missing expected export `[constructor]r` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac b/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac new file mode 100644 index 0000000..fe89b5c --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac @@ -0,0 +1,3 @@ +package test:comp; + +let x = new foo:bar {}; diff --git a/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac.result b/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac.result new file mode 100644 index 0000000..ec577e5 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/missing-inst-arg.wac.result @@ -0,0 +1,5 @@ +missing instantiation argument `foo` for package `foo:bar` + --> tests/resolution/fail/missing-inst-arg.wac:3:13 + | + 3 | let x = new foo:bar {}; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/missing-inst-arg/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/missing-inst-arg/foo/bar.wat new file mode 100644 index 0000000..a6eb7aa --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/missing-inst-arg/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result b/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result index fcefef9..9467824 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result @@ -5,4 +5,4 @@ mismatched instantiation argument `x` | ^ Caused by: - missing expected export `f` \ No newline at end of file + instance is missing expected export `f` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-method.wac.result b/crates/wac-parser/tests/resolution/fail/missing-method.wac.result index ffddfc8..8b538ab 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-method.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-method.wac.result @@ -5,5 +5,4 @@ mismatched instantiation argument `x` | ^ Caused by: - 0: mismatched type for export `r` - 1: missing expected method `foo` \ No newline at end of file + instance is missing expected export `[method]r.foo` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result b/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result index 566ec11..7fbfc3b 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result @@ -5,5 +5,4 @@ mismatched instantiation argument `x` | ^ Caused by: - 0: mismatched type for export `r` - 1: missing expected method `foo` \ No newline at end of file + instance is missing expected export `[static]r.foo` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/self-instantiation.wac b/crates/wac-parser/tests/resolution/fail/self-instantiation.wac new file mode 100644 index 0000000..751c808 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/self-instantiation.wac @@ -0,0 +1,3 @@ +package foo:bar; + +let i = new foo:bar {}; diff --git a/crates/wac-parser/tests/resolution/fail/self-instantiation.wac.result b/crates/wac-parser/tests/resolution/fail/self-instantiation.wac.result new file mode 100644 index 0000000..db97185 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/self-instantiation.wac.result @@ -0,0 +1,5 @@ +cannot instantiate the package being defined + --> tests/resolution/fail/self-instantiation.wac:3:13 + | + 3 | let i = new foo:bar {}; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac b/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac new file mode 100644 index 0000000..2a1c15d --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac @@ -0,0 +1,4 @@ +package test:comp; + +let a = new bar:baz { ... }; +let b = new foo:bar { ... }; diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result b/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result new file mode 100644 index 0000000..dc536d1 --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/unmergeable-args.wac.result @@ -0,0 +1,5 @@ +implicit instantiation argument `foo` (function) conflicts with an implicitly imported argument from the instantiation of `bar:baz` at tests/resolution/fail/unmergeable-args.wac:3:13 + --> tests/resolution/fail/unmergeable-args.wac:4:13 + | + 4 | let b = new foo:bar { ... }; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args/bar/baz.wat b/crates/wac-parser/tests/resolution/fail/unmergeable-args/bar/baz.wat new file mode 100644 index 0000000..a6eb7aa --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/unmergeable-args/bar/baz.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (func)) +) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/unmergeable-args/foo/bar.wat b/crates/wac-parser/tests/resolution/fail/unmergeable-args/foo/bar.wat new file mode 100644 index 0000000..754453e --- /dev/null +++ b/crates/wac-parser/tests/resolution/fail/unmergeable-args/foo/bar.wat @@ -0,0 +1,3 @@ +(component + (import "foo" (instance)) +) diff --git a/crates/wac-parser/tests/resolution/import.wac b/crates/wac-parser/tests/resolution/import.wac index 29e4673..d2593ba 100644 --- a/crates/wac-parser/tests/resolution/import.wac +++ b/crates/wac-parser/tests/resolution/import.wac @@ -19,7 +19,7 @@ import e: interface { }; /// Import by package path with version -import f: foo:bar/baz@1.0.0; +import f: foo:bar/baz; export d; export e with "e"; diff --git a/crates/wac-parser/tests/resolution/import.wac.result b/crates/wac-parser/tests/resolution/import.wac.result index cbcbb85..7c33fe2 100644 --- a/crates/wac-parser/tests/resolution/import.wac.result +++ b/crates/wac-parser/tests/resolution/import.wac.result @@ -27,96 +27,108 @@ "interfaces": [ { "id": null, + "uses": {}, "exports": { "x": { - "kind": { - "func": 3 - } + "func": 3 } - }, - "scope": 1 + } }, { "id": "foo:bar/baz", - "exports": {}, - "scope": null + "uses": {}, + "exports": {} } ], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/baz": { - "kind": { - "instance": 1 - } + "instance": 1 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "baz": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } } - }, - "scope": null + } } ], "modules": [] }, + "packages": [ + { + "name": "foo:bar", + "version": null, + "world": 1, + "definitions": { + "baz": { + "type": { + "interface": 1 + } + } + } + } + ], "items": [ { - "kind": { - "func": 0 - }, - "source": { - "import": "a" + "import": { + "name": "a", + "kind": { + "func": 0 + } } }, { - "kind": { - "type": { - "func": 1 + "definition": { + "name": "x", + "kind": { + "type": { + "func": 1 + } } - }, - "source": "definition" + } }, { - "kind": { - "func": 1 - }, - "source": { - "import": "b" + "import": { + "name": "b", + "kind": { + "func": 1 + } } }, { - "kind": { - "func": 2 - }, - "source": { - "import": "hello-world" + "import": { + "name": "hello-world", + "kind": { + "func": 2 + } } }, { - "kind": { - "instance": 0 - }, - "source": { - "import": "e" + "import": { + "name": "e", + "kind": { + "instance": 0 + } } }, { - "kind": { - "instance": 1 - }, - "source": { - "import": "foo:bar/baz" + "import": { + "name": "foo:bar/baz", + "kind": { + "instance": 1 + } } } ], @@ -128,25 +140,9 @@ "foo:bar/baz": 5 }, "exports": { + "x": 1, "hello-world": 3, "e": 4, "foo:bar/baz": 5 - }, - "scopes": [ - { - "parent": null, - "items": { - "a": 0, - "x": 1, - "b": 2, - "d": 3, - "e": 4, - "f": 5 - } - }, - { - "parent": 0, - "items": {} - } - ] + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/let-statements.wac b/crates/wac-parser/tests/resolution/let-statements.wac index 4d8fa92..a3875b3 100644 --- a/crates/wac-parser/tests/resolution/let-statements.wac +++ b/crates/wac-parser/tests/resolution/let-statements.wac @@ -2,7 +2,7 @@ package test:comp@1.0.0; import streams: wasi:io/streams; -let i = new foo:bar { streams, baz: new foo:bar@100.0.0 { streams, ... }.baz }; +let i = new foo:bar { streams, baz: new foo:bar { streams, ... }.baz }; export i.baz; export i with "i"; diff --git a/crates/wac-parser/tests/resolution/let-statements.wac.result b/crates/wac-parser/tests/resolution/let-statements.wac.result index 6196b0f..9eeb840 100644 --- a/crates/wac-parser/tests/resolution/let-statements.wac.result +++ b/crates/wac-parser/tests/resolution/let-statements.wac.result @@ -3,82 +3,67 @@ "version": "1.0.0", "definitions": { "types": [ - { - "resource": 0 - }, { "enum": [ "open", "ended" ] }, - { - "resource": 1 - }, { "enum": [ "last-operation-failed", "closed" ] }, - { - "resource": 2 - }, { "list": "u8" }, { "tuple": [ - 5, - 1 + 2, + 0 ] }, { "result": { - "ok": 6, + "ok": 3, "err": null } }, { "tuple": [ "u64", - 1 + 0 ] }, { "result": { - "ok": 8, + "ok": 5, "err": null } }, { "result": { "ok": "u64", - "err": 3 + "err": 1 } }, { "result": { "ok": null, - "err": 3 + "err": 1 } }, - { - "resource": 3 - }, { "enum": [ "last-operation-failed", "closed" ] }, - { - "resource": 4 - }, { "result": { "ok": "u64", - "err": 13 + "err": 9 } }, { @@ -87,198 +72,138 @@ { "result": { "ok": null, - "err": 13 + "err": 9 } } ], "resources": [ - {}, { - "methods": { - "read": { - "kind": "method", - "ty": 0 - }, - "blocking-read": { - "kind": "method", - "ty": 0 - }, - "skip": { - "kind": "method", - "ty": 1 - }, - "blocking-skip": { - "kind": "method", - "ty": 1 - }, - "subscribe": { - "kind": "method", - "ty": 2 - } - } + "name": "pollable" }, { - "methods": { - "check-write": { - "kind": "method", - "ty": 3 - }, - "write": { - "kind": "method", - "ty": 4 - }, - "blocking-write-and-flush": { - "kind": "method", - "ty": 4 - }, - "flush": { - "kind": "method", - "ty": 5 - }, - "blocking-flush": { - "kind": "method", - "ty": 5 - }, - "subscribe": { - "kind": "method", - "ty": 6 - }, - "write-zeroes": { - "kind": "method", - "ty": 7 - }, - "blocking-write-zeroes-and-flush": { - "kind": "method", - "ty": 7 - }, - "splice": { - "kind": "method", - "ty": 8 - }, - "blocking-splice": { - "kind": "method", - "ty": 8 - }, - "forward": { - "kind": "method", - "ty": 9 - } - } + "name": "input-stream" }, { - "methods": { - "check-write": { - "kind": "method", - "ty": 10 - }, - "write": { - "kind": "method", - "ty": 11 - }, - "blocking-write-and-flush": { - "kind": "method", - "ty": 11 - }, - "blocking-flush": { - "kind": "method", - "ty": 12 - } - } + "name": "output-stream" }, - {} + { + "name": "output-stream" + }, + { + "name": "input-stream" + } ], "funcs": [ { "params": { + "self": "borrow<1>", "len": "u64" }, "results": { - "scalar": 7 + "scalar": 4 } }, { "params": { + "self": "borrow<1>", "len": "u64" }, "results": { - "scalar": 9 + "scalar": 6 } }, { - "params": {}, + "params": { + "self": "borrow<1>" + }, "results": { - "scalar": 0 + "scalar": "own<0>" } }, { - "params": {}, + "params": { + "self": "borrow<2>" + }, "results": { - "scalar": 10 + "scalar": 7 } }, { "params": { - "contents": 5 + "self": "borrow<2>", + "contents": 2 }, "results": { - "scalar": 11 + "scalar": 8 } }, { - "params": {}, + "params": { + "self": "borrow<2>" + }, "results": { - "scalar": 11 + "scalar": 8 } }, { - "params": {}, + "params": { + "self": "borrow<2>" + }, "results": { - "scalar": 0 + "scalar": "own<0>" } }, { "params": { + "self": "borrow<2>", "len": "u64" }, "results": { - "scalar": 11 + "scalar": 8 } }, { "params": { - "src": 2, + "self": "borrow<2>", + "src": "own<1>", "len": "u64" }, "results": { - "scalar": 9 + "scalar": 6 } }, { "params": { - "src": 2 + "self": "borrow<2>", + "src": "own<1>" }, "results": { - "scalar": 9 + "scalar": 6 } }, { - "params": {}, + "params": { + "self": "borrow<3>" + }, "results": { - "scalar": 15 + "scalar": 10 } }, { "params": { - "contents": 16 + "self": "borrow<3>", + "contents": 11 }, "results": { - "scalar": 17 + "scalar": 12 } }, { - "params": {}, + "params": { + "self": "borrow<3>" + }, "results": { - "scalar": 17 + "scalar": 12 } }, { @@ -289,205 +214,248 @@ "interfaces": [ { "id": "wasi:io/streams@0.2.0-rc-2023-10-18", + "uses": {}, "exports": { "pollable": { - "kind": { - "type": { - "value": 0 - } - } + "resource": 0 }, "stream-status": { - "kind": { - "type": { - "value": 1 - } + "type": { + "value": 0 } }, "input-stream": { - "kind": { - "type": { - "value": 2 - } - } + "resource": 1 }, "write-error": { - "kind": { - "type": { - "value": 3 - } + "type": { + "value": 1 } }, "output-stream": { - "kind": { - "type": { - "value": 4 - } - } + "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 } - }, - "scope": null + } }, { "id": "wasi:io/streams@0.2.0-rc-2023-10-18", + "uses": {}, "exports": { "output-stream": { - "kind": { - "type": { - "value": 12 - } - } + "resource": 3 }, "write-error": { - "kind": { - "type": { - "value": 13 - } + "type": { + "value": 9 } }, "input-stream": { - "kind": { - "type": { - "value": 14 - } - } + "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 } - }, - "scope": null + } }, { "id": "foo:bar/baz@0.1.0", + "uses": {}, "exports": { "f": { - "kind": { - "func": 13 - } + "func": 13 } - }, - "scope": null + } + }, + { + "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": { - "kind": { - "instance": 0 - } + "instance": 0 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "streams": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": { "wasi:io/streams@0.2.0-rc-2023-10-18": { - "kind": { - "instance": 1 - } + "instance": 1 }, "foo:bar/baz@0.1.0": { - "kind": { - "instance": 2 - } + "instance": 2 } }, "exports": { "foo:bar/baz@0.1.0": { - "kind": { - "instance": 2 - } + "instance": 2 } - }, - "scope": null + } } ], "modules": [] }, + "packages": [ + { + "name": "wasi:io", + "version": null, + "world": 1, + "definitions": { + "streams": { + "type": { + "interface": 0 + } + } + } + }, + { + "name": "foo:bar", + "version": null, + "world": 2, + "definitions": {} + } + ], "items": [ { - "kind": { - "instance": 0 - }, - "source": { - "import": "wasi:io/streams@0.2.0-rc-2023-10-18" + "import": { + "name": "wasi:io/streams@0.2.0-rc-2023-10-18", + "kind": { + "instance": 0 + } } }, { - "kind": { - "instantiation": 2 - }, - "source": { - "instantiation": { - "arguments": { - "wasi:io/streams@0.2.0-rc-2023-10-18": 0 - } + "import": { + "name": "foo:bar/baz@0.1.0", + "kind": { + "instance": 3 } } }, { - "kind": { - "instance": 2 - }, - "source": { - "alias": { - "item": 1, - "export": "foo:bar/baz@0.1.0" + "instantiation": { + "package": 1, + "arguments": { + "wasi:io/streams@0.2.0-rc-2023-10-18": 0, + "foo:bar/baz@0.1.0": 1 } } }, { - "kind": { - "instantiation": 2 - }, - "source": { - "instantiation": { - "arguments": { - "wasi:io/streams@0.2.0-rc-2023-10-18": 0, - "foo:bar/baz@0.1.0": 2 - } + "alias": { + "item": 2, + "export": "foo:bar/baz@0.1.0", + "kind": { + "instance": 2 } } }, { - "kind": { - "instance": 2 - }, - "source": { - "alias": { - "item": 3, - "export": "foo:bar/baz@0.1.0" + "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 + "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": 4, - "i": 3 - }, - "scopes": [ - { - "parent": null, - "items": { - "streams": 0, - "i": 3 - } - } - ] + "foo:bar/baz@0.1.0": 5, + "i": 4 + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/no-imports.wac.result b/crates/wac-parser/tests/resolution/no-imports.wac.result index ef02287..3bcc4fb 100644 --- a/crates/wac-parser/tests/resolution/no-imports.wac.result +++ b/crates/wac-parser/tests/resolution/no-imports.wac.result @@ -8,35 +8,32 @@ "interfaces": [], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, - "exports": {}, - "scope": null + "exports": {} } ], "modules": [] }, + "packages": [ + { + "name": "foo:bar", + "version": null, + "world": 0, + "definitions": {} + } + ], "items": [ { - "kind": { - "instantiation": 0 - }, - "source": { - "instantiation": { - "arguments": {} - } + "instantiation": { + "package": 0, + "arguments": {} } } ], "imports": {}, "exports": { "i": 0 - }, - "scopes": [ - { - "parent": null, - "items": { - "i": 0 - } - } - ] + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/package-import.wac.result b/crates/wac-parser/tests/resolution/package-import.wac.result index 0601dac..ff5b47d 100644 --- a/crates/wac-parser/tests/resolution/package-import.wac.result +++ b/crates/wac-parser/tests/resolution/package-import.wac.result @@ -4,35 +4,25 @@ "definitions": { "types": [ { - "primitive": "string" - }, - { - "resource": 0 - }, - { - "borrow": 0 + "alias": "string" } ], "resources": [ { - "methods": { - "x": { - "kind": "method", - "ty": 0 - } - } + "name": "r" } ], "funcs": [ { "params": { - "r": 1 + "self": "borrow<0>", + "r": "own<0>" }, "results": null }, { "params": { - "r": 2 + "r": "borrow<0>" }, "results": null } @@ -40,65 +30,72 @@ "interfaces": [ { "id": "foo:bar/i", + "uses": {}, "exports": { "x": { - "kind": { - "type": { - "value": 0 - } + "type": { + "value": 0 } }, "r": { - "kind": { - "type": { - "value": 1 - } - } + "resource": 0 + }, + "[method]r.x": { + "func": 0 }, "y": { - "kind": { - "func": 1 - } + "func": 1 } - }, - "scope": null + } } ], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/i": { - "kind": { - "instance": 0 - } + "instance": 0 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "i": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } } - }, - "scope": null + } } ], "modules": [] }, + "packages": [ + { + "name": "foo:bar", + "version": null, + "world": 1, + "definitions": { + "i": { + "type": { + "interface": 0 + } + } + } + } + ], "items": [ { - "kind": { - "instance": 0 - }, - "source": { - "import": "foo:bar/i" + "import": { + "name": "foo:bar/i", + "kind": { + "instance": 0 + } } } ], @@ -107,13 +104,5 @@ }, "exports": { "foo:bar/i": 0 - }, - "scopes": [ - { - "parent": null, - "items": { - "i": 0 - } - } - ] + } } \ No newline at end of file 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 68ba0b9..3f7f597 100644 --- a/crates/wac-parser/tests/resolution/package-use-item.wac.result +++ b/crates/wac-parser/tests/resolution/package-use-item.wac.result @@ -4,288 +4,242 @@ "definitions": { "types": [ { - "primitive": "u8" + "alias": "u8" }, { - "primitive": "s64" + "alias": "s64" }, { - "primitive": "string" + "alias": "string" }, { - "resource": 0 + "alias": "u8" }, { - "borrow": 0 + "alias": "s64" + }, + { + "alias": "string" + }, + { + "alias": "string" } ], "resources": [ - {} + { + "name": "x" + } ], "funcs": [], "interfaces": [ { "id": "foo:bar/baz", + "uses": {}, "exports": { "a": { - "kind": { - "type": { - "value": 0 - } + "type": { + "value": 0 } }, "b": { - "kind": { - "type": { - "value": 1 - } + "type": { + "value": 1 } }, "c": { - "kind": { - "type": { - "value": 2 - } + "type": { + "value": 2 } } - }, - "scope": null + } + }, + { + "id": "foo:bar/baz", + "uses": {}, + "exports": { + "a": { + "type": { + "value": 3 + } + }, + "b": { + "type": { + "value": 4 + } + }, + "c": { + "type": { + "value": 5 + } + } + } }, { "id": "foo:bar/qux", + "uses": { + "1": [ + 2 + ] + }, "exports": { "z": { - "use": { - "interface": 0, - "exportIndex": 2, - "ty": 2 + "type": { + "value": 6 } }, "x": { - "kind": { - "type": { - "value": 3 - } - } + "resource": 0 }, "y": { - "kind": { - "type": { - "value": 4 - } + "type": { + "value": "borrow<0>" } } - }, - "scope": null + } }, { - "id": "test:comp/i", + "id": "test:comp/i@0.0.1", + "uses": { + "0": [ + 0, + 1, + 2 + ] + }, "exports": { "a": { - "use": { - "interface": 0, - "exportIndex": 0, - "ty": 0 + "type": { + "value": 0 } }, "b": { - "use": { - "interface": 0, - "exportIndex": 1, - "ty": 1 + "type": { + "value": 1 } }, "c": { - "use": { - "interface": 0, - "exportIndex": 2, - "ty": 2 + "type": { + "value": 2 } } - }, - "scope": 1 + } } ], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/baz": { - "kind": { - "instance": 0 - } + "instance": 0 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": { "foo:bar/baz": { - "kind": { - "instance": 0 - } + "instance": 1 } }, "exports": { "foo:bar/qux": { - "kind": { - "instance": 1 - } + "instance": 2 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "baz": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } }, "qux": { - "kind": { - "type": { - "world": 1 - } + "type": { + "world": 1 } } - }, - "scope": null + } }, { + "id": "test:comp/w@0.0.1", + "uses": { + "2": [ + 1, + 2, + 0 + ] + }, "imports": { - "foo:bar/qux": { - "kind": { - "instance": 1 - } - }, "x": { - "use": { - "interface": 1, - "exportIndex": 1, - "ty": 3 - } + "resource": 0 }, "y": { - "use": { - "interface": 1, - "exportIndex": 2, - "ty": 4 - } - }, - "foo:bar/baz": { - "kind": { - "instance": 0 + "type": { + "value": "borrow<0>" } }, "z": { - "use": { - "interface": 1, - "exportIndex": 0, - "ty": 2 + "type": { + "value": 6 } } }, - "exports": {}, - "scope": 2 + "exports": {} } ], "modules": [] }, - "items": [ - { - "kind": { - "type": { - "value": 0 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "value": 1 - } - }, - "source": "use" - }, + "packages": [ { - "kind": { - "type": { - "value": 2 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "interface": 2 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "value": 3 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "value": 4 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "value": 2 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "world": 3 + "name": "foo:bar", + "version": null, + "world": 2, + "definitions": { + "baz": { + "type": { + "interface": 0 + } + }, + "qux": { + "type": { + "interface": 2 + } } - }, - "source": "definition" + } } ], - "imports": {}, - "exports": {}, - "scopes": [ - { - "parent": null, - "items": { - "i": 3, - "w": 7 - } - }, + "items": [ { - "parent": 0, - "items": { - "a": 0, - "b": 1, - "c": 2 + "definition": { + "name": "i", + "kind": { + "type": { + "interface": 3 + } + } } }, { - "parent": 0, - "items": { - "x": 4, - "y": 5, - "z": 6 + "definition": { + "name": "w", + "kind": { + "type": { + "world": 3 + } + } } } - ] + ], + "imports": {}, + "exports": { + "i": 0, + "w": 1 + } } \ No newline at end of file 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 d7568ae..13f5f3c 100644 --- a/crates/wac-parser/tests/resolution/package-world-include.wac.result +++ b/crates/wac-parser/tests/resolution/package-world-include.wac.result @@ -2,36 +2,65 @@ "package": "test:comp", "version": "1.2.3-prerelease", "definitions": { - "types": [ - { - "resource": 0 - } - ], + "types": [], "resources": [ { - "methods": { - "": { - "kind": "constructor", - "ty": 0 - }, - "a": { - "kind": "method", - "ty": 1 - }, - "b": { - "kind": "static", - "ty": 2 - } - } + "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 }, { @@ -42,134 +71,171 @@ "interfaces": [ { "id": "foo:bar/i", + "uses": {}, "exports": { "x": { - "kind": { - "type": { - "value": 0 - } - } + "resource": 0 + }, + "[constructor]x": { + "func": 0 + }, + "[method]x.a": { + "func": 1 + }, + "[static]x.b": { + "func": 2 } - }, - "scope": null + } + }, + { + "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": { - "kind": { - "instance": 0 - } + "instance": 0 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, + "imports": {}, + "exports": { + "foo:bar/baz": { + "component": 2 + } + } + }, + { + "id": "foo:bar/baz", + "uses": { + "1": [ + 0 + ] + }, "imports": { "foo:bar/i": { - "kind": { - "instance": 0 - } + "instance": 1 }, "x": { - "use": { - "interface": 0, - "exportIndex": 0, - "ty": 0 - } + "resource": 2 } }, "exports": { "foo:bar/i": { - "kind": { - "instance": 0 - } + "instance": 2 } - }, - "scope": null - }, - { - "imports": {}, - "exports": { - "foo:bar/baz": { - "kind": { - "component": 1 - } - } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "i": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } }, "baz": { - "kind": { - "type": { - "world": 2 - } + "type": { + "world": 1 } } - }, - "scope": null + } }, { + "id": "test:comp/w@1.2.3-prerelease", + "uses": {}, "imports": { "foo:bar/i": { - "kind": { - "instance": 0 - } + "instance": 1 }, "x": { - "use": { - "interface": 0, - "exportIndex": 0, - "ty": 0 - } + "resource": 2 } }, "exports": { "foo:bar/i": { - "kind": { - "instance": 0 - } + "instance": 2 } - }, - "scope": 1 + } } ], "modules": [] }, - "items": [ + "packages": [ { - "kind": { - "type": { - "world": 4 + "name": "foo:bar", + "version": null, + "world": 3, + "definitions": { + "i": { + "type": { + "interface": 0 + } + }, + "baz": { + "type": { + "world": 2 + } } - }, - "source": "definition" + } } ], - "imports": {}, - "exports": {}, - "scopes": [ + "items": [ { - "parent": null, - "items": { - "w": 0 + "definition": { + "name": "w", + "kind": { + "type": { + "world": 4 + } + } } - }, - { - "parent": 0, - "items": {} } - ] + ], + "imports": {}, + "exports": { + "w": 0 + } } \ No newline at end of file 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 506ce3b..7bc9023 100644 --- a/crates/wac-parser/tests/resolution/package-world-item.wac.result +++ b/crates/wac-parser/tests/resolution/package-world-item.wac.result @@ -4,26 +4,20 @@ "definitions": { "types": [ { - "resource": 0 - }, - { - "primitive": "string" + "alias": "string" } ], "resources": [ { - "methods": { - "": { - "kind": "constructor", - "ty": 0 - } - } + "name": "z" } ], "funcs": [ { "params": {}, - "results": null + "results": { + "scalar": "own<0>" + } }, { "params": {}, @@ -33,127 +27,133 @@ "interfaces": [ { "id": "foo:bar/baz", + "uses": {}, "exports": { "z": { - "kind": { - "type": { - "value": 0 - } - } + "resource": 0 + }, + "[constructor]z": { + "func": 0 }, "x": { - "kind": { - "func": 1 - } + "func": 1 } - }, - "scope": null + } }, { "id": "bar:baz/qux", + "uses": {}, "exports": { "x": { - "kind": { - "type": { - "value": 1 - } + "type": { + "value": 0 } } - }, - "scope": null + } } ], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/baz": { - "kind": { - "instance": 0 - } + "instance": 0 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "baz": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "bar:baz/qux": { - "kind": { - "instance": 1 - } + "instance": 1 } - }, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "qux": { - "kind": { - "type": { - "world": 2 - } + "type": { + "world": 2 } } - }, - "scope": null + } }, { + "id": "test:comp/w", + "uses": {}, "imports": { "foo:bar/baz": { - "kind": { - "instance": 0 - } + "instance": 0 } }, "exports": { "bar:baz/qux": { - "kind": { - "instance": 1 - } + "instance": 1 } - }, - "scope": 1 + } } ], "modules": [] }, - "items": [ + "packages": [ { - "kind": { - "type": { - "world": 4 + "name": "foo:bar", + "version": null, + "world": 1, + "definitions": { + "baz": { + "type": { + "interface": 0 + } } - }, - "source": "definition" + } + }, + { + "name": "bar:baz", + "version": null, + "world": 3, + "definitions": { + "qux": { + "type": { + "interface": 1 + } + } + } } ], - "imports": {}, - "exports": {}, - "scopes": [ + "items": [ { - "parent": null, - "items": { - "w": 0 + "definition": { + "name": "w", + "kind": { + "type": { + "world": 4 + } + } } - }, - { - "parent": 0, - "items": {} } - ] + ], + "imports": {}, + "exports": { + "w": 0 + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/resource.wac b/crates/wac-parser/tests/resolution/resource.wac index 3aab2ed..2a2d207 100644 --- a/crates/wac-parser/tests/resolution/resource.wac +++ b/crates/wac-parser/tests/resolution/resource.wac @@ -4,4 +4,7 @@ interface foo { resource x; type f = func(x: borrow); f2: f; + + type x2 = x; + f3: func(x: x, x2: x2, x3: borrow, x4: borrow); } diff --git a/crates/wac-parser/tests/resolution/resource.wac.result b/crates/wac-parser/tests/resolution/resource.wac.result index 438c141..23c0ec3 100644 --- a/crates/wac-parser/tests/resolution/resource.wac.result +++ b/crates/wac-parser/tests/resolution/resource.wac.result @@ -2,21 +2,29 @@ "package": "test:comp", "version": null, "definitions": { - "types": [ + "types": [], + "resources": [ { - "resource": 0 + "name": "x" }, { - "borrow": 0 + "name": "x2", + "aliasOf": 0 } ], - "resources": [ - {} - ], "funcs": [ { "params": { - "x": 1 + "x": "borrow<0>" + }, + "results": null + }, + { + "params": { + "x": "own<0>", + "x2": "own<1>", + "x3": "borrow<0>", + "x4": "borrow<1>" }, "results": null } @@ -24,74 +32,46 @@ "interfaces": [ { "id": "test:comp/foo", + "uses": {}, "exports": { "x": { - "kind": { - "type": { - "value": 0 - } - } + "resource": 0 }, "f": { - "kind": { - "type": { - "func": 0 - } + "type": { + "func": 0 } }, "f2": { - "kind": { - "func": 0 - } + "func": 0 + }, + "x2": { + "resource": 1 + }, + "f3": { + "func": 1 } - }, - "scope": 1 + } } ], "worlds": [], "modules": [] }, + "packages": [], "items": [ { - "kind": { - "type": { - "value": 0 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "func": 0 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "interface": 0 + "definition": { + "name": "foo", + "kind": { + "type": { + "interface": 0 + } } - }, - "source": "definition" + } } ], "imports": {}, - "exports": {}, - "scopes": [ - { - "parent": null, - "items": { - "foo": 2 - } - }, - { - "parent": 0, - "items": { - "x": 0, - "f": 1 - } - } - ] + "exports": { + "foo": 0 + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/types.wac.result b/crates/wac-parser/tests/resolution/types.wac.result index 19a66d4..98d113f 100644 --- a/crates/wac-parser/tests/resolution/types.wac.result +++ b/crates/wac-parser/tests/resolution/types.wac.result @@ -10,19 +10,13 @@ } } }, - { - "resource": 0 - }, - { - "resource": 1 - }, { "alias": "u32" }, { "variant": { "cases": { - "a": 3, + "a": 1, "b": "string", "c": "u32", "d": null @@ -34,7 +28,7 @@ "fields": { "x": "u32", "y": "string", - "z": 4 + "z": 2 } } }, @@ -53,21 +47,18 @@ ] }, { - "alias": 7 + "alias": 5 }, { "alias": "string" } ], "resources": [ - {}, { - "methods": { - "": { - "kind": "constructor", - "ty": 5 - } - } + "name": "r" + }, + { + "name": "res" } ], "funcs": [ @@ -93,12 +84,14 @@ }, { "params": {}, - "results": null + "results": { + "scalar": "own<1>" + } }, { "params": { "a": "u32", - "b": 5 + "b": 3 }, "results": { "scalar": "u32" @@ -117,461 +110,371 @@ "interfaces": [ { "id": "test:comp/i", + "uses": {}, "exports": { "a": { - "kind": { - "type": { - "func": 0 - } + "type": { + "func": 0 } }, "r": { - "kind": { - "type": { - "value": 0 - } + "type": { + "value": 0 } }, "x": { - "kind": { - "func": 1 - } + "func": 1 }, "y": { - "kind": { - "func": 0 - } + "func": 0 } - }, - "scope": 1 + } }, { "id": "test:comp/i2", + "uses": { + "0": [ + 1 + ] + }, "exports": { "r": { - "use": { - "interface": 0, - "exportIndex": 1, - "ty": 0 + "type": { + "value": 0 } }, "z": { - "use": { - "interface": 0, - "exportIndex": 1, - "ty": 0 + "type": { + "value": 0 } } - }, - "scope": 2 + } }, { "id": "foo:bar/i", + "uses": {}, "exports": { "r": { - "kind": { - "type": { - "value": 1 - } - } + "resource": 0 } - }, - "scope": null + } }, { "id": null, + "uses": {}, "exports": { "x": { - "kind": { - "func": 4 - } + "func": 4 } - }, - "scope": 4 + } } ], "worlds": [ { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/i": { - "kind": { - "instance": 2 - } + "instance": 2 } - }, - "scope": null - }, - { - "imports": {}, - "exports": {}, - "scope": null + } }, { + "id": null, + "uses": {}, "imports": {}, "exports": { "foo:bar/baz": { - "kind": { - "component": 1 - } + "component": 2 } - }, - "scope": null + } }, { + "id": "foo:bar/baz", + "uses": {}, + "imports": {}, + "exports": {} + }, + { + "id": null, + "uses": {}, "imports": {}, "exports": { "i": { - "kind": { - "type": { - "world": 0 - } + "type": { + "world": 0 } }, "baz": { - "kind": { - "type": { - "world": 2 - } + "type": { + "world": 1 } } - }, - "scope": null + } }, { + "id": "test:comp/w1", + "uses": { + "2": [ + 0 + ] + }, "imports": { - "foo:bar/i": { - "kind": { - "instance": 2 - } - }, "r": { - "use": { - "interface": 2, - "exportIndex": 0, - "ty": 1 - } + "resource": 0 }, "a": { - "kind": { - "func": 3 - } + "func": 3 }, "test:comp/i": { - "kind": { - "instance": 0 - } + "instance": 0 }, "c": { - "kind": { - "func": 2 - } + "func": 2 } }, "exports": { "d": { - "kind": { - "type": { - "interface": 3 - } + "type": { + "interface": 3 } }, "test:comp/i2": { - "kind": { - "instance": 1 - } + "instance": 1 }, "f": { - "kind": { - "func": 2 - } + "func": 2 } - }, - "scope": 3 + } }, { + "id": "test:comp/w2", + "uses": {}, "imports": { - "foo:bar/i": { - "kind": { - "instance": 2 - } + "res": { + "resource": 1 + }, + "[constructor]res": { + "func": 5 }, "r": { - "use": { - "interface": 2, - "exportIndex": 0, - "ty": 1 - } + "resource": 0 }, "a": { - "kind": { - "func": 3 - } + "func": 3 }, "test:comp/i": { - "kind": { - "instance": 0 - } + "instance": 0 }, "c": { - "kind": { - "func": 2 - } + "func": 2 } }, "exports": { - "res": { - "kind": { - "type": { - "value": 2 - } - } - }, "d": { - "kind": { - "type": { - "interface": 3 - } + "type": { + "interface": 3 } }, "test:comp/i2": { - "kind": { - "instance": 1 - } + "instance": 1 }, "f": { - "kind": { - "func": 2 - } + "func": 2 } - }, - "scope": 5 + } } ], "modules": [] }, - "items": [ - { - "kind": { - "type": { - "func": 0 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "value": 0 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "interface": 0 - } - }, - "source": "definition" - }, - { - "kind": { - "type": { - "value": 0 - } - }, - "source": "use" - }, + "packages": [ { - "kind": { - "type": { - "value": 0 - } - }, - "source": "use" - }, - { - "kind": { - "type": { - "interface": 1 + "name": "foo:bar", + "version": null, + "world": 3, + "definitions": { + "i": { + "type": { + "interface": 2 + } + }, + "baz": { + "type": { + "world": 2 + } } - }, - "source": "definition" - }, + } + } + ], + "items": [ { - "kind": { - "type": { - "func": 2 + "definition": { + "name": "i", + "kind": { + "type": { + "interface": 0 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "func": 2 + "definition": { + "name": "i2", + "kind": { + "type": { + "interface": 1 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 1 + "definition": { + "name": "c", + "kind": { + "type": { + "func": 2 + } } - }, - "source": "use" + } }, { - "kind": { - "type": { - "world": 4 + "definition": { + "name": "f", + "kind": { + "type": { + "func": 2 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 2 + "definition": { + "name": "w1", + "kind": { + "type": { + "world": 4 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "world": 5 + "definition": { + "name": "w2", + "kind": { + "type": { + "world": 5 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 3 + "definition": { + "name": "x", + "kind": { + "type": { + "value": 1 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 4 + "definition": { + "name": "v", + "kind": { + "type": { + "value": 2 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 5 + "definition": { + "name": "r", + "kind": { + "type": { + "value": 3 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 6 + "definition": { + "name": "flags", + "kind": { + "type": { + "value": 4 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 7 + "definition": { + "name": "e", + "kind": { + "type": { + "value": 5 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 8 + "definition": { + "name": "t", + "kind": { + "type": { + "value": 6 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "value": 9 + "definition": { + "name": "t2", + "kind": { + "type": { + "value": 7 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "func": 6 + "definition": { + "name": "t3", + "kind": { + "type": { + "func": 6 + } } - }, - "source": "definition" + } }, { - "kind": { - "type": { - "func": 7 + "definition": { + "name": "t4", + "kind": { + "type": { + "func": 7 + } } - }, - "source": "definition" + } } ], "imports": {}, - "exports": {}, - "scopes": [ - { - "parent": null, - "items": { - "i": 2, - "i2": 5, - "c": 6, - "f": 7, - "w1": 9, - "w2": 11, - "x": 12, - "v": 13, - "r": 14, - "flags": 15, - "e": 16, - "t": 17, - "t2": 18, - "t3": 19, - "t4": 20 - } - }, - { - "parent": 0, - "items": { - "a": 0, - "r": 1 - } - }, - { - "parent": 0, - "items": { - "r": 3, - "z": 4 - } - }, - { - "parent": 0, - "items": { - "r": 8 - } - }, - { - "parent": 3, - "items": {} - }, - { - "parent": 0, - "items": { - "res": 10 - } - } - ] + "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 diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 1b84167..756f212 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Parser; use owo_colors::{OwoColorize, Stream, Style}; -use wac::commands::ParseCommand; +use wac::commands::{EncodeCommand, ParseCommand, ResolveCommand}; fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -18,6 +18,8 @@ fn version() -> &'static str { #[command(version = version())] enum Wac { Parse(ParseCommand), + Resolve(ResolveCommand), + Encode(EncodeCommand), } #[tokio::main] @@ -26,6 +28,8 @@ async fn main() -> Result<()> { if let Err(e) = match Wac::parse() { Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => cmd.exec().await, + Wac::Encode(cmd) => cmd.exec().await, } { eprintln!( "{error}: {e:?}", diff --git a/src/commands.rs b/src/commands.rs index 6cfa4fe..acd03ac 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,5 +1,9 @@ //! Module for CLI commands. +mod encode; mod parse; +mod resolve; +pub use self::encode::*; pub use self::parse::*; +pub use self::resolve::*; diff --git a/src/commands/encode.rs b/src/commands/encode.rs new file mode 100644 index 0000000..7f8ac64 --- /dev/null +++ b/src/commands/encode.rs @@ -0,0 +1,137 @@ +use anyhow::{anyhow, bail, Context, Result}; +use clap::Args; +use std::{ + fs, + io::{IsTerminal, Write}, + path::PathBuf, +}; +use wac_parser::{ + ast::Document, Composition, EncodingOptions, ErrorFormatter, FileSystemPackageResolver, +}; +use wasmparser::{Validator, WasmFeatures}; +use wasmprinter::print_bytes; + +fn parse(s: &str) -> Result<(T, U)> +where + T: std::str::FromStr, + T::Err: Into, + U: std::str::FromStr, + U::Err: Into, +{ + let (k, v) = s.split_once('=').context("value does not contain `=`")?; + + Ok(( + k.trim().parse().map_err(Into::into)?, + v.trim().parse().map_err(Into::into)?, + )) +} + +/// Encodes a composition into a WebAssembly component. +#[derive(Args)] +#[clap(disable_version_flag = true)] +pub struct EncodeCommand { + /// The directory to search for package dependencies. + #[clap(long, value_name = "PATH", default_value = "deps")] + pub deps_dir: PathBuf, + + /// The directory to search for package dependencies. + #[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::)] + pub deps: Vec<(String, PathBuf)>, + + /// Whether to skip validation of the encoded WebAssembly component. + #[clap(long)] + pub no_validate: bool, + + /// Whether to emit the WebAssembly text format. + #[clap(long, short = 't')] + pub wat: bool, + + /// Whether to not to define referenced packages. + /// + /// If not specified, all referenced packages will be imported. + #[clap(long)] + pub define: bool, + + /// The path to write the output to. + /// + /// If not specified, the output will be written to stdout. + #[clap(long, short = 'o')] + pub output: Option, + + /// The path to the composition file. + #[clap(value_name = "PATH")] + pub path: PathBuf, +} + +impl EncodeCommand { + /// Executes the command. + pub async fn exec(self) -> Result<()> { + log::debug!("executing encode command"); + + let contents = fs::read_to_string(&self.path) + .with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?; + + let document = Document::parse(&contents, &self.path).map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; + + let resolved = Composition::from_ast( + &document, + Some(Box::new(FileSystemPackageResolver::new( + self.deps_dir, + self.deps.into_iter().collect(), + ))), + ) + .map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; + + 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.define, + })?; + 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")? + .into_bytes(); + } + + match self.output { + Some(path) => { + fs::write(&path, bytes).context(format!( + "failed to write output file `{path}`", + path = path.display() + ))?; + } + None => { + std::io::stdout() + .write_all(&bytes) + .context("failed to write to stdout")?; + + if self.wat { + println!(); + } + } + } + + Ok(()) + } +} diff --git a/src/commands/parse.rs b/src/commands/parse.rs index b8d49d6..aee4614 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -1,46 +1,12 @@ use anyhow::{anyhow, Context, Result}; use clap::Args; -use serde::Serialize; use std::{fs, io::IsTerminal, path::PathBuf}; -use wac_parser::{ - ast::Document, - resolution::{FileSystemPackageResolver, ResolvedDocument}, - ErrorFormatter, -}; +use wac_parser::{ast::Document, ErrorFormatter}; -fn parse(s: &str) -> Result<(T, U)> -where - T: std::str::FromStr, - T::Err: Into, - U: std::str::FromStr, - U::Err: Into, -{ - let (k, v) = s.split_once('=').context("value does not contain `=`")?; - - Ok(( - k.trim().parse().map_err(Into::into)?, - v.trim().parse().map_err(Into::into)?, - )) -} - -#[derive(Serialize)] -struct Output<'a> { - ast: &'a Document<'a>, - resolved: &'a ResolvedDocument, -} - -/// Parses a composition into a WebAssembly component. +/// Parses a composition into a JSON AST representation. #[derive(Args)] #[clap(disable_version_flag = true)] pub struct ParseCommand { - /// The directory to search for package dependencies. - #[clap(long, value_name = "PATH", default_value = "deps")] - pub deps_dir: PathBuf, - - /// The directory to search for package dependencies. - #[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::)] - pub deps: Vec<(String, PathBuf)>, - /// The path to the composition file. #[clap(value_name = "PATH")] pub path: PathBuf, @@ -60,27 +26,8 @@ impl ParseCommand { e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) ) })?; - let resolved = ResolvedDocument::new( - &document, - Some(Box::new(FileSystemPackageResolver::new( - self.deps_dir, - self.deps.into_iter().collect(), - ))), - ) - .map_err(|e| { - anyhow!( - "{e}", - e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) - ) - })?; - serde_json::to_writer_pretty( - std::io::stdout(), - &Output { - ast: &document, - resolved: &resolved, - }, - )?; + serde_json::to_writer_pretty(std::io::stdout(), &document)?; println!(); Ok(()) diff --git a/src/commands/resolve.rs b/src/commands/resolve.rs new file mode 100644 index 0000000..f12e2bf --- /dev/null +++ b/src/commands/resolve.rs @@ -0,0 +1,72 @@ +use anyhow::{anyhow, Context, Result}; +use clap::Args; +use std::{fs, io::IsTerminal, path::PathBuf}; +use wac_parser::{ast::Document, Composition, ErrorFormatter, FileSystemPackageResolver}; + +fn parse(s: &str) -> Result<(T, U)> +where + T: std::str::FromStr, + T::Err: Into, + U: std::str::FromStr, + U::Err: Into, +{ + let (k, v) = s.split_once('=').context("value does not contain `=`")?; + + Ok(( + k.trim().parse().map_err(Into::into)?, + v.trim().parse().map_err(Into::into)?, + )) +} + +/// Resolves a composition into a JSON representation. +#[derive(Args)] +#[clap(disable_version_flag = true)] +pub struct ResolveCommand { + /// The directory to search for package dependencies. + #[clap(long, value_name = "PATH", default_value = "deps")] + pub deps_dir: PathBuf, + + /// The directory to search for package dependencies. + #[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::)] + pub deps: Vec<(String, PathBuf)>, + + /// The path to the composition file. + #[clap(value_name = "PATH")] + pub path: PathBuf, +} + +impl ResolveCommand { + /// Executes the command. + pub async fn exec(self) -> Result<()> { + log::debug!("executing resolve command"); + + let contents = fs::read_to_string(&self.path) + .with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?; + + let document = Document::parse(&contents, &self.path).map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; + + let resolved = Composition::from_ast( + &document, + Some(Box::new(FileSystemPackageResolver::new( + self.deps_dir, + self.deps.into_iter().collect(), + ))), + ) + .map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; + + serde_json::to_writer_pretty(std::io::stdout(), &resolved)?; + println!(); + + Ok(()) + } +}