diff --git a/Cargo.lock b/Cargo.lock index eb82e1e4a8..eb624f833f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1480,7 +1480,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "glob", @@ -1494,9 +1494,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.208.1", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasm-encoder 0.209.0", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wat", "wit-component", ] @@ -1512,17 +1512,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.208.1", + "wasmparser 0.209.0", ] [[package]] name = "wasm-metadata" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "clap", @@ -1531,14 +1531,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.208.1", - "wasmparser 0.208.1", + "wasm-encoder 0.209.0", + "wasmparser 0.209.0", "wat", ] [[package]] name = "wasm-mutate" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "clap", @@ -1547,9 +1547,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.208.1", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasm-encoder 0.209.0", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wat", ] @@ -1566,14 +1566,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "blake3", @@ -1582,14 +1582,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wat", ] [[package]] name = "wasm-smith" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "arbitrary", @@ -1602,15 +1602,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.208.1", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasm-encoder 0.209.0", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wat", ] [[package]] name = "wasm-tools" -version = "1.208.1" +version = "1.209.0" dependencies = [ "addr2line", "anyhow", @@ -1634,17 +1634,17 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.0", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wast", "wat", "wit-component", - "wit-parser 0.208.1", + "wit-parser 0.209.0", "wit-smith", ] @@ -1656,8 +1656,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wast", "wat", ] @@ -1672,16 +1672,16 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.0", "wasm-mutate", "wasm-smith", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.208.1", + "wit-parser 0.209.0", "wit-smith", ] @@ -1698,7 +1698,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.208.1" +version = "0.209.0" dependencies = [ "ahash", "anyhow", @@ -1712,7 +1712,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.0", "wast", "wat", ] @@ -1729,13 +1729,13 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "diff", "rayon", "tempfile", - "wasmparser 0.208.1", + "wasmparser 0.209.0", "wast", "wat", ] @@ -1953,7 +1953,7 @@ dependencies = [ [[package]] name = "wast" -version = "208.0.1" +version = "209.0.0" dependencies = [ "anyhow", "bumpalo", @@ -1961,14 +1961,14 @@ dependencies = [ "libtest-mimic", "memchr", "unicode-width", - "wasm-encoder 0.208.1", - "wasmparser 0.208.1", + "wasm-encoder 0.209.0", + "wasmparser 0.209.0", "wat", ] [[package]] name = "wat" -version = "1.208.1" +version = "1.209.0" dependencies = [ "wast", ] @@ -2096,7 +2096,7 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "wit-component" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "bitflags", @@ -2109,14 +2109,14 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.0", "wasm-metadata", - "wasmparser 0.208.1", - "wasmprinter 0.208.1", + "wasmparser 0.209.0", + "wasmprinter 0.209.0", "wasmtime", "wast", "wat", - "wit-parser 0.208.1", + "wit-parser 0.209.0", ] [[package]] @@ -2139,7 +2139,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.208.1" +version = "0.209.0" dependencies = [ "anyhow", "env_logger", @@ -2153,9 +2153,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.208.1", + "wasmparser 0.209.0", "wat", - "wit-parser 0.208.1", + "wit-parser 0.209.0", ] [[package]] @@ -2166,13 +2166,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.208.1", - "wit-parser 0.208.1", + "wasmprinter 0.209.0", + "wit-parser 0.209.0", ] [[package]] name = "wit-smith" -version = "0.208.1" +version = "0.209.0" dependencies = [ "arbitrary", "clap", @@ -2180,7 +2180,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.208.1", + "wit-parser 0.209.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dd70765acd..2c6ea24a89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.208.1" +version = "1.209.0" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -49,7 +49,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.208.1" +version = "0.209.0" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -81,19 +81,19 @@ bitflags = "2.5.0" hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] } ahash = { version = "0.8.11", default-features = false } -wasm-compose = { version = "0.208.1", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.208.1", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.208.1", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.208.1", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.208.1", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.208.1", path = "crates/wasm-smith" } -wasmparser = { version = "0.208.1", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.208.1", path = "crates/wasmprinter" } -wast = { version = "208.0.1", path = "crates/wast" } -wat = { version = "1.208.1", path = "crates/wat" } -wit-component = { version = "0.208.1", path = "crates/wit-component" } -wit-parser = { version = "0.208.1", path = "crates/wit-parser" } -wit-smith = { version = "0.208.1", path = "crates/wit-smith" } +wasm-compose = { version = "0.209.0", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.209.0", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.209.0", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.209.0", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.209.0", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.209.0", path = "crates/wasm-smith" } +wasmparser = { version = "0.209.0", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.209.0", path = "crates/wasmprinter" } +wast = { version = "209.0.0", path = "crates/wast" } +wat = { version = "1.209.0", path = "crates/wat" } +wit-component = { version = "0.209.0", path = "crates/wit-component" } +wit-parser = { version = "0.209.0", path = "crates/wit-parser" } +wit-smith = { version = "0.209.0", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index e4606bb0ab..24a5da5f92 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -923,12 +923,14 @@ impl Module { offset, )); } - if page_size_log2 > 16 { + // Currently 2**0 and 2**16 are the only valid page sizes, but this + // may be relaxed to allow any power of two in the future. + if page_size_log2 != 0 && page_size_log2 != 16 { return Err(BinaryReaderError::new("invalid custom page size", offset)); } let page_size = 1_u64 << page_size_log2; debug_assert!(page_size.is_power_of_two()); - debug_assert!(page_size <= DEFAULT_WASM_PAGE_SIZE); + debug_assert!(page_size == DEFAULT_WASM_PAGE_SIZE || page_size == 1); (page_size, page_size_log2) } else { let page_size_log2 = 16; diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 20d8db8ab2..651040ad32 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -956,7 +956,7 @@ impl Printer { if ty.is_final { self.result.push_str("final "); } - for idx in &ty.supertype_idx { + if let Some(idx) = ty.supertype_idx { self.print_idx(&state.core.type_names, idx.as_module_index().unwrap())?; self.result.push(' '); } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 14874f77cd..af878c4bdd 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "208.0.1" +version = "209.0.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wast/src/lexer.rs b/crates/wast/src/lexer.rs index efe9da22d3..0fd9a72d64 100644 --- a/crates/wast/src/lexer.rs +++ b/crates/wast/src/lexer.rs @@ -31,6 +31,7 @@ use std::char; use std::fmt; use std::slice; use std::str; +use std::str::Utf8Error; /// A structure used to lex the s-expression syntax of WAT files. /// @@ -99,6 +100,12 @@ pub enum TokenKind { /// The payload here is the original source text. Keyword, + /// An annotation (like `@foo`). + /// + /// All annotations start with `@` and the payload will be the name of the + /// annotation. + Annotation, + /// A reserved series of `idchar` symbols. Unknown what this is meant to be /// used for, you'll probably generate an error about an unexpected token. Reserved, @@ -136,8 +143,15 @@ pub enum FloatKind { } enum ReservedKind { + /// "..." String, + /// anything that's just a sequence of `idchars!()` Idchars, + /// $"..." + IdString, + /// @"..." + AnnotationString, + /// everything else (a conglomeration of strings, idchars, etc) Reserved, } @@ -199,6 +213,16 @@ pub enum LexError { /// version to behave differently than the compiler-visible version, so /// these are simply rejected for now. ConfusingUnicode(char), + + /// An invalid utf-8 sequence was found in a quoted identifier, such as + /// `$"\ff"`. + InvalidUtf8Id(Utf8Error), + + /// An empty identifier was found, or a lone `$`. + EmptyId, + + /// An empty identifier was found, or a lone `@`. + EmptyAnnotation, } /// A sign token for an integer. @@ -420,14 +444,21 @@ impl<'a> Lexer<'a> { if let Some(ret) = self.classify_number(src) { return Ok(Some(ret)); // https://webassembly.github.io/spec/core/text/values.html#text-id - } else if *c == b'$' && src.len() > 1 { + } else if *c == b'$' { return Ok(Some(TokenKind::Id)); + // part of the WebAssembly/annotations proposal + // (no online url yet) + } else if *c == b'@' { + return Ok(Some(TokenKind::Annotation)); // https://webassembly.github.io/spec/core/text/lexical.html#text-keyword } else if b'a' <= *c && *c <= b'z' { return Ok(Some(TokenKind::Keyword)); } } + ReservedKind::IdString => return Ok(Some(TokenKind::Id)), + ReservedKind::AnnotationString => return Ok(Some(TokenKind::Annotation)), + // ... otherwise this was a conglomeration of idchars, // strings, or just idchars that don't match a prior rule, // meaning this falls through to the fallback `Reserved` @@ -538,7 +569,7 @@ impl<'a> Lexer<'a> { /// eaten. The classification assists in determining what the actual token /// here eaten looks like. fn parse_reserved(&self, pos: &mut usize) -> Result<(ReservedKind, &'a str), Error> { - let mut idchars = false; + let mut idchars = 0u32; let mut strings = 0u32; let start = *pos; while let Some(byte) = self.input.as_bytes().get(*pos) { @@ -546,7 +577,7 @@ impl<'a> Lexer<'a> { // Normal `idchars` production which appends to the reserved // token that's being produced. idchars!() => { - idchars = true; + idchars += 1; *pos += 1; } @@ -575,9 +606,13 @@ impl<'a> Lexer<'a> { } let ret = &self.input[start..*pos]; Ok(match (idchars, strings) { - (false, 0) => unreachable!(), - (false, 1) => (ReservedKind::String, ret), - (true, 0) => (ReservedKind::Idchars, ret), + (0, 0) => unreachable!(), + (0, 1) => (ReservedKind::String, ret), + (_, 0) => (ReservedKind::Idchars, ret), + // Pattern match `@"..."` and `$"..."` for string-based + // identifiers and annotations. + (1, 1) if ret.starts_with("$") => (ReservedKind::IdString, ret), + (1, 1) if ret.starts_with("@") => (ReservedKind::AnnotationString, ret), _ => (ReservedKind::Reserved, ret), }) } @@ -813,6 +848,37 @@ impl<'a> Lexer<'a> { } } + /// Parses an id-or-string-based name from `it`. + /// + /// Note that `it` should already have been lexed and this is just + /// extracting the value. If the token lexed was `@a` then this should point + /// to `a`. + /// + /// This will automatically detect quoted syntax such as `@"..."` and the + /// byte string will be parsed and validated as utf-8. + /// + /// # Errors + /// + /// Returns an error if a quoted byte string is found and contains invalid + /// utf-8. + fn parse_name(it: &mut str::Chars<'a>) -> Result, LexError> { + if it.clone().next() == Some('"') { + it.next(); + match Lexer::parse_str(it, true)? { + Cow::Borrowed(bytes) => match std::str::from_utf8(bytes) { + Ok(s) => Ok(Cow::Borrowed(s)), + Err(e) => Err(LexError::InvalidUtf8Id(e)), + }, + Cow::Owned(bytes) => match String::from_utf8(bytes) { + Ok(s) => Ok(Cow::Owned(s)), + Err(e) => Err(LexError::InvalidUtf8Id(e.utf8_error())), + }, + } + } else { + Ok(Cow::Borrowed(it.as_str())) + } + } + fn hexnum(it: &mut str::Chars<'_>) -> Result { let n = Lexer::hexdigit(it)?; let mut last_underscore = false; @@ -878,28 +944,23 @@ impl<'a> Lexer<'a> { std::iter::from_fn(move || self.parse(&mut pos).transpose()) } - /// Returns whether an annotation is present at `pos` and the name of the - /// annotation. - pub fn annotation(&self, mut pos: usize) -> Option<&'a str> { + /// Returns whether an annotation is present at `pos`. If it is present then + /// `Ok(Some(token))` is returned corresponding to the token, otherwise + /// `Ok(None)` is returned. If the next token cannot be parsed then an error + /// is returned. + pub fn annotation(&self, mut pos: usize) -> Result, Error> { let bytes = self.input.as_bytes(); // Quickly reject anything that for sure isn't an annotation since this // method is used every time an lparen is parsed. if bytes.get(pos) != Some(&b'@') { - return None; + return Ok(None); } - match self.parse(&mut pos) { - Ok(Some(token)) => { - match token.kind { - TokenKind::Reserved => {} - _ => return None, - } - if token.len == 1 { - None // just the `@` character isn't a valid annotation - } else { - Some(&token.src(self.input)[1..]) - } - } - Ok(None) | Err(_) => None, + match self.parse(&mut pos)? { + Some(token) => match token.kind { + TokenKind::Annotation => Ok(Some(token)), + _ => Ok(None), + }, + None => Ok(None), } } } @@ -913,9 +974,49 @@ impl Token { /// Returns the identifier, without the leading `$` symbol, that this token /// represents. /// + /// Note that this method returns the contents of the identifier. With a + /// string-based identifier this means that escapes have been resolved to + /// their string-based equivalent. + /// /// Should only be used with `TokenKind::Id`. - pub fn id<'a>(&self, s: &'a str) -> &'a str { - &self.src(s)[1..] + /// + /// # Errors + /// + /// Returns an error if this is a string-based identifier (e.g. `$"..."`) + /// which is invalid utf-8. + pub fn id<'a>(&self, s: &'a str) -> Result, Error> { + let mut ch = self.src(s).chars(); + let dollar = ch.next(); + debug_assert_eq!(dollar, Some('$')); + let id = Lexer::parse_name(&mut ch).map_err(|e| self.error(s, e))?; + if id.is_empty() { + return Err(self.error(s, LexError::EmptyId)); + } + Ok(id) + } + + /// Returns the annotation, without the leading `@` symbol, that this token + /// represents. + /// + /// Note that this method returns the contents of the identifier. With a + /// string-based identifier this means that escapes have been resolved to + /// their string-based equivalent. + /// + /// Should only be used with `TokenKind::Annotation`. + /// + /// # Errors + /// + /// Returns an error if this is a string-based identifier (e.g. `$"..."`) + /// which is invalid utf-8. + pub fn annotation<'a>(&self, s: &'a str) -> Result, Error> { + let mut ch = self.src(s).chars(); + let at = ch.next(); + debug_assert_eq!(at, Some('@')); + let id = Lexer::parse_name(&mut ch).map_err(|e| self.error(s, e))?; + if id.is_empty() { + return Err(self.error(s, LexError::EmptyAnnotation)); + } + Ok(id) } /// Returns the keyword this token represents. @@ -1061,6 +1162,16 @@ impl Token { val, } } + + fn error(&self, src: &str, err: LexError) -> Error { + Error::lex( + Span { + offset: self.offset, + }, + src, + err, + ) + } } impl<'a> Integer<'a> { @@ -1107,6 +1218,9 @@ impl fmt::Display for LexError { InvalidUnicodeValue(c) => write!(f, "invalid unicode scalar value 0x{:x}", c)?, LoneUnderscore => write!(f, "bare underscore in numeric literal")?, ConfusingUnicode(c) => write!(f, "likely-confusing unicode character found {:?}", c)?, + InvalidUtf8Id(_) => write!(f, "malformed UTF-8 encoding of string-based id")?, + EmptyId => write!(f, "empty identifier")?, + EmptyAnnotation => write!(f, "empty annotation id")?, } Ok(()) } @@ -1254,10 +1368,10 @@ mod tests { #[test] fn id() { - fn get_id(input: &str) -> &str { + fn get_id(input: &str) -> String { let token = get_token(input); match token.kind { - TokenKind::Id => token.id(input), + TokenKind::Id => token.id(input).unwrap().to_string(), other => panic!("not id {:?}", other), } } @@ -1267,6 +1381,23 @@ mod tests { assert_eq!(get_id("$0^"), "0^"); assert_eq!(get_id("$0^;;"), "0^"); assert_eq!(get_id("$0^ ;;"), "0^"); + assert_eq!(get_id("$\"x\" ;;"), "x"); + } + + #[test] + fn annotation() { + fn get_annotation(input: &str) -> String { + let token = get_token(input); + match token.kind { + TokenKind::Annotation => token.annotation(input).unwrap().to_string(), + other => panic!("not annotation {:?}", other), + } + } + assert_eq!(get_annotation("@foo"), "foo"); + assert_eq!(get_annotation("@foo "), "foo"); + assert_eq!(get_annotation("@f "), "f"); + assert_eq!(get_annotation("@\"x\" "), "x"); + assert_eq!(get_annotation("@0 "), "0"); } #[test] @@ -1294,7 +1425,6 @@ mod tests { other => panic!("not reserved {:?}", other), } } - assert_eq!(get_reserved("$ "), "$"); assert_eq!(get_reserved("^_x "), "^_x"); } diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index 0c57aa54d8..ade5b2e658 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -317,8 +317,8 @@ macro_rules! annotation { impl<'a> $crate::parser::Parse<'a> for $name { fn parse(parser: $crate::parser::Parser<'a>) -> $crate::parser::Result { parser.step(|c| { - if let Some((a, rest)) = c.reserved()? { - if a == concat!("@", $annotation) { + if let Some((a, rest)) = c.annotation()? { + if a == $annotation { return Ok(($name(c.cur_span()), rest)); } } @@ -329,8 +329,8 @@ macro_rules! annotation { impl $crate::parser::Peek for $name { fn peek(cursor: $crate::parser::Cursor<'_>) -> $crate::parser::Result { - Ok(if let Some((a, _rest)) = cursor.reserved()? { - a == concat!("@", $annotation) + Ok(if let Some((a, _rest)) = cursor.annotation()? { + a == $annotation } else { false }) diff --git a/crates/wast/src/parser.rs b/crates/wast/src/parser.rs index cb65502b72..2bd9e4665e 100644 --- a/crates/wast/src/parser.rs +++ b/crates/wast/src/parser.rs @@ -425,8 +425,9 @@ impl ParseBuffer<'_> { // annotation as known annotations are specifically registered // as "someone's gonna parse this". TokenKind::LParen => { - if let Some(annotation) = self.lexer.annotation(pos) { - match self.known_annotations.borrow().get(annotation) { + if let Some(annotation) = self.lexer.annotation(pos)? { + let text = annotation.annotation(self.lexer.input())?; + match self.known_annotations.borrow().get(&text[..]) { Some(0) | None => { self.skip_annotation(&mut pos)?; continue; @@ -1154,7 +1155,14 @@ impl<'a> Cursor<'a> { _ => return Ok(None), } self.advance_past(&token); - Ok(Some((token.id(self.parser.buf.lexer.input()), self))) + let id = match token.id(self.parser.buf.lexer.input())? { + Cow::Borrowed(id) => id, + // Our `self.parser.buf` only retains `Vec` so briefly convert + // this owned string to `Vec` and then convert it back to `&str` + // out the other end. + Cow::Owned(s) => std::str::from_utf8(self.parser.buf.push_str(s.into_bytes())).unwrap(), + }; + Ok(Some((id, self))) } /// Attempts to advance this cursor if the current token is a @@ -1179,6 +1187,35 @@ impl<'a> Cursor<'a> { Ok(Some((token.keyword(self.parser.buf.lexer.input()), self))) } + /// Attempts to advance this cursor if the current token is a + /// [`Token::Annotation`](crate::lexer::Token) + /// + /// If the current token is `Annotation`, returns the annotation token as well + /// as a new [`Cursor`] pointing at the rest of the tokens in the stream. + /// Otherwise returns `None`. + /// + /// This function will automatically skip over any comments, whitespace, or + /// unknown annotations. + pub fn annotation(mut self) -> Result> { + let token = match self.token()? { + Some(token) => token, + None => return Ok(None), + }; + match token.kind { + TokenKind::Annotation => {} + _ => return Ok(None), + } + self.advance_past(&token); + let annotation = match token.annotation(self.parser.buf.lexer.input())? { + Cow::Borrowed(id) => id, + // Our `self.parser.buf` only retains `Vec` so briefly convert + // this owned string to `Vec` and then convert it back to `&str` + // out the other end. + Cow::Owned(s) => std::str::from_utf8(self.parser.buf.push_str(s.into_bytes())).unwrap(), + }; + Ok(Some((annotation, self))) + } + /// Attempts to advance this cursor if the current token is a /// [`Token::Reserved`](crate::lexer::Token) /// diff --git a/crates/wast/src/token.rs b/crates/wast/src/token.rs index 8094183dd4..c161ee4975 100644 --- a/crates/wast/src/token.rs +++ b/crates/wast/src/token.rs @@ -2,9 +2,9 @@ //! associated specifically with the wasm text format per se (useful in other //! contexts too perhaps). -use crate::lexer::{Float, Lexer, TokenKind}; +use crate::annotation; +use crate::lexer::Float; use crate::parser::{Cursor, Parse, Parser, Peek, Result}; -use crate::{annotation, Error}; use std::fmt; use std::hash::{Hash, Hasher}; use std::str; @@ -60,19 +60,10 @@ pub struct Id<'a> { impl<'a> Id<'a> { /// Construct a new identifier from given string. /// - /// Returns an error if the string does not contain a leading `$`, or is not a - /// valid WASM text format identifier. - pub fn new(name: &'a str, span: Span) -> Result> { - let mut _pos: usize = 0; - let tok = Lexer::new(name).parse(&mut _pos)?; - match tok { - Some(tok) if tok.kind == TokenKind::Id => Ok(Id { - name: tok.id(name), - gen: 0, - span, - }), - _ => Err(Error::parse(span, name, "expected an identifier".into())), - } + /// Note that `name` can be any arbitrary string according to the + /// WebAssembly/annotations proposal. + pub fn new(name: &'a str, span: Span) -> Id<'a> { + Id { name, gen: 0, span } } #[cfg(feature = "wasm-module")] diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index 17d2c38ab9..87fed3c305 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.208.1" +version = "1.209.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index 5aba44f97c..6a3cbb457d 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -16,7 +16,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { push_tys(&mut wat, "result", &sig.results); wat.push_str("))\n"); } - WorldItem::Interface(import) => { + WorldItem::Interface { id: import, .. } => { let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*import].functions.iter() { let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); @@ -39,7 +39,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { // Import any resource-related functions for exports. for (name, export) in world.exports.iter() { let export = match export { - WorldItem::Interface(export) => *export, + WorldItem::Interface { id, .. } => *id, _ => continue, }; let module = format!("[export]{}", resolve.name_world_key(name)); @@ -64,7 +64,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { WorldItem::Function(func) => { push_func(&mut wat, &func.name, resolve, func); } - WorldItem::Interface(export) => { + WorldItem::Interface { id: export, .. } => { let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*export].functions.iter() { let name = func.core_export_name(Some(&name)); diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 37107c7b92..336266dead 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -515,7 +515,7 @@ impl<'a> EncodingState<'a> { for (name, item) in resolve.worlds[world].imports.iter() { let func = match item { WorldItem::Function(f) => f, - WorldItem::Interface(_) | WorldItem::Type(_) => continue, + WorldItem::Interface { .. } | WorldItem::Type(_) => continue, }; let name = resolve.name_world_key(name); if !info.lowerings.contains_key(&name) { @@ -751,8 +751,8 @@ impl<'a> EncodingState<'a> { self.component .export(&export_string, ComponentExportKind::Func, idx, None); } - WorldItem::Interface(export) => { - self.encode_interface_export(&export_string, module, *export)?; + WorldItem::Interface { id, .. } => { + self.encode_interface_export(&export_string, module, *id)?; } WorldItem::Type(_) => unreachable!(), } @@ -2001,7 +2001,7 @@ impl ComponentEncoder { .with_context(|| { format!("failed to merge WIT packages of adapter `{name}` into main packages") })? - .worlds[metadata.world.index()]; + .map_world(metadata.world, None)?; self.metadata .resolve .merge_worlds(world, self.metadata.world) diff --git a/crates/wit-component/src/encoding/wit/v1.rs b/crates/wit-component/src/encoding/wit/v1.rs index 5a0a8c6467..ed6c39a35f 100644 --- a/crates/wit-component/src/encoding/wit/v1.rs +++ b/crates/wit-component/src/encoding/wit/v1.rs @@ -32,10 +32,10 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result Result { - component.interface = Some(*i); - let idx = component.encode_instance(*i)?; + WorldItem::Interface { id, .. } => { + component.interface = Some(*id); + let idx = component.encode_instance(*id)?; ComponentTypeRef::Instance(idx) } WorldItem::Function(f) => { @@ -376,9 +376,9 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result { - component.interface = Some(*i); - let idx = component.encode_instance(*i)?; + WorldItem::Interface { id, .. } => { + component.interface = Some(*id); + let idx = component.encode_instance(*id)?; ComponentTypeRef::Instance(idx) } WorldItem::Function(f) => { diff --git a/crates/wit-component/src/encoding/wit/v2.rs b/crates/wit-component/src/encoding/wit/v2.rs index 06a7f4a9df..7024c6ccf6 100644 --- a/crates/wit-component/src/encoding/wit/v2.rs +++ b/crates/wit-component/src/encoding/wit/v2.rs @@ -32,10 +32,10 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result ComponentWorld<'a> { .iter() .all(|name| match &resolve.worlds[world].exports[name] { WorldItem::Function(_) => false, - WorldItem::Interface(id) => resolve.interfaces[*id].functions.is_empty(), + WorldItem::Interface { id, .. } => { + resolve.interfaces[*id].functions.is_empty() + } WorldItem::Type(_) => true, }) }; @@ -209,7 +211,7 @@ impl<'a> ComponentWorld<'a> { for name in required_exports { match &resolve.worlds[world].exports[name] { WorldItem::Function(func) => add_func(func, None), - WorldItem::Interface(id) => { + WorldItem::Interface { id, .. } => { let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*id].functions.iter() { add_func(func, Some(&name)); @@ -277,11 +279,11 @@ impl<'a> ComponentWorld<'a> { let empty = IndexSet::new(); let import_map_key = match item { WorldItem::Function(_) | WorldItem::Type(_) => None, - WorldItem::Interface(_) => Some(name), + WorldItem::Interface { .. } => Some(name), }; let interface_id = match item { WorldItem::Function(_) | WorldItem::Type(_) => None, - WorldItem::Interface(id) => Some(*id), + WorldItem::Interface { id, .. } => Some(*id), }; let required = required .get(import_map_key.as_deref().unwrap_or(BARE_FUNC_MODULE_NAME)) @@ -300,7 +302,7 @@ impl<'a> ComponentWorld<'a> { WorldItem::Type(ty) => { interface.add_type(required, resolve, *ty); } - WorldItem::Interface(id) => { + WorldItem::Interface { id, .. } => { for (_name, ty) in resolve.interfaces[*id].types.iter() { interface.add_type(required, resolve, *ty); } @@ -344,7 +346,7 @@ impl<'a> ComponentWorld<'a> { for (name, item) in resolve.worlds[world].exports.iter() { log::trace!("add live world export `{}`", resolve.name_world_key(name)); let id = match item { - WorldItem::Interface(id) => id, + WorldItem::Interface { id, .. } => id, WorldItem::Function(_) | WorldItem::Type(_) => { live.add_world_item(resolve, item); continue; @@ -400,7 +402,7 @@ impl<'a> ComponentWorld<'a> { log::trace!("add live function import `{name}`"); live.add_func(resolve, func); } - WorldItem::Interface(id) => { + WorldItem::Interface { id, .. } => { let required = match required.get(name.as_str()) { Some(set) => set, None => continue, @@ -431,7 +433,7 @@ impl<'a> ComponentWorld<'a> { for (_name, item) in exports.iter() { let id = match item { WorldItem::Function(_) => continue, - WorldItem::Interface(id) => *id, + WorldItem::Interface { id, .. } => *id, WorldItem::Type(_) => unreachable!(), }; let mut set = HashSet::new(); diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 5b3286d3ea..0fdc7e5d9c 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -92,6 +92,7 @@ impl Default for Bindgen { includes: Default::default(), include_names: Default::default(), package: Some(package), + stability: Default::default(), }); resolve.packages[package] .worlds @@ -306,7 +307,7 @@ impl Bindgen { .resolve .merge(resolve) .context("failed to merge WIT package sets together")? - .worlds[world.index()]; + .map_world(world, None)?; self.resolve .merge_worlds(world, self.world) .context("failed to merge worlds from two documents")?; @@ -361,8 +362,8 @@ impl ModuleMetadata { .insert((BARE_FUNC_MODULE_NAME.to_string(), name.clone()), encoding); assert!(prev.is_none()); } - WorldItem::Interface(i) => { - for (func, _) in resolve.interfaces[*i].functions.iter() { + WorldItem::Interface { id, .. } => { + for (func, _) in resolve.interfaces[*id].functions.iter() { let prev = ret .import_encodings .insert((name.clone(), func.clone()), encoding); @@ -381,8 +382,8 @@ impl ModuleMetadata { let prev = ret.export_encodings.insert(name.clone(), encoding); assert!(prev.is_none()); } - WorldItem::Interface(i) => { - for (_, func) in resolve.interfaces[*i].functions.iter() { + WorldItem::Interface { id, .. } => { + for (_, func) in resolve.interfaces[*id].functions.iter() { let name = func.core_export_name(Some(&name)).into_owned(); let prev = ret.export_encodings.insert(name, encoding); assert!(prev.is_none()); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index f5be31c196..33be432dc1 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -65,6 +65,7 @@ impl WitPrinter { self.output.push_str("\n\n"); for (name, id) in pkg.interfaces.iter() { self.print_docs(&resolve.interfaces[*id].docs); + self.print_stability(&resolve.interfaces[*id].stability); self.output.push_str("interface "); self.print_name(name); self.output.push_str(" {\n"); @@ -74,6 +75,7 @@ impl WitPrinter { for (name, id) in pkg.worlds.iter() { self.print_docs(&resolve.worlds[*id].docs); + self.print_stability(&resolve.worlds[*id].stability); self.output.push_str("world "); self.print_name(name); self.output.push_str(" {\n"); @@ -125,6 +127,7 @@ impl WitPrinter { for (name, func) in freestanding { self.new_item(); self.print_docs(&func.docs); + self.print_stability(&func.stability); self.print_name(name); self.output.push_str(": "); self.print_function(resolve, func)?; @@ -147,7 +150,7 @@ impl WitPrinter { // Partition types defined in this interface into either those imported // from foreign interfaces or those defined locally. let mut types_to_declare = Vec::new(); - let mut types_to_import: Vec<(_, Vec<_>)> = Vec::new(); + let mut types_to_import: Vec<(_, &_, Vec<_>)> = Vec::new(); for (name, ty_id) in types { let ty = &resolve.types[ty_id]; if let TypeDefKind::Type(Type::Id(other)) = ty.kind { @@ -159,13 +162,17 @@ impl WitPrinter { .name .as_ref() .ok_or_else(|| anyhow!("cannot import unnamed type"))?; - if let Some((owner, list)) = types_to_import.last_mut() { - if *owner == other_owner { + if let Some((owner, stability, list)) = types_to_import.last_mut() { + if *owner == other_owner && ty.stability == **stability { list.push((name, other_name)); continue; } } - types_to_import.push((other_owner, vec![(name, other_name)])); + types_to_import.push(( + other_owner, + &ty.stability, + vec![(name, other_name)], + )); continue; } _ => {} @@ -181,8 +188,9 @@ impl WitPrinter { TypeOwner::World(id) => resolve.worlds[id].package.unwrap(), TypeOwner::None => unreachable!(), }; - for (owner, tys) in types_to_import { + for (owner, stability, tys) in types_to_import { self.any_items = true; + self.print_stability(stability); write!(&mut self.output, "use ")?; let id = match owner { TypeOwner::Interface(id) => id, @@ -212,6 +220,7 @@ impl WitPrinter { for id in types_to_declare { self.new_item(); self.print_docs(&resolve.types[id].docs); + self.print_stability(&resolve.types[id].stability); match resolve.types[id].kind { TypeDefKind::Resource => self.print_resource( resolve, @@ -236,17 +245,16 @@ impl WitPrinter { } self.output.push_str(" {\n"); for func in funcs { + self.print_docs(&func.docs); + self.print_stability(&func.stability); + match &func.kind { - FunctionKind::Constructor(_) => { - self.print_docs(&func.docs); - } + FunctionKind::Constructor(_) => {} FunctionKind::Method(_) => { - self.print_docs(&func.docs); self.print_name(func.item_name()); self.output.push_str(": "); } FunctionKind::Static(_) => { - self.print_docs(&func.docs); self.print_name(func.item_name()); self.output.push_str(": "); self.output.push_str("static "); @@ -367,13 +375,14 @@ impl WitPrinter { // Print inline item docs if matches!(name, WorldKey::Name(_)) { self.print_docs(match item { - WorldItem::Interface(id) => &resolve.interfaces[*id].docs, + WorldItem::Interface { id, .. } => &resolve.interfaces[*id].docs, WorldItem::Function(f) => &f.docs, // Types are handled separately WorldItem::Type(_) => unreachable!(), }); } + self.print_stability(item.stability(resolve)); self.output.push_str(desc); self.output.push_str(" "); match name { @@ -381,7 +390,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(": "); match item { - WorldItem::Interface(id) => { + WorldItem::Interface { id, .. } => { assert!(resolve.interfaces[*id].name.is_none()); writeln!(self.output, "interface {{")?; self.print_interface(resolve, *id)?; @@ -398,7 +407,7 @@ impl WitPrinter { } WorldKey::Interface(id) => { match item { - WorldItem::Interface(id2) => assert_eq!(id, id2), + WorldItem::Interface { id: id2, .. } => assert_eq!(id, id2), _ => unreachable!(), } self.print_path_to_interface(resolve, *id, cur_pkg)?; @@ -868,6 +877,26 @@ impl WitPrinter { } } } + + fn print_stability(&mut self, stability: &Stability) { + match stability { + Stability::Unknown => {} + Stability::Stable { since, feature } => { + self.output.push_str("@since(version = "); + self.output.push_str(&since.to_string()); + if let Some(feature) = feature { + self.output.push_str(", feature = "); + self.output.push_str(feature); + } + self.output.push_str(")\n"); + } + Stability::Unstable { feature } => { + self.output.push_str("@unstable(feature = "); + self.output.push_str(feature); + self.output.push_str(")\n"); + } + } + } } fn resource_func(f: &Function) -> Option { diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 4ce35f9ffc..fadd430c1f 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -237,7 +237,7 @@ pub fn validate_module<'a>( } } else { match world.imports.get(&world_key(&metadata.resolve, name)) { - Some(WorldItem::Interface(interface)) => { + Some(WorldItem::Interface { id: interface, .. }) => { let required = validate_imported_interface( &metadata.resolve, *interface, @@ -272,10 +272,10 @@ pub fn validate_module<'a>( for (name, interface_name, funcs) in exported_resource_funcs { let world_key = world_key(&metadata.resolve, interface_name); match world.exports.get(&world_key) { - Some(WorldItem::Interface(i)) => { + Some(WorldItem::Interface { id, .. }) => { validate_exported_interface_resource_imports( &metadata.resolve, - *i, + *id, name, funcs, &types, @@ -526,7 +526,7 @@ pub fn validate_adapter_module<'a>( if !(is_library && adapters.contains(name)) { match resolve.worlds[world].imports.get(&world_key(resolve, name)) { - Some(WorldItem::Interface(interface)) => { + Some(WorldItem::Interface { id: interface, .. }) => { let required = validate_imported_interface(resolve, *interface, name, funcs, &types) .with_context(|| { @@ -576,10 +576,10 @@ pub fn validate_adapter_module<'a>( for (name, interface_name, funcs) in exported_resource_funcs { let world_key = world_key(resolve, interface_name); match world.exports.get(&world_key) { - Some(WorldItem::Interface(i)) => { + Some(WorldItem::Interface { id, .. }) => { validate_exported_interface_resource_imports( resolve, - *i, + *id, name, funcs, &types, @@ -809,7 +809,7 @@ fn validate_exported_item<'a>( }; match item { WorldItem::Function(func) => validate(func, None)?, - WorldItem::Interface(interface) => { + WorldItem::Interface { id: interface, .. } => { let interface = &resolve.interfaces[*interface]; for (_, f) in interface.functions.iter() { validate(f, Some(export_name)).with_context(|| { diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 835b39aa34..05bd50cf0b 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -127,10 +127,13 @@ enum AstItem<'a> { impl<'a> AstItem<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let attributes = Attribute::parse_list(tokens)?; match tokens.clone().next()? { - Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Self::Interface), - Some((_span, Token::World)) => World::parse(tokens, docs).map(Self::World), - Some((_span, Token::Use)) => ToplevelUse::parse(tokens).map(Self::Use), + Some((_span, Token::Interface)) => { + Interface::parse(tokens, docs, attributes).map(Self::Interface) + } + Some((_span, Token::World)) => World::parse(tokens, docs, attributes).map(Self::World), + Some((_span, Token::Use)) => ToplevelUse::parse(tokens, attributes).map(Self::Use), other => Err(err_expected(tokens, "`world`, `interface` or `use`", other).into()), } } @@ -176,13 +179,15 @@ impl<'a> PackageName<'a> { } struct ToplevelUse<'a> { + span: Span, + attributes: Vec>, item: UsePath<'a>, as_: Option>, } impl<'a> ToplevelUse<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { - tokens.expect(Token::Use)?; + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { + let span = tokens.expect(Token::Use)?; let item = UsePath::parse(tokens)?; let as_ = if tokens.eat(Token::As)? { Some(parse_id(tokens)?) @@ -190,22 +195,37 @@ impl<'a> ToplevelUse<'a> { None }; tokens.expect_semicolon()?; - Ok(ToplevelUse { item, as_ }) + Ok(ToplevelUse { + span, + attributes, + item, + as_, + }) } } struct World<'a> { docs: Docs<'a>, + attributes: Vec>, name: Id<'a>, items: Vec>, } impl<'a> World<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::World)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(World { docs, name, items }) + Ok(World { + docs, + attributes, + name, + items, + }) } fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -216,7 +236,8 @@ impl<'a> World<'a> { if tokens.eat(Token::RightBrace)? { break; } - items.push(WorldItem::parse(tokens, docs)?); + let attributes = Attribute::parse_list(tokens)?; + items.push(WorldItem::parse(tokens, docs, attributes)?); } Ok(items) } @@ -231,24 +252,40 @@ enum WorldItem<'a> { } impl<'a> WorldItem<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result> { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result> { match tokens.clone().next()? { - Some((_span, Token::Import)) => Import::parse(tokens, docs).map(WorldItem::Import), - Some((_span, Token::Export)) => Export::parse(tokens, docs).map(WorldItem::Export), - Some((_span, Token::Use)) => Use::parse(tokens).map(WorldItem::Use), - Some((_span, Token::Type)) => TypeDef::parse(tokens, docs).map(WorldItem::Type), - Some((_span, Token::Flags)) => TypeDef::parse_flags(tokens, docs).map(WorldItem::Type), + Some((_span, Token::Import)) => { + Import::parse(tokens, docs, attributes).map(WorldItem::Import) + } + Some((_span, Token::Export)) => { + Export::parse(tokens, docs, attributes).map(WorldItem::Export) + } + Some((_span, Token::Use)) => Use::parse(tokens, attributes).map(WorldItem::Use), + Some((_span, Token::Type)) => { + TypeDef::parse(tokens, docs, attributes).map(WorldItem::Type) + } + Some((_span, Token::Flags)) => { + TypeDef::parse_flags(tokens, docs, attributes).map(WorldItem::Type) + } Some((_span, Token::Resource)) => { - TypeDef::parse_resource(tokens, docs).map(WorldItem::Type) + TypeDef::parse_resource(tokens, docs, attributes).map(WorldItem::Type) } Some((_span, Token::Record)) => { - TypeDef::parse_record(tokens, docs).map(WorldItem::Type) + TypeDef::parse_record(tokens, docs, attributes).map(WorldItem::Type) } Some((_span, Token::Variant)) => { - TypeDef::parse_variant(tokens, docs).map(WorldItem::Type) + TypeDef::parse_variant(tokens, docs, attributes).map(WorldItem::Type) + } + Some((_span, Token::Enum)) => { + TypeDef::parse_enum(tokens, docs, attributes).map(WorldItem::Type) + } + Some((_span, Token::Include)) => { + Include::parse(tokens, attributes).map(WorldItem::Include) } - Some((_span, Token::Enum)) => TypeDef::parse_enum(tokens, docs).map(WorldItem::Type), - Some((_span, Token::Include)) => Include::parse(tokens).map(WorldItem::Include), other => Err(err_expected( tokens, "`import`, `export`, `include`, `use`, or type definition", @@ -261,27 +298,45 @@ impl<'a> WorldItem<'a> { struct Import<'a> { docs: Docs<'a>, + attributes: Vec>, kind: ExternKind<'a>, } impl<'a> Import<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result> { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result> { tokens.expect(Token::Import)?; let kind = ExternKind::parse(tokens)?; - Ok(Import { docs, kind }) + Ok(Import { + docs, + attributes, + kind, + }) } } struct Export<'a> { docs: Docs<'a>, + attributes: Vec>, kind: ExternKind<'a>, } impl<'a> Export<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result> { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result> { tokens.expect(Token::Export)?; let kind = ExternKind::parse(tokens)?; - Ok(Export { docs, kind }) + Ok(Export { + docs, + attributes, + kind, + }) } } @@ -339,16 +394,26 @@ impl<'a> ExternKind<'a> { struct Interface<'a> { docs: Docs<'a>, + attributes: Vec>, name: Id<'a>, items: Vec>, } impl<'a> Interface<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Interface)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; - Ok(Interface { docs, name, items }) + Ok(Interface { + docs, + attributes, + name, + items, + }) } pub(super) fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { @@ -359,7 +424,8 @@ impl<'a> Interface<'a> { if tokens.eat(Token::RightBrace)? { break; } - items.push(InterfaceItem::parse(tokens, docs)?); + let attributes = Attribute::parse_list(tokens)?; + items.push(InterfaceItem::parse(tokens, docs, attributes)?); } Ok(items) } @@ -379,6 +445,7 @@ enum InterfaceItem<'a> { } struct Use<'a> { + attributes: Vec>, from: UsePath<'a>, names: Vec>, } @@ -432,7 +499,7 @@ struct UseName<'a> { } impl<'a> Use<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { tokens.expect(Token::Use)?; let from = UsePath::parse(tokens)?; tokens.expect(Token::Period)?; @@ -454,12 +521,17 @@ impl<'a> Use<'a> { } } tokens.expect_semicolon()?; - Ok(Use { from, names }) + Ok(Use { + attributes, + from, + names, + }) } } struct Include<'a> { from: UsePath<'a>, + attributes: Vec>, names: Vec>, } @@ -469,7 +541,7 @@ struct IncludeName<'a> { } impl<'a> Include<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { tokens.expect(Token::Include)?; let from = UsePath::parse(tokens)?; @@ -490,7 +562,11 @@ impl<'a> Include<'a> { Vec::new() }; - Ok(Include { from, names }) + Ok(Include { + attributes, + from, + names, + }) } } @@ -526,36 +602,37 @@ impl<'a> Default for Docs<'a> { struct TypeDef<'a> { docs: Docs<'a>, + attributes: Vec>, name: Id<'a>, ty: Type<'a>, } enum Type<'a> { - Bool, - U8, - U16, - U32, - U64, - S8, - S16, - S32, - S64, - F32, - F64, - Char, - String, + Bool(Span), + U8(Span), + U16(Span), + U32(Span), + U64(Span), + S8(Span), + S16(Span), + S32(Span), + S64(Span), + F32(Span), + F64(Span), + Char(Span), + String(Span), Name(Id<'a>), - List(Box>), + List(List<'a>), Handle(Handle<'a>), Resource(Resource<'a>), Record(Record<'a>), Flags(Flags<'a>), Variant(Variant<'a>), - Tuple(Vec>), + Tuple(Tuple<'a>), Enum(Enum<'a>), - Option(Box>), + Option(Option_<'a>), Result(Result_<'a>), - Future(Option>>), + Future(Future<'a>), Stream(Stream<'a>), } @@ -564,7 +641,16 @@ enum Handle<'a> { Borrow { resource: Id<'a> }, } +impl Handle<'_> { + fn span(&self) -> Span { + match self { + Handle::Own { resource } | Handle::Borrow { resource } => resource.span, + } + } +} + struct Resource<'a> { + span: Span, funcs: Vec>, } @@ -575,7 +661,11 @@ enum ResourceFunc<'a> { } impl<'a> ResourceFunc<'a> { - fn parse(docs: Docs<'a>, tokens: &mut Tokenizer<'a>) -> Result { + fn parse( + docs: Docs<'a>, + attributes: Vec>, + tokens: &mut Tokenizer<'a>, + ) -> Result { match tokens.clone().next()? { Some((span, Token::Constructor)) => { tokens.expect(Token::Constructor)?; @@ -589,11 +679,13 @@ impl<'a> ResourceFunc<'a> { tokens.expect_semicolon()?; Ok(ResourceFunc::Constructor(NamedFunc { docs, + attributes, name: Id { span, name: "constructor", }, func: Func { + span, params, results: ResultList::Named(Vec::new()), }, @@ -609,7 +701,12 @@ impl<'a> ResourceFunc<'a> { }; let func = Func::parse(tokens)?; tokens.expect_semicolon()?; - Ok(ctor(NamedFunc { docs, name, func })) + Ok(ctor(NamedFunc { + docs, + attributes, + name, + func, + })) } other => Err(err_expected(tokens, "`constructor` or identifier", other).into()), } @@ -624,6 +721,7 @@ impl<'a> ResourceFunc<'a> { } struct Record<'a> { + span: Span, fields: Vec>, } @@ -634,6 +732,7 @@ struct Field<'a> { } struct Flags<'a> { + span: Span, flags: Vec>, } @@ -663,18 +762,41 @@ struct EnumCase<'a> { name: Id<'a>, } +struct Option_<'a> { + span: Span, + ty: Box>, +} + +struct List<'a> { + span: Span, + ty: Box>, +} + +struct Future<'a> { + span: Span, + ty: Option>>, +} + +struct Tuple<'a> { + span: Span, + types: Vec>, +} + struct Result_<'a> { + span: Span, ok: Option>>, err: Option>>, } struct Stream<'a> { + span: Span, element: Option>>, end: Option>>, } struct NamedFunc<'a> { docs: Docs<'a>, + attributes: Vec>, name: Id<'a>, func: Func<'a>, } @@ -687,6 +809,7 @@ enum ResultList<'a> { } struct Func<'a> { + span: Span, params: ParamList<'a>, results: ResultList<'a>, } @@ -705,7 +828,7 @@ impl<'a> Func<'a> { }) } - tokens.expect(Token::Func)?; + let span = tokens.expect(Token::Func)?; let params = parse_params(tokens, true)?; let results = if tokens.eat(Token::RArrow)? { // If we eat a '(', parse the remainder of the named @@ -720,52 +843,76 @@ impl<'a> Func<'a> { } else { ResultList::Named(Vec::new()) }; - Ok(Func { params, results }) + Ok(Func { + span, + params, + results, + }) } } impl<'a> InterfaceItem<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result> { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result> { match tokens.clone().next()? { - Some((_span, Token::Type)) => TypeDef::parse(tokens, docs).map(InterfaceItem::TypeDef), + Some((_span, Token::Type)) => { + TypeDef::parse(tokens, docs, attributes).map(InterfaceItem::TypeDef) + } Some((_span, Token::Flags)) => { - TypeDef::parse_flags(tokens, docs).map(InterfaceItem::TypeDef) + TypeDef::parse_flags(tokens, docs, attributes).map(InterfaceItem::TypeDef) } Some((_span, Token::Enum)) => { - TypeDef::parse_enum(tokens, docs).map(InterfaceItem::TypeDef) + TypeDef::parse_enum(tokens, docs, attributes).map(InterfaceItem::TypeDef) } Some((_span, Token::Variant)) => { - TypeDef::parse_variant(tokens, docs).map(InterfaceItem::TypeDef) + TypeDef::parse_variant(tokens, docs, attributes).map(InterfaceItem::TypeDef) } Some((_span, Token::Resource)) => { - TypeDef::parse_resource(tokens, docs).map(InterfaceItem::TypeDef) + TypeDef::parse_resource(tokens, docs, attributes).map(InterfaceItem::TypeDef) } Some((_span, Token::Record)) => { - TypeDef::parse_record(tokens, docs).map(InterfaceItem::TypeDef) + TypeDef::parse_record(tokens, docs, attributes).map(InterfaceItem::TypeDef) } Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => { - NamedFunc::parse(tokens, docs).map(InterfaceItem::Func) + NamedFunc::parse(tokens, docs, attributes).map(InterfaceItem::Func) } - Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use), + Some((_span, Token::Use)) => Use::parse(tokens, attributes).map(InterfaceItem::Use), other => Err(err_expected(tokens, "`type`, `resource` or `func`", other).into()), } } } impl<'a> TypeDef<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Type)?; let name = parse_id(tokens)?; tokens.expect(Token::Equals)?; let ty = Type::parse(tokens)?; tokens.expect_semicolon()?; - Ok(TypeDef { docs, name, ty }) + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } - fn parse_flags(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse_flags( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Flags)?; let name = parse_id(tokens)?; let ty = Type::Flags(Flags { + span: name.span, flags: parse_list( tokens, Token::LeftBrace, @@ -776,28 +923,52 @@ impl<'a> TypeDef<'a> { }, )?, }); - Ok(TypeDef { docs, name, ty }) + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } - fn parse_resource(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse_resource( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Resource)?; let name = parse_id(tokens)?; let mut funcs = Vec::new(); if tokens.eat(Token::LeftBrace)? { while !tokens.eat(Token::RightBrace)? { - funcs.push(ResourceFunc::parse(parse_docs(tokens)?, tokens)?); + let docs = parse_docs(tokens)?; + let attributes = Attribute::parse_list(tokens)?; + funcs.push(ResourceFunc::parse(docs, attributes, tokens)?); } } else { tokens.expect_semicolon()?; } - let ty = Type::Resource(Resource { funcs }); - Ok(TypeDef { docs, name, ty }) + let ty = Type::Resource(Resource { + span: name.span, + funcs, + }); + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } - fn parse_record(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse_record( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Record)?; let name = parse_id(tokens)?; let ty = Type::Record(Record { + span: name.span, fields: parse_list( tokens, Token::LeftBrace, @@ -810,10 +981,19 @@ impl<'a> TypeDef<'a> { }, )?, }); - Ok(TypeDef { docs, name, ty }) + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } - fn parse_variant(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse_variant( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Variant)?; let name = parse_id(tokens)?; let ty = Type::Variant(Variant { @@ -835,10 +1015,19 @@ impl<'a> TypeDef<'a> { }, )?, }); - Ok(TypeDef { docs, name, ty }) + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } - fn parse_enum(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse_enum( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Enum)?; let name = parse_id(tokens)?; let ty = Type::Enum(Enum { @@ -853,17 +1042,31 @@ impl<'a> TypeDef<'a> { }, )?, }); - Ok(TypeDef { docs, name, ty }) + Ok(TypeDef { + docs, + attributes, + name, + ty, + }) } } impl<'a> NamedFunc<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + docs: Docs<'a>, + attributes: Vec>, + ) -> Result { let name = parse_id(tokens)?; tokens.expect(Token::Colon)?; let func = Func::parse(tokens)?; tokens.expect_semicolon()?; - Ok(NamedFunc { docs, name, func }) + Ok(NamedFunc { + docs, + attributes, + name, + func, + }) } } @@ -882,9 +1085,14 @@ fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { } fn parse_opt_version(tokens: &mut Tokenizer<'_>) -> Result> { - if !tokens.eat(Token::At)? { - return Ok(None); + if tokens.eat(Token::At)? { + parse_version(tokens).map(Some) + } else { + Ok(None) } +} + +fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version)> { let start = tokens.expect(Token::Integer)?.start; tokens.expect(Token::Period)?; tokens.expect(Token::Integer)?; @@ -894,11 +1102,8 @@ fn parse_opt_version(tokens: &mut Tokenizer<'_>) -> Result, prefix: Token, end: &mut Span) -> Result<()> { if !tokens.eat(prefix)? { @@ -962,53 +1167,59 @@ fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result> { impl<'a> Type<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result { match tokens.next()? { - Some((_span, Token::U8)) => Ok(Type::U8), - Some((_span, Token::U16)) => Ok(Type::U16), - Some((_span, Token::U32)) => Ok(Type::U32), - Some((_span, Token::U64)) => Ok(Type::U64), - Some((_span, Token::S8)) => Ok(Type::S8), - Some((_span, Token::S16)) => Ok(Type::S16), - Some((_span, Token::S32)) => Ok(Type::S32), - Some((_span, Token::S64)) => Ok(Type::S64), - Some((_span, Token::F32)) => Ok(Type::F32), - Some((_span, Token::F64)) => Ok(Type::F64), - Some((_span, Token::Char)) => Ok(Type::Char), + Some((span, Token::U8)) => Ok(Type::U8(span)), + Some((span, Token::U16)) => Ok(Type::U16(span)), + Some((span, Token::U32)) => Ok(Type::U32(span)), + Some((span, Token::U64)) => Ok(Type::U64(span)), + Some((span, Token::S8)) => Ok(Type::S8(span)), + Some((span, Token::S16)) => Ok(Type::S16(span)), + Some((span, Token::S32)) => Ok(Type::S32(span)), + Some((span, Token::S64)) => Ok(Type::S64(span)), + Some((span, Token::F32)) => Ok(Type::F32(span)), + Some((span, Token::F64)) => Ok(Type::F64(span)), + Some((span, Token::Char)) => Ok(Type::Char(span)), // tuple - Some((_span, Token::Tuple)) => { + Some((span, Token::Tuple)) => { let types = parse_list( tokens, Token::LessThan, Token::GreaterThan, |_docs, tokens| Type::parse(tokens), )?; - Ok(Type::Tuple(types)) + Ok(Type::Tuple(Tuple { span, types })) } - Some((_span, Token::Bool)) => Ok(Type::Bool), - Some((_span, Token::String_)) => Ok(Type::String), + Some((span, Token::Bool)) => Ok(Type::Bool(span)), + Some((span, Token::String_)) => Ok(Type::String(span)), // list - Some((_span, Token::List)) => { + Some((span, Token::List)) => { tokens.expect(Token::LessThan)?; let ty = Type::parse(tokens)?; tokens.expect(Token::GreaterThan)?; - Ok(Type::List(Box::new(ty))) + Ok(Type::List(List { + span, + ty: Box::new(ty), + })) } // option - Some((_span, Token::Option_)) => { + Some((span, Token::Option_)) => { tokens.expect(Token::LessThan)?; let ty = Type::parse(tokens)?; tokens.expect(Token::GreaterThan)?; - Ok(Type::Option(Box::new(ty))) + Ok(Type::Option(Option_ { + span, + ty: Box::new(ty), + })) } // result // result<_, E> // result // result - Some((_span, Token::Result_)) => { + Some((span, Token::Result_)) => { let mut ok = None; let mut err = None; @@ -1024,26 +1235,26 @@ impl<'a> Type<'a> { }; tokens.expect(Token::GreaterThan)?; }; - Ok(Type::Result(Result_ { ok, err })) + Ok(Type::Result(Result_ { span, ok, err })) } // future // future - Some((_span, Token::Future)) => { + Some((span, Token::Future)) => { let mut ty = None; if tokens.eat(Token::LessThan)? { ty = Some(Box::new(Type::parse(tokens)?)); tokens.expect(Token::GreaterThan)?; }; - Ok(Type::Future(ty)) + Ok(Type::Future(Future { span, ty })) } // stream // stream<_, Z> // stream // stream - Some((_span, Token::Stream)) => { + Some((span, Token::Stream)) => { let mut element = None; let mut end = None; @@ -1059,7 +1270,7 @@ impl<'a> Type<'a> { }; tokens.expect(Token::GreaterThan)?; }; - Ok(Type::Stream(Stream { element, end })) + Ok(Type::Stream(Stream { span, element, end })) } // own @@ -1092,6 +1303,37 @@ impl<'a> Type<'a> { other => Err(err_expected(tokens, "a type", other).into()), } } + + fn span(&self) -> Span { + match self { + Type::Bool(span) + | Type::U8(span) + | Type::U16(span) + | Type::U32(span) + | Type::U64(span) + | Type::S8(span) + | Type::S16(span) + | Type::S32(span) + | Type::S64(span) + | Type::F32(span) + | Type::F64(span) + | Type::Char(span) + | Type::String(span) => *span, + Type::Name(id) => id.span, + Type::List(l) => l.span, + Type::Handle(h) => h.span(), + Type::Resource(r) => r.span, + Type::Record(r) => r.span, + Type::Flags(f) => f.span, + Type::Variant(v) => v.span, + Type::Tuple(t) => t.span, + Type::Enum(e) => e.span, + Type::Option(o) => o.span, + Type::Result(r) => r.span, + Type::Future(f) => f.span, + Type::Stream(s) => s.span, + } + } } fn parse_list<'a, T>( @@ -1138,20 +1380,95 @@ fn err_expected( found: Option<(Span, Token)>, ) -> Error { match found { - Some((span, token)) => Error { + Some((span, token)) => Error::new( span, - msg: format!("expected {}, found {}", expected, token.describe()), - }, - None => Error { - span: Span { + format!("expected {}, found {}", expected, token.describe()), + ), + None => Error::new( + Span { start: u32::try_from(tokens.input().len()).unwrap(), end: u32::try_from(tokens.input().len()).unwrap(), }, - msg: format!("expected {}, found eof", expected), - }, + format!("expected {}, found eof", expected), + ), + } +} + +enum Attribute<'a> { + Since { + span: Span, + version: Version, + feature: Option>, + }, + Unstable { + span: Span, + feature: Id<'a>, + }, +} + +impl<'a> Attribute<'a> { + fn parse_list(tokens: &mut Tokenizer<'a>) -> Result>> { + let mut ret = Vec::new(); + while tokens.eat(Token::At)? { + let id = parse_id(tokens)?; + let attr = match id.name { + "since" => { + tokens.eat(Token::LeftParen)?; + eat_id(tokens, "version")?; + tokens.eat(Token::Equals)?; + let (_span, version) = parse_version(tokens)?; + let feature = if tokens.eat(Token::Comma)? { + eat_id(tokens, "feature")?; + tokens.eat(Token::Equals)?; + Some(parse_id(tokens)?) + } else { + None + }; + tokens.eat(Token::RightParen)?; + Attribute::Since { + span: id.span, + version, + feature, + } + } + "unstable" => { + tokens.eat(Token::LeftParen)?; + eat_id(tokens, "feature")?; + tokens.eat(Token::Equals)?; + let feature = parse_id(tokens)?; + tokens.eat(Token::RightParen)?; + Attribute::Unstable { + span: id.span, + feature, + } + } + other => { + bail!(Error::new(id.span, format!("unknown attribute `{other}`"),)) + } + }; + ret.push(attr); + } + Ok(ret) + } + + fn span(&self) -> Span { + match self { + Attribute::Since { span, .. } | Attribute::Unstable { span, .. } => *span, + } } } +fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> Result { + let id = parse_id(tokens)?; + if id.name != expected { + bail!(Error::new( + id.span, + format!("expected `{expected}`, found `{}`", id.name), + )); + } + Ok(id.span) +} + /// A listing of source files which are used to get parsed into an /// [`UnresolvedPackage`]. #[derive(Clone, Default)] @@ -1241,13 +1558,16 @@ impl SourceMap { where F: FnOnce() -> Result, { - let err = match f() { + let mut err = match f() { Ok(t) => return Ok(t), Err(e) => e, }; - if let Some(parse) = err.downcast_ref::() { - let msg = self.highlight_err(parse.span.start, Some(parse.span.end), parse); - bail!("{msg}") + if let Some(parse) = err.downcast_mut::() { + if parse.highlighted.is_none() { + let msg = self.highlight_err(parse.span.start, Some(parse.span.end), &parse.msg); + parse.highlighted = Some(msg); + } + return Err(err); } if let Some(lex) = err.downcast_ref::() { diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 0962d05da8..4004a84635 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -64,9 +64,12 @@ pub struct Resolver<'a> { /// pointing to it if the item isn't actually defined. unknown_type_spans: Vec, - /// Spans for each world in `self.world` + /// Spans for each world in `self.worlds` world_spans: Vec, + /// Spans for each type in `self.types` + type_spans: Vec, + /// The span of each interface's definition which is used for error /// reporting during the final `Resolve` phase. interface_spans: Vec, @@ -114,13 +117,13 @@ impl<'a> Resolver<'a> { let cur_name = cur.package_name(); if let Some(prev) = &self.package_name { if cur_name != *prev { - bail!(Error { - span: cur.span, - msg: format!( + bail!(Error::new( + cur.span, + format!( "package identifier `{cur_name}` does not match \ previous package name of `{prev}`" ), - }) + )) } } self.package_name = Some(cur_name); @@ -129,10 +132,10 @@ impl<'a> Resolver<'a> { let docs = self.docs(&cur.docs); if docs.contents.is_some() { if self.package_docs.contents.is_some() { - bail!(Error { - span: cur.docs.span, - msg: "found doc comments on multiple 'package' items".into(), - }) + bail!(Error::new( + cur.docs.span, + "found doc comments on multiple 'package' items" + )) } self.package_docs = docs; } @@ -187,7 +190,7 @@ impl<'a> Resolver<'a> { for id in iface_order { let (interface, i) = &iface_id_to_ast[&id]; self.cur_ast_index = *i; - self.resolve_interface(id, &interface.items, &interface.docs)?; + self.resolve_interface(id, &interface.items, &interface.docs, &interface.attributes)?; } for id in world_order { @@ -217,6 +220,7 @@ impl<'a> Resolver<'a> { unknown_type_spans: mem::take(&mut self.unknown_type_spans), interface_spans: mem::take(&mut self.interface_spans), world_spans: mem::take(&mut self.world_spans), + type_spans: mem::take(&mut self.type_spans), foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), source_map: SourceMap::default(), required_resource_types: mem::take(&mut self.required_resource_types), @@ -289,6 +293,7 @@ impl<'a> Resolver<'a> { name: None, types: IndexMap::new(), docs: Docs::default(), + stability: Default::default(), functions: IndexMap::new(), package: None, }) @@ -309,6 +314,7 @@ impl<'a> Resolver<'a> { package: None, includes: Default::default(), include_names: Default::default(), + stability: Default::default(), }) } @@ -332,10 +338,10 @@ impl<'a> Resolver<'a> { match item { ast::AstItem::Interface(i) => { if package_items.insert(i.name.name, i.name.span).is_some() { - bail!(Error { - span: i.name.span, - msg: format!("duplicate item named `{}`", i.name.name), - }) + bail!(Error::new( + i.name.span, + format!("duplicate item named `{}`", i.name.name), + )) } let prev = ast_ns.insert(i.name.name, ()); assert!(prev.is_none()); @@ -346,10 +352,10 @@ impl<'a> Resolver<'a> { } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { - bail!(Error { - span: w.name.span, - msg: format!("duplicate item named `{}`", w.name.name), - }) + bail!(Error::new( + w.name.span, + format!("duplicate item named `{}`", w.name.name), + )) } let prev = ast_ns.insert(w.name.name, ()); assert!(prev.is_none()); @@ -394,10 +400,10 @@ impl<'a> Resolver<'a> { ast::AstItem::World(w) => (&w.name, ItemSource::Local(w.name.clone())), }; if ast_ns.insert(name.name, (name.span, src)).is_some() { - bail!(Error { - span: name.span, - msg: format!("duplicate name `{}` in this file", name.name), - }); + bail!(Error::new( + name.span, + format!("duplicate name `{}` in this file", name.name), + )); } } @@ -425,13 +431,13 @@ impl<'a> Resolver<'a> { order[iface.name].push(used_name.clone()); } None => { - bail!(Error { - span: used_name.span, - msg: format!( + bail!(Error::new( + used_name.span, + format!( "interface or world `{name}` not found in package", name = used_name.name ), - }) + )) } }, } @@ -472,14 +478,22 @@ impl<'a> Resolver<'a> { for item in ast.items.iter() { let (name, ast_item) = match item { ast::AstItem::Use(u) => { + if !u.attributes.is_empty() { + bail!(Error::new( + u.span, + format!("attributes not allowed on top-level use"), + )) + } let name = u.as_.as_ref().unwrap_or(u.item.name()); let item = match &u.item { - ast::UsePath::Id(name) => *ids.get(name.name).ok_or_else(|| Error { - span: name.span, - msg: format!( - "interface or world `{name}` does not exist", - name = name.name - ), + ast::UsePath::Id(name) => *ids.get(name.name).ok_or_else(|| { + Error::new( + name.span, + format!( + "interface or world `{name}` does not exist", + name = name.name + ), + ) })?, ast::UsePath::Package { id, name } => { self.foreign_deps[&id.package_name()][name.name] @@ -543,11 +557,13 @@ impl<'a> Resolver<'a> { } let id = self.types.alloc(TypeDef { docs: Docs::default(), + stability: Default::default(), kind: TypeDefKind::Unknown, name: Some(name.name.name.to_string()), owner: TypeOwner::Interface(iface), }); self.unknown_type_spans.push(name.name.span); + self.type_spans.push(name.name.span); lookup.insert(name.name.name, (TypeOrItem::Type(id), name.name.span)); self.interfaces[iface] .types @@ -563,6 +579,8 @@ impl<'a> Resolver<'a> { fn resolve_world(&mut self, world_id: WorldId, world: &ast::World<'a>) -> Result { let docs = self.docs(&world.docs); self.worlds[world_id].docs = docs; + let stability = self.stability(&world.attributes)?; + self.worlds[world_id].stability = stability; self.resolve_types( TypeOwner::World(world_id), @@ -593,13 +611,10 @@ impl<'a> Resolver<'a> { TypeOrItem::Type(id) => { let prev = import_names.insert(*name, "import"); if let Some(prev) = prev { - return Err(Error { - span: *span, - msg: format!( - "import `{name}` conflicts with prior {prev} of same name", - ), - } - .into()); + bail!(Error::new( + *span, + format!("import `{name}` conflicts with prior {prev} of same name",), + )) } self.worlds[world_id] .imports @@ -613,9 +628,10 @@ impl<'a> Resolver<'a> { let mut imported_interfaces = HashSet::new(); let mut exported_interfaces = HashSet::new(); for item in world.items.iter() { - let (docs, kind, desc, spans, interfaces, names) = match item { + let (docs, attrs, kind, desc, spans, interfaces, names) = match item { ast::WorldItem::Import(import) => ( &import.docs, + &import.attributes, &import.kind, "import", &mut import_spans, @@ -624,6 +640,7 @@ impl<'a> Resolver<'a> { ), ast::WorldItem::Export(export) => ( &export.docs, + &export.attributes, &export.kind, "export", &mut export_spans, @@ -659,14 +676,13 @@ impl<'a> Resolver<'a> { ast::ExternKind::Interface(name, _) | ast::ExternKind::Func(name, _) => { let prev = names.insert(name.name, desc); if let Some(prev) = prev { - return Err(Error { - span: kind.span(), - msg: format!( + bail!(Error::new( + kind.span(), + format!( "{desc} `{name}` conflicts with prior {prev} of same name", name = name.name ), - } - .into()); + )) } WorldKey::Name(name.name.to_string()) } @@ -676,13 +692,13 @@ impl<'a> Resolver<'a> { WorldKey::Interface(id) } }; - let world_item = self.resolve_world_item(docs, kind)?; - if let WorldItem::Interface(id) = world_item { + let world_item = self.resolve_world_item(docs, attrs, kind)?; + if let WorldItem::Interface { id, .. } = world_item { if !interfaces.insert(id) { - bail!(Error { - span: kind.span(), - msg: format!("interface cannot be {desc}ed more than once"), - }) + bail!(Error::new( + kind.span(), + format!("interface cannot be {desc}ed more than once"), + )) } } let dst = if desc == "import" { @@ -704,24 +720,32 @@ impl<'a> Resolver<'a> { fn resolve_world_item( &mut self, docs: &ast::Docs<'a>, + attrs: &[ast::Attribute<'a>], kind: &ast::ExternKind<'a>, ) -> Result { match kind { ast::ExternKind::Interface(name, items) => { let prev = mem::take(&mut self.type_lookup); let id = self.alloc_interface(name.span); - self.resolve_interface(id, items, docs)?; + self.resolve_interface(id, items, docs, attrs)?; self.type_lookup = prev; - Ok(WorldItem::Interface(id)) + let stability = self.interfaces[id].stability.clone(); + Ok(WorldItem::Interface { id, stability }) } ast::ExternKind::Path(path) => { + let stability = self.stability(attrs)?; let (item, name, span) = self.resolve_ast_item_path(path)?; let id = self.extract_iface_from_item(&item, &name, span)?; - Ok(WorldItem::Interface(id)) + Ok(WorldItem::Interface { id, stability }) } ast::ExternKind::Func(name, func) => { - let func = - self.resolve_function(docs, name.name, func, FunctionKind::Freestanding)?; + let func = self.resolve_function( + docs, + attrs, + name.name, + func, + FunctionKind::Freestanding, + )?; Ok(WorldItem::Function(func)) } } @@ -732,9 +756,12 @@ impl<'a> Resolver<'a> { interface_id: InterfaceId, fields: &[ast::InterfaceItem<'a>], docs: &ast::Docs<'a>, + attrs: &[ast::Attribute<'a>], ) -> Result<()> { let docs = self.docs(docs); self.interfaces[interface_id].docs = docs; + let stability = self.stability(attrs)?; + self.interfaces[interface_id].stability = stability; self.resolve_types( TypeOwner::Interface(interface_id), @@ -765,6 +792,7 @@ impl<'a> Resolver<'a> { self.define_interface_name(&f.name, TypeOrItem::Item("function"))?; funcs.push(self.resolve_function( &f.docs, + &f.attributes, &f.name.name, &f.func, FunctionKind::Freestanding, @@ -833,11 +861,10 @@ impl<'a> Resolver<'a> { TypeItem::Def(t) => { let prev = type_defs.insert(t.name.name, Some(t)); if prev.is_some() { - return Err(Error { - span: t.name.span, - msg: format!("name `{}` is defined more than once", t.name.name), - } - .into()); + bail!(Error::new( + t.name.span, + format!("name `{}` is defined more than once", t.name.name), + )) } let mut deps = Vec::new(); collect_deps(&t.ty, &mut deps); @@ -859,13 +886,16 @@ impl<'a> Resolver<'a> { None => continue, }; let docs = self.docs(&def.docs); - let kind = self.resolve_type_def(&def.ty)?; + let stability = self.stability(&def.attributes)?; + let kind = self.resolve_type_def(&def.ty, &stability)?; let id = self.types.alloc(TypeDef { docs, + stability, kind, name: Some(def.name.name.to_string()), owner, }); + self.type_spans.push(def.name.span); self.define_interface_name(&def.name, TypeOrItem::Type(id))?; } Ok(()) @@ -874,25 +904,28 @@ impl<'a> Resolver<'a> { fn resolve_use(&mut self, owner: TypeOwner, u: &ast::Use<'a>) -> Result<()> { let (item, name, span) = self.resolve_ast_item_path(&u.from)?; let use_from = self.extract_iface_from_item(&item, &name, span)?; + let stability = self.stability(&u.attributes)?; for name in u.names.iter() { let lookup = &self.interface_types[use_from.index()]; let id = match lookup.get(name.name.name) { Some((TypeOrItem::Type(id), _)) => *id, Some((TypeOrItem::Item(s), _)) => { - bail!(Error { - span: name.name.span, - msg: format!("cannot import {s} `{}`", name.name.name), - }) + bail!(Error::new( + name.name.span, + format!("cannot import {s} `{}`", name.name.name), + )) } - None => bail!(Error { - span: name.name.span, - msg: format!("name `{}` is not defined", name.name.name), - }), + None => bail!(Error::new( + name.name.span, + format!("name `{}` is not defined", name.name.name), + )), }; + self.type_spans.push(name.name.span); let name = name.as_.as_ref().unwrap_or(&name.name); let id = self.types.alloc(TypeDef { docs: Docs::default(), + stability: stability.clone(), kind: TypeDefKind::Type(Type::Id(id)), name: Some(name.name.to_string()), owner, @@ -904,9 +937,12 @@ impl<'a> Resolver<'a> { /// For each name in the `include`, resolve the path of the include, add it to the self.includes fn resolve_include(&mut self, world_id: WorldId, i: &ast::Include<'a>) -> Result<()> { + let stability = self.stability(&i.attributes)?; let (item, name, span) = self.resolve_ast_item_path(&i.from)?; let include_from = self.extract_world_from_item(&item, &name, span)?; - self.worlds[world_id].includes.push(include_from); + self.worlds[world_id] + .includes + .push((stability, include_from)); self.worlds[world_id].include_names.push( i.names .iter() @@ -945,21 +981,30 @@ impl<'a> Resolver<'a> { } } let named_func = func.named_func(); - self.resolve_function(&named_func.docs, &name, &named_func.func, kind) + self.resolve_function( + &named_func.docs, + &named_func.attributes, + &name, + &named_func.func, + kind, + ) } fn resolve_function( &mut self, docs: &ast::Docs<'_>, + attrs: &[ast::Attribute<'_>], name: &str, func: &ast::Func, kind: FunctionKind, ) -> Result { let docs = self.docs(docs); - let params = self.resolve_params(&func.params, &kind)?; - let results = self.resolve_results(&func.results, &kind)?; + let stability = self.stability(attrs)?; + let params = self.resolve_params(&func.params, &kind, func.span, &stability)?; + let results = self.resolve_results(&func.results, &kind, func.span, &stability)?; Ok(Function { docs, + stability, name: name.to_string(), kind, params, @@ -976,10 +1021,10 @@ impl<'a> Resolver<'a> { match item { Some(item) => Ok((*item, id.name.into(), id.span)), None => { - bail!(Error { - span: id.span, - msg: format!("interface or world `{}` does not exist", id.name), - }) + bail!(Error::new( + id.span, + format!("interface or world `{}` does not exist", id.name), + )) } } } @@ -1000,10 +1045,10 @@ impl<'a> Resolver<'a> { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { - bail!(Error { - span: span, - msg: format!("name `{}` is defined as a world, not an interface", name), - }) + bail!(Error::new( + span, + format!("name `{}` is defined as a world, not an interface", name), + )) } } } @@ -1012,10 +1057,10 @@ impl<'a> Resolver<'a> { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { - bail!(Error { - span: span, - msg: format!("name `{}` is defined as an interface, not a world", name), - }) + bail!(Error::new( + span, + format!("name `{}` is defined as an interface, not a world", name), + )) } } } @@ -1023,37 +1068,40 @@ impl<'a> Resolver<'a> { fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> Result<()> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { - Err(Error { - span: name.span, - msg: format!("name `{}` is defined more than once", name.name), - } - .into()) + bail!(Error::new( + name.span, + format!("name `{}` is defined more than once", name.name), + )) } else { Ok(()) } } - fn resolve_type_def(&mut self, ty: &ast::Type<'_>) -> Result { + fn resolve_type_def( + &mut self, + ty: &ast::Type<'_>, + stability: &Stability, + ) -> Result { Ok(match ty { - ast::Type::Bool => TypeDefKind::Type(Type::Bool), - ast::Type::U8 => TypeDefKind::Type(Type::U8), - ast::Type::U16 => TypeDefKind::Type(Type::U16), - ast::Type::U32 => TypeDefKind::Type(Type::U32), - ast::Type::U64 => TypeDefKind::Type(Type::U64), - ast::Type::S8 => TypeDefKind::Type(Type::S8), - ast::Type::S16 => TypeDefKind::Type(Type::S16), - ast::Type::S32 => TypeDefKind::Type(Type::S32), - ast::Type::S64 => TypeDefKind::Type(Type::S64), - ast::Type::F32 => TypeDefKind::Type(Type::F32), - ast::Type::F64 => TypeDefKind::Type(Type::F64), - ast::Type::Char => TypeDefKind::Type(Type::Char), - ast::Type::String => TypeDefKind::Type(Type::String), + ast::Type::Bool(_) => TypeDefKind::Type(Type::Bool), + ast::Type::U8(_) => TypeDefKind::Type(Type::U8), + ast::Type::U16(_) => TypeDefKind::Type(Type::U16), + ast::Type::U32(_) => TypeDefKind::Type(Type::U32), + ast::Type::U64(_) => TypeDefKind::Type(Type::U64), + ast::Type::S8(_) => TypeDefKind::Type(Type::S8), + ast::Type::S16(_) => TypeDefKind::Type(Type::S16), + ast::Type::S32(_) => TypeDefKind::Type(Type::S32), + ast::Type::S64(_) => TypeDefKind::Type(Type::S64), + ast::Type::F32(_) => TypeDefKind::Type(Type::F32), + ast::Type::F64(_) => TypeDefKind::Type(Type::F64), + ast::Type::Char(_) => TypeDefKind::Type(Type::Char), + ast::Type::String(_) => TypeDefKind::Type(Type::String), ast::Type::Name(name) => { let id = self.resolve_type_name(name)?; TypeDefKind::Type(Type::Id(id)) } ast::Type::List(list) => { - let ty = self.resolve_type(list)?; + let ty = self.resolve_type(&list.ty, stability)?; TypeDefKind::List(ty) } ast::Type::Handle(handle) => TypeDefKind::Handle(match handle { @@ -1071,19 +1119,16 @@ impl<'a> Resolver<'a> { match func { ast::ResourceFunc::Method(f) | ast::ResourceFunc::Static(f) => { if !names.insert(&f.name.name) { - bail!(Error { - span: f.name.span, - msg: format!("duplicate function name `{}`", f.name.name), - }) + bail!(Error::new( + f.name.span, + format!("duplicate function name `{}`", f.name.name), + )) } } ast::ResourceFunc::Constructor(f) => { ctors += 1; if ctors > 1 { - bail!(Error { - span: f.name.span, - msg: format!("duplicate constructors"), - }) + bail!(Error::new(f.name.span, "duplicate constructors")) } } } @@ -1099,7 +1144,7 @@ impl<'a> Resolver<'a> { Ok(Field { docs: self.docs(&field.docs), name: field.name.name.to_string(), - ty: self.resolve_type(&field.ty)?, + ty: self.resolve_type(&field.ty, stability)?, }) }) .collect::>>()?; @@ -1116,20 +1161,17 @@ impl<'a> Resolver<'a> { .collect::>(); TypeDefKind::Flags(Flags { flags }) } - ast::Type::Tuple(types) => { - let types = types + ast::Type::Tuple(t) => { + let types = t + .types .iter() - .map(|ty| self.resolve_type(ty)) + .map(|ty| self.resolve_type(ty, stability)) .collect::>>()?; TypeDefKind::Tuple(Tuple { types }) } ast::Type::Variant(variant) => { if variant.cases.is_empty() { - return Err(Error { - span: variant.span, - msg: "empty variant".to_string(), - } - .into()); + bail!(Error::new(variant.span, "empty variant")) } let cases = variant .cases @@ -1138,7 +1180,7 @@ impl<'a> Resolver<'a> { Ok(Case { docs: self.docs(&case.docs), name: case.name.name.to_string(), - ty: self.resolve_optional_type(case.ty.as_ref())?, + ty: self.resolve_optional_type(case.ty.as_ref(), stability)?, }) }) .collect::>>()?; @@ -1146,11 +1188,7 @@ impl<'a> Resolver<'a> { } ast::Type::Enum(e) => { if e.cases.is_empty() { - return Err(Error { - span: e.span, - msg: "empty enum".to_string(), - } - .into()); + bail!(Error::new(e.span, "empty enum")) } let cases = e .cases @@ -1164,15 +1202,17 @@ impl<'a> Resolver<'a> { .collect::>>()?; TypeDefKind::Enum(Enum { cases }) } - ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), + ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(&ty.ty, stability)?), ast::Type::Result(r) => TypeDefKind::Result(Result_ { - ok: self.resolve_optional_type(r.ok.as_deref())?, - err: self.resolve_optional_type(r.err.as_deref())?, + ok: self.resolve_optional_type(r.ok.as_deref(), stability)?, + err: self.resolve_optional_type(r.err.as_deref(), stability)?, }), - ast::Type::Future(t) => TypeDefKind::Future(self.resolve_optional_type(t.as_deref())?), + ast::Type::Future(t) => { + TypeDefKind::Future(self.resolve_optional_type(t.ty.as_deref(), stability)?) + } ast::Type::Stream(s) => TypeDefKind::Stream(Stream { - element: self.resolve_optional_type(s.element.as_deref())?, - end: self.resolve_optional_type(s.end.as_deref())?, + element: self.resolve_optional_type(s.element.as_deref(), stability)?, + end: self.resolve_optional_type(s.end.as_deref(), stability)?, }), }) } @@ -1180,14 +1220,14 @@ impl<'a> Resolver<'a> { fn resolve_type_name(&mut self, name: &ast::Id<'_>) -> Result { match self.type_lookup.get(name.name) { Some((TypeOrItem::Type(id), _)) => Ok(*id), - Some((TypeOrItem::Item(s), _)) => bail!(Error { - span: name.span, - msg: format!("cannot use {s} `{name}` as a type", name = name.name), - }), - None => bail!(Error { - span: name.span, - msg: format!("name `{name}` is not defined", name = name.name), - }), + Some((TypeOrItem::Item(s), _)) => bail!(Error::new( + name.span, + format!("cannot use {s} `{name}` as a type", name = name.name), + )), + None => bail!(Error::new( + name.span, + format!("name `{name}` is not defined", name = name.name), + )), } } @@ -1202,15 +1242,15 @@ impl<'a> Resolver<'a> { self.required_resource_types.push((cur, name.span)); break Ok(id); } - _ => bail!(Error { - span: name.span, - msg: format!("type `{}` used in a handle must be a resource", name.name), - }), + _ => bail!(Error::new( + name.span, + format!("type `{}` used in a handle must be a resource", name.name), + )), } } } - fn resolve_type(&mut self, ty: &super::Type<'_>) -> Result { + fn resolve_type(&mut self, ty: &super::Type<'_>, stability: &Stability) -> Result { // Resources must be declared at the top level to have their methods // processed appropriately, but resources also shouldn't show up // recursively so assert that's not happening here. @@ -1218,23 +1258,31 @@ impl<'a> Resolver<'a> { ast::Type::Resource(_) => unreachable!(), _ => {} } - let kind = self.resolve_type_def(ty)?; - Ok(self.anon_type_def(TypeDef { - kind, - name: None, - docs: Docs::default(), - owner: TypeOwner::None, - })) + let kind = self.resolve_type_def(ty, stability)?; + Ok(self.anon_type_def( + TypeDef { + kind, + name: None, + docs: Docs::default(), + stability: stability.clone(), + owner: TypeOwner::None, + }, + ty.span(), + )) } - fn resolve_optional_type(&mut self, ty: Option<&super::Type<'_>>) -> Result> { + fn resolve_optional_type( + &mut self, + ty: Option<&super::Type<'_>>, + stability: &Stability, + ) -> Result> { match ty { - Some(ty) => Ok(Some(self.resolve_type(ty)?)), + Some(ty) => Ok(Some(self.resolve_type(ty, stability)?)), None => Ok(None), } } - fn anon_type_def(&mut self, ty: TypeDef) -> Type { + fn anon_type_def(&mut self, ty: TypeDef, span: Span) -> Type { let key = match &ty.kind { TypeDefKind::Type(t) => return *t, TypeDefKind::Variant(v) => Key::Variant( @@ -1270,11 +1318,10 @@ impl<'a> Resolver<'a> { TypeDefKind::Stream(s) => Key::Stream(s.element, s.end), TypeDefKind::Unknown => unreachable!(), }; - let types = &mut self.types; - let id = self - .anon_types - .entry(key) - .or_insert_with(|| types.alloc(ty)); + let id = self.anon_types.entry(key).or_insert_with(|| { + self.type_spans.push(span); + self.types.alloc(ty) + }); Type::Id(*id) } @@ -1311,7 +1358,34 @@ impl<'a> Resolver<'a> { Docs { contents } } - fn resolve_params(&mut self, params: &ParamList<'_>, kind: &FunctionKind) -> Result { + fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result { + match attrs { + [] => Ok(Stability::Unknown), + [ast::Attribute::Since { + version, feature, .. + }] => Ok(Stability::Stable { + since: version.clone(), + feature: feature.as_ref().map(|s| s.name.to_string()), + }), + [ast::Attribute::Unstable { feature, .. }] => Ok(Stability::Unstable { + feature: feature.name.to_string(), + }), + [_, b, ..] => { + bail!(Error::new( + b.span(), + "only one stability attribute is allowed per-item", + )) + } + } + } + + fn resolve_params( + &mut self, + params: &ParamList<'_>, + kind: &FunctionKind, + span: Span, + stability: &Stability, + ) -> Result { let mut ret = IndexMap::new(); match *kind { // These kinds of methods don't have any adjustments to the @@ -1322,23 +1396,26 @@ impl<'a> Resolver<'a> { // Methods automatically get a `self` initial argument so insert // that here before processing the normal parameters. FunctionKind::Method(id) => { - let shared = self.anon_type_def(TypeDef { - docs: Docs::default(), - kind: TypeDefKind::Handle(Handle::Borrow(id)), - name: None, - owner: TypeOwner::None, - }); + let shared = self.anon_type_def( + TypeDef { + docs: Docs::default(), + stability: stability.clone(), + kind: TypeDefKind::Handle(Handle::Borrow(id)), + name: None, + owner: TypeOwner::None, + }, + span, + ); ret.insert("self".to_string(), shared); } } for (name, ty) in params { - let prev = ret.insert(name.name.to_string(), self.resolve_type(ty)?); + let prev = ret.insert(name.name.to_string(), self.resolve_type(ty, stability)?); if prev.is_some() { - return Err(Error { - span: name.span, - msg: format!("param `{}` is defined more than once", name.name), - } - .into()); + bail!(Error::new( + name.span, + format!("param `{}` is defined more than once", name.name), + )) } } Ok(ret.into_iter().collect()) @@ -1348,16 +1425,21 @@ impl<'a> Resolver<'a> { &mut self, results: &ResultList<'_>, kind: &FunctionKind, + span: Span, + stability: &Stability, ) -> Result { match *kind { // These kinds of methods don't have any adjustments to the return // values, so plumb them through as-is. FunctionKind::Freestanding | FunctionKind::Method(_) | FunctionKind::Static(_) => { match results { - ResultList::Named(rs) => Ok(Results::Named( - self.resolve_params(rs, &FunctionKind::Freestanding)?, - )), - ResultList::Anon(ty) => Ok(Results::Anon(self.resolve_type(ty)?)), + ResultList::Named(rs) => Ok(Results::Named(self.resolve_params( + rs, + &FunctionKind::Freestanding, + span, + stability, + )?)), + ResultList::Anon(ty) => Ok(Results::Anon(self.resolve_type(ty, stability)?)), } } @@ -1377,23 +1459,23 @@ impl<'a> Resolver<'a> { fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { match ty { - ast::Type::Bool - | ast::Type::U8 - | ast::Type::U16 - | ast::Type::U32 - | ast::Type::U64 - | ast::Type::S8 - | ast::Type::S16 - | ast::Type::S32 - | ast::Type::S64 - | ast::Type::F32 - | ast::Type::F64 - | ast::Type::Char - | ast::Type::String + ast::Type::Bool(_) + | ast::Type::U8(_) + | ast::Type::U16(_) + | ast::Type::U32(_) + | ast::Type::U64(_) + | ast::Type::S8(_) + | ast::Type::S16(_) + | ast::Type::S32(_) + | ast::Type::S64(_) + | ast::Type::F32(_) + | ast::Type::F64(_) + | ast::Type::Char(_) + | ast::Type::String(_) | ast::Type::Flags(_) | ast::Type::Enum(_) => {} ast::Type::Name(name) => deps.push(name.clone()), - ast::Type::List(list) => collect_deps(list, deps), + ast::Type::List(list) => collect_deps(&list.ty, deps), ast::Type::Handle(handle) => match handle { ast::Handle::Own { resource } => deps.push(resource.clone()), ast::Handle::Borrow { resource } => deps.push(resource.clone()), @@ -1404,8 +1486,8 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { collect_deps(&field.ty, deps); } } - ast::Type::Tuple(types) => { - for ty in types { + ast::Type::Tuple(t) => { + for ty in t.types.iter() { collect_deps(ty, deps); } } @@ -1416,7 +1498,7 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { } } } - ast::Type::Option(ty) => collect_deps(ty, deps), + ast::Type::Option(ty) => collect_deps(&ty.ty, deps), ast::Type::Result(r) => { if let Some(ty) = &r.ok { collect_deps(ty, deps); @@ -1426,7 +1508,7 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { } } ast::Type::Future(t) => { - if let Some(t) = t { + if let Some(t) = &t.ty { collect_deps(t, deps) } } diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 8c3c9e01cf..848aa6ea66 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -19,8 +19,8 @@ struct ComponentInfo { types: types::Types, /// List of all imports and exports from this component. externs: Vec<(String, Extern)>, - /// Decoded package docs - package_docs: Option, + /// Decoded package metadata + package_metadata: Option, } struct DecodingExport { @@ -48,7 +48,7 @@ impl ComponentInfo { let mut externs = Vec::new(); let mut depth = 1; let mut types = None; - let mut _package_docs = None; + let mut _package_metadata = None; let mut cur = Parser::new(0); let mut eof = false; let mut stack = Vec::new(); @@ -111,11 +111,11 @@ impl ComponentInfo { } } #[cfg(feature = "serde")] - Payload::CustomSection(s) if s.name() == PackageDocs::SECTION_NAME => { - if _package_docs.is_some() { - bail!("multiple {:?} sections", PackageDocs::SECTION_NAME); + Payload::CustomSection(s) if s.name() == PackageMetadata::SECTION_NAME => { + if _package_metadata.is_some() { + bail!("multiple {:?} sections", PackageMetadata::SECTION_NAME); } - _package_docs = Some(PackageDocs::decode(s.data())?); + _package_metadata = Some(PackageMetadata::decode(s.data())?); } Payload::ModuleSection { parser, .. } | Payload::ComponentSection { parser, .. } => { @@ -140,7 +140,7 @@ impl ComponentInfo { Ok(Self { types: types.unwrap(), externs, - package_docs: _package_docs, + package_metadata: _package_metadata, }) } @@ -204,8 +204,8 @@ impl ComponentInfo { let pkg = pkg.ok_or_else(|| anyhow!("no exported component type found"))?; let (mut resolve, package) = decoder.finish(pkg); - if let Some(package_docs) = &self.package_docs { - package_docs.inject(&mut resolve, package)?; + if let Some(package_metadata) = &self.package_metadata { + package_metadata.inject(&mut resolve, package)?; } Ok((resolve, package)) } @@ -284,8 +284,8 @@ impl ComponentInfo { }; let (mut resolve, package) = decoder.finish(pkg); - if let Some(package_docs) = &self.package_docs { - package_docs.inject(&mut resolve, package)?; + if let Some(package_metadata) = &self.package_metadata { + package_metadata.inject(&mut resolve, package)?; } Ok((resolve, package)) } @@ -305,6 +305,7 @@ impl ComponentInfo { package: None, includes: Default::default(), include_names: Default::default(), + stability: Default::default(), }); let mut package = Package { // Similar to `world_name` above this is arbitrarily chosen as it's @@ -651,7 +652,13 @@ impl WitPackageDecoder<'_> { self.register_interface(name, ty, package) .with_context(|| format!("failed to decode WIT from import `{name}`"))? }; - (name, WorldItem::Interface(id)) + ( + name, + WorldItem::Interface { + id, + stability: Default::default(), + }, + ) } types::ComponentEntityType::Func(i) => { let ty = &self.types[i]; @@ -704,7 +711,13 @@ impl WitPackageDecoder<'_> { self.register_interface(name, ty, package) .with_context(|| format!("failed to decode WIT from export `{name}`"))? }; - (name, WorldItem::Interface(id)) + ( + name, + WorldItem::Interface { + id, + stability: Default::default(), + }, + ) } _ => { bail!("component export `{name}` was not a function or instance") @@ -884,6 +897,7 @@ impl WitPackageDecoder<'_> { types: IndexMap::default(), functions: IndexMap::new(), package: None, + stability: Default::default(), }) }); @@ -938,6 +952,7 @@ impl WitPackageDecoder<'_> { types: IndexMap::default(), functions: IndexMap::new(), package: None, + stability: Default::default(), }; let owner = TypeOwner::Interface(self.resolve.interfaces.next_id()); @@ -1032,6 +1047,7 @@ impl WitPackageDecoder<'_> { name: Some(name.to_string()), kind, docs: Default::default(), + stability: Default::default(), owner, }); @@ -1069,6 +1085,7 @@ impl WitPackageDecoder<'_> { includes: Default::default(), include_names: Default::default(), package: None, + stability: Default::default(), }; let owner = TypeOwner::World(self.resolve.worlds.next_id()); @@ -1089,7 +1106,13 @@ impl WitPackageDecoder<'_> { // with no name. self.register_interface(name, ty, package)? }; - (name, WorldItem::Interface(id)) + ( + name, + WorldItem::Interface { + id, + stability: Default::default(), + }, + ) } types::ComponentEntityType::Type { created, @@ -1125,7 +1148,13 @@ impl WitPackageDecoder<'_> { } else { self.register_interface(name, ty, package)? }; - (name, WorldItem::Interface(id)) + ( + name, + WorldItem::Interface { + id, + stability: Default::default(), + }, + ) } types::ComponentEntityType::Func(idx) => { @@ -1178,6 +1207,7 @@ impl WitPackageDecoder<'_> { }; Ok(Function { docs: Default::default(), + stability: Default::default(), kind: match name.kind() { ComponentNameKind::Label(_) => FunctionKind::Freestanding, ComponentNameKind::Constructor(resource) => { @@ -1247,6 +1277,7 @@ impl WitPackageDecoder<'_> { let ty = self.resolve.types.alloc(TypeDef { name: None, docs: Default::default(), + stability: Default::default(), owner: TypeOwner::None, kind, }); @@ -1480,8 +1511,8 @@ impl WitPackageDecoder<'_> { world.package = Some(pkg); for (name, item) in world.imports.iter().chain(world.exports.iter()) { if let WorldKey::Name(_) = name { - if let WorldItem::Interface(iface) = item { - self.resolve.interfaces[*iface].package = Some(pkg); + if let WorldItem::Interface { id, .. } = item { + self.resolve.interfaces[*id].package = Some(pkg); } } } @@ -1504,7 +1535,7 @@ impl WitPackageDecoder<'_> { world.imports.values().chain(world.exports.values()) }) .filter_map(|item| match item { - WorldItem::Interface(id) => Some(*id), + WorldItem::Interface { id, .. } => Some(*id), WorldItem::Function(_) | WorldItem::Type(_) => None, }), ); diff --git a/crates/wit-parser/src/docs.rs b/crates/wit-parser/src/docs.rs deleted file mode 100644 index a21ba185be..0000000000 --- a/crates/wit-parser/src/docs.rs +++ /dev/null @@ -1,389 +0,0 @@ -use crate::{ - Docs, InterfaceId, PackageId, Resolve, TypeDefKind, TypeId, WorldId, WorldItem, WorldKey, -}; -use anyhow::{bail, Result}; -use indexmap::IndexMap; -#[cfg(feature = "serde")] -use serde_derive::{Deserialize, Serialize}; - -type StringMap = IndexMap; - -#[cfg(feature = "serde")] -const PACKAGE_DOCS_SECTION_VERSION: u8 = 0; - -/// Represents serializable doc comments parsed from a WIT package. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -pub struct PackageDocs { - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "Option::is_none") - )] - docs: Option, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - worlds: StringMap, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - interfaces: StringMap, -} - -impl PackageDocs { - pub const SECTION_NAME: &'static str = "package-docs"; - - /// Extract package docs for the given package. - pub fn extract(resolve: &Resolve, package: PackageId) -> Self { - let package = &resolve.packages[package]; - - let worlds = package - .worlds - .iter() - .map(|(name, id)| (name.to_string(), WorldDocs::extract(resolve, *id))) - .filter(|(_, item)| !item.is_empty()) - .collect(); - let interfaces = package - .interfaces - .iter() - .map(|(name, id)| (name.to_string(), InterfaceDocs::extract(resolve, *id))) - .filter(|(_, item)| !item.is_empty()) - .collect(); - - Self { - docs: package.docs.contents.as_deref().map(Into::into), - worlds, - interfaces, - } - } - - /// Inject package docs for the given package. - /// - /// This will override any existing docs in the [`Resolve`]. - pub fn inject(&self, resolve: &mut Resolve, package: PackageId) -> Result<()> { - for (name, docs) in &self.worlds { - let Some(&id) = resolve.packages[package].worlds.get(name) else { - bail!("missing world {name:?}"); - }; - docs.inject(resolve, id)?; - } - for (name, docs) in &self.interfaces { - let Some(&id) = resolve.packages[package].interfaces.get(name) else { - bail!("missing interface {name:?}"); - }; - docs.inject(resolve, id)?; - } - if let Some(docs) = &self.docs { - resolve.packages[package].docs.contents = Some(docs.to_string()); - } - Ok(()) - } - - /// Encode package docs as a package-docs custom section. - #[cfg(feature = "serde")] - pub fn encode(&self) -> Result> { - // Version byte (0), followed by JSON encoding of docs - let mut data = vec![PACKAGE_DOCS_SECTION_VERSION]; - serde_json::to_writer(&mut data, self)?; - Ok(data) - } - - /// Decode package docs from package-docs custom section content. - #[cfg(feature = "serde")] - pub fn decode(data: &[u8]) -> Result { - let version = data.first(); - if version != Some(&PACKAGE_DOCS_SECTION_VERSION) { - bail!("expected package-docs version {PACKAGE_DOCS_SECTION_VERSION}, got {version:?}"); - } - Ok(serde_json::from_slice(&data[1..])?) - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -struct WorldDocs { - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "Option::is_none") - )] - docs: Option, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - interfaces: StringMap, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - types: StringMap, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - funcs: StringMap, -} - -impl WorldDocs { - fn extract(resolve: &Resolve, id: WorldId) -> Self { - let world = &resolve.worlds[id]; - - let mut interfaces = StringMap::default(); - let mut types = StringMap::default(); - let mut funcs = StringMap::default(); - - // Iterate over all imports and exports, extracting documented items - for (key, item) in world.imports.iter().chain(&world.exports) { - if let WorldKey::Name(name) = key { - match item { - WorldItem::Interface(id) => { - let docs = InterfaceDocs::extract(resolve, *id); - if !docs.is_empty() { - interfaces.insert(name.to_string(), docs); - } - } - WorldItem::Type(id) => { - let docs = TypeDocs::extract(resolve, *id); - if !docs.is_empty() { - types.insert(name.to_string(), docs); - } - } - WorldItem::Function(f) => { - if let Some(docs) = f.docs.contents.as_deref() { - funcs.insert(name.to_string(), docs.to_string()); - } - } - } - } - } - - Self { - docs: world.docs.contents.clone(), - interfaces, - types, - funcs, - } - } - - fn inject(&self, resolve: &mut Resolve, id: WorldId) -> Result<()> { - for (name, docs) in &self.interfaces { - let key = WorldKey::Name(name.to_string()); - let Some(WorldItem::Interface(id)) = resolve.worlds[id] - .imports - .get(&key) - .or_else(|| resolve.worlds[id].exports.get(&key)) - else { - bail!("missing interface {name:?}"); - }; - docs.inject(resolve, *id)?; - } - for (name, docs) in &self.types { - let key = WorldKey::Name(name.to_string()); - let Some(WorldItem::Type(id)) = resolve.worlds[id] - .imports - .get(&key) - .or_else(|| resolve.worlds[id].exports.get(&key)) - else { - bail!("missing type {name:?}"); - }; - docs.inject(resolve, *id)?; - } - let world = &mut resolve.worlds[id]; - for (name, docs) in &self.funcs { - let key = WorldKey::Name(name.to_string()); - if let Some(WorldItem::Function(f)) = world.exports.get_mut(&key) { - f.docs.contents = Some(docs.to_string()) - } else if let Some(WorldItem::Function(f)) = world.imports.get_mut(&key) { - f.docs.contents = Some(docs.to_string()) - } else { - bail!("missing func {name:?}"); - }; - } - if let Some(docs) = &self.docs { - world.docs.contents = Some(docs.to_string()); - } - Ok(()) - } - - fn is_empty(&self) -> bool { - self.docs.is_none() - && self.interfaces.is_empty() - && self.types.is_empty() - && self.funcs.is_empty() - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -struct InterfaceDocs { - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "Option::is_none") - )] - docs: Option, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - funcs: StringMap, - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - types: StringMap, -} - -impl InterfaceDocs { - fn extract(resolve: &Resolve, id: InterfaceId) -> Self { - let interface = &resolve.interfaces[id]; - - let funcs = interface - .functions - .iter() - .flat_map(|(name, func)| Some((name.to_string(), func.docs.contents.clone()?))) - .collect(); - let types = interface - .types - .iter() - .map(|(name, id)| (name.to_string(), TypeDocs::extract(resolve, *id))) - .filter(|(_, item)| !item.is_empty()) - .collect(); - - Self { - docs: interface.docs.contents.clone(), - funcs, - types, - } - } - - fn inject(&self, resolve: &mut Resolve, id: InterfaceId) -> Result<()> { - for (name, docs) in &self.types { - let Some(&id) = resolve.interfaces[id].types.get(name) else { - bail!("missing type {name:?}"); - }; - docs.inject(resolve, id)?; - } - let interface = &mut resolve.interfaces[id]; - for (name, docs) in &self.funcs { - let Some(f) = interface.functions.get_mut(name) else { - bail!("missing func {name:?}"); - }; - f.docs.contents = Some(docs.to_string()); - } - if let Some(docs) = &self.docs { - interface.docs.contents = Some(docs.to_string()); - } - Ok(()) - } - - fn is_empty(&self) -> bool { - self.docs.is_none() && self.funcs.is_empty() && self.types.is_empty() - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -struct TypeDocs { - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "Option::is_none") - )] - docs: Option, - // record fields, variant cases, etc. - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "StringMap::is_empty") - )] - items: StringMap, -} - -impl TypeDocs { - fn extract(resolve: &Resolve, id: TypeId) -> Self { - fn extract_items(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap { - items - .iter() - .flat_map(|item| { - let (name, docs) = f(item); - Some((name.to_string(), docs.contents.clone()?)) - }) - .collect() - } - let ty = &resolve.types[id]; - let items = match &ty.kind { - TypeDefKind::Record(record) => { - extract_items(&record.fields, |item| (&item.name, &item.docs)) - } - TypeDefKind::Flags(flags) => { - extract_items(&flags.flags, |item| (&item.name, &item.docs)) - } - TypeDefKind::Variant(variant) => { - extract_items(&variant.cases, |item| (&item.name, &item.docs)) - } - TypeDefKind::Enum(enum_) => { - extract_items(&enum_.cases, |item| (&item.name, &item.docs)) - } - // other types don't have inner items - _ => IndexMap::default(), - }; - - Self { - docs: ty.docs.contents.clone(), - items, - } - } - - fn inject(&self, resolve: &mut Resolve, id: TypeId) -> Result<()> { - let ty = &mut resolve.types[id]; - if !self.items.is_empty() { - match &mut ty.kind { - TypeDefKind::Record(record) => { - self.inject_items(&mut record.fields, |item| (&item.name, &mut item.docs))? - } - TypeDefKind::Flags(flags) => { - self.inject_items(&mut flags.flags, |item| (&item.name, &mut item.docs))? - } - TypeDefKind::Variant(variant) => { - self.inject_items(&mut variant.cases, |item| (&item.name, &mut item.docs))? - } - TypeDefKind::Enum(enum_) => { - self.inject_items(&mut enum_.cases, |item| (&item.name, &mut item.docs))? - } - _ => { - bail!("got 'items' for unexpected type {ty:?}"); - } - } - } - if let Some(docs) = &self.docs { - ty.docs.contents = Some(docs.to_string()); - } - Ok(()) - } - - fn inject_items( - &self, - items: &mut [T], - f: impl Fn(&mut T) -> (&String, &mut Docs), - ) -> Result<()> { - let mut unused_docs = self.items.len(); - for item in items.iter_mut() { - let (name, item_docs) = f(item); - if let Some(docs) = self.items.get(name.as_str()) { - item_docs.contents = Some(docs.to_string()); - unused_docs -= 1; - } - } - if unused_docs > 0 { - bail!( - "not all 'items' match type items; {item_docs:?} vs {items:?}", - item_docs = self.items - ); - } - Ok(()) - } - - fn is_empty(&self) -> bool { - self.docs.is_none() && self.items.is_empty() - } -} diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 25700fb382..a9f54bd13c 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -9,9 +9,9 @@ use std::path::Path; #[cfg(feature = "decoding")] pub mod decoding; #[cfg(feature = "decoding")] -mod docs; +mod metadata; #[cfg(feature = "decoding")] -pub use docs::PackageDocs; +pub use metadata::PackageMetadata; pub mod abi; mod ast; @@ -29,10 +29,7 @@ use serde_derive::Serialize; #[cfg(feature = "serde")] mod serde_; #[cfg(feature = "serde")] -use serde_::{ - serialize_anon_result, serialize_id, serialize_id_map, serialize_none, serialize_optional_id, - serialize_params, -}; +use serde_::*; /// Checks if the given string is a legal identifier in wit. pub fn validate_id(s: &str) -> Result<()> { @@ -111,6 +108,7 @@ pub struct UnresolvedPackage { unknown_type_spans: Vec, interface_spans: Vec, world_spans: Vec, + type_spans: Vec, foreign_dep_spans: Vec, source_map: SourceMap, required_resource_types: Vec<(TypeId, Span)>, @@ -190,11 +188,22 @@ impl fmt::Display for PackageName { struct Error { span: Span, msg: String, + highlighted: Option, +} + +impl Error { + fn new(span: Span, msg: impl Into) -> Error { + Error { + span, + msg: msg.into(), + highlighted: None, + } + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.msg.fmt(f) + self.highlighted.as_ref().unwrap_or(&self.msg).fmt(f) } } @@ -292,9 +301,16 @@ pub struct World { #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Stability annotation for this world itself. + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + pub stability: Stability, + /// All the included worlds from this world. Empty if this is fully resolved #[cfg_attr(feature = "serde", serde(skip))] - pub includes: Vec, + pub includes: Vec<(Stability, WorldId)>, /// All the included worlds names. Empty if this is fully resolved #[cfg_attr(feature = "serde", serde(skip))] @@ -348,8 +364,15 @@ impl WorldKey { pub enum WorldItem { /// An interface is being imported or exported from a world, indicating that /// it's a namespace of functions. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] - Interface(InterfaceId), + Interface { + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_id"))] + id: InterfaceId, + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + stability: Stability, + }, /// A function is being directly imported or exported from this world. Function(Function), @@ -361,6 +384,16 @@ pub enum WorldItem { Type(TypeId), } +impl WorldItem { + pub fn stability<'a>(&'a self, resolve: &'a Resolve) -> &'a Stability { + match self { + WorldItem::Interface { stability, .. } => stability, + WorldItem::Function(f) => &f.stability, + WorldItem::Type(id) => &resolve.types[*id].stability, + } + } +} + #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Interface { @@ -383,6 +416,13 @@ pub struct Interface { #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Stability attribute for this interface. + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + pub stability: Stability, + /// The package that owns this interface. #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_optional_id"))] pub package: Option, @@ -396,6 +436,12 @@ pub struct TypeDef { pub owner: TypeOwner, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Stability attribute for this type. + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + pub stability: Stability, } #[derive(Debug, Clone, PartialEq)] @@ -724,6 +770,12 @@ pub struct Function { pub results: Results, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Docs::is_empty"))] pub docs: Docs, + /// Stability attribute for this function. + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Stability::is_unknown") + )] + pub stability: Stability, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -759,6 +811,50 @@ impl Function { } } +/// Representation of the stability attributes associated with a world, +/// interface, function, or type. +/// +/// This is added for WebAssembly/component-model#332 where @since and @unstable +/// annotations were added to WIT. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase", tag = "type"))] +pub enum Stability { + /// `@since(version = 1.2.3)` + /// + /// This item is explicitly tagged with `@since` as stable since the + /// specified version. This may optionally have a feature listed as well. + Stable { + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_version"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_version"))] + since: Version, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + feature: Option, + }, + + /// `@unstable(feature = foo)` + /// + /// This item is explicitly tagged `@unstable`. A feature name is listed and + /// this item is excluded by default in `Resolve` unless explicitly enabled. + Unstable { feature: String }, + + /// This item does not have either `@since` or `@unstable`. + Unknown, +} + +impl Stability { + /// Returns whether this is `Stability::Unknown`. + pub fn is_unknown(&self) -> bool { + matches!(self, Stability::Unknown) + } +} + +impl Default for Stability { + fn default() -> Stability { + Stability::Unknown + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index ee9b78551d..1b6986c839 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -36,7 +36,7 @@ impl LiveTypes { pub fn add_world_item(&mut self, resolve: &Resolve, item: &WorldItem) { match item { - WorldItem::Interface(id) => self.add_interface(resolve, *id), + WorldItem::Interface { id, .. } => self.add_interface(resolve, *id), WorldItem::Function(f) => self.add_func(resolve, f), WorldItem::Type(t) => self.add_type_id(resolve, *t), } diff --git a/crates/wit-parser/src/metadata.rs b/crates/wit-parser/src/metadata.rs new file mode 100644 index 0000000000..103e3d03af --- /dev/null +++ b/crates/wit-parser/src/metadata.rs @@ -0,0 +1,772 @@ +//! Implementation of encoding/decoding package metadata (docs/stability) in a +//! custom section. +//! +//! This module contains the particulars for how this custom section is encoded +//! and decoded at this time. As of the time of this writing the component model +//! binary format does not have any means of storing documentation and/or item +//! stability inline with items themselves. These are important to preserve when +//! round-tripping WIT through the WebAssembly binary format, however, so this +//! module implements this with a custom section. +//! +//! The custom section, named `SECTION_NAME`, is stored within the component +//! that encodes a WIT package. This section is itself JSON-encoded with a small +//! version header to help forwards/backwards compatibility. The hope is that +//! one day this custom section will be obsoleted by extensions to the binary +//! format to store this information inline. + +use crate::{ + Docs, Function, InterfaceId, PackageId, Resolve, Stability, TypeDefKind, TypeId, WorldId, + WorldItem, WorldKey, +}; +use anyhow::{bail, Result}; +use indexmap::IndexMap; +#[cfg(feature = "serde")] +use serde_derive::{Deserialize, Serialize}; + +type StringMap = IndexMap; + +/// Current supported format of the custom section. +/// +/// This byte is a prefix byte intended to be a general version marker for the +/// entire custom section. This is bumped when backwards-incompatible changes +/// are made to prevent older implementations from loading newer versions. +/// +/// The history of this is: +/// +/// * [????/??/??] 0 - the original format added +/// * [2024/04/19] 1 - extensions were added for item stability and +/// additionally having world imports/exports have the same name. +#[cfg(feature = "serde")] +const PACKAGE_DOCS_SECTION_VERSION: u8 = 1; + +/// At this time the v1 format was just written. For compatibility with older +/// tools we'll still try to emit the v0 format by default, if the input is +/// compatible. This will be turned off in the future once enough published +/// versions support the v1 format. +const TRY_TO_EMIT_V0_BY_DEFAULT: bool = true; + +/// Represents serializable doc comments parsed from a WIT package. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +pub struct PackageMetadata { + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + docs: Option, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + worlds: StringMap, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + interfaces: StringMap, +} + +impl PackageMetadata { + pub const SECTION_NAME: &'static str = "package-docs"; + + /// Extract package docs for the given package. + pub fn extract(resolve: &Resolve, package: PackageId) -> Self { + let package = &resolve.packages[package]; + + let worlds = package + .worlds + .iter() + .map(|(name, id)| (name.to_string(), WorldMetadata::extract(resolve, *id))) + .filter(|(_, item)| !item.is_empty()) + .collect(); + let interfaces = package + .interfaces + .iter() + .map(|(name, id)| (name.to_string(), InterfaceMetadata::extract(resolve, *id))) + .filter(|(_, item)| !item.is_empty()) + .collect(); + + Self { + docs: package.docs.contents.as_deref().map(Into::into), + worlds, + interfaces, + } + } + + /// Inject package docs for the given package. + /// + /// This will override any existing docs in the [`Resolve`]. + pub fn inject(&self, resolve: &mut Resolve, package: PackageId) -> Result<()> { + for (name, docs) in &self.worlds { + let Some(&id) = resolve.packages[package].worlds.get(name) else { + bail!("missing world {name:?}"); + }; + docs.inject(resolve, id)?; + } + for (name, docs) in &self.interfaces { + let Some(&id) = resolve.packages[package].interfaces.get(name) else { + bail!("missing interface {name:?}"); + }; + docs.inject(resolve, id)?; + } + if let Some(docs) = &self.docs { + resolve.packages[package].docs.contents = Some(docs.to_string()); + } + Ok(()) + } + + /// Encode package docs as a package-docs custom section. + #[cfg(feature = "serde")] + pub fn encode(&self) -> Result> { + // Version byte, followed by JSON encoding of docs. + // + // Note that if this document is compatible with the v0 format then + // that's preferred to keep older tools working at this time. + // Eventually this branch will be removed and v1 will unconditionally + // be used. + let mut data = vec![ + if TRY_TO_EMIT_V0_BY_DEFAULT && self.is_compatible_with_v0() { + 0 + } else { + PACKAGE_DOCS_SECTION_VERSION + }, + ]; + serde_json::to_writer(&mut data, self)?; + Ok(data) + } + + /// Decode package docs from package-docs custom section content. + #[cfg(feature = "serde")] + pub fn decode(data: &[u8]) -> Result { + match data.first().copied() { + // Our serde structures transparently support v0 and the current + // version, so allow either here. + Some(0) | Some(PACKAGE_DOCS_SECTION_VERSION) => {} + version => { + bail!( + "expected package-docs version {PACKAGE_DOCS_SECTION_VERSION}, got {version:?}" + ); + } + } + Ok(serde_json::from_slice(&data[1..])?) + } + + #[cfg(feature = "serde")] + fn is_compatible_with_v0(&self) -> bool { + self.worlds.iter().all(|(_, w)| w.is_compatible_with_v0()) + && self + .interfaces + .iter() + .all(|(_, w)| w.is_compatible_with_v0()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +struct WorldMetadata { + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + docs: Option, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Stability::is_unknown") + )] + stability: Stability, + + /// Metadata for named interface, e.g.: + /// + /// ```wit + /// world foo { + /// import x: interface {} + /// } + /// ``` + /// + /// In the v0 format this was called "interfaces", hence the + /// `serde(rename)`. When support was originally added here imports/exports + /// could not overlap in their name, but now they can. This map has thus + /// been repurposed as: + /// + /// * If an interface is imported, it goes here. + /// * If an interface is exported, and no interface was imported with the + /// same name, it goes here. + /// + /// Otherwise exports go inside the `interface_exports` map. + /// + /// In the future when v0 support is dropped this should become only + /// imports, not either imports-or-exports. + #[cfg_attr( + feature = "serde", + serde( + default, + rename = "interfaces", + skip_serializing_if = "StringMap::is_empty" + ) + )] + interface_imports_or_exports: StringMap, + + /// All types in this interface. + /// + /// Note that at this time types are only imported, never exported. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + types: StringMap, + + /// Same as `interface_imports_or_exports`, but for functions. + #[cfg_attr( + feature = "serde", + serde(default, rename = "funcs", skip_serializing_if = "StringMap::is_empty") + )] + func_imports_or_exports: StringMap, + + /// The "export half" of `interface_imports_or_exports`. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + interface_exports: StringMap, + + /// The "export half" of `func_imports_or_exports`. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + func_exports: StringMap, + + /// Stability annotations for interface imports that aren't inline, for + /// example: + /// + /// ```wit + /// world foo { + /// @since(version = 1.0.0) + /// import an-interface; + /// } + /// ``` + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + interface_import_stability: StringMap, + + /// Same as `interface_import_stability`, but for exports. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + interface_export_stability: StringMap, +} + +impl WorldMetadata { + fn extract(resolve: &Resolve, id: WorldId) -> Self { + let world = &resolve.worlds[id]; + + let mut interface_imports_or_exports = StringMap::default(); + let mut types = StringMap::default(); + let mut func_imports_or_exports = StringMap::default(); + let mut interface_exports = StringMap::default(); + let mut func_exports = StringMap::default(); + let mut interface_import_stability = StringMap::default(); + let mut interface_export_stability = StringMap::default(); + + for ((key, item), import) in world + .imports + .iter() + .map(|p| (p, true)) + .chain(world.exports.iter().map(|p| (p, false))) + { + match key { + // For all named imports with kebab-names extract their + // docs/stability and insert it into one of our maps. + WorldKey::Name(name) => match item { + WorldItem::Interface { id, .. } => { + let data = InterfaceMetadata::extract(resolve, *id); + if data.is_empty() { + continue; + } + let map = if import { + &mut interface_imports_or_exports + } else if !TRY_TO_EMIT_V0_BY_DEFAULT + || interface_imports_or_exports.contains_key(name) + { + &mut interface_exports + } else { + &mut interface_imports_or_exports + }; + let prev = map.insert(name.to_string(), data); + assert!(prev.is_none()); + } + WorldItem::Type(id) => { + let data = TypeMetadata::extract(resolve, *id); + if !data.is_empty() { + types.insert(name.to_string(), data); + } + } + WorldItem::Function(f) => { + let data = FunctionMetadata::extract(f); + if data.is_empty() { + continue; + } + let map = if import { + &mut func_imports_or_exports + } else if !TRY_TO_EMIT_V0_BY_DEFAULT + || func_imports_or_exports.contains_key(name) + { + &mut func_exports + } else { + &mut func_imports_or_exports + }; + let prev = map.insert(name.to_string(), data); + assert!(prev.is_none()); + } + }, + + // For interface imports/exports extract the stability and + // record it if necessary. + WorldKey::Interface(_) => { + let stability = match item { + WorldItem::Interface { stability, .. } => stability, + _ => continue, + }; + if stability.is_unknown() { + continue; + } + + let map = if import { + &mut interface_import_stability + } else { + &mut interface_export_stability + }; + let name = resolve.name_world_key(key); + map.insert(name, stability.clone()); + } + } + } + + Self { + docs: world.docs.contents.clone(), + stability: world.stability.clone(), + interface_imports_or_exports, + types, + func_imports_or_exports, + interface_exports, + func_exports, + interface_import_stability, + interface_export_stability, + } + } + + fn inject(&self, resolve: &mut Resolve, id: WorldId) -> Result<()> { + // Inject docs/stability for all kebab-named interfaces, both imports + // and exports. + for ((name, data), only_export) in self + .interface_imports_or_exports + .iter() + .map(|p| (p, false)) + .chain(self.interface_exports.iter().map(|p| (p, true))) + { + let key = WorldKey::Name(name.to_string()); + let world = &mut resolve.worlds[id]; + + let item = if only_export { + world.exports.get_mut(&key) + } else { + match world.imports.get_mut(&key) { + Some(item) => Some(item), + None => world.exports.get_mut(&key), + } + }; + let Some(WorldItem::Interface { id, stability }) = item else { + bail!("missing interface {name:?}"); + }; + *stability = data.stability.clone(); + let id = *id; + data.inject(resolve, id)?; + } + + // Process all types, which are always imported, for this world. + for (name, data) in &self.types { + let key = WorldKey::Name(name.to_string()); + let Some(WorldItem::Type(id)) = resolve.worlds[id].imports.get(&key) else { + bail!("missing type {name:?}"); + }; + data.inject(resolve, *id)?; + } + + // Build a map of `name_world_key` for interface imports/exports to the + // actual key. This map is then consluted in the next loop. + let world = &resolve.worlds[id]; + let stabilities = world + .imports + .iter() + .map(|i| (i, true)) + .chain(world.exports.iter().map(|i| (i, false))) + .filter_map(|((key, item), import)| match item { + WorldItem::Interface { .. } => { + Some(((resolve.name_world_key(key), import), key.clone())) + } + _ => None, + }) + .collect::>(); + + let world = &mut resolve.worlds[id]; + + // Update the stability of an interface imports/exports that aren't + // kebab-named. + for ((name, stability), import) in self + .interface_import_stability + .iter() + .map(|p| (p, true)) + .chain(self.interface_export_stability.iter().map(|p| (p, false))) + { + let key = match stabilities.get(&(name.clone(), import)) { + Some(key) => key.clone(), + None => bail!("missing interface `{name}`"), + }; + let item = if import { + world.imports.get_mut(&key) + } else { + world.exports.get_mut(&key) + }; + match item { + Some(WorldItem::Interface { stability: s, .. }) => *s = stability.clone(), + _ => bail!("item `{name}` wasn't an interface"), + } + } + + // Update the docs/stability of all functions imported/exported from + // this world. + for ((name, data), only_export) in self + .func_imports_or_exports + .iter() + .map(|p| (p, false)) + .chain(self.func_exports.iter().map(|p| (p, true))) + { + let key = WorldKey::Name(name.to_string()); + let item = if only_export { + world.exports.get_mut(&key) + } else { + match world.imports.get_mut(&key) { + Some(item) => Some(item), + None => world.exports.get_mut(&key), + } + }; + match item { + Some(WorldItem::Function(f)) => data.inject(f)?, + _ => bail!("missing func {name:?}"), + } + } + if let Some(docs) = &self.docs { + world.docs.contents = Some(docs.to_string()); + } + world.stability = self.stability.clone(); + Ok(()) + } + + fn is_empty(&self) -> bool { + self.docs.is_none() + && self.interface_imports_or_exports.is_empty() + && self.types.is_empty() + && self.func_imports_or_exports.is_empty() + && self.stability.is_unknown() + && self.interface_exports.is_empty() + && self.func_exports.is_empty() + && self.interface_import_stability.is_empty() + && self.interface_export_stability.is_empty() + } + + #[cfg(feature = "serde")] + fn is_compatible_with_v0(&self) -> bool { + self.stability.is_unknown() + && self + .interface_imports_or_exports + .iter() + .all(|(_, w)| w.is_compatible_with_v0()) + && self + .func_imports_or_exports + .iter() + .all(|(_, w)| w.is_compatible_with_v0()) + && self.types.iter().all(|(_, w)| w.is_compatible_with_v0()) + // These maps weren't present in v0, so we're only compatible if + // they're empty. + && self.interface_exports.is_empty() + && self.func_exports.is_empty() + && self.interface_import_stability.is_empty() + && self.interface_export_stability.is_empty() + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +struct InterfaceMetadata { + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + docs: Option, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Stability::is_unknown") + )] + stability: Stability, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + funcs: StringMap, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + types: StringMap, +} + +impl InterfaceMetadata { + fn extract(resolve: &Resolve, id: InterfaceId) -> Self { + let interface = &resolve.interfaces[id]; + + let funcs = interface + .functions + .iter() + .map(|(name, func)| (name.to_string(), FunctionMetadata::extract(func))) + .filter(|(_, item)| !item.is_empty()) + .collect(); + let types = interface + .types + .iter() + .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id))) + .filter(|(_, item)| !item.is_empty()) + .collect(); + + Self { + docs: interface.docs.contents.clone(), + stability: interface.stability.clone(), + funcs, + types, + } + } + + fn inject(&self, resolve: &mut Resolve, id: InterfaceId) -> Result<()> { + for (name, data) in &self.types { + let Some(&id) = resolve.interfaces[id].types.get(name) else { + bail!("missing type {name:?}"); + }; + data.inject(resolve, id)?; + } + let interface = &mut resolve.interfaces[id]; + for (name, data) in &self.funcs { + let Some(f) = interface.functions.get_mut(name) else { + bail!("missing func {name:?}"); + }; + data.inject(f)?; + } + if let Some(docs) = &self.docs { + interface.docs.contents = Some(docs.to_string()); + } + interface.stability = self.stability.clone(); + Ok(()) + } + + fn is_empty(&self) -> bool { + self.docs.is_none() + && self.funcs.is_empty() + && self.types.is_empty() + && self.stability.is_unknown() + } + + #[cfg(feature = "serde")] + fn is_compatible_with_v0(&self) -> bool { + self.stability.is_unknown() + && self.funcs.iter().all(|(_, w)| w.is_compatible_with_v0()) + && self.types.iter().all(|(_, w)| w.is_compatible_with_v0()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] +enum FunctionMetadata { + /// In the v0 format function metadata was only a string so this variant + /// is preserved for the v0 format. In the future this can be removed + /// entirely in favor of just the below struct variant. + /// + /// Note that this is an untagged enum so the name `JustDocs` is just for + /// rust. + JustDocs(Option), + + /// In the v1+ format we're tracking at least docs but also the stability + /// of functions. + DocsAndStabilty { + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + docs: Option, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Stability::is_unknown") + )] + stability: Stability, + }, +} + +impl FunctionMetadata { + fn extract(func: &Function) -> Self { + if TRY_TO_EMIT_V0_BY_DEFAULT && func.stability.is_unknown() { + FunctionMetadata::JustDocs(func.docs.contents.clone()) + } else { + FunctionMetadata::DocsAndStabilty { + docs: func.docs.contents.clone(), + stability: func.stability.clone(), + } + } + } + + fn inject(&self, func: &mut Function) -> Result<()> { + match self { + FunctionMetadata::JustDocs(docs) => { + func.docs.contents = docs.clone(); + } + FunctionMetadata::DocsAndStabilty { docs, stability } => { + func.docs.contents = docs.clone(); + func.stability = stability.clone(); + } + } + Ok(()) + } + + fn is_empty(&self) -> bool { + match self { + FunctionMetadata::JustDocs(docs) => docs.is_none(), + FunctionMetadata::DocsAndStabilty { docs, stability } => { + docs.is_none() && stability.is_unknown() + } + } + } + + #[cfg(feature = "serde")] + fn is_compatible_with_v0(&self) -> bool { + match self { + FunctionMetadata::JustDocs(_) => true, + FunctionMetadata::DocsAndStabilty { .. } => false, + } + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +struct TypeMetadata { + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] + docs: Option, + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Stability::is_unknown") + )] + stability: Stability, + // record fields, variant cases, etc. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "StringMap::is_empty") + )] + items: StringMap, +} + +impl TypeMetadata { + fn extract(resolve: &Resolve, id: TypeId) -> Self { + fn extract_items(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap { + items + .iter() + .flat_map(|item| { + let (name, docs) = f(item); + Some((name.to_string(), docs.contents.clone()?)) + }) + .collect() + } + let ty = &resolve.types[id]; + let items = match &ty.kind { + TypeDefKind::Record(record) => { + extract_items(&record.fields, |item| (&item.name, &item.docs)) + } + TypeDefKind::Flags(flags) => { + extract_items(&flags.flags, |item| (&item.name, &item.docs)) + } + TypeDefKind::Variant(variant) => { + extract_items(&variant.cases, |item| (&item.name, &item.docs)) + } + TypeDefKind::Enum(enum_) => { + extract_items(&enum_.cases, |item| (&item.name, &item.docs)) + } + // other types don't have inner items + _ => IndexMap::default(), + }; + + Self { + docs: ty.docs.contents.clone(), + stability: ty.stability.clone(), + items, + } + } + + fn inject(&self, resolve: &mut Resolve, id: TypeId) -> Result<()> { + let ty = &mut resolve.types[id]; + if !self.items.is_empty() { + match &mut ty.kind { + TypeDefKind::Record(record) => { + self.inject_items(&mut record.fields, |item| (&item.name, &mut item.docs))? + } + TypeDefKind::Flags(flags) => { + self.inject_items(&mut flags.flags, |item| (&item.name, &mut item.docs))? + } + TypeDefKind::Variant(variant) => { + self.inject_items(&mut variant.cases, |item| (&item.name, &mut item.docs))? + } + TypeDefKind::Enum(enum_) => { + self.inject_items(&mut enum_.cases, |item| (&item.name, &mut item.docs))? + } + _ => { + bail!("got 'items' for unexpected type {ty:?}"); + } + } + } + if let Some(docs) = &self.docs { + ty.docs.contents = Some(docs.to_string()); + } + ty.stability = self.stability.clone(); + Ok(()) + } + + fn inject_items( + &self, + items: &mut [T], + f: impl Fn(&mut T) -> (&String, &mut Docs), + ) -> Result<()> { + let mut unused_docs = self.items.len(); + for item in items.iter_mut() { + let (name, item_docs) = f(item); + if let Some(docs) = self.items.get(name.as_str()) { + item_docs.contents = Some(docs.to_string()); + unused_docs -= 1; + } + } + if unused_docs > 0 { + bail!( + "not all 'items' match type items; {item_docs:?} vs {items:?}", + item_docs = self.items + ); + } + Ok(()) + } + + fn is_empty(&self) -> bool { + self.docs.is_none() && self.items.is_empty() && self.stability.is_unknown() + } + + #[cfg(feature = "serde")] + fn is_compatible_with_v0(&self) -> bool { + self.stability.is_unknown() + } +} diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 19ccee6b69..fa294f986d 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -4,7 +4,7 @@ use crate::ast::{parse_use_path, AstUsePath}; use crate::serde_::{serialize_arena, serialize_id_map}; use crate::{ AstItem, Docs, Error, Function, FunctionKind, Handle, IncludeName, Interface, InterfaceId, - InterfaceSpan, PackageName, Results, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, + InterfaceSpan, PackageName, Results, Stability, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, WorldKey, WorldSpan, }; use anyhow::{anyhow, bail, Context, Result}; @@ -64,6 +64,15 @@ pub struct Resolve { /// A map of package names to the ID of the package with that name. #[cfg_attr(feature = "serde", serde(skip))] pub package_names: IndexMap, + + /// Activated features for this [`Resolve`]. + /// + /// This set of features is empty by default. This is consulted for + /// `@unstable` annotations in loaded WIT documents. Any items with + /// `@unstable` are filtered out unless their feature is present within this + /// set. + #[cfg_attr(feature = "serde", serde(skip))] + pub features: IndexSet, } /// A WIT package within a `Resolve`. @@ -205,10 +214,7 @@ impl Resolve { for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() { let span = pkg.foreign_dep_spans[i]; if !visiting.insert(dep) { - bail!(Error { - span, - msg: format!("package depends on itself"), - }); + bail!(Error::new(span, "package depends on itself")); } if let Some(dep) = deps.get(dep) { visit(dep, deps, order, visiting)?; @@ -447,18 +453,22 @@ impl Resolve { interfaces, packages, package_names, + features: _, } = resolve; let mut moved_types = Vec::new(); for (id, mut ty) in types { - let new_id = type_map.get(&id).copied().unwrap_or_else(|| { - log::debug!("moving type {:?}", ty.name); - moved_types.push(id); - remap.update_typedef(self, &mut ty); - self.types.alloc(ty) - }); + let new_id = match type_map.get(&id).copied() { + Some(id) => id, + None => { + log::debug!("moving type {:?}", ty.name); + moved_types.push(id); + remap.update_typedef(self, &mut ty, None)?; + self.types.alloc(ty) + } + }; assert_eq!(remap.types.len(), id.index()); - remap.types.push(new_id); + remap.types.push(Some(new_id)); } let mut moved_interfaces = Vec::new(); @@ -473,7 +483,7 @@ impl Resolve { } }; assert_eq!(remap.interfaces.len(), id.index()); - remap.interfaces.push(new_id); + remap.interfaces.push(Some(new_id)); } let mut moved_worlds = Vec::new(); @@ -485,11 +495,13 @@ impl Resolve { moved_worlds.push(id); let mut update = |map: &mut IndexMap| -> Result<_> { for (mut name, mut item) in mem::take(map) { - remap.update_world_key(&mut name); + remap.update_world_key(&mut name, None)?; match &mut item { WorldItem::Function(f) => remap.update_function(self, f, None)?, - WorldItem::Interface(i) => *i = remap.interfaces[i.index()], - WorldItem::Type(i) => *i = remap.types[i.index()], + WorldItem::Interface { id, .. } => { + *id = remap.map_interface(*id, None)? + } + WorldItem::Type(i) => *i = remap.map_type(*i, None)?, } map.insert(name, item); } @@ -501,19 +513,22 @@ impl Resolve { } }; assert_eq!(remap.worlds.len(), id.index()); - remap.worlds.push(new_id); + remap.worlds.push(Some(new_id)); } for (id, mut pkg) in packages { - let new_id = package_map.get(&id).copied().unwrap_or_else(|| { - for (_, id) in pkg.interfaces.iter_mut() { - *id = remap.interfaces[id.index()]; - } - for (_, id) in pkg.worlds.iter_mut() { - *id = remap.worlds[id.index()]; + let new_id = match package_map.get(&id).copied() { + Some(id) => id, + None => { + for (_, id) in pkg.interfaces.iter_mut() { + *id = remap.map_interface(*id, None)?; + } + for (_, id) in pkg.worlds.iter_mut() { + *id = remap.map_world(*id, None)?; + } + self.packages.alloc(pkg) } - self.packages.alloc(pkg) - }); + }; assert_eq!(remap.packages.len(), id.index()); remap.packages.push(new_id); } @@ -533,20 +548,20 @@ impl Resolve { // are ids within `resolve`, so they're translated through `remap` to // ids within `self`. for id in moved_worlds { - let id = remap.worlds[id.index()]; + let id = remap.map_world(id, None)?; let pkg = self.worlds[id].package.as_mut().unwrap(); *pkg = remap.packages[pkg.index()]; } for id in moved_interfaces { - let id = remap.interfaces[id.index()]; + let id = remap.map_interface(id, None)?; let pkg = self.interfaces[id].package.as_mut().unwrap(); *pkg = remap.packages[pkg.index()]; } for id in moved_types { - let id = remap.types[id.index()]; + let id = remap.map_type(id, None)?; match &mut self.types[id].owner { - TypeOwner::Interface(id) => *id = remap.interfaces[id.index()], - TypeOwner::World(id) => *id = remap.worlds[id.index()], + TypeOwner::Interface(id) => *id = remap.map_interface(*id, None)?, + TypeOwner::World(id) => *id = remap.map_world(*id, None)?, TypeOwner::None => {} } } @@ -558,13 +573,13 @@ impl Resolve { for (name, pkg, iface) in interfaces_to_add { let prev = self.packages[pkg] .interfaces - .insert(name, remap.interfaces[iface.index()]); + .insert(name, remap.map_interface(iface, None)?); assert!(prev.is_none()); } for (name, pkg, world) in worlds_to_add { let prev = self.packages[pkg] .worlds - .insert(name, remap.worlds[world.index()]); + .insert(name, remap.map_world(world, None)?); assert!(prev.is_none()); } @@ -596,13 +611,13 @@ impl Resolve { let mut into_imports_by_id = HashMap::new(); let mut into_exports_by_id = HashMap::new(); for (name, import) in into_world.imports.iter() { - if let WorldItem::Interface(id) = *import { + if let WorldItem::Interface { id, .. } = *import { let prev = into_imports_by_id.insert(id, name); assert!(prev.is_none()); } } for (name, export) in into_world.exports.iter() { - if let WorldItem::Interface(id) = *export { + if let WorldItem::Interface { id, .. } = *export { let prev = into_exports_by_id.insert(id, name); assert!(prev.is_none()); } @@ -613,7 +628,7 @@ impl Resolve { // are the same. Importing the same interface under different names // isn't allowed, but otherwise merging imports of // same-named-interfaces is allowed to merge them together. - if let WorldItem::Interface(id) = import { + if let WorldItem::Interface { id, .. } = import { if let Some(prev) = into_imports_by_id.get(id) { if *prev != name { let name = self.name_world_key(name); @@ -627,7 +642,7 @@ impl Resolve { // Note that unlike imports same-named exports are not handled here // since if something is exported twice there's no way to "unify" it // so it's left as an error. - if let WorldItem::Interface(id) = export { + if let WorldItem::Interface { id, .. } = export { if let Some(prev) = into_exports_by_id.get(id) { let name = self.name_world_key(name); let prev = self.name_world_key(prev); @@ -643,9 +658,10 @@ impl Resolve { Some(into_import) => match (from_import, into_import) { // If these imports, which have the same name, are of the // same interface then union them together at this point. - (WorldItem::Interface(from), WorldItem::Interface(into)) if from == into => { - continue - } + ( + WorldItem::Interface { id: from, .. }, + WorldItem::Interface { id: into, .. }, + ) if from == into => continue, _ => { let name = self.name_world_key(name); bail!("duplicate import found for interface `{name}`"); @@ -849,7 +865,7 @@ impl Resolve { .iter() .chain(world.exports.iter()) .filter_map(move |(_name, item)| match item { - WorldItem::Interface(id) => Some(*id), + WorldItem::Interface { id, .. } => Some(*id), WorldItem::Function(_) => None, WorldItem::Type(t) => self.type_interface_dep(*t), }) @@ -945,7 +961,7 @@ impl Resolve { for (name, item) in world.imports.iter().chain(world.exports.iter()) { log::debug!("validating world item: {}", self.name_world_key(name)); match item { - WorldItem::Interface(_) => {} + WorldItem::Interface { .. } => {} WorldItem::Function(f) => { assert_eq!(f.name, name.clone().unwrap_name()); } @@ -1006,7 +1022,7 @@ impl Resolve { for (_, item) in world.imports.iter().chain(&world.exports) { let id = match item { - WorldItem::Interface(id) => *id, + WorldItem::Interface { id, .. } => *id, _ => continue, }; let other_package = self.interfaces[id].package; @@ -1050,15 +1066,22 @@ impl Resolve { } } } + + fn include_stability(&self, stability: &Stability) -> bool { + match stability { + Stability::Stable { .. } | Stability::Unknown => true, + Stability::Unstable { feature } => self.features.contains(feature), + } + } } /// Structure returned by [`Resolve::merge`] which contains mappings from /// old-ids to new-ids after the merge. #[derive(Default)] pub struct Remap { - pub types: Vec, - pub interfaces: Vec, - pub worlds: Vec, + pub types: Vec>, + pub interfaces: Vec>, + pub worlds: Vec>, pub packages: Vec, /// A cache of anonymous `own` handles for resource types. @@ -1075,7 +1098,36 @@ pub struct Remap { type_has_borrow: Vec>, } +fn apply_map(map: &[Option>], id: Id, desc: &str, span: Option) -> Result> { + match map.get(id.index()) { + Some(Some(id)) => Ok(*id), + Some(None) => { + let msg = format!( + "found a reference to a {desc} which is excluded \ + due to its feature not being activated" + ); + match span { + Some(span) => Err(Error::new(span, msg).into()), + None => bail!("{msg}"), + } + } + None => panic!("request to remap a {desc} that has not yet been registered"), + } +} + impl Remap { + pub fn map_type(&self, id: TypeId, span: Option) -> Result { + apply_map(&self.types, id, "type", span) + } + + pub fn map_interface(&self, id: InterfaceId, span: Option) -> Result { + apply_map(&self.interfaces, id, "interface", span) + } + + pub fn map_world(&self, id: WorldId, span: Option) -> Result { + apply_map(&self.worlds, id, "world", span) + } + fn append( &mut self, resolve: &mut Resolve, @@ -1087,13 +1139,33 @@ impl Remap { let foreign_interfaces = self.interfaces.len(); let foreign_worlds = self.worlds.len(); + let pkgid = resolve.packages.alloc(Package { + name: unresolved.name.clone(), + docs: unresolved.docs.clone(), + interfaces: Default::default(), + worlds: Default::default(), + }); + let prev = resolve.package_names.insert(unresolved.name.clone(), pkgid); + assert!(prev.is_none()); + // Copy over all types first, updating any intra-type references. Note // that types are sorted topologically which means this iteration // order should be sufficient. Also note though that the interface // owner of a type isn't updated here due to interfaces not being known // yet. - for (id, mut ty) in unresolved.types.into_iter().skip(foreign_types) { - self.update_typedef(resolve, &mut ty); + assert_eq!(unresolved.types.len(), unresolved.type_spans.len()); + for ((id, mut ty), span) in unresolved + .types + .into_iter() + .zip(&unresolved.type_spans) + .skip(foreign_types) + { + if !resolve.include_stability(&ty.stability) { + self.types.push(None); + continue; + } + + self.update_typedef(resolve, &mut ty, Some(*span))?; let new_id = resolve.types.alloc(ty); assert_eq!(self.types.len(), id.index()); @@ -1107,34 +1179,55 @@ impl Remap { owner: TypeOwner::None, kind: TypeDefKind::Handle(Handle::Own(id)), docs: _, + stability: _, } => *self.own_handles.entry(id).or_insert(new_id), // Everything not-related to `own` doesn't get its ID // modified. _ => new_id, }; - self.types.push(new_id); + self.types.push(Some(new_id)); } // Next transfer all interfaces into `Resolve`, updating type ids // referenced along the way. + assert_eq!( + unresolved.interfaces.len(), + unresolved.interface_spans.len() + ); for ((id, mut iface), span) in unresolved .interfaces .into_iter() .zip(&unresolved.interface_spans) .skip(foreign_interfaces) { + if !resolve.include_stability(&iface.stability) { + self.interfaces.push(None); + continue; + } self.update_interface(resolve, &mut iface, Some(span))?; + assert!(iface.package.is_none()); + iface.package = Some(pkgid); let new_id = resolve.interfaces.alloc(iface); assert_eq!(self.interfaces.len(), id.index()); - self.interfaces.push(new_id); + self.interfaces.push(Some(new_id)); } // Now that interfaces are identified go back through the types and // update their interface owners. - for id in self.types.iter().skip(foreign_types) { - match &mut resolve.types[*id].owner { - TypeOwner::Interface(id) => *id = self.interfaces[id.index()], + for (i, id) in self.types.iter().enumerate().skip(foreign_types) { + let id = match id { + Some(id) => *id, + None => continue, + }; + match &mut resolve.types[id].owner { + TypeOwner::Interface(id) => { + let span = unresolved.type_spans[i]; + *id = self.map_interface(*id, Some(span)) + .with_context(|| { + "this type is not gated by a feature but its interface is gated by a feature" + })?; + } TypeOwner::World(_) | TypeOwner::None => {} } } @@ -1153,44 +1246,58 @@ impl Remap { .zip(unresolved.world_spans) .skip(foreign_worlds) { + if !resolve.include_stability(&world.stability) { + self.worlds.push(None); + continue; + } self.update_world(&mut world, resolve, &span)?; let new_id = resolve.worlds.alloc(world); assert_eq!(self.worlds.len(), id.index()); - self.worlds.push(new_id); + self.worlds.push(Some(new_id)); } // As with interfaces, now update the ids of world-owned types. - for id in self.types.iter().skip(foreign_types) { - match &mut resolve.types[*id].owner { - TypeOwner::World(id) => *id = self.worlds[id.index()], + for (i, id) in self.types.iter().enumerate().skip(foreign_types) { + let id = match id { + Some(id) => *id, + None => continue, + }; + match &mut resolve.types[id].owner { + TypeOwner::World(id) => { + let span = unresolved.type_spans[i]; + *id = self.map_world(*id, Some(span)) + .with_context(|| { + "this type is not gated by a feature but its interface is gated by a feature" + })?; + } TypeOwner::Interface(_) | TypeOwner::None => {} } } // Fixup "parent" ids now that everything has been identified - let pkgid = resolve.packages.alloc(Package { - name: unresolved.name.clone(), - docs: unresolved.docs.clone(), - interfaces: Default::default(), - worlds: Default::default(), - }); - let prev = resolve.package_names.insert(unresolved.name.clone(), pkgid); - assert!(prev.is_none()); for id in self.interfaces.iter().skip(foreign_interfaces) { - let iface = &mut resolve.interfaces[*id]; + let id = match id { + Some(id) => *id, + None => continue, + }; + let iface = &mut resolve.interfaces[id]; iface.package = Some(pkgid); if let Some(name) = &iface.name { - let prev = resolve.packages[pkgid].interfaces.insert(name.clone(), *id); + let prev = resolve.packages[pkgid].interfaces.insert(name.clone(), id); assert!(prev.is_none()); } } for id in self.worlds.iter().skip(foreign_worlds) { - let world = &mut resolve.worlds[*id]; + let id = match id { + Some(id) => *id, + None => continue, + }; + let world = &mut resolve.worlds[id]; world.package = Some(pkgid); let prev = resolve.packages[pkgid] .worlds - .insert(world.name.clone(), *id); + .insert(world.name.clone(), id); assert!(prev.is_none()); } Ok(pkgid) @@ -1241,15 +1348,15 @@ impl Remap { self.process_foreign_types(unresolved, resolve)?; for (id, span) in unresolved.required_resource_types.iter() { - let mut id = self.types[id.index()]; + let mut id = self.map_type(*id, Some(*span))?; loop { match resolve.types[id].kind { TypeDefKind::Type(Type::Id(i)) => id = i, TypeDefKind::Resource => break, - _ => bail!(Error { - span: *span, - msg: format!("type used in a handle must be a resource"), - }), + _ => bail!(Error::new( + *span, + format!("type used in a handle must be a resource"), + )), } } } @@ -1275,10 +1382,7 @@ impl Remap { .package_names .get(pkg_name) .copied() - .ok_or_else(|| Error { - span, - msg: format!("package not found"), - })?; + .ok_or_else(|| Error::new(span, "package not found"))?; // Functions can't be imported so this should be empty. assert!(unresolved_iface.functions.is_empty()); @@ -1289,12 +1393,9 @@ impl Remap { .interfaces .get(interface) .copied() - .ok_or_else(|| Error { - span: span.span, - msg: format!("interface not found in package"), - })?; + .ok_or_else(|| Error::new(span.span, "interface not found in package"))?; assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); - self.interfaces.push(iface_id); + self.interfaces.push(Some(iface_id)); } for (id, _) in unresolved.interfaces.iter().skip(self.interfaces.len()) { assert!( @@ -1323,18 +1424,16 @@ impl Remap { .package_names .get(pkg_name) .copied() - .ok_or_else(|| Error { - span, - msg: format!("package not found"), - })?; + .ok_or_else(|| Error::new(span, "package not found"))?; let pkg = &resolve.packages[pkgid]; let span = &unresolved.world_spans[unresolved_world_id.index()]; - let world_id = pkg.worlds.get(world).copied().ok_or_else(|| Error { - span: span.span, - msg: format!("world not found in package"), - })?; + let world_id = pkg + .worlds + .get(world) + .copied() + .ok_or_else(|| Error::new(span.span, "world not found in package"))?; assert_eq!(self.worlds.len(), unresolved_world_id.index()); - self.worlds.push(world_id); + self.worlds.push(Some(world_id)); } for (id, _) in unresolved.worlds.iter().skip(self.worlds.len()) { assert!( @@ -1362,18 +1461,17 @@ impl Remap { TypeOwner::Interface(id) => id, _ => unreachable!(), }; - let iface_id = self.interfaces[unresolved_iface_id.index()]; + let iface_id = self.map_interface(unresolved_iface_id, None)?; let name = unresolved_ty.name.as_ref().unwrap(); let span = unresolved.unknown_type_spans[unresolved_type_id.index()]; let type_id = *resolve.interfaces[iface_id] .types .get(name) - .ok_or_else(|| Error { - span, - msg: format!("type `{name}` not defined in interface"), + .ok_or_else(|| { + Error::new(span, format!("type `{name}` not defined in interface")) })?; assert_eq!(self.types.len(), unresolved_type_id.index()); - self.types.push(type_id); + self.types.push(Some(type_id)); } for (_, ty) in unresolved.types.iter().skip(self.types.len()) { if let TypeDefKind::Unknown = ty.kind { @@ -1383,49 +1481,57 @@ impl Remap { Ok(()) } - fn update_typedef(&mut self, resolve: &mut Resolve, ty: &mut TypeDef) { + fn update_typedef( + &mut self, + resolve: &mut Resolve, + ty: &mut TypeDef, + span: Option, + ) -> Result<()> { // NB: note that `ty.owner` is not updated here since interfaces // haven't been mapped yet and that's done in a separate step. use crate::TypeDefKind::*; match &mut ty.kind { Handle(handle) => match handle { - crate::Handle::Own(ty) | crate::Handle::Borrow(ty) => self.update_type_id(ty), + crate::Handle::Own(ty) | crate::Handle::Borrow(ty) => { + self.update_type_id(ty, span)? + } }, Resource => {} Record(r) => { for field in r.fields.iter_mut() { - self.update_ty(resolve, &mut field.ty); + self.update_ty(resolve, &mut field.ty, span) + .with_context(|| format!("failed to update field `{}`", field.name))?; } } Tuple(t) => { for ty in t.types.iter_mut() { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } } Variant(v) => { for case in v.cases.iter_mut() { if let Some(t) = &mut case.ty { - self.update_ty(resolve, t); + self.update_ty(resolve, t, span)?; } } } - Option(t) => self.update_ty(resolve, t), + Option(t) => self.update_ty(resolve, t, span)?, Result(r) => { if let Some(ty) = &mut r.ok { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } if let Some(ty) = &mut r.err { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } } - List(t) => self.update_ty(resolve, t), - Future(Some(t)) => self.update_ty(resolve, t), + List(t) => self.update_ty(resolve, t, span)?, + Future(Some(t)) => self.update_ty(resolve, t, span)?, Stream(t) => { if let Some(ty) = &mut t.element { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } if let Some(ty) = &mut t.end { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } } @@ -1433,7 +1539,7 @@ impl Remap { // because for the `type a = b` form that doesn't force `a` to be a // handle type if `b` is a resource type, instead `a` is // simultaneously usable as a resource and a handle type - Type(crate::Type::Id(id)) => self.update_type_id(id), + Type(crate::Type::Id(id)) => self.update_type_id(id, span)?, Type(_) => {} // nothing to do for these as they're just names or empty @@ -1441,14 +1547,21 @@ impl Remap { Unknown => unreachable!(), } + + Ok(()) } - fn update_ty(&mut self, resolve: &mut Resolve, ty: &mut Type) { + fn update_ty( + &mut self, + resolve: &mut Resolve, + ty: &mut Type, + span: Option, + ) -> Result<()> { let id = match ty { Type::Id(id) => id, - _ => return, + _ => return Ok(()), }; - self.update_type_id(id); + self.update_type_id(id, span)?; // If `id` points to a `Resource` type then this means that what was // just discovered was a reference to what will implicitly become an @@ -1470,13 +1583,16 @@ impl Remap { owner: TypeOwner::None, kind: TypeDefKind::Handle(Handle::Own(*id)), docs: Default::default(), + stability: Default::default(), }) }); } + Ok(()) } - fn update_type_id(&self, id: &mut TypeId) { - *id = self.types[id.index()]; + fn update_type_id(&self, id: &mut TypeId, span: Option) -> Result<()> { + *id = self.map_type(*id, span)?; + Ok(()) } fn update_interface( @@ -1485,18 +1601,27 @@ impl Remap { iface: &mut Interface, spans: Option<&InterfaceSpan>, ) -> Result<()> { + iface.types.retain(|_, ty| self.types[ty.index()].is_some()); + // NB: note that `iface.doc` is not updated here since interfaces // haven't been mapped yet and that's done in a separate step. for (_name, ty) in iface.types.iter_mut() { - self.update_type_id(ty); + self.update_type_id(ty, spans.map(|s| s.span))?; } if let Some(spans) = spans { assert_eq!(iface.functions.len(), spans.funcs.len()); } for (i, (_, func)) in iface.functions.iter_mut().enumerate() { + if !resolve.include_stability(&func.stability) { + continue; + } let span = spans.map(|s| s.funcs[i]); - self.update_function(resolve, func, span)?; + self.update_function(resolve, func, span) + .with_context(|| format!("failed to update function `{}`", func.name))?; } + iface + .functions + .retain(|_, f| resolve.include_stability(&f.stability)); Ok(()) } @@ -1509,19 +1634,19 @@ impl Remap { match &mut func.kind { FunctionKind::Freestanding => {} FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => { - self.update_type_id(id); + self.update_type_id(id, span)?; } } for (_, ty) in func.params.iter_mut() { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } match &mut func.results { Results::Named(named) => { for (_, ty) in named.iter_mut() { - self.update_ty(resolve, ty); + self.update_ty(resolve, ty, span)?; } } - Results::Anon(ty) => self.update_ty(resolve, ty), + Results::Anon(ty) => self.update_ty(resolve, ty, span)?, } for ty in func.results.iter_types() { @@ -1530,13 +1655,13 @@ impl Remap { } match span { Some(span) => { - bail!(Error { + bail!(Error::new( span, - msg: format!( + format!( "function returns a type which contains \ a `borrow` which is not supported" ) - }) + )) } None => unreachable!(), } @@ -1569,22 +1694,30 @@ impl Remap { // determining names later on. let mut import_funcs = Vec::new(); let mut import_types = Vec::new(); - for ((mut name, item), span) in mem::take(&mut world.imports) + for ((mut name, mut item), span) in mem::take(&mut world.imports) .into_iter() .zip(&spans.imports) { - self.update_world_key(&mut name); + // Update the `id` eagerly here so `item.stability(..)` below + // works. + if let WorldItem::Type(id) = &mut item { + *id = self.map_type(*id, Some(*span))?; + } + let stability = item.stability(resolve); + if !resolve.include_stability(stability) { + continue; + } + self.update_world_key(&mut name, Some(*span))?; match item { - WorldItem::Interface(id) => { - let id = self.interfaces[id.index()]; - self.add_world_import(resolve, world, name, id); + WorldItem::Interface { id, stability } => { + let id = self.map_interface(id, Some(*span))?; + self.add_world_import(resolve, world, name, id, &stability); } WorldItem::Function(mut f) => { self.update_function(resolve, &mut f, Some(*span))?; import_funcs.push((name.unwrap_name(), f, *span)); } WorldItem::Type(id) => { - let id = self.types[id.index()]; import_types.push((name.unwrap_name(), id, *span)); } } @@ -1594,7 +1727,13 @@ impl Remap { if let TypeDefKind::Type(Type::Id(other)) = resolve.types[*id].kind { if let TypeOwner::Interface(owner) = resolve.types[other].owner { let name = WorldKey::Interface(owner); - self.add_world_import(resolve, world, name, owner); + self.add_world_import( + resolve, + world, + name, + owner, + &resolve.types[*id].stability, + ); } } } @@ -1605,11 +1744,15 @@ impl Remap { .into_iter() .zip(&spans.exports) { - self.update_world_key(&mut name); + let stability = item.stability(resolve); + if !resolve.include_stability(stability) { + continue; + } + self.update_world_key(&mut name, Some(*span))?; match item { - WorldItem::Interface(id) => { - let id = self.interfaces[id.index()]; - let prev = export_interfaces.insert(id, (name, *span)); + WorldItem::Interface { id, stability } => { + let id = self.map_interface(id, Some(*span))?; + let prev = export_interfaces.insert(id, (name, *span, stability)); assert!(prev.is_none()); } WorldItem::Function(mut f) => { @@ -1630,9 +1773,14 @@ impl Remap { assert_eq!(world.includes.len(), spans.includes.len()); let includes = mem::take(&mut world.includes); let include_names = mem::take(&mut world.include_names); - for (index, (include_world, span)) in includes.into_iter().zip(&spans.includes).enumerate() + for (((stability, include_world), span), names) in includes + .into_iter() + .zip(&spans.includes) + .zip(&include_names) { - let names = &include_names[index]; + if !resolve.include_stability(&stability) { + continue; + } self.resolve_include(world, include_world, names, *span, resolve)?; } @@ -1641,10 +1789,10 @@ impl Remap { .imports .insert(WorldKey::Name(name.clone()), WorldItem::Type(id)); if prev.is_some() { - bail!(Error { - msg: format!("export of type `{name}` shadows previously imported interface"), + bail!(Error::new( span, - }) + format!("export of type `{name}` shadows previously imported interface"), + )) } } @@ -1653,12 +1801,10 @@ impl Remap { .imports .insert(WorldKey::Name(name.clone()), WorldItem::Function(func)); if prev.is_some() { - bail!(Error { - msg: format!( - "import of function `{name}` shadows previously imported interface" - ), + bail!(Error::new( span, - }) + format!("import of function `{name}` shadows previously imported interface"), + )) } } @@ -1667,12 +1813,10 @@ impl Remap { .exports .insert(WorldKey::Name(name.clone()), WorldItem::Function(func)); if prev.is_some() { - bail!(Error { - msg: format!( - "export of function `{name}` shadows previously exported interface" - ), + bail!(Error::new( span, - }) + format!("export of function `{name}` shadows previously exported interface"), + )) } } @@ -1697,7 +1841,7 @@ impl Remap { let rank = |item: &WorldItem| match item { WorldItem::Type(_) => unreachable!(), WorldItem::Function(_) => 0, - WorldItem::Interface(_) => 1, + WorldItem::Interface { .. } => 1, }; rank(a).cmp(&rank(b)) }); @@ -1708,13 +1852,14 @@ impl Remap { Ok(()) } - fn update_world_key(&self, key: &mut WorldKey) { + fn update_world_key(&self, key: &mut WorldKey, span: Option) -> Result<()> { match key { WorldKey::Name(_) => {} WorldKey::Interface(id) => { - *id = self.interfaces[id.index()]; + *id = self.map_interface(*id, span)?; } } + Ok(()) } fn add_world_import( @@ -1723,14 +1868,21 @@ impl Remap { world: &mut World, key: WorldKey, id: InterfaceId, + stability: &Stability, ) { if world.imports.contains_key(&key) { return; } for dep in resolve.interface_direct_deps(id) { - self.add_world_import(resolve, world, WorldKey::Interface(dep), dep); + self.add_world_import(resolve, world, WorldKey::Interface(dep), dep, stability); } - let prev = world.imports.insert(key, WorldItem::Interface(id)); + let prev = world.imports.insert( + key, + WorldItem::Interface { + id, + stability: stability.clone(), + }, + ); assert!(prev.is_none()); } @@ -1784,10 +1936,10 @@ impl Remap { &self, resolve: &Resolve, world: &mut World, - export_interfaces: &IndexMap, + export_interfaces: &IndexMap, ) -> Result<()> { let mut required_imports = HashSet::new(); - for (id, (key, span)) in export_interfaces.iter() { + for (id, (key, span, stability)) in export_interfaces.iter() { let ok = add_world_export( resolve, world, @@ -1796,9 +1948,11 @@ impl Remap { *id, key, true, + &stability, ); if !ok { - bail!(Error { + bail!(Error::new( + *span, // FIXME: this is not a great error message and basically no // one will know what to do when it gets printed. Improving // this error message, however, is a chunk of work that may @@ -1816,12 +1970,11 @@ impl Remap { // more refactoring, so it's left to a future date in the // hopes that most folks won't actually run into this for // the time being. - msg: format!( + format!( "interface transitively depends on an interface in \ incompatible ways", ), - span: *span, - }); + )); } } return Ok(()); @@ -1829,11 +1982,12 @@ impl Remap { fn add_world_export( resolve: &Resolve, world: &mut World, - export_interfaces: &IndexMap, + export_interfaces: &IndexMap, required_imports: &mut HashSet, id: InterfaceId, key: &WorldKey, add_export: bool, + stability: &Stability, ) -> bool { if world.exports.contains_key(key) { if add_export { @@ -1858,19 +2012,24 @@ impl Remap { dep, &key, add_export, + stability, ) }); if !ok { return false; } + let item = WorldItem::Interface { + id, + stability: stability.clone(), + }; if add_export { if required_imports.contains(&id) { return false; } - world.exports.insert(key.clone(), WorldItem::Interface(id)); + world.exports.insert(key.clone(), item); } else { required_imports.insert(id); - world.imports.insert(key.clone(), WorldItem::Interface(id)); + world.imports.insert(key.clone(), item); } true } @@ -1884,7 +2043,7 @@ impl Remap { span: Span, resolve: &Resolve, ) -> Result<()> { - let include_world_id = self.worlds[include_world.index()]; + let include_world_id = self.map_world(include_world, Some(span))?; let include_world = &resolve.worlds[include_world_id]; let mut names_ = names.to_owned(); @@ -1896,10 +2055,10 @@ impl Remap { self.remove_matching_name(export, &mut names_); } if !names_.is_empty() { - bail!(Error { - msg: format!("no import or export kebab-name `{}`. Note that an ID does not support renaming", names_[0].name), - span: span, - }); + bail!(Error::new( + span, + format!("no import or export kebab-name `{}`. Note that an ID does not support renaming", names_[0].name), + )); } // copy the imports and exports from the included world into the current world @@ -1934,10 +2093,10 @@ impl Remap { let prev = items.insert(WorldKey::Name(n.clone()), item.1.clone()); if prev.is_some() { - bail!(Error { - msg: format!("{item_type} of `{n}` shadows previously {item_type}ed items"), + bail!(Error::new( span, - }) + format!("{item_type} of `{n}` shadows previously {item_type}ed items"), + )) } } key => { @@ -2232,7 +2391,7 @@ impl<'a> MergeMap<'a> { fn match_world_item(&mut self, from: &WorldItem, into: &WorldItem) -> Result<()> { match (from, into) { - (WorldItem::Interface(from), WorldItem::Interface(into)) => { + (WorldItem::Interface { id: from, .. }, WorldItem::Interface { id: into, .. }) => { match ( &self.from.interfaces[*from].name, &self.into.interfaces[*into].name, @@ -2265,7 +2424,7 @@ impl<'a> MergeMap<'a> { assert!(prev.is_none()); } - (WorldItem::Interface(_), _) + (WorldItem::Interface { .. }, _) | (WorldItem::Function(_), _) | (WorldItem::Type(_), _) => { bail!("world items do not have the same type") diff --git a/crates/wit-parser/src/serde_.rs b/crates/wit-parser/src/serde_.rs index b554d2e63a..e78e1f7564 100644 --- a/crates/wit-parser/src/serde_.rs +++ b/crates/wit-parser/src/serde_.rs @@ -1,8 +1,9 @@ use crate::{Params, Type}; use id_arena::{Arena, Id}; use indexmap::IndexMap; +use semver::Version; use serde::ser::{SerializeMap, SerializeSeq, Serializer}; -use serde::Serialize; +use serde::{de::Error, Deserialize, Serialize}; pub fn serialize_none(serializer: S) -> Result where @@ -106,3 +107,18 @@ struct Param { #[serde(rename = "type")] pub typ: Type, } + +pub fn serialize_version(version: &Version, serializer: S) -> Result +where + S: Serializer, +{ + version.to_string().serialize(serializer) +} + +pub fn deserialize_version<'de, D>(deserializer: D) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let version: String = String::deserialize(deserializer)?; + version.parse().map_err(|e| D::Error::custom(e)) +} diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index bdb6632108..6c7af1f75b 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -75,6 +75,7 @@ struct Runner {} impl Runner { fn run(&mut self, test: &Path) -> Result<()> { let mut resolve = Resolve::new(); + resolve.features.insert("active".to_string()); let result = resolve.push_path(test); let result = if test.iter().any(|s| s == "parse-fail") { match result { diff --git a/crates/wit-parser/tests/ui/complex-include.wit.json b/crates/wit-parser/tests/ui/complex-include.wit.json index 2c657bea8b..f9b17f2792 100644 --- a/crates/wit-parser/tests/ui/complex-include.wit.json +++ b/crates/wit-parser/tests/ui/complex-include.wit.json @@ -4,10 +4,14 @@ "name": "bar-a", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, @@ -17,10 +21,14 @@ "name": "baz-a", "imports": { "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } }, "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } } }, "exports": {}, @@ -30,10 +38,14 @@ "name": "a", "imports": { "interface-4": { - "interface": 4 + "interface": { + "id": 4 + } }, "interface-5": { - "interface": 5 + "interface": { + "id": 5 + } } }, "exports": {}, @@ -43,10 +55,14 @@ "name": "b", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, @@ -56,10 +72,14 @@ "name": "c", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, @@ -69,22 +89,34 @@ "name": "union-world", "imports": { "interface-4": { - "interface": 4 + "interface": { + "id": 4 + } }, "interface-5": { - "interface": 5 + "interface": { + "id": 5 + } }, "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } }, "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/diamond1.wit.json b/crates/wit-parser/tests/ui/diamond1.wit.json index 8d7ce082ea..54e87e7b04 100644 --- a/crates/wit-parser/tests/ui/diamond1.wit.json +++ b/crates/wit-parser/tests/ui/diamond1.wit.json @@ -4,10 +4,14 @@ "name": "foo", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json index 0e3ebea318..34f675da51 100644 --- a/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json +++ b/crates/wit-parser/tests/ui/disambiguate-diamond.wit.json @@ -4,16 +4,24 @@ "name": "foo", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "foo": { - "interface": 2 + "interface": { + "id": 2 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "bar": { - "interface": 3 + "interface": { + "id": 3 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/feature-gates.wit b/crates/wit-parser/tests/ui/feature-gates.wit new file mode 100644 index 0000000000..019471d31a --- /dev/null +++ b/crates/wit-parser/tests/ui/feature-gates.wit @@ -0,0 +1,118 @@ +package a:b; + +@unstable(feature = not-active) +interface gated { +} + +@unstable(feature = active) +interface ungated { + @unstable(feature = not-active) + gated: func(); + + @unstable(feature = active) + ungated: func(); +} + +@unstable(feature = active) +interface ungated2 { + @unstable(feature = not-active) + type gated = u32; + @unstable(feature = not-active) + type gated2 = gated; + + @unstable(feature = not-active) + type gated-with-anonymous-type = option>; + + @unstable(feature = active) + type ungated = u32; + @unstable(feature = active) + type ungated2 = ungated; +} + +@unstable(feature = inactive) +interface gated-use-target { + @unstable(feature = inactive) + type t = u32; +} + +@unstable(feature = inactive) +interface gated-use { + @unstable(feature = inactive) + use gated-use-target.{t}; +} + +@unstable(feature = active) +interface ungated-use-target { + @unstable(feature = active) + type t = u32; +} + +@unstable(feature = active) +interface ungated-use { + @unstable(feature = active) + use ungated-use-target.{t}; +} + +@unstable(feature = inactive) +interface gated-for-world {} + +@unstable(feature = inactive) +world gated-world { + @unstable(feature = inactive) + import gated-for-world; + @unstable(feature = inactive) + export gated-for-world; +} + +@unstable(feature = active) +interface ungated-for-world {} + +@unstable(feature = active) +world ungated-world { + @unstable(feature = active) + import ungated-for-world; + @unstable(feature = active) + export ungated-for-world; +} + +world mixed-world { + @unstable(feature = inactive) + import gated-for-world; + @unstable(feature = inactive) + export gated-for-world; + @unstable(feature = inactive) + include gated-world; + + @unstable(feature = active) + import ungated-for-world; + @unstable(feature = active) + export ungated-for-world; + @unstable(feature = inactive) + include ungated-world; +} + +interface with-resources { + @unstable(feature = inactive) + resource gated { + @unstable(feature = inactive) + constructor(); + + @unstable(feature = inactive) + x: static func(); + + @unstable(feature = inactive) + y: func(); + } + + @unstable(feature = active) + resource ungated { + @unstable(feature = active) + constructor(); + + @unstable(feature = active) + x: static func(); + + @unstable(feature = active) + y: func(); + } +} diff --git a/crates/wit-parser/tests/ui/feature-gates.wit.json b/crates/wit-parser/tests/ui/feature-gates.wit.json new file mode 100644 index 0000000000..e211f04527 --- /dev/null +++ b/crates/wit-parser/tests/ui/feature-gates.wit.json @@ -0,0 +1,288 @@ +{ + "worlds": [ + { + "name": "ungated-world", + "imports": { + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "unstable", + "feature": "active" + } + } + } + }, + "exports": { + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "unstable", + "feature": "active" + } + } + } + }, + "package": 0, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": "mixed-world", + "imports": { + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "unstable", + "feature": "active" + } + } + } + }, + "exports": { + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "unstable", + "feature": "active" + } + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "ungated", + "types": {}, + "functions": { + "ungated": { + "name": "ungated", + "kind": "freestanding", + "params": [], + "results": [], + "stability": { + "type": "unstable", + "feature": "active" + } + } + }, + "stability": { + "type": "unstable", + "feature": "active" + }, + "package": 0 + }, + { + "name": "ungated2", + "types": { + "ungated": 0, + "ungated2": 1 + }, + "functions": {}, + "stability": { + "type": "unstable", + "feature": "active" + }, + "package": 0 + }, + { + "name": "ungated-use-target", + "types": { + "t": 2 + }, + "functions": {}, + "stability": { + "type": "unstable", + "feature": "active" + }, + "package": 0 + }, + { + "name": "ungated-use", + "types": { + "t": 3 + }, + "functions": {}, + "stability": { + "type": "unstable", + "feature": "active" + }, + "package": 0 + }, + { + "name": "ungated-for-world", + "types": {}, + "functions": {}, + "stability": { + "type": "unstable", + "feature": "active" + }, + "package": 0 + }, + { + "name": "with-resources", + "types": { + "ungated": 4 + }, + "functions": { + "[constructor]ungated": { + "name": "[constructor]ungated", + "kind": { + "constructor": 4 + }, + "params": [], + "results": [ + { + "type": 6 + } + ], + "stability": { + "type": "unstable", + "feature": "active" + } + }, + "[static]ungated.x": { + "name": "[static]ungated.x", + "kind": { + "static": 4 + }, + "params": [], + "results": [], + "stability": { + "type": "unstable", + "feature": "active" + } + }, + "[method]ungated.y": { + "name": "[method]ungated.y", + "kind": { + "method": 4 + }, + "params": [ + { + "name": "self", + "type": 5 + } + ], + "results": [], + "stability": { + "type": "unstable", + "feature": "active" + } + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "ungated", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 1 + }, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": "ungated2", + "kind": { + "type": 0 + }, + "owner": { + "interface": 1 + }, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": "t", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 2 + }, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": "t", + "kind": { + "type": 2 + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": "ungated", + "kind": "resource", + "owner": { + "interface": 5 + }, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": null, + "kind": { + "handle": { + "borrow": 4 + } + }, + "owner": null, + "stability": { + "type": "unstable", + "feature": "active" + } + }, + { + "name": null, + "kind": { + "handle": { + "own": 4 + } + }, + "owner": null + } + ], + "packages": [ + { + "name": "a:b", + "interfaces": { + "ungated": 0, + "ungated2": 1, + "ungated-use-target": 2, + "ungated-use": 3, + "ungated-for-world": 4, + "with-resources": 5 + }, + "worlds": { + "ungated-world": 0, + "mixed-world": 1 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json index 3c1e2fb406..419d62e7ce 100644 --- a/crates/wit-parser/tests/ui/foreign-deps-union.wit.json +++ b/crates/wit-parser/tests/ui/foreign-deps-union.wit.json @@ -4,10 +4,14 @@ "name": "wasi", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": {}, @@ -17,15 +21,21 @@ "name": "my-world", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 6 @@ -34,18 +44,26 @@ "name": "my-world2", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": { "interface-9": { - "interface": 9 + "interface": { + "id": 9 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 6 @@ -54,10 +72,14 @@ "name": "bars-world", "imports": { "interface-4": { - "interface": 4 + "interface": { + "id": 4 + } }, "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": {}, @@ -67,18 +89,26 @@ "name": "unionw-world", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "interface-9": { - "interface": 9 + "interface": { + "id": 9 + } } }, "package": 6 diff --git a/crates/wit-parser/tests/ui/foreign-deps.wit.json b/crates/wit-parser/tests/ui/foreign-deps.wit.json index da5020f0a9..503c1b32c9 100644 --- a/crates/wit-parser/tests/ui/foreign-deps.wit.json +++ b/crates/wit-parser/tests/ui/foreign-deps.wit.json @@ -4,15 +4,21 @@ "name": "my-world", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 6 @@ -21,18 +27,26 @@ "name": "my-world2", "imports": { "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } } }, "exports": { "interface-9": { - "interface": 9 + "interface": { + "id": 9 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 6 @@ -41,10 +55,14 @@ "name": "bars-world", "imports": { "interface-4": { - "interface": 4 + "interface": { + "id": 4 + } }, "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/ignore-files-deps.wit.json b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json index 1b9d00c360..7a812dbecf 100644 --- a/crates/wit-parser/tests/ui/ignore-files-deps.wit.json +++ b/crates/wit-parser/tests/ui/ignore-files-deps.wit.json @@ -4,7 +4,9 @@ "name": "foo", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/import-export-overlap2.wit.json b/crates/wit-parser/tests/ui/import-export-overlap2.wit.json index fff5bfd3de..0c991269c5 100644 --- a/crates/wit-parser/tests/ui/import-export-overlap2.wit.json +++ b/crates/wit-parser/tests/ui/import-export-overlap2.wit.json @@ -14,7 +14,9 @@ }, "exports": { "a": { - "interface": 0 + "interface": { + "id": 0 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/include-reps.wit.json b/crates/wit-parser/tests/ui/include-reps.wit.json index 8217e2ee33..21debef2fe 100644 --- a/crates/wit-parser/tests/ui/include-reps.wit.json +++ b/crates/wit-parser/tests/ui/include-reps.wit.json @@ -4,12 +4,16 @@ "name": "bar", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 0 @@ -18,12 +22,16 @@ "name": "foo", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/kinds-of-deps.wit.json b/crates/wit-parser/tests/ui/kinds-of-deps.wit.json index 07f01a7cba..f13afe6301 100644 --- a/crates/wit-parser/tests/ui/kinds-of-deps.wit.json +++ b/crates/wit-parser/tests/ui/kinds-of-deps.wit.json @@ -4,16 +4,24 @@ "name": "a", "imports": { "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } }, "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } }, "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/many-names.wit.json b/crates/wit-parser/tests/ui/many-names.wit.json index 87d111bd1f..590c68471a 100644 --- a/crates/wit-parser/tests/ui/many-names.wit.json +++ b/crates/wit-parser/tests/ui/many-names.wit.json @@ -4,7 +4,9 @@ "name": "name", "imports": { "name": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/multi-file.wit.json b/crates/wit-parser/tests/ui/multi-file.wit.json index ef6171bce2..45af0f5d32 100644 --- a/crates/wit-parser/tests/ui/multi-file.wit.json +++ b/crates/wit-parser/tests/ui/multi-file.wit.json @@ -4,12 +4,16 @@ "name": "more-depends-on-later-things", "imports": { "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } } }, "exports": { "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } } }, "package": 0 @@ -18,7 +22,9 @@ "name": "the-world", "imports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "x": { "type": 15 diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit b/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit new file mode 100644 index 0000000000..36347f5cde --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit @@ -0,0 +1,8 @@ +package a:b; + +interface foo { + @unstable(feature = not-active) + type gated = u32; + + type foo = gated; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit.result new file mode 100644 index 0000000000..3fb29c089b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate1.wit.result @@ -0,0 +1,5 @@ +found a reference to a type which is excluded due to its feature not being activated + --> tests/ui/parse-fail/bad-gate1.wit:7:8 + | + 7 | type foo = gated; + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit b/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit new file mode 100644 index 0000000000..fb5d86f15b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit @@ -0,0 +1,8 @@ +package a:b; + +@unstable(feature = not-active) +world a {} + +world b { + include a; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit.result new file mode 100644 index 0000000000..6e56c67027 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate2.wit.result @@ -0,0 +1,5 @@ +found a reference to a world which is excluded due to its feature not being activated + --> tests/ui/parse-fail/bad-gate2.wit:7:11 + | + 7 | include a; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit new file mode 100644 index 0000000000..3201389f42 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit @@ -0,0 +1,6 @@ +package a:b; + +@unstable(feature = not-active) +interface a { + type a = u32; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result new file mode 100644 index 0000000000..e8e4df1dfe --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate3.wit.result @@ -0,0 +1,8 @@ +this type is not gated by a feature but its interface is gated by a feature + +Caused by: + found a reference to a interface which is excluded due to its feature not being activated + --> tests/ui/parse-fail/bad-gate3.wit:5:8 + | + 5 | type a = u32; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit new file mode 100644 index 0000000000..b266674177 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit @@ -0,0 +1,8 @@ +package a:b; + +interface a { + @unstable(feature = inactive) + resource a { + constructor(); + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result new file mode 100644 index 0000000000..8b47145a4a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate4.wit.result @@ -0,0 +1,8 @@ +failed to update function `[constructor]a` + +Caused by: + found a reference to a type which is excluded due to its feature not being activated + --> tests/ui/parse-fail/bad-gate4.wit:6:5 + | + 6 | constructor(); + | ^---------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit new file mode 100644 index 0000000000..7316bd1ae8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit @@ -0,0 +1,11 @@ +package a:b; + +interface a { + @unstable(feature = inactive) + resource a { + @unstable(feature = inactive) + constructor(); + + x: static func(); + } +} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result new file mode 100644 index 0000000000..df063dd334 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-gate5.wit.result @@ -0,0 +1,8 @@ +failed to update function `[static]a.x` + +Caused by: + found a reference to a type which is excluded due to its feature not being activated + --> tests/ui/parse-fail/bad-gate5.wit:9:5 + | + 9 | x: static func(); + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit b/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit new file mode 100644 index 0000000000..255522bd24 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit @@ -0,0 +1,4 @@ +package a:b; + +@since +interface foo1 {} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit.result new file mode 100644 index 0000000000..9585d62b8a --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-since1.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found keyword `interface` + --> tests/ui/parse-fail/bad-since1.wit:4:1 + | + 4 | interface foo1 {} + | ^-------- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit b/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit new file mode 100644 index 0000000000..17d2b5cbb6 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit @@ -0,0 +1,5 @@ +package a:b; + +// for now `version` must come first +@since(feature = foo, version = 1.0.0) +interface foo2 {} diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit.result new file mode 100644 index 0000000000..74c1d17500 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-since3.wit.result @@ -0,0 +1,5 @@ +expected `version`, found `feature` + --> tests/ui/parse-fail/bad-since3.wit:4:8 + | + 4 | @since(feature = foo, version = 1.0.0) + | ^------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result b/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result index bdf37086eb..bffa1fc4f1 100644 --- a/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/conflicting-package.wit.result @@ -2,7 +2,8 @@ failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/conf Caused by: 0: failed to parse package: tests/ui/parse-fail/conflicting-package - 1: package identifier `foo:b` does not match previous package name of `foo:a` + 1: failed to start resolving path: tests/ui/parse-fail/conflicting-package/b.wit + 2: package identifier `foo:b` does not match previous package name of `foo:a` --> tests/ui/parse-fail/conflicting-package/b.wit:1:9 | 1 | package foo:b; diff --git a/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result b/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result index 15356332a8..4ce4423875 100644 --- a/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/multiple-package-docs.wit.result @@ -2,7 +2,8 @@ failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/mult Caused by: 0: failed to parse package: tests/ui/parse-fail/multiple-package-docs - 1: found doc comments on multiple 'package' items + 1: failed to start resolving path: tests/ui/parse-fail/multiple-package-docs/b.wit + 2: found doc comments on multiple 'package' items --> tests/ui/parse-fail/multiple-package-docs/b.wit:1:1 | 1 | /// Multiple package docs, B diff --git a/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result index 62e78ca1ee..2094237fa4 100644 --- a/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/resources-multiple-returns-borrow.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/resources-multiple-returns-borrow.wit:7:5 - | - 7 | f1: func() -> (a: s32, handle: borrow); - | ^- \ No newline at end of file +failed to update function `[method]r1.f1` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/resources-multiple-returns-borrow.wit:7:5 + | + 7 | f1: func() -> (a: s32, handle: borrow); + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result index 3919bf7268..79de329242 100644 --- a/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/resources-return-borrow.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/resources-return-borrow.wit:7:5 - | - 7 | f1: func() -> borrow; - | ^- \ No newline at end of file +failed to update function `[method]r1.f1` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/resources-return-borrow.wit:7:5 + | + 7 | f1: func() -> borrow; + | ^- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result index aa7dd50790..0a0468dd60 100644 --- a/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow1.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/return-borrow1.wit:6:3 - | - 6 | x: func() -> borrow; - | ^ \ No newline at end of file +failed to update function `x` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/return-borrow1.wit:6:3 + | + 6 | x: func() -> borrow; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result index d29f0ee7aa..b46631bb96 100644 --- a/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow2.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/return-borrow2.wit:5:5 - | - 5 | x: func() -> borrow; - | ^ \ No newline at end of file +failed to update function `[method]y.x` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/return-borrow2.wit:5:5 + | + 5 | x: func() -> borrow; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result index 4c0f86ab16..13d61fba84 100644 --- a/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow6.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/return-borrow6.wit:6:3 - | - 6 | x: func() -> tuple>; - | ^ \ No newline at end of file +failed to update function `x` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/return-borrow6.wit:6:3 + | + 6 | x: func() -> tuple>; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result index 5d8532f944..3caaf3feae 100644 --- a/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow7.wit.result @@ -1,5 +1,8 @@ -function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/return-borrow7.wit:10:3 - | - 10 | x: func() -> y2; - | ^ \ No newline at end of file +failed to update function `x` + +Caused by: + function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/return-borrow7.wit:10:3 + | + 10 | x: func() -> y2; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result b/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result index 6382feba8e..9fd1f02711 100644 --- a/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/return-borrow8.wit.result @@ -1,8 +1,9 @@ failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/return-borrow8] Caused by: - function returns a type which contains a `borrow` which is not supported - --> tests/ui/parse-fail/return-borrow8/foo.wit:6:3 - | - 6 | x: func() -> r; - | ^ \ No newline at end of file + 0: failed to update function `x` + 1: function returns a type which contains a `borrow` which is not supported + --> tests/ui/parse-fail/return-borrow8/foo.wit:6:3 + | + 6 | x: func() -> r; + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/shared-types.wit.json b/crates/wit-parser/tests/ui/shared-types.wit.json index d0dba6598c..569f6d3861 100644 --- a/crates/wit-parser/tests/ui/shared-types.wit.json +++ b/crates/wit-parser/tests/ui/shared-types.wit.json @@ -4,12 +4,16 @@ "name": "foo", "imports": { "foo": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": { "bar": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/since-and-unstable.wit b/crates/wit-parser/tests/ui/since-and-unstable.wit new file mode 100644 index 0000000000..130d34a6ef --- /dev/null +++ b/crates/wit-parser/tests/ui/since-and-unstable.wit @@ -0,0 +1,89 @@ +package a:b@1.0.1; + +@since(version = 1.0.0) +interface foo1 {} + +@since(version = 1.0.0, feature = foo) +interface foo2 {} + +@since(version = 1.0.0, feature = foo-bar) +interface foo3 {} + +@unstable(feature = foo2) +interface foo4 {} + +@since(version = 1.0.1) +world w1 {} + +@since(version = 1.0.0) +world w2 {} + +interface in-an-interface { + @since(version = 1.0.0) + foo: func(); + @since(version = 1.0.0) + resource r1; + @since(version = 1.0.0) + resource r2 {} + + @since(version = 1.0.0) + type t1 = u32; + @since(version = 1.0.0) + record t2 { a: u32 } + @since(version = 1.0.0) + enum t3 { a } + @since(version = 1.0.0) + flags t4 { a } + @since(version = 1.0.0) + variant t5 { a } + + @since(version = 1.0.0) + resource r3 { + @since(version = 1.0.0) + constructor(); + + @since(version = 1.0.0) + x1: static func(); + + @since(version = 1.0.0) + x2: func(); + } +} + +interface z {} + +world in-a-world { + @since(version = 1.0.0) + import x: func(); + @since(version = 1.0.0) + export x: func(); + + @since(version = 1.0.0) + import y: interface {} + @since(version = 1.0.0) + export y: interface {} + + @since(version = 1.0.0) + import z; + @since(version = 1.0.0) + export z; + + + @since(version = 1.0.0) + record t1 { x: u32 } + @since(version = 1.0.0) + enum t2 { a } + @since(version = 1.0.0) + variant t3 { a } + @since(version = 1.0.0) + flags t4 { a } + @since(version = 1.0.0) + type t5 = u32; + @since(version = 1.0.0) + resource t6; + @since(version = 1.0.0) + resource t7 { + @since(version = 1.0.0) + constructor(); + } +} diff --git a/crates/wit-parser/tests/ui/since-and-unstable.wit.json b/crates/wit-parser/tests/ui/since-and-unstable.wit.json new file mode 100644 index 0000000000..c5efc9381d --- /dev/null +++ b/crates/wit-parser/tests/ui/since-and-unstable.wit.json @@ -0,0 +1,549 @@ +{ + "worlds": [ + { + "name": "w1", + "imports": {}, + "exports": {}, + "package": 0, + "stability": { + "type": "stable", + "since": "1.0.1" + } + }, + { + "name": "w2", + "imports": {}, + "exports": {}, + "package": 0, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "in-a-world", + "imports": { + "y": { + "interface": { + "id": 5, + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "t1": { + "type": 9 + }, + "t2": { + "type": 10 + }, + "t3": { + "type": 11 + }, + "t4": { + "type": 12 + }, + "t5": { + "type": 13 + }, + "t6": { + "type": 14 + }, + "t7": { + "type": 15 + }, + "x": { + "function": { + "name": "x", + "kind": "freestanding", + "params": [], + "results": [], + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "[constructor]t7": { + "function": { + "name": "[constructor]t7", + "kind": { + "constructor": 15 + }, + "params": [], + "results": [ + { + "type": 17 + } + ], + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + } + }, + "exports": { + "x": { + "function": { + "name": "x", + "kind": "freestanding", + "params": [], + "results": [], + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "y": { + "interface": { + "id": 6, + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "interface-4": { + "interface": { + "id": 4, + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "foo1", + "types": {}, + "functions": {}, + "stability": { + "type": "stable", + "since": "1.0.0" + }, + "package": 0 + }, + { + "name": "foo2", + "types": {}, + "functions": {}, + "stability": { + "type": "stable", + "since": "1.0.0", + "feature": "foo" + }, + "package": 0 + }, + { + "name": "foo3", + "types": {}, + "functions": {}, + "stability": { + "type": "stable", + "since": "1.0.0", + "feature": "foo-bar" + }, + "package": 0 + }, + { + "name": "in-an-interface", + "types": { + "r1": 0, + "r2": 1, + "t1": 2, + "t2": 3, + "t3": 4, + "t4": 5, + "t5": 6, + "r3": 7 + }, + "functions": { + "foo": { + "name": "foo", + "kind": "freestanding", + "params": [], + "results": [], + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + "[constructor]r3": { + "name": "[constructor]r3", + "kind": { + "constructor": 7 + }, + "params": [], + "results": [ + { + "type": 16 + } + ], + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + "[static]r3.x1": { + "name": "[static]r3.x1", + "kind": { + "static": 7 + }, + "params": [], + "results": [], + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + "[method]r3.x2": { + "name": "[method]r3.x2", + "kind": { + "method": 7 + }, + "params": [ + { + "name": "self", + "type": 8 + } + ], + "results": [], + "stability": { + "type": "stable", + "since": "1.0.0" + } + } + }, + "package": 0 + }, + { + "name": "z", + "types": {}, + "functions": {}, + "package": 0 + }, + { + "name": null, + "types": {}, + "functions": {}, + "stability": { + "type": "stable", + "since": "1.0.0" + }, + "package": 0 + }, + { + "name": null, + "types": {}, + "functions": {}, + "stability": { + "type": "stable", + "since": "1.0.0" + }, + "package": 0 + } + ], + "types": [ + { + "name": "r1", + "kind": "resource", + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "r2", + "kind": "resource", + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t1", + "kind": { + "type": "u32" + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t2", + "kind": { + "record": { + "fields": [ + { + "name": "a", + "type": "u32" + } + ] + } + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t3", + "kind": { + "enum": { + "cases": [ + { + "name": "a" + } + ] + } + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t4", + "kind": { + "flags": { + "flags": [ + { + "name": "a" + } + ] + } + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t5", + "kind": { + "variant": { + "cases": [ + { + "name": "a", + "type": null + } + ] + } + }, + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "r3", + "kind": "resource", + "owner": { + "interface": 3 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": null, + "kind": { + "handle": { + "borrow": 7 + } + }, + "owner": null, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t1", + "kind": { + "record": { + "fields": [ + { + "name": "x", + "type": "u32" + } + ] + } + }, + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t2", + "kind": { + "enum": { + "cases": [ + { + "name": "a" + } + ] + } + }, + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t3", + "kind": { + "variant": { + "cases": [ + { + "name": "a", + "type": null + } + ] + } + }, + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t4", + "kind": { + "flags": { + "flags": [ + { + "name": "a" + } + ] + } + }, + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t5", + "kind": { + "type": "u32" + }, + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t6", + "kind": "resource", + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": "t7", + "kind": "resource", + "owner": { + "world": 2 + }, + "stability": { + "type": "stable", + "since": "1.0.0" + } + }, + { + "name": null, + "kind": { + "handle": { + "own": 7 + } + }, + "owner": null + }, + { + "name": null, + "kind": { + "handle": { + "own": 15 + } + }, + "owner": null + } + ], + "packages": [ + { + "name": "a:b@1.0.1", + "interfaces": { + "foo1": 0, + "foo2": 1, + "foo3": 2, + "in-an-interface": 3, + "z": 4 + }, + "worlds": { + "w1": 0, + "w2": 1, + "in-a-world": 2 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json index bd8fdfceed..d77812776b 100644 --- a/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json +++ b/crates/wit-parser/tests/ui/stress-export-elaborate.wit.json @@ -4,36 +4,56 @@ "name": "foo", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } }, "interface-3": { - "interface": 3 + "interface": { + "id": 3 + } }, "interface-4": { - "interface": 4 + "interface": { + "id": 4 + } }, "interface-5": { - "interface": 5 + "interface": { + "id": 5 + } }, "interface-6": { - "interface": 6 + "interface": { + "id": 6 + } }, "interface-7": { - "interface": 7 + "interface": { + "id": 7 + } }, "interface-8": { - "interface": 8 + "interface": { + "id": 8 + } } }, "exports": { "interface-9": { - "interface": 9 + "interface": { + "id": 9 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/world-diamond.wit.json b/crates/wit-parser/tests/ui/world-diamond.wit.json index 037141c345..6dba45edb9 100644 --- a/crates/wit-parser/tests/ui/world-diamond.wit.json +++ b/crates/wit-parser/tests/ui/world-diamond.wit.json @@ -4,13 +4,19 @@ "name": "the-world", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } }, "a": { "function": { diff --git a/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json index b9ad08466f..fffc11557e 100644 --- a/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json +++ b/crates/wit-parser/tests/ui/world-iface-no-collide.wit.json @@ -4,7 +4,9 @@ "name": "bar", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "t": { "type": 1 diff --git a/crates/wit-parser/tests/ui/world-implicit-import1.wit.json b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json index f0b550b712..708c339b40 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import1.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import1.wit.json @@ -4,13 +4,19 @@ "name": "the-world", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "bar": { - "interface": 1 + "interface": { + "id": 1 + } }, "foo": { - "interface": 2 + "interface": { + "id": 2 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/world-implicit-import2.wit.json b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json index 84e150e4db..97049842a4 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import2.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import2.wit.json @@ -4,7 +4,9 @@ "name": "w", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "g": { "type": 1 diff --git a/crates/wit-parser/tests/ui/world-implicit-import3.wit.json b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json index 3e716e41ef..159bee7738 100644 --- a/crates/wit-parser/tests/ui/world-implicit-import3.wit.json +++ b/crates/wit-parser/tests/ui/world-implicit-import3.wit.json @@ -4,7 +4,9 @@ "name": "w", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "g": { "type": 1 diff --git a/crates/wit-parser/tests/ui/world-same-fields4.wit.json b/crates/wit-parser/tests/ui/world-same-fields4.wit.json index c15baf187d..3be16acf9b 100644 --- a/crates/wit-parser/tests/ui/world-same-fields4.wit.json +++ b/crates/wit-parser/tests/ui/world-same-fields4.wit.json @@ -4,15 +4,21 @@ "name": "foo", "imports": { "shared-items": { - "interface": 1 + "interface": { + "id": 1 + } }, "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } } }, "exports": { "a-name": { - "interface": 2 + "interface": { + "id": 2 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json index a4ef0416e9..794e699f45 100644 --- a/crates/wit-parser/tests/ui/world-top-level-resources.wit.json +++ b/crates/wit-parser/tests/ui/world-top-level-resources.wit.json @@ -4,15 +4,21 @@ "name": "proxy", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "exports": { "interface-1": { - "interface": 1 + "interface": { + "id": 1 + } } }, "package": 0 diff --git a/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json index 7fb8ff0ae9..d1e24e97b6 100644 --- a/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json +++ b/crates/wit-parser/tests/ui/worlds-union-dedup.wit.json @@ -4,10 +4,14 @@ "name": "my-world-a", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } } }, "exports": {}, @@ -17,10 +21,14 @@ "name": "my-world-b", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } } }, "exports": {}, @@ -30,10 +38,14 @@ "name": "union-my-world", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "interface-2": { - "interface": 2 + "interface": { + "id": 2 + } } }, "exports": {}, diff --git a/crates/wit-parser/tests/ui/worlds-with-types.wit.json b/crates/wit-parser/tests/ui/worlds-with-types.wit.json index 9ca60c0a3b..70e857d68b 100644 --- a/crates/wit-parser/tests/ui/worlds-with-types.wit.json +++ b/crates/wit-parser/tests/ui/worlds-with-types.wit.json @@ -35,7 +35,9 @@ "name": "bar", "imports": { "interface-0": { - "interface": 0 + "interface": { + "id": 0 + } }, "t": { "type": 3 diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index bc3615ddf6..366d325fda 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -13,7 +13,7 @@ use wat::Detect; use wit_component::{ embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter, }; -use wit_parser::{Resolve, UnresolvedPackage}; +use wit_parser::{PackageId, Resolve, UnresolvedPackage}; /// WebAssembly wit-based component tooling. #[derive(Parser)] @@ -170,6 +170,42 @@ impl NewOpts { } } +#[derive(Parser)] +struct WitResolve { + /// Path to WIT files to load. + /// + /// This can be a directory containing `*.wit` files, a `*.wit` file itself, + /// or a `*.wasm` file which is a WIT package encoded as WebAssembly. + wit: PathBuf, + + /// Features to enable when parsing the `wit` option. + /// + /// This flag enables the `@unstable` feature in WIT documents where the + /// items are otherwise hidden by default. + #[clap(long)] + features: Vec, +} + +impl WitResolve { + fn resolve_with_features(features: &[String]) -> Resolve { + let mut resolve = Resolve::default(); + for feature in features { + for f in feature.split_whitespace() { + for f in f.split(',').filter(|s| !s.is_empty()) { + resolve.features.insert(f.to_string()); + } + } + } + return resolve; + } + + fn load(&self) -> Result<(Resolve, PackageId)> { + let mut resolve = Self::resolve_with_features(&self.features); + let id = resolve.push_path(&self.wit)?.0; + Ok((resolve, id)) + } +} + /// Embeds metadata for a component inside of a core wasm module. /// /// This subcommand is a convenience tool provided for producing core wasm @@ -187,11 +223,8 @@ impl NewOpts { /// working with text format wasm. #[derive(Parser)] pub struct EmbedOpts { - /// The WIT package where the `world` that the core wasm module implements - /// lives. - /// - /// This can either be a directory or a path to a single `*.wit` file. - wit: PathBuf, + #[clap(flatten)] + resolve: WitResolve, #[clap(flatten)] io: wasm_tools::InputOutput, @@ -242,8 +275,7 @@ impl EmbedOpts { } else { Some(self.io.parse_input_wasm()?) }; - let mut resolve = Resolve::default(); - let id = resolve.push_path(&self.wit)?.0; + let (resolve, id) = self.resolve.load()?; let world = resolve.select_world(id, self.world.as_deref())?; let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world)); @@ -461,6 +493,13 @@ pub struct WitOpts { conflicts_with = "wat" )] json: bool, + + /// Features to enable when parsing the `wit` option. + /// + /// This flag enables the `@unstable` feature in WIT documents where the + /// items are otherwise hidden by default. + #[clap(long)] + features: Vec, } impl WitOpts { @@ -491,7 +530,7 @@ impl WitOpts { // `parse_wit_from_path`. if let Some(input) = &self.input { if input.is_dir() { - let mut resolve = Resolve::default(); + let mut resolve = WitResolve::resolve_with_features(&self.features); let id = resolve.push_dir(&input)?.0; return Ok(DecodedWasm::WitPackage(resolve, id)); } @@ -539,7 +578,7 @@ impl WitOpts { Ok(s) => s, Err(_) => bail!("input was not valid utf-8"), }; - let mut resolve = Resolve::default(); + let mut resolve = WitResolve::resolve_with_features(&self.features); let pkg = UnresolvedPackage::parse(path, input)?; let id = resolve.push(pkg)?; Ok(DecodedWasm::WitPackage(resolve, id)) @@ -645,10 +684,8 @@ pub struct TargetsOpts { #[clap(flatten)] general: wasm_tools::GeneralOpts, - /// The WIT package containing the `world` used to test a component for conformance. - /// - /// This can either be a directory or a path to a single `*.wit` file. - wit: PathBuf, + #[clap(flatten)] + resolve: WitResolve, /// The world used to test whether a component conforms to its signature. #[clap(short, long)] @@ -665,8 +702,7 @@ impl TargetsOpts { /// Executes the application. fn run(self) -> Result<()> { - let mut resolve = Resolve::default(); - let package_id = resolve.push_path(&self.wit)?.0; + let (resolve, package_id) = self.resolve.load()?; let world = resolve.select_world(package_id, self.world.as_deref())?; let component_to_test = self.input.parse_wasm()?; @@ -683,12 +719,8 @@ pub struct SemverCheckOpts { #[clap(flatten)] general: wasm_tools::GeneralOpts, - /// The WIT package containing the `prev` and `new` worlds used in - /// arguments. - /// - /// This can either be a directory, a path to a single `*.wit` file, or a - /// path to a wasm-encoded WIT package. - wit: PathBuf, + #[clap(flatten)] + resolve: WitResolve, /// The "previous" world, or older version, of what's being tested. /// @@ -710,8 +742,7 @@ impl SemverCheckOpts { } fn run(self) -> Result<()> { - let mut resolve = Resolve::default(); - let package_id = resolve.push_path(&self.wit)?.0; + let (resolve, package_id) = self.resolve.load()?; let prev = resolve.select_world(package_id, Some(&self.prev))?; let new = resolve.select_world(package_id, Some(&self.new))?; wit_component::semver_check(resolve, prev, new)?; diff --git a/tests/cli/wit-stability-in-binary-format.wit b/tests/cli/wit-stability-in-binary-format.wit new file mode 100644 index 0000000000..73a33b271b --- /dev/null +++ b/tests/cli/wit-stability-in-binary-format.wit @@ -0,0 +1,39 @@ +// RUN: component wit % --wasm | component wit + +package a:b; + +@since(version = 1.0.0) +interface foo { + @since(version = 1.0.0) + type t = u32; + + @since(version = 1.0.0) + f: func(); + + @since(version = 1.0.0) + resource r { + @since(version = 1.0.0) + constructor(); + } +} + +@since(version = 1.0.0) +world w { + @since(version = 1.0.0) + import foo; + @since(version = 1.0.0) + export foo; + + @since(version = 1.0.0) + import f: func(); + @since(version = 1.0.0) + export f: func(); + + @since(version = 1.0.0) + type t = u32; + + @since(version = 1.0.0) + import a: interface {} + @since(version = 1.0.0) + export a: interface {} +} diff --git a/tests/cli/wit-stability-in-binary-format.wit.stdout b/tests/cli/wit-stability-in-binary-format.wit.stdout new file mode 100644 index 0000000000..f3191af2d4 --- /dev/null +++ b/tests/cli/wit-stability-in-binary-format.wit.stdout @@ -0,0 +1,39 @@ +/// RUN: component wit % --wasm | component wit +package a:b; + +@since(version = 1.0.0) +interface foo { + @since(version = 1.0.0) + type t = u32; + + @since(version = 1.0.0) + resource r { + @since(version = 1.0.0) + constructor(); + } + + @since(version = 1.0.0) + f: func(); +} + +@since(version = 1.0.0) +world w { + @since(version = 1.0.0) + import foo; + @since(version = 1.0.0) + import a: interface { + } + @since(version = 1.0.0) + import f: func(); + + @since(version = 1.0.0) + type t = u32; + + @since(version = 1.0.0) + export f: func(); + @since(version = 1.0.0) + export foo; + @since(version = 1.0.0) + export a: interface { + } +} diff --git a/tests/cli/wit-stability-inherited.wit b/tests/cli/wit-stability-inherited.wit new file mode 100644 index 0000000000..572e1c2579 --- /dev/null +++ b/tests/cli/wit-stability-inherited.wit @@ -0,0 +1,25 @@ +// RUN: component wit % + +package a:b; + +interface foo { + type t = u32; +} + +interface bar { + use foo.{t}; +} + +interface baz { + use bar.{t}; +} + +world x { + @since(version = 1.0.0) + import baz; +} + +world y { + @since(version = 1.0.0) + export baz; +} diff --git a/tests/cli/wit-stability-inherited.wit.stdout b/tests/cli/wit-stability-inherited.wit.stdout new file mode 100644 index 0000000000..651b90e2f8 --- /dev/null +++ b/tests/cli/wit-stability-inherited.wit.stdout @@ -0,0 +1,32 @@ +/// RUN: component wit % +package a:b; + +interface foo { + type t = u32; +} + +interface bar { + use foo.{t}; +} + +interface baz { + use bar.{t}; +} + +world x { + @since(version = 1.0.0) + import foo; + @since(version = 1.0.0) + import bar; + @since(version = 1.0.0) + import baz; +} +world y { + @since(version = 1.0.0) + import foo; + @since(version = 1.0.0) + import bar; + + @since(version = 1.0.0) + export baz; +} diff --git a/tests/cli/wit-with-features.wit b/tests/cli/wit-with-features.wit new file mode 100644 index 0000000000..1db1d9161f --- /dev/null +++ b/tests/cli/wit-with-features.wit @@ -0,0 +1,56 @@ +// RUN: component wit --features foo % + +package a:b; + +@unstable(feature = foo) +interface foo { + @unstable(feature = foo) + type t = u32; +} + +@unstable(feature = foo) +interface bar { + @unstable(feature = foo) + use foo.{t}; + + @unstable(feature = foo) + record x { y: t } +} + +@unstable(feature = foo) +world the-world { + @unstable(feature = foo) + import foo; + @unstable(feature = foo) + export bar; + + @unstable(feature = foo) + import x: func(); + @unstable(feature = foo) + export y: func(); + + @unstable(feature = foo) + resource thing { + @unstable(feature = foo) + constructor(); + } +} + +interface another-interface { + @unstable(feature = foo) + resource x { + @unstable(feature = foo) + constructor(); + + @unstable(feature = foo) + x: static func(); + + @unstable(feature = foo) + y: func(); + } + + @unstable(feature = foo) + variant y { + x(x), + } +} diff --git a/tests/cli/wit-with-features.wit.stdout b/tests/cli/wit-with-features.wit.stdout new file mode 100644 index 0000000000..b402b6aa07 --- /dev/null +++ b/tests/cli/wit-with-features.wit.stdout @@ -0,0 +1,55 @@ +/// RUN: component wit --features foo % +package a:b; + +@unstable(feature = foo) +interface foo { + @unstable(feature = foo) + type t = u32; +} + +@unstable(feature = foo) +interface bar { + @unstable(feature = foo) + use foo.{t}; + + @unstable(feature = foo) + record x { + y: t, + } +} + +interface another-interface { + @unstable(feature = foo) + resource x { + @unstable(feature = foo) + constructor(); + @unstable(feature = foo) + x: static func(); + @unstable(feature = foo) + y: func(); + } + + @unstable(feature = foo) + variant y { + x(x), + } +} + +@unstable(feature = foo) +world the-world { + @unstable(feature = foo) + import foo; + @unstable(feature = foo) + import x: func(); + + @unstable(feature = foo) + resource thing { + @unstable(feature = foo) + constructor(); + } + + @unstable(feature = foo) + export y: func(); + @unstable(feature = foo) + export bar; +} diff --git a/tests/local/custom-page-sizes/custom-page-sizes-invalid.wast b/tests/local/custom-page-sizes/custom-page-sizes-invalid.wast index 83f3db7437..8436f5707c 100644 --- a/tests/local/custom-page-sizes/custom-page-sizes-invalid.wast +++ b/tests/local/custom-page-sizes/custom-page-sizes-invalid.wast @@ -1,9 +1,72 @@ +;; Page size that is not a power of two. (assert_malformed (module quote "(memory 0 (pagesize 3))") "invalid custom page size" ) -;; Power of two page size that is larger than 64KiB. +;; Power-of-two page sizes that are not 1 or 64KiB. +(assert_invalid + (module (memory 0 (pagesize 2))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 4))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 8))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 16))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 32))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 64))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 128))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 256))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 512))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 1024))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 2048))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 4096))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 8192))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 16384))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 32768))) + "invalid custom page size" +) + +;; Power-of-two page size that is larger than 64KiB. (assert_invalid (module (memory 0 (pagesize 0x20000))) "invalid custom page size" diff --git a/tests/local/custom-page-sizes/custom-page-sizes.wast b/tests/local/custom-page-sizes/custom-page-sizes.wast index 3132c15b2a..0e9909ea67 100644 --- a/tests/local/custom-page-sizes/custom-page-sizes.wast +++ b/tests/local/custom-page-sizes/custom-page-sizes.wast @@ -1,39 +1,9 @@ ;; Check all the valid custom page sizes. (module (memory 1 (pagesize 1))) -(module (memory 1 (pagesize 2))) -(module (memory 1 (pagesize 4))) -(module (memory 1 (pagesize 8))) -(module (memory 1 (pagesize 16))) -(module (memory 1 (pagesize 32))) -(module (memory 1 (pagesize 64))) -(module (memory 1 (pagesize 128))) -(module (memory 1 (pagesize 256))) -(module (memory 1 (pagesize 512))) -(module (memory 1 (pagesize 1024))) -(module (memory 1 (pagesize 2048))) -(module (memory 1 (pagesize 4096))) -(module (memory 1 (pagesize 8192))) -(module (memory 1 (pagesize 16384))) -(module (memory 1 (pagesize 32768))) (module (memory 1 (pagesize 65536))) ;; Check them all again with maximums specified. (module (memory 1 2 (pagesize 1))) -(module (memory 1 2 (pagesize 2))) -(module (memory 1 2 (pagesize 4))) -(module (memory 1 2 (pagesize 8))) -(module (memory 1 2 (pagesize 16))) -(module (memory 1 2 (pagesize 32))) -(module (memory 1 2 (pagesize 64))) -(module (memory 1 2 (pagesize 128))) -(module (memory 1 2 (pagesize 256))) -(module (memory 1 2 (pagesize 512))) -(module (memory 1 2 (pagesize 1024))) -(module (memory 1 2 (pagesize 2048))) -(module (memory 1 2 (pagesize 4096))) -(module (memory 1 2 (pagesize 8192))) -(module (memory 1 2 (pagesize 16384))) -(module (memory 1 2 (pagesize 32768))) (module (memory 1 2 (pagesize 65536))) ;; Check the behavior of memories with page size 1. @@ -70,40 +40,6 @@ (assert_return (invoke "load" (i32.const 131071)) (i32.const 1)) (assert_trap (invoke "load" (i32.const 131072)) "out of bounds memory access") -;; Check the behavior of memories with page size 4. -(module - (memory 0 (pagesize 4)) - (func (export "size") (result i32) - memory.size - ) - (func (export "grow") (param i32) (result i32) - (memory.grow (local.get 0)) - ) - (func (export "load") (param i32) (result i32) - (i32.load (local.get 0)) - ) - (func (export "store") (param i32 i32) - (i32.store (local.get 0) (local.get 1)) - ) -) - -(assert_return (invoke "size") (i32.const 0)) -(assert_trap (invoke "load" (i32.const 0)) "out of bounds memory access") - -(assert_return (invoke "grow" (i32.const 65536)) (i32.const 0)) -(assert_return (invoke "size") (i32.const 65536)) -(assert_return (invoke "load" (i32.const 262143)) (i32.const 0)) -(assert_return (invoke "store" (i32.const 262143) (i32.const 1))) -(assert_return (invoke "load" (i32.const 262143)) (i32.const 1)) -(assert_trap (invoke "load" (i32.const 262144)) "out of bounds memory access") - -(assert_return (invoke "grow" (i32.const 65536)) (i32.const 65536)) -(assert_return (invoke "size") (i32.const 131072)) -(assert_return (invoke "load" (i32.const 524287)) (i32.const 0)) -(assert_return (invoke "store" (i32.const 524287) (i32.const 1))) -(assert_return (invoke "load" (i32.const 524287)) (i32.const 1)) -(assert_trap (invoke "load" (i32.const 524288)) "out of bounds memory access") - ;; Although smaller page sizes let us get to memories larger than 2**16 pages, ;; we can't do that with the default page size, even if we explicitly state it ;; as a custom page size. @@ -116,3 +52,37 @@ (assert_return (invoke "size") (i32.const 0)) (assert_return (invoke "grow" (i32.const 65537)) (i32.const -1)) (assert_return (invoke "size") (i32.const 0)) + +;; Can copy between memories of different page sizes. +(module + (memory $small 10 (pagesize 1)) + (memory $large 1 (pagesize 65536)) + + (data (memory $small) (i32.const 0) "\11\22\33\44") + (data (memory $large) (i32.const 0) "\55\66\77\88") + + (func (export "copy-small-to-large") (param i32 i32 i32) + (memory.copy $small $large (local.get 0) (local.get 1) (local.get 2)) + ) + + (func (export "copy-large-to-small") (param i32 i32 i32) + (memory.copy $large $small (local.get 0) (local.get 1) (local.get 2)) + ) + + (func (export "load8-small") (param i32) (result i32) + (i32.load8_u (local.get 0)) + ) + + (func (export "load8-large") (param i32) (result i32) + (i32.load8_u (local.get 0)) + ) +) + +(assert_return (invoke "copy-small-to-large") (i32.const 0) (i32.const 2) (i32.const 6)) +(assert_return (invoke "load8-large") (i32.const 6) (i32.const 0x11)) +(assert_return (invoke "load8-large") (i32.const 7) (i32.const 0x22)) + +(assert_return (invoke "copy-large-to-small") (i32.const 1) (i32.const 3) (i32.const 4)) +(assert_return (invoke "load8-large") (i32.const 4) (i32.const 0x66)) +(assert_return (invoke "load8-large") (i32.const 5) (i32.const 0x77)) +(assert_return (invoke "load8-large") (i32.const 6) (i32.const 0x88)) diff --git a/tests/local/id.wast b/tests/local/id.wast new file mode 100644 index 0000000000..222f5fcd6f --- /dev/null +++ b/tests/local/id.wast @@ -0,0 +1,4 @@ +(assert_malformed + (module quote + "(func $)") + "empty identifier") diff --git a/tests/local/invalid-utf8-id.wast b/tests/local/invalid-utf8-id.wast new file mode 100644 index 0000000000..8500d1371a --- /dev/null +++ b/tests/local/invalid-utf8-id.wast @@ -0,0 +1,5 @@ +(assert_malformed + (module quote + "(func $\"\\ff\")" + ) + "malformed UTF-8 encoding of string-based id") diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 3f28b71665..2dfd5b12fe 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -788,5 +788,11 @@ fn error_matches(error: &str, message: &str) -> bool { return error.contains("invalid u32 number: constant out of range"); } + // WebAssembly/annotations#25 - the spec interpreter's lexer is different + // than ours which produces a different error message. + if message == "empty identifier" || message == "empty annotation id" { + return error.contains("invalid character in string"); + } + return false; } diff --git a/tests/snapshots/local/component-model/import.wast/62.print b/tests/snapshots/local/component-model/import.wast/62.print deleted file mode 100644 index 0f3e3ae93a..0000000000 --- a/tests/snapshots/local/component-model/import.wast/62.print +++ /dev/null @@ -1,8 +0,0 @@ -(component - (type (;0;) (func)) - (import "relative-url=<>" (func (;0;) (type 0))) - (type (;1;) (func)) - (import "relative-url=" (func (;1;) (type 1))) - (type (;2;) (func)) - (import "relative-url=,integrity=" (func (;2;) (type 2))) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes-invalid.wast.json b/tests/snapshots/local/custom-page-sizes/custom-page-sizes-invalid.wast.json index 148d28e839..4ce8847be7 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes-invalid.wast.json +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes-invalid.wast.json @@ -3,24 +3,129 @@ "commands": [ { "type": "assert_malformed", - "line": 2, + "line": 3, "filename": "custom-page-sizes-invalid.0.wat", "text": "invalid custom page size", "module_type": "text" }, { "type": "assert_invalid", - "line": 8, + "line": 9, "filename": "custom-page-sizes-invalid.1.wasm", "text": "invalid custom page size", "module_type": "binary" }, { - "type": "assert_malformed", - "line": 15, + "type": "assert_invalid", + "line": 13, "filename": "custom-page-sizes-invalid.2.wasm", "text": "invalid custom page size", "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 17, + "filename": "custom-page-sizes-invalid.3.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 21, + "filename": "custom-page-sizes-invalid.4.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 25, + "filename": "custom-page-sizes-invalid.5.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 29, + "filename": "custom-page-sizes-invalid.6.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 33, + "filename": "custom-page-sizes-invalid.7.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 37, + "filename": "custom-page-sizes-invalid.8.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 41, + "filename": "custom-page-sizes-invalid.9.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 45, + "filename": "custom-page-sizes-invalid.10.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 49, + "filename": "custom-page-sizes-invalid.11.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 53, + "filename": "custom-page-sizes-invalid.12.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 57, + "filename": "custom-page-sizes-invalid.13.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 61, + "filename": "custom-page-sizes-invalid.14.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 65, + "filename": "custom-page-sizes-invalid.15.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 71, + "filename": "custom-page-sizes-invalid.16.wasm", + "text": "invalid custom page size", + "module_type": "binary" + }, + { + "type": "assert_malformed", + "line": 78, + "filename": "custom-page-sizes-invalid.17.wasm", + "text": "invalid custom page size", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast.json b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast.json index cb8a60cbe2..4efa263e54 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast.json +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast.json @@ -11,174 +11,24 @@ "line": 3, "filename": "custom-page-sizes.1.wasm" }, - { - "type": "module", - "line": 4, - "filename": "custom-page-sizes.2.wasm" - }, - { - "type": "module", - "line": 5, - "filename": "custom-page-sizes.3.wasm" - }, { "type": "module", "line": 6, - "filename": "custom-page-sizes.4.wasm" + "filename": "custom-page-sizes.2.wasm" }, { "type": "module", "line": 7, - "filename": "custom-page-sizes.5.wasm" - }, - { - "type": "module", - "line": 8, - "filename": "custom-page-sizes.6.wasm" - }, - { - "type": "module", - "line": 9, - "filename": "custom-page-sizes.7.wasm" + "filename": "custom-page-sizes.3.wasm" }, { "type": "module", "line": 10, - "filename": "custom-page-sizes.8.wasm" - }, - { - "type": "module", - "line": 11, - "filename": "custom-page-sizes.9.wasm" - }, - { - "type": "module", - "line": 12, - "filename": "custom-page-sizes.10.wasm" - }, - { - "type": "module", - "line": 13, - "filename": "custom-page-sizes.11.wasm" - }, - { - "type": "module", - "line": 14, - "filename": "custom-page-sizes.12.wasm" - }, - { - "type": "module", - "line": 15, - "filename": "custom-page-sizes.13.wasm" - }, - { - "type": "module", - "line": 16, - "filename": "custom-page-sizes.14.wasm" - }, - { - "type": "module", - "line": 17, - "filename": "custom-page-sizes.15.wasm" - }, - { - "type": "module", - "line": 18, - "filename": "custom-page-sizes.16.wasm" - }, - { - "type": "module", - "line": 21, - "filename": "custom-page-sizes.17.wasm" - }, - { - "type": "module", - "line": 22, - "filename": "custom-page-sizes.18.wasm" - }, - { - "type": "module", - "line": 23, - "filename": "custom-page-sizes.19.wasm" - }, - { - "type": "module", - "line": 24, - "filename": "custom-page-sizes.20.wasm" - }, - { - "type": "module", - "line": 25, - "filename": "custom-page-sizes.21.wasm" - }, - { - "type": "module", - "line": 26, - "filename": "custom-page-sizes.22.wasm" - }, - { - "type": "module", - "line": 27, - "filename": "custom-page-sizes.23.wasm" - }, - { - "type": "module", - "line": 28, - "filename": "custom-page-sizes.24.wasm" - }, - { - "type": "module", - "line": 29, - "filename": "custom-page-sizes.25.wasm" - }, - { - "type": "module", - "line": 30, - "filename": "custom-page-sizes.26.wasm" - }, - { - "type": "module", - "line": 31, - "filename": "custom-page-sizes.27.wasm" - }, - { - "type": "module", - "line": 32, - "filename": "custom-page-sizes.28.wasm" - }, - { - "type": "module", - "line": 33, - "filename": "custom-page-sizes.29.wasm" - }, - { - "type": "module", - "line": 34, - "filename": "custom-page-sizes.30.wasm" - }, - { - "type": "module", - "line": 35, - "filename": "custom-page-sizes.31.wasm" - }, - { - "type": "module", - "line": 36, - "filename": "custom-page-sizes.32.wasm" - }, - { - "type": "module", - "line": 37, - "filename": "custom-page-sizes.33.wasm" - }, - { - "type": "module", - "line": 40, - "filename": "custom-page-sizes.34.wasm" + "filename": "custom-page-sizes.4.wasm" }, { "type": "assert_return", - "line": 56, + "line": 26, "action": { "type": "invoke", "field": "size", @@ -193,7 +43,7 @@ }, { "type": "assert_trap", - "line": 57, + "line": 27, "action": { "type": "invoke", "field": "load", @@ -208,7 +58,7 @@ }, { "type": "assert_return", - "line": 59, + "line": 29, "action": { "type": "invoke", "field": "grow", @@ -228,7 +78,7 @@ }, { "type": "assert_return", - "line": 60, + "line": 30, "action": { "type": "invoke", "field": "size", @@ -243,7 +93,7 @@ }, { "type": "assert_return", - "line": 61, + "line": 31, "action": { "type": "invoke", "field": "load", @@ -263,7 +113,7 @@ }, { "type": "assert_return", - "line": 62, + "line": 32, "action": { "type": "invoke", "field": "store", @@ -282,7 +132,7 @@ }, { "type": "assert_return", - "line": 63, + "line": 33, "action": { "type": "invoke", "field": "load", @@ -302,7 +152,7 @@ }, { "type": "assert_trap", - "line": 64, + "line": 34, "action": { "type": "invoke", "field": "load", @@ -317,7 +167,7 @@ }, { "type": "assert_return", - "line": 66, + "line": 36, "action": { "type": "invoke", "field": "grow", @@ -337,7 +187,7 @@ }, { "type": "assert_return", - "line": 67, + "line": 37, "action": { "type": "invoke", "field": "size", @@ -352,7 +202,7 @@ }, { "type": "assert_return", - "line": 68, + "line": 38, "action": { "type": "invoke", "field": "load", @@ -372,7 +222,7 @@ }, { "type": "assert_return", - "line": 69, + "line": 39, "action": { "type": "invoke", "field": "store", @@ -391,7 +241,7 @@ }, { "type": "assert_return", - "line": 70, + "line": 40, "action": { "type": "invoke", "field": "load", @@ -411,7 +261,7 @@ }, { "type": "assert_trap", - "line": 71, + "line": 41, "action": { "type": "invoke", "field": "load", @@ -426,12 +276,12 @@ }, { "type": "module", - "line": 74, - "filename": "custom-page-sizes.35.wasm" + "line": 46, + "filename": "custom-page-sizes.5.wasm" }, { "type": "assert_return", - "line": 90, + "line": 52, "action": { "type": "invoke", "field": "size", @@ -444,69 +294,34 @@ } ] }, - { - "type": "assert_trap", - "line": 91, - "action": { - "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "0" - } - ] - }, - "text": "out of bounds memory access" - }, { "type": "assert_return", - "line": 93, + "line": 53, "action": { "type": "invoke", "field": "grow", "args": [ { "type": "i32", - "value": "65536" + "value": "65537" } ] }, "expected": [ { "type": "i32", - "value": "0" + "value": "-1" } ] }, { "type": "assert_return", - "line": 94, + "line": 54, "action": { "type": "invoke", "field": "size", "args": [] }, - "expected": [ - { - "type": "i32", - "value": "65536" - } - ] - }, - { - "type": "assert_return", - "line": 95, - "action": { - "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "262143" - } - ] - }, "expected": [ { "type": "i32", @@ -515,220 +330,148 @@ ] }, { - "type": "assert_return", - "line": 96, - "action": { - "type": "invoke", - "field": "store", - "args": [ - { - "type": "i32", - "value": "262143" - }, - { - "type": "i32", - "value": "1" - } - ] - }, - "expected": [] + "type": "module", + "line": 57, + "filename": "custom-page-sizes.6.wasm" }, { "type": "assert_return", - "line": 97, + "line": 81, "action": { "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "262143" - } - ] + "field": "copy-small-to-large", + "args": [] }, "expected": [ { "type": "i32", - "value": "1" - } - ] - }, - { - "type": "assert_trap", - "line": 98, - "action": { - "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "262144" - } - ] - }, - "text": "out of bounds memory access" - }, - { - "type": "assert_return", - "line": 100, - "action": { - "type": "invoke", - "field": "grow", - "args": [ - { - "type": "i32", - "value": "65536" - } - ] - }, - "expected": [ + "value": "0" + }, { "type": "i32", - "value": "65536" + "value": "2" + }, + { + "type": "i32", + "value": "6" } ] }, { "type": "assert_return", - "line": 101, + "line": 82, "action": { "type": "invoke", - "field": "size", + "field": "load8-large", "args": [] }, "expected": [ { "type": "i32", - "value": "131072" + "value": "6" + }, + { + "type": "i32", + "value": "17" } ] }, { "type": "assert_return", - "line": 102, + "line": 83, "action": { "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "524287" - } - ] + "field": "load8-large", + "args": [] }, "expected": [ { "type": "i32", - "value": "0" + "value": "7" + }, + { + "type": "i32", + "value": "34" } ] }, { "type": "assert_return", - "line": 103, + "line": 85, "action": { "type": "invoke", - "field": "store", - "args": [ - { - "type": "i32", - "value": "524287" - }, - { - "type": "i32", - "value": "1" - } - ] - }, - "expected": [] - }, - { - "type": "assert_return", - "line": 104, - "action": { - "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "524287" - } - ] + "field": "copy-large-to-small", + "args": [] }, "expected": [ { "type": "i32", "value": "1" + }, + { + "type": "i32", + "value": "3" + }, + { + "type": "i32", + "value": "4" } ] }, - { - "type": "assert_trap", - "line": 105, - "action": { - "type": "invoke", - "field": "load", - "args": [ - { - "type": "i32", - "value": "524288" - } - ] - }, - "text": "out of bounds memory access" - }, - { - "type": "module", - "line": 110, - "filename": "custom-page-sizes.36.wasm" - }, { "type": "assert_return", - "line": 116, + "line": 86, "action": { "type": "invoke", - "field": "size", + "field": "load8-large", "args": [] }, "expected": [ { "type": "i32", - "value": "0" + "value": "4" + }, + { + "type": "i32", + "value": "102" } ] }, { "type": "assert_return", - "line": 117, + "line": 87, "action": { "type": "invoke", - "field": "grow", - "args": [ - { - "type": "i32", - "value": "65537" - } - ] + "field": "load8-large", + "args": [] }, "expected": [ { "type": "i32", - "value": "-1" + "value": "5" + }, + { + "type": "i32", + "value": "119" } ] }, { "type": "assert_return", - "line": 118, + "line": 88, "action": { "type": "invoke", - "field": "size", + "field": "load8-large", "args": [] }, "expected": [ { "type": "i32", - "value": "0" + "value": "6" + }, + { + "type": "i32", + "value": "136" } ] } diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/1.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/1.print index d64978992a..c263f1833f 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/1.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/1.print @@ -1,3 +1,3 @@ (module - (memory (;0;) 1(pagesize 0x2)) + (memory (;0;) 1(pagesize 0x10000)) ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/10.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/10.print deleted file mode 100644 index 95c594b2dd..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/10.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x400)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/11.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/11.print deleted file mode 100644 index 8733c88708..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/11.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x800)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/12.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/12.print deleted file mode 100644 index 7aa6bfca81..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/12.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x1000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/13.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/13.print deleted file mode 100644 index 9e81289b0d..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/13.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x2000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/14.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/14.print deleted file mode 100644 index e7992c880b..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/14.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x4000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/15.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/15.print deleted file mode 100644 index 95c30b5b7d..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/15.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x8000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/16.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/16.print deleted file mode 100644 index c263f1833f..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/16.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x10000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/17.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/17.print deleted file mode 100644 index b28b44796e..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/17.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x1)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/18.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/18.print deleted file mode 100644 index fb6f4167fc..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/18.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x2)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/19.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/19.print index 4ef285e5d4..2f377e5a28 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/19.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/19.print @@ -1,3 +1,9 @@ (module - (memory (;0;) 1 2(pagesize 0x4)) + (type (;0;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param i32) (result i32) + local.get 0 + memory.grow + ) + (memory (;0;) 0(pagesize 0x10000)) + (export "grow" (func 0)) ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/2.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/2.print index 4b59733535..b28b44796e 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/2.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/2.print @@ -1,3 +1,3 @@ (module - (memory (;0;) 1(pagesize 0x4)) + (memory (;0;) 1 2(pagesize 0x1)) ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/20.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/20.print deleted file mode 100644 index 856b94badc..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/20.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x8)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/21.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/21.print deleted file mode 100644 index 6e89c150f6..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/21.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x10)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/22.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/22.print deleted file mode 100644 index 7a208af490..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/22.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x20)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/23.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/23.print index f71aeb6620..1c3b7326d8 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/23.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/23.print @@ -1,3 +1,32 @@ (module - (memory (;0;) 1 2(pagesize 0x40)) + (type (;0;) (func (param i32 i32 i32))) + (type (;1;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + memory.copy $small $large + ) + (func (;1;) (type 0) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + memory.copy $large $small + ) + (func (;2;) (type 1) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (func (;3;) (type 1) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (memory $small (;0;) 10(pagesize 0x1)) + (memory $large (;1;) 1(pagesize 0x10000)) + (export "copy-small-to-large" (func 0)) + (export "copy-large-to-small" (func 1)) + (export "load8-small" (func 2)) + (export "load8-large" (func 3)) + (data (;0;) (i32.const 0) "\11\223D") + (data (;1;) (memory $large) (i32.const 0) "Ufw\88") ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/24.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/24.print deleted file mode 100644 index ff0f9a9712..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/24.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x80)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/25.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/25.print deleted file mode 100644 index 3cf7a788a6..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/25.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x100)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/26.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/26.print deleted file mode 100644 index d42f2812e6..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/26.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x200)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/27.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/27.print deleted file mode 100644 index 0c210e3114..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/27.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x400)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/28.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/28.print deleted file mode 100644 index 039df3ae07..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/28.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x800)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/29.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/29.print deleted file mode 100644 index 1f62101a4f..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/29.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x1000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/3.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/3.print index f163e6608b..afb7ad5c56 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/3.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/3.print @@ -1,3 +1,3 @@ (module - (memory (;0;) 1(pagesize 0x8)) + (memory (;0;) 1 2(pagesize 0x10000)) ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/30.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/30.print deleted file mode 100644 index 79d258eb4e..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/30.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x2000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/31.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/31.print deleted file mode 100644 index c1d9489a0f..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/31.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x4000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/32.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/32.print deleted file mode 100644 index e7d337a342..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/32.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x8000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/33.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/33.print deleted file mode 100644 index afb7ad5c56..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/33.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1 2(pagesize 0x10000)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/34.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/34.print deleted file mode 100644 index 388265c290..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/34.print +++ /dev/null @@ -1,26 +0,0 @@ -(module - (type (;0;) (func (result i32))) - (type (;1;) (func (param i32) (result i32))) - (type (;2;) (func (param i32 i32))) - (func (;0;) (type 0) (result i32) - memory.size - ) - (func (;1;) (type 1) (param i32) (result i32) - local.get 0 - memory.grow - ) - (func (;2;) (type 1) (param i32) (result i32) - local.get 0 - i32.load8_u - ) - (func (;3;) (type 2) (param i32 i32) - local.get 0 - local.get 1 - i32.store - ) - (memory (;0;) 0(pagesize 0x1)) - (export "size" (func 0)) - (export "grow" (func 1)) - (export "load" (func 2)) - (export "store" (func 3)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/4.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/4.print index 1fe3d04bf1..388265c290 100644 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/4.print +++ b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/4.print @@ -1,3 +1,26 @@ (module - (memory (;0;) 1(pagesize 0x10)) + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (func (;0;) (type 0) (result i32) + memory.size + ) + (func (;1;) (type 1) (param i32) (result i32) + local.get 0 + memory.grow + ) + (func (;2;) (type 1) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (func (;3;) (type 2) (param i32 i32) + local.get 0 + local.get 1 + i32.store + ) + (memory (;0;) 0(pagesize 0x1)) + (export "size" (func 0)) + (export "grow" (func 1)) + (export "load" (func 2)) + (export "store" (func 3)) ) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/49.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/49.print deleted file mode 100644 index 758f2d3499..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/49.print +++ /dev/null @@ -1,26 +0,0 @@ -(module - (type (;0;) (func (result i32))) - (type (;1;) (func (param i32) (result i32))) - (type (;2;) (func (param i32 i32))) - (func (;0;) (type 0) (result i32) - memory.size - ) - (func (;1;) (type 1) (param i32) (result i32) - local.get 0 - memory.grow - ) - (func (;2;) (type 1) (param i32) (result i32) - local.get 0 - i32.load - ) - (func (;3;) (type 2) (param i32 i32) - local.get 0 - local.get 1 - i32.store - ) - (memory (;0;) 0(pagesize 0x4)) - (export "size" (func 0)) - (export "grow" (func 1)) - (export "load" (func 2)) - (export "store" (func 3)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/5.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/5.print deleted file mode 100644 index a210ac4bf3..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/5.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x20)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/6.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/6.print deleted file mode 100644 index 4c98bf4393..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/6.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x40)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/64.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/64.print deleted file mode 100644 index 2f377e5a28..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/64.print +++ /dev/null @@ -1,9 +0,0 @@ -(module - (type (;0;) (func (param i32) (result i32))) - (func (;0;) (type 0) (param i32) (result i32) - local.get 0 - memory.grow - ) - (memory (;0;) 0(pagesize 0x10000)) - (export "grow" (func 0)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/7.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/7.print deleted file mode 100644 index d82edd6de5..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/7.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x80)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/8.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/8.print deleted file mode 100644 index daf57e3daf..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/8.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x100)) -) diff --git a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/9.print b/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/9.print deleted file mode 100644 index 1a6096b43b..0000000000 --- a/tests/snapshots/local/custom-page-sizes/custom-page-sizes.wast/9.print +++ /dev/null @@ -1,3 +0,0 @@ -(module - (memory (;0;) 1(pagesize 0x200)) -) diff --git a/tests/snapshots/local/id.wast.json b/tests/snapshots/local/id.wast.json new file mode 100644 index 0000000000..728a67e245 --- /dev/null +++ b/tests/snapshots/local/id.wast.json @@ -0,0 +1,12 @@ +{ + "source_filename": "tests/local/id.wast", + "commands": [ + { + "type": "assert_malformed", + "line": 2, + "filename": "id.0.wat", + "text": "empty identifier", + "module_type": "text" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/invalid-utf8-id.wast.json b/tests/snapshots/local/invalid-utf8-id.wast.json new file mode 100644 index 0000000000..d3549e5040 --- /dev/null +++ b/tests/snapshots/local/invalid-utf8-id.wast.json @@ -0,0 +1,12 @@ +{ + "source_filename": "tests/local/invalid-utf8-id.wast", + "commands": [ + { + "type": "assert_malformed", + "line": 2, + "filename": "invalid-utf8-id.0.wat", + "text": "malformed UTF-8 encoding of string-based id", + "module_type": "text" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast.json b/tests/snapshots/testsuite/proposals/annotations/annotations.wast.json index 613e15654b..00852de1d4 100644 --- a/tests/snapshots/testsuite/proposals/annotations/annotations.wast.json +++ b/tests/snapshots/testsuite/proposals/annotations/annotations.wast.json @@ -8,471 +8,499 @@ }, { "type": "assert_malformed", - "line": 20, + "line": 23, "filename": "annotations.1.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 21, + "line": 24, "filename": "annotations.2.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 22, + "line": 25, "filename": "annotations.3.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 23, + "line": 26, "filename": "annotations.4.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 24, + "line": 27, "filename": "annotations.5.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 25, + "line": 28, "filename": "annotations.6.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 26, + "line": 29, "filename": "annotations.7.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 27, + "line": 30, "filename": "annotations.8.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 28, + "line": 31, "filename": "annotations.9.wat", "text": "illegal character", "module_type": "text" }, { "type": "module", - "line": 29, + "line": 32, "filename": "annotations.10.wat" }, { "type": "module", - "line": 30, + "line": 33, "filename": "annotations.11.wat" }, { "type": "assert_malformed", - "line": 31, + "line": 34, "filename": "annotations.12.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 32, + "line": 35, "filename": "annotations.13.wat", "text": "illegal character", "module_type": "text" }, { "type": "module", - "line": 33, + "line": 36, "filename": "annotations.14.wat" }, { "type": "assert_malformed", - "line": 34, + "line": 37, "filename": "annotations.15.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 35, + "line": 38, "filename": "annotations.16.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 36, + "line": 39, "filename": "annotations.17.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 37, + "line": 40, "filename": "annotations.18.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 38, + "line": 41, "filename": "annotations.19.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 39, + "line": 42, "filename": "annotations.20.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 40, + "line": 43, "filename": "annotations.21.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 41, + "line": 44, "filename": "annotations.22.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 42, + "line": 45, "filename": "annotations.23.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 43, + "line": 46, "filename": "annotations.24.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 44, + "line": 47, "filename": "annotations.25.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 45, + "line": 48, "filename": "annotations.26.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 46, + "line": 49, "filename": "annotations.27.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 47, + "line": 50, "filename": "annotations.28.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 48, + "line": 51, "filename": "annotations.29.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 49, + "line": 52, "filename": "annotations.30.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 50, + "line": 53, "filename": "annotations.31.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 51, + "line": 54, "filename": "annotations.32.wat", "text": "illegal character", "module_type": "text" }, { "type": "module", - "line": 52, + "line": 55, "filename": "annotations.33.wat" }, { "type": "assert_malformed", - "line": 53, + "line": 56, "filename": "annotations.34.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 54, + "line": 57, "filename": "annotations.35.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 55, + "line": 58, "filename": "annotations.36.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 56, + "line": 59, "filename": "annotations.37.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 57, + "line": 60, "filename": "annotations.38.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 58, + "line": 61, "filename": "annotations.39.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 59, + "line": 62, "filename": "annotations.40.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 60, + "line": 63, "filename": "annotations.41.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 61, + "line": 64, "filename": "annotations.42.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 62, + "line": 65, "filename": "annotations.43.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 63, + "line": 66, "filename": "annotations.44.wat", "text": "malformed UTF-8 encoding", "module_type": "text" }, { "type": "assert_malformed", - "line": 64, + "line": 67, "filename": "annotations.45.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 65, + "line": 68, "filename": "annotations.46.wat", "text": "illegal character", "module_type": "text" }, { "type": "assert_malformed", - "line": 67, + "line": 70, "filename": "annotations.47.wat", "text": "unknown operator", "module_type": "text" }, { "type": "assert_malformed", - "line": 69, + "line": 72, "filename": "annotations.48.wat", - "text": "malformed annotation id", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 70, + "line": 73, "filename": "annotations.49.wat", - "text": "malformed annotation id", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 71, + "line": 74, "filename": "annotations.50.wat", - "text": "malformed annotation id", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 72, + "line": 75, "filename": "annotations.51.wat", - "text": "malformed annotation id", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 74, + "line": 76, "filename": "annotations.52.wat", - "text": "unclosed annotation", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 75, + "line": 77, "filename": "annotations.53.wat", - "text": "unclosed annotation", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 76, + "line": 78, "filename": "annotations.54.wat", - "text": "unclosed annotation", + "text": "empty annotation id", "module_type": "text" }, { "type": "assert_malformed", - "line": 77, + "line": 79, "filename": "annotations.55.wat", - "text": "unclosed annotation", + "text": "malformed UTF-8", "module_type": "text" }, { "type": "assert_malformed", - "line": 79, + "line": 81, "filename": "annotations.56.wat", - "text": "unexpected token", + "text": "unclosed annotation", "module_type": "text" }, { "type": "assert_malformed", - "line": 80, + "line": 82, "filename": "annotations.57.wat", - "text": "unexpected token", + "text": "unclosed annotation", "module_type": "text" }, { "type": "assert_malformed", - "line": 81, + "line": 83, "filename": "annotations.58.wat", - "text": "unexpected token", + "text": "unclosed annotation", "module_type": "text" }, { "type": "assert_malformed", - "line": 82, + "line": 84, "filename": "annotations.59.wat", - "text": "unexpected token", + "text": "unclosed annotation", "module_type": "text" }, { "type": "assert_malformed", - "line": 84, + "line": 86, "filename": "annotations.60.wat", - "text": "unclosed string", + "text": "unexpected token", "module_type": "text" }, { "type": "assert_malformed", - "line": 85, + "line": 87, "filename": "annotations.61.wat", - "text": "unclosed string", + "text": "unexpected token", "module_type": "text" }, { "type": "assert_malformed", - "line": 87, + "line": 88, "filename": "annotations.62.wat", - "text": "unknown operator", + "text": "unexpected token", "module_type": "text" }, { "type": "assert_malformed", - "line": 88, + "line": 89, "filename": "annotations.63.wat", - "text": "unknown operator", + "text": "unexpected token", "module_type": "text" }, { "type": "assert_malformed", - "line": 89, + "line": 91, "filename": "annotations.64.wat", + "text": "unclosed string", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 92, + "filename": "annotations.65.wat", + "text": "unclosed string", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 94, + "filename": "annotations.66.wat", "text": "unknown operator", "module_type": "text" }, + { + "type": "assert_malformed", + "line": 95, + "filename": "annotations.67.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 96, + "filename": "annotations.68.wat", + "text": "empty identifier", + "module_type": "text" + }, { "type": "module", - "line": 91, + "line": 98, "name": "m", - "filename": "annotations.65.wasm" + "filename": "annotations.69.wasm" }, { "type": "module", - "line": 122, + "line": 129, "name": "m1", - "filename": "annotations.66.wasm" + "filename": "annotations.70.wasm" }, { "type": "module", - "line": 147, + "line": 154, "name": "m2", - "filename": "annotations.67.wasm" + "filename": "annotations.71.wasm" }, { "type": "module", - "line": 199, - "filename": "annotations.68.wat" + "line": 206, + "filename": "annotations.72.wat" }, { "type": "module", - "line": 200, - "filename": "annotations.69.wat" + "line": 207, + "filename": "annotations.73.wat" } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/65.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/65.print deleted file mode 100644 index 902223c4e0..0000000000 --- a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/65.print +++ /dev/null @@ -1,11 +0,0 @@ -(module $m - (type (;0;) (func (param i32 f32))) - (import "spectest" "global_i32" (global $g (;0;) i32)) - (import "spectest" "table" (table $t (;0;) 10 20 funcref)) - (import "spectest" "memory" (memory $m (;0;) 1 2)) - (import "spectest" "print_i32_f32" (func $f (;0;) (type 0))) - (export "g" (global $g)) - (export "t" (table $t)) - (export "m" (memory $m)) - (export "f" (func $f)) -) diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/69.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/69.print index ad3c1ce924..902223c4e0 100644 --- a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/69.print +++ b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/69.print @@ -1,4 +1,11 @@ -(module - (type (;0;) (func)) - (func (;0;) (type 0)) +(module $m + (type (;0;) (func (param i32 f32))) + (import "spectest" "global_i32" (global $g (;0;) i32)) + (import "spectest" "table" (table $t (;0;) 10 20 funcref)) + (import "spectest" "memory" (memory $m (;0;) 1 2)) + (import "spectest" "print_i32_f32" (func $f (;0;) (type 0))) + (export "g" (global $g)) + (export "t" (table $t)) + (export "m" (memory $m)) + (export "f" (func $f)) ) diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/66.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/70.print similarity index 100% rename from tests/snapshots/testsuite/proposals/annotations/annotations.wast/66.print rename to tests/snapshots/testsuite/proposals/annotations/annotations.wast/70.print diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/67.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/71.print similarity index 100% rename from tests/snapshots/testsuite/proposals/annotations/annotations.wast/67.print rename to tests/snapshots/testsuite/proposals/annotations/annotations.wast/71.print diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/68.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/72.print similarity index 100% rename from tests/snapshots/testsuite/proposals/annotations/annotations.wast/68.print rename to tests/snapshots/testsuite/proposals/annotations/annotations.wast/72.print diff --git a/tests/snapshots/testsuite/proposals/annotations/annotations.wast/73.print b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/73.print new file mode 100644 index 0000000000..ad3c1ce924 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/annotations/annotations.wast/73.print @@ -0,0 +1,4 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0)) +) diff --git a/tests/snapshots/testsuite/proposals/annotations/id.wast.json b/tests/snapshots/testsuite/proposals/annotations/id.wast.json new file mode 100644 index 0000000000..4b7f59079c --- /dev/null +++ b/tests/snapshots/testsuite/proposals/annotations/id.wast.json @@ -0,0 +1,52 @@ +{ + "source_filename": "tests/testsuite/proposals/annotations/id.wast", + "commands": [ + { + "type": "module", + "line": 1, + "filename": "id.0.wasm" + }, + { + "type": "assert_malformed", + "line": 26, + "filename": "id.1.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 27, + "filename": "id.2.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 28, + "filename": "id.3.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 29, + "filename": "id.4.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 30, + "filename": "id.5.wat", + "text": "empty identifier", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 31, + "filename": "id.6.wat", + "text": "malformed UTF-8", + "module_type": "text" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print b/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print new file mode 100644 index 0000000000..4ef0bab69e --- /dev/null +++ b/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print @@ -0,0 +1,75 @@ +(module + (type (;0;) (func)) + (func $fg (;0;) (type 0)) + (func (;1;) (type 0) + call $fg + ) + (func $03 (;2;) (type 0)) + (func (;3;) (type 0) + call $03 + ) + (func $!?@#a$%^&*b-+_.:9'`|/\<=>~ (;4;) (type 0)) + (func (;5;) (type 0) + call $!?@#a$%^&*b-+_.:9'`|/\<=>~ + ) + (func $#func6<_random_____stuff_> (@name " random \t \n stuff ") (;6;) (type 0)) + (func (;7;) (type 0) + call $#func6<_random_____stuff_> + ) + (func $#func8<___> (@name " ") (;8;) (type 0)) + (func (;9;) (type 0) + call $#func8<___> + ) + (func $fh (;10;) (type 0)) + (func (;11;) (type 0) + call $fh + ) + (func $fi (;12;) (type 0)) + (func (;13;) (type 0) + call $fi + ) + (func $!?@#a$%^&*-+_.:9'`|/\<=>~ (;14;) (type 0)) + (func (;15;) (type 0) + call $!?@#a$%^&*-+_.:9'`|/\<=>~ + ) + (func $AB (;16;) (type 0)) + (func (;17;) (type 0) + call $AB + call $AB + call $AB + call $AB + ) + (func $#func18<_> (@name "\t") (;18;) (type 0)) + (func (;19;) (type 0) + call $#func18<_> + call $#func18<_> + ) + (func $#func20<__> (@name "") (;20;) (type 0)) + (func (;21;) (type 0) + call $#func20<__> + call $#func20<__> + ) + (func (;22;) (type 0) + block $l1 + br $l1 + end + block $007 + br $007 + end + block $!?@#a$%^&*-+_.:9'`|/\<=>~ + end + i32.const 0 + if $AB + br $AB + else + end + i32.const 0 + if $#label4<_> (@name "\t") + else + end + i32.const 0 + if $#label5<___> (@name " ") + else + end + ) +) diff --git a/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast.json b/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast.json new file mode 100644 index 0000000000..208e9729fd --- /dev/null +++ b/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast.json @@ -0,0 +1,31 @@ +{ + "source_filename": "tests/testsuite/proposals/memory64/table_copy_mixed.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "table_copy_mixed.0.wasm" + }, + { + "type": "assert_invalid", + "line": 20, + "filename": "table_copy_mixed.1.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 30, + "filename": "table_copy_mixed.2.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 40, + "filename": "table_copy_mixed.3.wasm", + "text": "type mismatch", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast/0.print b/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast/0.print new file mode 100644 index 0000000000..51534b1fdd --- /dev/null +++ b/tests/snapshots/testsuite/proposals/memory64/table_copy_mixed.wast/0.print @@ -0,0 +1,33 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + i32.const 13 + i32.const 2 + i32.const 3 + table.copy + ) + (func (;1;) (type 0) + i64.const 13 + i64.const 2 + i64.const 3 + table.copy $t64 $t64 + ) + (func (;2;) (type 0) + i32.const 13 + i64.const 2 + i32.const 3 + table.copy $t32 $t64 + ) + (func (;3;) (type 0) + i64.const 13 + i32.const 2 + i32.const 3 + table.copy $t64 $t32 + ) + (table $t32 (;0;) 30 30 funcref) + (table $t64 (;1;) i64 30 30 funcref) + (export "test32" (func 0)) + (export "test64" (func 1)) + (export "test_64to32" (func 2)) + (export "test_32to64" (func 3)) +) diff --git a/tests/testsuite b/tests/testsuite index 9df2c8a23c..6dfedc8b84 160000 --- a/tests/testsuite +++ b/tests/testsuite @@ -1 +1 @@ -Subproject commit 9df2c8a23c4d2f889c2c1a62e5fb9b744579efc5 +Subproject commit 6dfedc8b8423a91c1dc340d3af1a7f4fbf7868b4