From f3c9fe29fa910265fb17d743780eeb7e92097699 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:09:20 +0000 Subject: [PATCH 01/14] [wit-parser] Migrate to structured errors in the AST/package parser --- crates/wit-parser/src/ast.rs | 307 ++++++++++--------- crates/wit-parser/src/ast/error.rs | 124 ++++++++ crates/wit-parser/src/ast/resolve.rs | 424 +++++++++++++++----------- crates/wit-parser/src/ast/toposort.rs | 46 +-- crates/wit-parser/src/lib.rs | 2 + 5 files changed, 563 insertions(+), 340 deletions(-) create mode 100644 crates/wit-parser/src/ast/error.rs diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index bf9da178b5..a4e5095337 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,17 +1,21 @@ -use crate::{Error, PackageNotFoundError, UnresolvedPackageGroup}; +use crate::ast::error::{PackageParseErrorKind, PackageParseErrors}; +use crate::{UnresolvedPackage, UnresolvedPackageGroup}; use alloc::borrow::Cow; use alloc::boxed::Box; use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use anyhow::{Context, Result, bail}; +#[cfg(feature = "std")] +use anyhow::Context as _; use core::fmt; use core::mem; +use core::result::Result; use lex::{Span, Token, Tokenizer}; use semver::Version; #[cfg(feature = "std")] use std::path::Path; +pub mod error; pub mod lex; pub use resolve::Resolver; @@ -33,7 +37,7 @@ impl<'a> PackageFile<'a> { /// /// This will optionally start with `package foo:bar;` and then will have a /// list of ast items after it. - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { let mut package_name_tokens_peek = tokens.clone(); let docs = parse_docs(&mut package_name_tokens_peek)?; @@ -62,13 +66,13 @@ impl<'a> PackageFile<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { let span = tokens.expect(Token::Package)?; if !attributes.is_empty() { - bail!(Error::new( - span, - format!("cannot place attributes on nested packages"), - )); + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: span, + message: format!("cannot place attributes on nested packages"), + })); } let package_id = PackageName::parse(tokens, docs)?; tokens.expect(Token::LeftBrace)?; @@ -121,7 +125,10 @@ pub struct DeclList<'a> { } impl<'a> DeclList<'a> { - fn parse_until(tokens: &mut Tokenizer<'a>, end: Option) -> Result> { + fn parse_until( + tokens: &mut Tokenizer<'a>, + end: Option, + ) -> Result, PackageParseErrors> { let mut items = Vec::new(); let mut docs = parse_docs(tokens)?; loop { @@ -151,8 +158,8 @@ impl<'a> DeclList<'a> { &'b UsePath<'a>, Option<&'b [UseName<'a>]>, WorldOrInterface, - ) -> Result<()>, - ) -> Result<()> { + ) -> Result<(), PackageParseErrors>, + ) -> Result<(), PackageParseErrors> { for item in self.items.iter() { match item { AstItem::World(world) => { @@ -259,7 +266,7 @@ enum AstItem<'a> { } impl<'a> AstItem<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { let attributes = Attribute::parse_list(tokens)?; match tokens.clone().next()? { Some((_span, Token::Interface)) => { @@ -285,7 +292,7 @@ struct PackageName<'a> { } impl<'a> PackageName<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { let namespace = parse_id(tokens)?; tokens.expect(Token::Colon)?; let name = parse_id(tokens)?; @@ -322,7 +329,10 @@ struct ToplevelUse<'a> { } impl<'a> ToplevelUse<'a> { - fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { + 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)? { @@ -352,7 +362,7 @@ impl<'a> World<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::World)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; @@ -364,7 +374,7 @@ impl<'a> World<'a> { }) } - fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { + fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>, PackageParseErrors> { tokens.expect(Token::LeftBrace)?; let mut items = Vec::new(); loop { @@ -392,7 +402,7 @@ impl<'a> WorldItem<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result> { + ) -> Result, PackageParseErrors> { match tokens.clone().next()? { Some((_span, Token::Import)) => { Import::parse(tokens, docs, attributes).map(WorldItem::Import) @@ -443,7 +453,7 @@ impl<'a> Import<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result> { + ) -> Result, PackageParseErrors> { tokens.expect(Token::Import)?; let kind = ExternKind::parse(tokens)?; Ok(Import { @@ -465,7 +475,7 @@ impl<'a> Export<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result> { + ) -> Result, PackageParseErrors> { tokens.expect(Token::Export)?; let kind = ExternKind::parse(tokens)?; Ok(Export { @@ -483,7 +493,7 @@ enum ExternKind<'a> { } impl<'a> ExternKind<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { // Create a copy of the token stream to test out if this is a function // or an interface import. In those situations the token stream gets // reset to the state of the clone and we continue down those paths. @@ -540,7 +550,7 @@ impl<'a> Interface<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Interface)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; @@ -552,7 +562,9 @@ impl<'a> Interface<'a> { }) } - pub(super) fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>> { + pub(super) fn parse_items( + tokens: &mut Tokenizer<'a>, + ) -> Result>, PackageParseErrors> { tokens.expect(Token::LeftBrace)?; let mut items = Vec::new(); loop { @@ -593,7 +605,7 @@ enum UsePath<'a> { } impl<'a> UsePath<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { let id = parse_id(tokens)?; if tokens.eat(Token::Colon)? { // `foo:bar/baz@1.0` @@ -632,7 +644,10 @@ struct UseName<'a> { } impl<'a> Use<'a> { - fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Use)?; let from = UsePath::parse(tokens)?; tokens.expect(Token::Period)?; @@ -674,7 +689,10 @@ struct IncludeName<'a> { } impl<'a> Include<'a> { - fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> Result { + fn parse( + tokens: &mut Tokenizer<'a>, + attributes: Vec>, + ) -> Result { tokens.expect(Token::Include)?; let from = UsePath::parse(tokens)?; @@ -801,7 +819,7 @@ impl<'a> ResourceFunc<'a> { docs: Docs<'a>, attributes: Vec>, tokens: &mut Tokenizer<'a>, - ) -> Result { + ) -> Result { match tokens.clone().next()? { Some((span, Token::Constructor)) => { tokens.expect(Token::Constructor)?; @@ -965,8 +983,11 @@ struct Func<'a> { } impl<'a> Func<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result> { - fn parse_params<'a>(tokens: &mut Tokenizer<'a>, left_paren: bool) -> Result> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { + fn parse_params<'a>( + tokens: &mut Tokenizer<'a>, + left_paren: bool, + ) -> Result, PackageParseErrors> { if left_paren { tokens.expect(Token::LeftParen)?; }; @@ -1001,7 +1022,7 @@ impl<'a> InterfaceItem<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result> { + ) -> Result, PackageParseErrors> { match tokens.clone().next()? { Some((_span, Token::Type)) => { TypeDef::parse(tokens, docs, attributes).map(InterfaceItem::TypeDef) @@ -1035,7 +1056,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Type)?; let name = parse_id(tokens)?; tokens.expect(Token::Equals)?; @@ -1053,7 +1074,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Flags)?; let name = parse_id(tokens)?; let ty = Type::Flags(Flags { @@ -1080,7 +1101,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Resource)?; let name = parse_id(tokens)?; let mut funcs = Vec::new(); @@ -1109,7 +1130,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Record)?; let name = parse_id(tokens)?; let ty = Type::Record(Record { @@ -1138,7 +1159,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Variant)?; let name = parse_id(tokens)?; let ty = Type::Variant(Variant { @@ -1172,7 +1193,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { tokens.expect(Token::Enum)?; let name = parse_id(tokens)?; let ty = Type::Enum(Enum { @@ -1201,7 +1222,7 @@ impl<'a> NamedFunc<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> Result { let name = parse_id(tokens)?; tokens.expect(Token::Colon)?; let func = Func::parse(tokens)?; @@ -1215,7 +1236,7 @@ impl<'a> NamedFunc<'a> { } } -fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { +fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { match tokens.next()? { Some((span, Token::Id)) => Ok(Id { name: tokens.parse_id(span)?, @@ -1225,11 +1246,13 @@ fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { name: tokens.parse_explicit_id(span)?, span, }), - other => Err(err_expected(tokens, "an identifier or string", other).into()), + other => Err(err_expected(tokens, "an identifier or string", other)), } } -fn parse_opt_version(tokens: &mut Tokenizer<'_>) -> Result> { +fn parse_opt_version( + tokens: &mut Tokenizer<'_>, +) -> Result, PackageParseErrors> { if tokens.eat(Token::At)? { parse_version(tokens).map(Some) } else { @@ -1237,7 +1260,7 @@ fn parse_opt_version(tokens: &mut Tokenizer<'_>) -> Result) -> Result<(Span, Version)> { +fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version), PackageParseErrors> { let start = tokens.expect(Token::Integer)?.start(); tokens.expect(Token::Period)?; tokens.expect(Token::Integer)?; @@ -1247,7 +1270,12 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version)> { eat_ids(tokens, Token::Minus, &mut span)?; eat_ids(tokens, Token::Plus, &mut span)?; let string = tokens.get_span(span); - let version = Version::parse(string).map_err(|e| Error::new(span, e.to_string()))?; + let version = Version::parse(string).map_err(|e| { + PackageParseErrors::from(PackageParseErrorKind::Syntax { + span, + message: e.to_string(), + }) + })?; return Ok((span, version)); // According to `semver.org` this is what we're parsing: @@ -1303,7 +1331,11 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version)> { // Note that this additionally doesn't try to return any first-class errors. // Instead this bails out on something unrecognized for something else in // the system to return an error. - fn eat_ids(tokens: &mut Tokenizer<'_>, prefix: Token, end: &mut Span) -> Result<()> { + fn eat_ids( + tokens: &mut Tokenizer<'_>, + prefix: Token, + end: &mut Span, + ) -> Result<(), lex::Error> { if !tokens.eat(prefix)? { return Ok(()); } @@ -1327,7 +1359,7 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version)> { } } -fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result> { +fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result, lex::Error> { let mut docs = Docs::default(); let mut clone = tokens.clone(); let mut started = false; @@ -1356,7 +1388,7 @@ fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result> { } impl<'a> Type<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { match tokens.next()? { Some((span, Token::U8)) => Ok(Type::U8(span)), Some((span, Token::U16)) => Ok(Type::U16(span)), @@ -1392,7 +1424,12 @@ impl<'a> Type<'a> { let size = if tokens.eat(Token::Comma)? { let number = tokens.next()?; if let Some((span, Token::Integer)) = number { - let size: u32 = tokens.get_span(span).parse()?; + let size: u32 = tokens.get_span(span).parse().map_err(|e| { + PackageParseErrors::from(PackageParseErrorKind::Syntax { + span, + message: format!("invalid list size: {e}"), + }) + })?; Some(size) } else { return Err(err_expected(tokens, "fixed-length", number).into()); @@ -1560,8 +1597,8 @@ fn parse_list<'a, T>( tokens: &mut Tokenizer<'a>, start: Token, end: Token, - parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, -) -> Result> { + parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, +) -> Result, PackageParseErrors> { tokens.expect(start)?; parse_list_trailer(tokens, end, parse) } @@ -1569,8 +1606,8 @@ fn parse_list<'a, T>( fn parse_list_trailer<'a, T>( tokens: &mut Tokenizer<'a>, end: Token, - mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, -) -> Result> { + mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, +) -> Result, PackageParseErrors> { let mut items = Vec::new(); loop { // get docs before we skip them to try to eat the end token @@ -1598,13 +1635,16 @@ fn err_expected( tokens: &Tokenizer<'_>, expected: &'static str, found: Option<(Span, Token)>, -) -> Error { +) -> PackageParseErrors { match found { - Some((span, token)) => Error::new( + Some((span, token)) => PackageParseErrors::from(PackageParseErrorKind::Syntax { span, - format!("expected {}, found {}", expected, token.describe()), - ), - None => Error::new(tokens.eof_span(), format!("expected {expected}, found eof")), + message: format!("expected {}, found {}", expected, token.describe()), + }), + None => PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: tokens.eof_span(), + message: format!("expected {expected}, found eof"), + }), } } @@ -1615,7 +1655,7 @@ enum Attribute<'a> { } impl<'a> Attribute<'a> { - fn parse_list(tokens: &mut Tokenizer<'a>) -> Result>> { + fn parse_list(tokens: &mut Tokenizer<'a>) -> Result>, PackageParseErrors> { let mut ret = Vec::new(); while tokens.eat(Token::At)? { let id = parse_id(tokens)?; @@ -1654,7 +1694,10 @@ impl<'a> Attribute<'a> { } } other => { - bail!(Error::new(id.span, format!("unknown attribute `{other}`"),)) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: id.span, + message: format!("unknown attribute `{other}`"), + })); } }; ret.push(attr); @@ -1671,13 +1714,13 @@ impl<'a> Attribute<'a> { } } -fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> Result { +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), - )); + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: id.span, + message: format!("expected `{expected}`, found `{}`", id.name), + })); } Ok(id.span) } @@ -1708,7 +1751,7 @@ impl SourceMap { /// Reads the file `path` on the filesystem and appends its contents to this /// [`SourceMap`]. #[cfg(feature = "std")] - pub fn push_file(&mut self, path: &Path) -> Result<()> { + pub fn push_file(&mut self, path: &Path) -> anyhow::Result<()> { let contents = std::fs::read_to_string(path) .with_context(|| format!("failed to read file {path:?}"))?; self.push(path, contents); @@ -1768,94 +1811,86 @@ impl SourceMap { /// Parses the files added to this source map into a /// [`UnresolvedPackageGroup`]. - pub fn parse(self) -> Result { + /// + /// On failure returns `Err((self, e))` so the caller can use the source + /// map for error formatting if needed. + pub fn parse(self) -> Result { + match self.parse_inner() { + Ok((main, nested)) => Ok(UnresolvedPackageGroup { + main, + nested, + source_map: self, + }), + Err(e) => Err((self, e)), + } + } + + fn parse_inner( + &self, + ) -> Result<(UnresolvedPackage, Vec), PackageParseErrors> { let mut nested = Vec::new(); - let main = self.rewrite_error(|| { - let mut resolver = Resolver::default(); - let mut srcs = self.sources.iter().collect::>(); - srcs.sort_by_key(|src| &src.path); - - // Parse each source file individually. A tokenizer is created here - // form settings and then `PackageFile` is used to parse the whole - // stream of tokens. - for src in srcs { - let mut tokens = Tokenizer::new( - // chop off the forcibly appended `\n` character when - // passing through the source to get tokenized. - &src.contents[..src.contents.len() - 1], - src.offset, - ) - .with_context(|| format!("failed to tokenize path: {}", src.path))?; - let mut file = PackageFile::parse(&mut tokens)?; - - // Filter out any nested packages and resolve them separately. - // Nested packages have only a single "file" so only one item - // is pushed into a `Resolver`. Note that a nested `Resolver` - // is used here, not the outer one. - // - // Note that filtering out `Package` items is required due to - // how the implementation of disallowing nested packages in - // nested packages currently works. - for item in mem::take(&mut file.decl_list.items) { - match item { - AstItem::Package(nested_pkg) => { - let mut resolve = Resolver::default(); - resolve.push(nested_pkg).with_context(|| { - format!("failed to handle nested package in: {}", src.path) - })?; - - nested.push(resolve.resolve()?); - } - other => file.decl_list.items.push(other), + let mut resolver = Resolver::default(); + let mut srcs = self.sources.iter().collect::>(); + srcs.sort_by_key(|src| &src.path); + + // Parse each source file individually. A tokenizer is created here + // from settings and then `PackageFile` is used to parse the whole + // stream of tokens. + for src in srcs { + let mut tokens = Tokenizer::new( + // chop off the forcibly appended `\n` character when + // passing through the source to get tokenized. + &src.contents[..src.contents.len() - 1], + src.offset, + )?; + let mut file = PackageFile::parse(&mut tokens)?; + + // Filter out any nested packages and resolve them separately. + // Nested packages have only a single "file" so only one item + // is pushed into a `Resolver`. Note that a nested `Resolver` + // is used here, not the outer one. + // + // Note that filtering out `Package` items is required due to + // how the implementation of disallowing nested packages in + // nested packages currently works. + for item in mem::take(&mut file.decl_list.items) { + match item { + AstItem::Package(nested_pkg) => { + let mut resolve = Resolver::default(); + resolve.push(nested_pkg)?; + nested.push(resolve.resolve()?); } + other => file.decl_list.items.push(other), } - - // With nested packages handled push this file into the - // resolver. - resolver - .push(file) - .with_context(|| format!("failed to start resolving path: {}", src.path))?; } - Ok(resolver.resolve()?) - })?; - Ok(UnresolvedPackageGroup { - main, - nested, - source_map: self, - }) + + // With nested packages handled push this file into the resolver. + resolver.push(file)?; + } + + Ok((resolver.resolve()?, nested)) } - pub(crate) fn rewrite_error(&self, f: F) -> Result + /// Runs `f` and, on error, attempts to add source highlighting to resolver + /// error types that still use `anyhow`. Only needed until the resolver is + /// migrated to structured errors. + pub(crate) fn rewrite_error(&self, f: F) -> anyhow::Result where - F: FnOnce() -> Result, + F: FnOnce() -> anyhow::Result, { let mut err = match f() { Ok(t) => return Ok(t), Err(e) => e, }; - if let Some(parse) = err.downcast_mut::() { - parse.highlight(self); - return Err(err); - } - if let Some(notfound) = err.downcast_mut::() { - notfound.highlight(self); - return Err(err); - } - - if let Some(lex) = err.downcast_ref::() { - let pos = lex.position(); - let msg = self.highlight_err(pos, None, lex); - bail!("{msg}") - } - - if let Some(sort) = err.downcast_mut::() { - sort.highlight(self); + if let Some(e) = err.downcast_mut::() { + e.highlight(self); + } else if let Some(e) = err.downcast_mut::() { + e.highlight(self); } - Err(err) } - pub(crate) fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option { + pub fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option { if !span.is_known() { return None; } @@ -1960,11 +1995,11 @@ pub enum ParsedUsePath { Package(crate::PackageName, String), } -pub fn parse_use_path(s: &str) -> Result { +pub fn parse_use_path(s: &str) -> anyhow::Result { let mut tokens = Tokenizer::new(s, 0)?; let path = UsePath::parse(&mut tokens)?; if tokens.next()?.is_some() { - bail!("trailing tokens in path specifier"); + anyhow::bail!("trailing tokens in path specifier"); } Ok(match path { UsePath::Id(id) => ParsedUsePath::Name(id.name.to_string()), diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs new file mode 100644 index 0000000000..68dff05d61 --- /dev/null +++ b/crates/wit-parser/src/ast/error.rs @@ -0,0 +1,124 @@ +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use core::fmt; + +use crate::{ + SourceMap, Span, + ast::{lex, toposort}, +}; + +#[non_exhaustive] +#[derive(Debug)] +pub enum PackageParseErrorKind { + /// Lexer error (invalid character, unterminated comment, etc.) + Lex(lex::Error), + /// Syntactic or semantic error within a single package (duplicate name, + /// invalid attribute, etc.) + Syntax { span: Span, message: String }, + /// A type/interface/world references a name that does not exist within + /// the same package. Arises from within-package toposort. + ItemNotFound { + span: Span, + name: String, + kind: String, + hint: Option, + }, + /// A type/interface/world depends on itself. Arises from within-package + /// toposort. + TypeCycle { + span: Span, + name: String, + kind: String, + }, +} + +impl PackageParseErrorKind { + pub fn span(&self) -> Span { + match self { + PackageParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1), + PackageParseErrorKind::Syntax { span, .. } + | PackageParseErrorKind::ItemNotFound { span, .. } + | PackageParseErrorKind::TypeCycle { span, .. } => *span, + } + } +} + +impl fmt::Display for PackageParseErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PackageParseErrorKind::Lex(e) => fmt::Display::fmt(e, f), + PackageParseErrorKind::Syntax { message, .. } => message.fmt(f), + PackageParseErrorKind::ItemNotFound { + kind, name, hint, .. + } => { + write!(f, "{kind} `{name}` does not exist")?; + if let Some(hint) = hint { + write!(f, "\n{hint}")?; + } + Ok(()) + } + PackageParseErrorKind::TypeCycle { kind, name, .. } => { + write!(f, "{kind} `{name}` depends on itself") + } + } + } +} + +#[derive(Debug)] +pub struct PackageParseErrors(Box); + +impl PackageParseErrors { + pub fn kind(&self) -> &PackageParseErrorKind { + &self.0 + } + + /// Format this error with source context (file:line:col + snippet) + pub fn highlight(&self, source_map: &SourceMap) -> String { + let e = self.kind(); + source_map + .highlight_span(e.span(), e) + .unwrap_or_else(|| e.to_string()) + } +} + +impl fmt::Display for PackageParseErrors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.kind(), f) + } +} + +impl core::error::Error for PackageParseErrors {} + +impl From for PackageParseErrors { + fn from(kind: PackageParseErrorKind) -> Self { + PackageParseErrors(Box::new(kind)) + } +} + +impl From for PackageParseErrors { + fn from(e: lex::Error) -> Self { + PackageParseErrorKind::Lex(e).into() + } +} + +impl From for PackageParseErrors { + fn from(e: toposort::Error) -> Self { + let kind = match e { + toposort::Error::NonexistentDep { + span, + name, + kind, + hint, + } => PackageParseErrorKind::ItemNotFound { + span, + name, + kind, + hint, + }, + toposort::Error::Cycle { span, name, kind } => { + PackageParseErrorKind::TypeCycle { span, name, kind } + } + }; + kind.into() + } +} diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index e77ac0f260..cd6a05437b 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,11 +1,13 @@ use super::{ParamList, WorldOrInterface}; +use crate::alloc::borrow::ToOwned; +use crate::ast::error::{PackageParseErrorKind, PackageParseErrors}; use crate::ast::toposort::toposort; use crate::*; use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::{format, vec}; -use anyhow::bail; use core::mem; +use core::result::Result; #[derive(Default)] pub struct Resolver<'a> { @@ -105,7 +107,7 @@ enum TypeOrItem { } impl<'a> Resolver<'a> { - pub(super) fn push(&mut self, file: ast::PackageFile<'a>) -> Result<()> { + pub(super) fn push(&mut self, file: ast::PackageFile<'a>) -> Result<(), PackageParseErrors> { // As each WIT file is pushed into this resolver keep track of the // current package name assigned. Only one file needs to mention it, but // if multiple mention it then they must all match. @@ -113,13 +115,13 @@ impl<'a> Resolver<'a> { let cur_name = cur.package_name(); if let Some((prev, _)) = &self.package_name { if cur_name != *prev { - bail!(Error::new( - cur.span, - format!( + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: cur.span, + message: format!( "package identifier `{cur_name}` does not match \ previous package name of `{prev}`" ), - )) + })); } } self.package_name = Some((cur_name, cur.span)); @@ -128,10 +130,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::new( - cur.docs.span, - "found doc comments on multiple 'package' items" - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: cur.docs.span, + message: "found doc comments on multiple 'package' items".to_owned(), + })); } self.package_docs = docs; } @@ -145,22 +147,26 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(pkg) => pkg.package_id.as_ref().unwrap().span, _ => continue, }; - bail!(Error::new( - span, - "nested packages must be placed at the top-level" - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: span, + message: "nested packages must be placed at the top-level".to_owned(), + })); } self.decl_lists.push(file.decl_list); Ok(()) } - pub(crate) fn resolve(&mut self) -> Result { + pub(crate) fn resolve(&mut self) -> Result { // At least one of the WIT files must have a `package` annotation. let (name, package_name_span) = match &self.package_name { Some(name) => name.clone(), None => { - bail!("no `package` header was found in any WIT file for this package") + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: Span::default(), + message: "no `package` header was found in any WIT file for this package" + .to_owned(), + })); } }; @@ -336,7 +342,7 @@ impl<'a> Resolver<'a> { fn populate_ast_items( &mut self, decl_lists: &[ast::DeclList<'a>], - ) -> Result<(Vec, Vec)> { + ) -> Result<(Vec, Vec), PackageParseErrors> { let mut package_items = IndexMap::default(); // Validate that all worlds and interfaces have unique names within this @@ -350,10 +356,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::new( - i.name.span, - format!("duplicate item named `{}`", i.name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: i.name.span, + message: format!("duplicate item named `{}`", i.name.name), + })); } let prev = decl_list_ns.insert(i.name.name, ()); assert!(prev.is_none()); @@ -364,10 +370,10 @@ impl<'a> Resolver<'a> { } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { - bail!(Error::new( - w.name.span, - format!("duplicate item named `{}`", w.name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: w.name.span, + message: format!("duplicate item named `{}`", w.name.name), + })); } let prev = decl_list_ns.insert(w.name.name, ()); assert!(prev.is_none()); @@ -415,10 +421,10 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(_) => unreachable!(), }; if decl_list_ns.insert(name.name, (name.span, src)).is_some() { - bail!(Error::new( - name.span, - format!("duplicate name `{}` in this file", name.name), - )); + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.span, + message: format!("duplicate name `{}` in this file", name.name), + })); } } @@ -446,13 +452,14 @@ impl<'a> Resolver<'a> { order[iface.name].push(used_name.clone()); } None => { - bail!(Error::new( - used_name.span, - format!( - "interface or world `{name}` not found in package", - name = used_name.name - ), - )) + return Err(PackageParseErrors::from( + PackageParseErrorKind::ItemNotFound { + span: used_name.span, + name: used_name.name.to_string(), + kind: "interface or world".to_string(), + hint: None, + }, + )); } }, } @@ -494,21 +501,20 @@ impl<'a> Resolver<'a> { 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"), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: u.span, + message: 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::new( - name.span, - format!( - "interface or world `{name}` does not exist", - name = name.name - ), - ) + PackageParseErrors::from(PackageParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "interface or world".to_owned(), + hint: None, + }) })?, ast::UsePath::Package { id, name } => { self.foreign_deps[&id.package_name()][name.name].0 @@ -549,7 +555,10 @@ impl<'a> Resolver<'a> { /// This is done after all interfaces are generated so `self.resolve_path` /// can be used to determine if what's being imported from is a foreign /// interface or not. - fn populate_foreign_types(&mut self, decl_lists: &[ast::DeclList<'a>]) -> Result<()> { + fn populate_foreign_types( + &mut self, + decl_lists: &[ast::DeclList<'a>], + ) -> Result<(), PackageParseErrors> { for (i, decl_list) in decl_lists.iter().enumerate() { self.cur_ast_index = i; decl_list.for_each_path(&mut |_, attrs, path, names, _| { @@ -593,7 +602,11 @@ impl<'a> Resolver<'a> { Ok(()) } - fn resolve_world(&mut self, world_id: WorldId, world: &ast::World<'a>) -> Result { + 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)?; @@ -627,10 +640,12 @@ impl<'a> Resolver<'a> { WorldItem::Type { id, span: *span }, ); if prev.is_some() { - bail!(Error::new( - *span, - format!("import `{name}` conflicts with prior import of same name"), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: *span, + message: format!( + "import `{name}` conflicts with prior import of same name" + ), + })); } } TypeOrItem::Item(_) => unreachable!(), @@ -704,10 +719,10 @@ impl<'a> Resolver<'a> { }; if let WorldItem::Interface { id, .. } = world_item { if !interfaces.insert(id) { - bail!(Error::new( - kind.span(), - format!("interface cannot be {desc}ed more than once"), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: kind.span(), + message: format!("interface cannot be {desc}ed more than once"), + })); } } let dst = if desc == "import" { @@ -726,10 +741,10 @@ impl<'a> Resolver<'a> { WorldKey::Name(name) => name, WorldKey::Interface(..) => unreachable!(), }; - bail!(Error::new( - kind.span(), - format!("{desc} `{name}` conflicts with prior {prev} of same name",), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: kind.span(), + message: format!("{desc} `{name}` conflicts with prior {prev} of same name",), + })); } } self.type_lookup.clear(); @@ -742,7 +757,7 @@ impl<'a> Resolver<'a> { docs: &ast::Docs<'a>, attrs: &[ast::Attribute<'a>], kind: &ast::ExternKind<'a>, - ) -> Result { + ) -> Result { match kind { ast::ExternKind::Interface(name, items) => { let prev = mem::take(&mut self.type_lookup); @@ -790,7 +805,7 @@ impl<'a> Resolver<'a> { fields: &[ast::InterfaceItem<'a>], docs: &ast::Docs<'a>, attrs: &[ast::Attribute<'a>], - ) -> Result<()> { + ) -> Result<(), PackageParseErrors> { let docs = self.docs(docs); self.interfaces[interface_id].docs = docs; let stability = self.stability(attrs)?; @@ -866,7 +881,7 @@ impl<'a> Resolver<'a> { &mut self, owner: TypeOwner, fields: impl Iterator> + Clone, - ) -> Result<()> + ) -> Result<(), PackageParseErrors> where 'a: 'b, { @@ -893,10 +908,10 @@ impl<'a> Resolver<'a> { TypeItem::Def(t) => { let prev = type_defs.insert(t.name.name, Some(t)); if prev.is_some() { - bail!(Error::new( - t.name.span, - format!("name `{}` is defined more than once", t.name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: t.name.span, + message: format!("name `{}` is defined more than once", t.name.name), + })); } let mut deps = Vec::new(); collect_deps(&t.ty, &mut deps); @@ -911,7 +926,9 @@ impl<'a> Resolver<'a> { } } } - let order = toposort("type", &type_deps).map_err(attach_old_float_type_context)?; + let order = toposort("type", &type_deps) + .map_err(attach_old_float_type_context) + .map_err(PackageParseErrors::from)?; for ty in order { let def = match type_defs.swap_remove(&ty).unwrap() { Some(def) => def, @@ -932,28 +949,29 @@ impl<'a> Resolver<'a> { } return Ok(()); - fn attach_old_float_type_context(err: ast::toposort::Error) -> anyhow::Error { - let name = match &err { - ast::toposort::Error::NonexistentDep { name, .. } => name, - _ => return err.into(), - }; - let new = match name.as_str() { - "float32" => "f32", - "float64" => "f64", - _ => return err.into(), - }; - - let context = format!( - "the `{name}` type has been renamed to `{new}` and is \ - no longer accepted, but the `WIT_REQUIRE_F32_F64=0` \ - environment variable can be used to temporarily \ - disable this error" - ); - anyhow::Error::from(err).context(context) + fn attach_old_float_type_context(mut err: ast::toposort::Error) -> ast::toposort::Error { + if let ast::toposort::Error::NonexistentDep { name, hint, .. } = &mut err { + let new = match name.as_str() { + "float32" => "f32", + "float64" => "f64", + _ => return err, + }; + *hint = Some(format!( + "the `{name}` type has been renamed to `{new}` and is \ + no longer accepted, but the `WIT_REQUIRE_F32_F64=0` \ + environment variable can be used to temporarily \ + disable this error" + )); + } + err } } - fn resolve_use(&mut self, owner: TypeOwner, u: &ast::Use<'a>) -> Result<()> { + fn resolve_use( + &mut self, + owner: TypeOwner, + u: &ast::Use<'a>, + ) -> Result<(), PackageParseErrors> { 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)?; @@ -963,15 +981,21 @@ impl<'a> Resolver<'a> { let id = match lookup.get(name.name.name) { Some((TypeOrItem::Type(id), _)) => *id, Some((TypeOrItem::Item(s), _)) => { - bail!(Error::new( - name.name.span, - format!("cannot import {s} `{}`", name.name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.name.span, + message: format!("cannot import {s} `{}`", name.name.name), + })); + } + None => { + return Err(PackageParseErrors::from( + PackageParseErrorKind::ItemNotFound { + span: name.name.span, + name: name.name.name.to_string(), + kind: "name".to_string(), + hint: None, + }, + )); } - None => bail!(Error::new( - name.name.span, - format!("name `{}` is not defined", name.name.name), - )), }; let span = name.name.span; let name = name.as_.as_ref().unwrap_or(&name.name); @@ -989,7 +1013,11 @@ 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<()> { + fn resolve_include( + &mut self, + world_id: WorldId, + i: &ast::Include<'a>, + ) -> Result<(), PackageParseErrors> { 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)?; @@ -1013,7 +1041,7 @@ impl<'a> Resolver<'a> { &mut self, func: &ast::ResourceFunc<'_>, resource: &ast::Id<'_>, - ) -> Result { + ) -> Result { let resource_id = match self.type_lookup.get(resource.name) { Some((TypeOrItem::Type(id), _)) => *id, _ => panic!("type lookup for resource failed"), @@ -1062,7 +1090,7 @@ impl<'a> Resolver<'a> { name_span: Span, func: &ast::Func, kind: FunctionKind, - ) -> Result { + ) -> Result { let docs = self.docs(docs); let stability = self.stability(attrs)?; let params = self.resolve_params(&func.params, &kind, func.span)?; @@ -1078,7 +1106,10 @@ impl<'a> Resolver<'a> { }) } - fn resolve_ast_item_path(&self, path: &ast::UsePath<'a>) -> Result<(AstItem, String, Span)> { + fn resolve_ast_item_path( + &self, + path: &ast::UsePath<'a>, + ) -> Result<(AstItem, String, Span), PackageParseErrors> { match path { ast::UsePath::Id(id) => { let item = self.ast_items[self.cur_ast_index] @@ -1087,10 +1118,14 @@ impl<'a> Resolver<'a> { match item { Some(item) => Ok((*item, id.name.into(), id.span)), None => { - bail!(Error::new( - id.span, - format!("interface or world `{}` does not exist", id.name), - )) + return Err(PackageParseErrors::from( + PackageParseErrorKind::ItemNotFound { + span: id.span, + name: id.name.to_string(), + kind: "interface or world".to_owned(), + hint: None, + }, + )); } } } @@ -1107,37 +1142,46 @@ impl<'a> Resolver<'a> { item: &AstItem, name: &str, span: Span, - ) -> Result { + ) -> Result { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { - bail!(Error::new( + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { span, - format!("name `{name}` is defined as a world, not an interface"), - )) + message: format!("name `{name}` is defined as a world, not an interface"), + })); } } } - fn extract_world_from_item(&self, item: &AstItem, name: &str, span: Span) -> Result { + fn extract_world_from_item( + &self, + item: &AstItem, + name: &str, + span: Span, + ) -> Result { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { - bail!(Error::new( + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { span, - format!("name `{name}` is defined as an interface, not a world"), - )) + message: format!("name `{name}` is defined as an interface, not a world"), + })); } } } - fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> Result<()> { + fn define_interface_name( + &mut self, + name: &ast::Id<'a>, + item: TypeOrItem, + ) -> Result<(), PackageParseErrors> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { - bail!(Error::new( - name.span, - format!("name `{}` is defined more than once", name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.span, + message: format!("name `{}` is defined more than once", name.name), + })); } else { Ok(()) } @@ -1147,7 +1191,7 @@ impl<'a> Resolver<'a> { &mut self, ty: &ast::Type<'_>, stability: &Stability, - ) -> Result { + ) -> Result { Ok(match ty { ast::Type::Bool(_) => TypeDefKind::Type(Type::Bool), ast::Type::U8(_) => TypeDefKind::Type(Type::U8), @@ -1188,10 +1232,10 @@ impl<'a> Resolver<'a> { | Type::Char | Type::String => {} _ => { - bail!(Error::new( - map.span, - "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string", - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: map.span, + message: "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned(), + })); } } @@ -1216,16 +1260,26 @@ impl<'a> Resolver<'a> { match func { ast::ResourceFunc::Method(f) | ast::ResourceFunc::Static(f) => { if !names.insert(&f.name.name) { - bail!(Error::new( - f.name.span, - format!("duplicate function name `{}`", f.name.name), - )) + return Err(PackageParseErrors::from( + PackageParseErrorKind::Syntax { + span: f.name.span, + message: format!( + "duplicate function name `{}`", + f.name.name + ), + }, + )); } } ast::ResourceFunc::Constructor(f) => { ctors += 1; if ctors > 1 { - bail!(Error::new(f.name.span, "duplicate constructors")) + return Err(PackageParseErrors::from( + PackageParseErrorKind::Syntax { + span: f.name.span, + message: "duplicate constructors".to_owned(), + }, + )); } } } @@ -1245,7 +1299,7 @@ impl<'a> Resolver<'a> { span: field.name.span, }) }) - .collect::>>()?; + .collect::, PackageParseErrors>>()?; TypeDefKind::Record(Record { fields }) } ast::Type::Flags(flags) => { @@ -1265,12 +1319,15 @@ impl<'a> Resolver<'a> { .types .iter() .map(|ty| self.resolve_type(ty, stability)) - .collect::>>()?; + .collect::, PackageParseErrors>>()?; TypeDefKind::Tuple(Tuple { types }) } ast::Type::Variant(variant) => { if variant.cases.is_empty() { - bail!(Error::new(variant.span, "empty variant")) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: variant.span, + message: "empty variant".to_owned(), + })); } let cases = variant .cases @@ -1283,12 +1340,15 @@ impl<'a> Resolver<'a> { span: case.name.span, }) }) - .collect::>>()?; + .collect::, PackageParseErrors>>()?; TypeDefKind::Variant(Variant { cases }) } ast::Type::Enum(e) => { if e.cases.is_empty() { - bail!(Error::new(e.span, "empty enum")) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: e.span, + message: "empty enum".to_owned(), + })); } let cases = e .cases @@ -1300,7 +1360,7 @@ impl<'a> Resolver<'a> { span: case.name.span, }) }) - .collect::>>()?; + .collect::, PackageParseErrors>>()?; TypeDefKind::Enum(Enum { cases }) } ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(&ty.ty, stability)?), @@ -1317,21 +1377,29 @@ impl<'a> Resolver<'a> { }) } - fn resolve_type_name(&mut self, name: &ast::Id<'_>) -> Result { + 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::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), - )), + Some((TypeOrItem::Item(s), _)) => { + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.span, + message: format!("cannot use {s} `{name}` as a type", name = name.name), + })); + } + None => { + return Err(PackageParseErrors::from( + PackageParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "name".to_owned(), + hint: None, + }, + )); + } } } - fn validate_resource(&mut self, name: &ast::Id<'_>) -> Result { + fn validate_resource(&mut self, name: &ast::Id<'_>) -> Result { let id = self.resolve_type_name(name)?; let mut cur = id; loop { @@ -1342,10 +1410,15 @@ impl<'a> Resolver<'a> { self.required_resource_types.push((cur, name.span)); break Ok(id); } - _ => bail!(Error::new( - name.span, - format!("type `{}` used in a handle must be a resource", name.name), - )), + _ => { + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.span, + message: format!( + "type `{}` used in a handle must be a resource", + name.name + ), + })); + } } } } @@ -1419,7 +1492,11 @@ impl<'a> Resolver<'a> { } } - fn resolve_type(&mut self, ty: &super::Type<'_>, stability: &Stability) -> 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. @@ -1443,7 +1520,7 @@ impl<'a> Resolver<'a> { &mut self, ty: Option<&super::Type<'_>>, stability: &Stability, - ) -> Result> { + ) -> Result, PackageParseErrors> { match ty { Some(ty) => Ok(Some(self.resolve_type(ty, stability)?)), None => Ok(None), @@ -1549,7 +1626,7 @@ impl<'a> Resolver<'a> { Docs { contents } } - fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result { + fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result { match attrs { [] => Ok(Stability::Unknown), @@ -1593,16 +1670,16 @@ impl<'a> Resolver<'a> { deprecated: Some(version.clone()), }), [ast::Attribute::Deprecated { span, .. }] => { - bail!(Error::new( - *span, - "must pair @deprecated with either @since or @unstable", - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: *span, + message: "must pair @deprecated with either @since or @unstable".to_owned(), + })); } [_, b, ..] => { - bail!(Error::new( - b.span(), - "unsupported combination of attributes", - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: b.span(), + message: "unsupported combination of attributes".to_owned(), + })); } } } @@ -1612,7 +1689,7 @@ impl<'a> Resolver<'a> { params: &ParamList<'_>, kind: &FunctionKind, span: Span, - ) -> Result> { + ) -> Result, PackageParseErrors> { let mut ret = Vec::new(); match *kind { // These kinds of methods don't have any adjustments to the @@ -1645,10 +1722,10 @@ impl<'a> Resolver<'a> { } for (name, ty) in params { if ret.iter().any(|p| p.name == name.name) { - bail!(Error::new( - name.span, - format!("param `{}` is defined more than once", name.name), - )) + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: name.span, + message: format!("param `{}` is defined more than once", name.name), + })); } ret.push(Param { name: name.name.to_string(), @@ -1664,7 +1741,7 @@ impl<'a> Resolver<'a> { result: &Option>, kind: &FunctionKind, _span: Span, - ) -> Result> { + ) -> Result, PackageParseErrors> { match *kind { // These kinds of methods don't have any adjustments to the return // values, so plumb them through as-is. @@ -1696,7 +1773,7 @@ impl<'a> Resolver<'a> { &mut self, resource_id: TypeId, result_ast: &ast::Type<'_>, - ) -> Result { + ) -> Result { let result = self.resolve_type(result_ast, &Stability::Unknown)?; let ok_type = match result { Type::Id(id) => match &self.types[id].kind { @@ -1706,10 +1783,11 @@ impl<'a> Resolver<'a> { _ => None, }; let Some(ok_type) = ok_type else { - bail!(Error::new( - result_ast.span(), - "if a constructor return type is declared it must be a `result`", - )); + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: result_ast.span(), + message: "if a constructor return type is declared it must be a `result`" + .to_owned(), + })); }; match ok_type { Some(Type::Id(ok_id)) if resource_id == ok_id => Ok(result), @@ -1720,10 +1798,10 @@ impl<'a> Resolver<'a> { } else { result_ast.span() }; - bail!(Error::new( - ok_span, - "the `ok` type must be the resource being constructed", - )); + return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + span: ok_span, + message: "the `ok` type must be the resource being constructed".to_owned(), + })); } } } diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index b54b3f647b..5dd36fb4a7 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,13 +1,12 @@ use crate::IndexMap; use crate::ast::{Id, Span}; use alloc::collections::BinaryHeap; -use alloc::format; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use anyhow::Result; use core::fmt; use core::mem; +use core::result::Result; #[derive(Default, Clone)] struct State { @@ -59,7 +58,7 @@ pub fn toposort<'a>( span: edge.span, name: edge.name.to_string(), kind: kind.to_string(), - highlighted: None, + hint: None, })?; states[j].reverse_deps.push(i); } @@ -120,7 +119,6 @@ pub fn toposort<'a>( span: dep.span, name: dep.name.to_string(), kind: kind.to_string(), - highlighted: None, }); } } @@ -128,56 +126,42 @@ pub fn toposort<'a>( unreachable!() } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Error { NonexistentDep { span: Span, name: String, kind: String, - highlighted: Option, + /// Optional hint to display after the main error message, e.g. to + /// suggest a renamed type. + hint: Option, }, Cycle { span: Span, name: String, kind: String, - highlighted: Option, }, } impl Error { - pub(crate) fn highlighted(&self) -> Option<&str> { + pub fn span(&self) -> Span { match self { - Error::NonexistentDep { highlighted, .. } | Error::Cycle { highlighted, .. } => { - highlighted.as_deref() - } - } - } - - /// Highlights this error using the given source map, if the span is known. - pub(crate) fn highlight(&mut self, source_map: &crate::ast::SourceMap) { - if self.highlighted().is_some() { - return; - } - let span = match self { Error::NonexistentDep { span, .. } | Error::Cycle { span, .. } => *span, - }; - let msg = source_map.highlight_span(span, &format!("{self}")); - match self { - Error::NonexistentDep { highlighted, .. } | Error::Cycle { highlighted, .. } => { - *highlighted = msg; - } } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(s) = self.highlighted() { - return f.write_str(s); - } match self { - Error::NonexistentDep { kind, name, .. } => { - write!(f, "{kind} `{name}` does not exist") + Error::NonexistentDep { + kind, name, hint, .. + } => { + write!(f, "{kind} `{name}` does not exist")?; + if let Some(hint) = hint { + write!(f, "\n{hint}")?; + } + Ok(()) } Error::Cycle { kind, name, .. } => { write!(f, "{kind} `{name}` depends on itself") diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 742d75159d..123215eccf 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -402,6 +402,7 @@ impl UnresolvedPackageGroup { let mut map = SourceMap::default(); map.push_str(path, contents); map.parse() + .map_err(|(map, e)| anyhow::anyhow!("{}", e.highlight(&map))) } /// Parse a WIT package at the provided path. @@ -464,6 +465,7 @@ impl UnresolvedPackageGroup { map.push_file(&path)?; } map.parse() + .map_err(|(map, e)| anyhow::anyhow!("{}", e.highlight(&map))) } } From 0a58d5da3bf0268c3adc3441e9b74ea20ef48ee1 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:12:43 +0000 Subject: [PATCH 02/14] Update snapshots --- crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result | 2 +- crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result | 2 +- crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result | 2 +- crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result | 2 +- .../tests/ui/parse-fail/conflicting-package.wit.result | 2 +- .../tests/ui/parse-fail/multiple-package-docs.wit.result | 2 +- .../wit-parser/tests/ui/parse-fail/old-float-types.wit.result | 3 ++- .../wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result | 2 +- .../wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result | 2 +- .../wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result | 2 +- .../wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result | 2 +- .../tests/ui/parse-fail/very-nested-packages.wit.result | 2 +- .../tests/ui/parse-fail/world-top-level-func2.wit.result | 2 +- 13 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result index a2c71f1785..430d2e9b6e 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function.wit.result @@ -1,4 +1,4 @@ -name `nonexistent` is not defined +name `nonexistent` does not exist --> tests/ui/parse-fail/bad-function.wit:6:18 | 6 | x: func(param: nonexistent); diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result index 5cd183197e..83e5c52a3a 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-function2.wit.result @@ -1,4 +1,4 @@ -name `nonexistent` is not defined +name `nonexistent` does not exist --> tests/ui/parse-fail/bad-function2.wit:6:16 | 6 | x: func() -> nonexistent; diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result index d4c634eaf4..ccbe1bb3d2 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result @@ -1,4 +1,4 @@ -interface or world `non-existance` not found in package +interface or world `non-existance` does not exist --> tests/ui/parse-fail/bad-include1.wit:4:11 | 4 | include non-existance; diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result index ddc7c7c307..61fdb95c24 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg1]: failed to parse package: tests/ui/parse-fail/bad-pkg1: interface or world `nonexistent` not found in package +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/bad-pkg1]: failed to parse package: tests/ui/parse-fail/bad-pkg1: interface or world `nonexistent` does not exist --> tests/ui/parse-fail/bad-pkg1/root.wit:4:7 | 4 | use nonexistent.{}; 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 dc9dcee416..04eca323f1 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 @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/conflicting-package]: failed to parse package: tests/ui/parse-fail/conflicting-package: failed to start resolving path: tests/ui/parse-fail/conflicting-package/b.wit: package identifier `foo:b` does not match previous package name of `foo:a` +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/conflicting-package]: failed to parse package: tests/ui/parse-fail/conflicting-package: 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 fd70371f39..928beef7c1 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 @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multiple-package-docs]: failed to parse package: tests/ui/parse-fail/multiple-package-docs: failed to start resolving path: tests/ui/parse-fail/multiple-package-docs/b.wit: found doc comments on multiple 'package' items +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multiple-package-docs]: failed to parse package: tests/ui/parse-fail/multiple-package-docs: 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/old-float-types.wit.result b/crates/wit-parser/tests/ui/parse-fail/old-float-types.wit.result index e3a34fd62e..e98c3d74d5 100644 --- a/crates/wit-parser/tests/ui/parse-fail/old-float-types.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/old-float-types.wit.result @@ -1,4 +1,5 @@ -the `float32` type has been renamed to `f32` and is no longer accepted, but the `WIT_REQUIRE_F32_F64=0` environment variable can be used to temporarily disable this error: type `float32` does not exist +type `float32` does not exist +the `float32` type has been renamed to `f32` and is no longer accepted, but the `WIT_REQUIRE_F32_F64=0` environment variable can be used to temporarily disable this error --> tests/ui/parse-fail/old-float-types.wit:4:13 | 4 | type t1 = float32; diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result index d61301e4a1..d735d177d0 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result @@ -1,4 +1,4 @@ -interface or world `bar` not found in package +interface or world `bar` does not exist --> tests/ui/parse-fail/unresolved-use1.wit:6:7 | 6 | use bar.{x}; diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result index 8d36b21d13..2336a708b8 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use10.wit.result @@ -1,4 +1,4 @@ -failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/unresolved-use10]: failed to parse package: tests/ui/parse-fail/unresolved-use10: name `thing` is not defined +failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/unresolved-use10]: failed to parse package: tests/ui/parse-fail/unresolved-use10: name `thing` does not exist --> tests/ui/parse-fail/unresolved-use10/bar.wit:4:12 | 4 | use foo.{thing}; diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result index 51349921de..68812f702f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use2.wit.result @@ -1,4 +1,4 @@ -name `x` is not defined +name `x` does not exist --> tests/ui/parse-fail/unresolved-use2.wit:6:12 | 6 | use bar.{x}; diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result index 917215b7cf..1cd4c4c449 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use7.wit.result @@ -1,4 +1,4 @@ -name `x` is not defined +name `x` does not exist --> tests/ui/parse-fail/unresolved-use7.wit:6:12 | 6 | use bar.{x}; diff --git a/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result index 3109de9582..16fd6ae435 100644 --- a/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/very-nested-packages.wit.result @@ -1,4 +1,4 @@ -failed to handle nested package in: tests/ui/parse-fail/very-nested-packages.wit: nested packages must be placed at the top-level +nested packages must be placed at the top-level --> tests/ui/parse-fail/very-nested-packages.wit:4:11 | 4 | package a:c2 { diff --git a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result index 9ebb6629b6..df292707c0 100644 --- a/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/world-top-level-func2.wit.result @@ -1,4 +1,4 @@ -name `b` is not defined +name `b` does not exist --> tests/ui/parse-fail/world-top-level-func2.wit:3:23 | 3 | import foo: func(a: b); From 5164020ed682ed45bb0cee4ddfd44c4a6a075e0b Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:16:13 +0000 Subject: [PATCH 03/14] Derive Eq and PartialEq --- crates/wit-parser/src/ast/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 68dff05d61..34730f4dbb 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -8,7 +8,7 @@ use crate::{ }; #[non_exhaustive] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum PackageParseErrorKind { /// Lexer error (invalid character, unterminated comment, etc.) Lex(lex::Error), @@ -64,7 +64,7 @@ impl fmt::Display for PackageParseErrorKind { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct PackageParseErrors(Box); impl PackageParseErrors { From 1d20ddcb558a2c25d726a55fd1c4672e1ce5c961 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:25:22 +0000 Subject: [PATCH 04/14] Remove toposort::Error --- crates/wit-parser/src/ast/error.rs | 27 +-------- crates/wit-parser/src/ast/resolve.rs | 8 +-- crates/wit-parser/src/ast/toposort.rs | 84 +++++++-------------------- 3 files changed, 26 insertions(+), 93 deletions(-) diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 34730f4dbb..caa732cabb 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -2,10 +2,7 @@ use alloc::boxed::Box; use alloc::string::{String, ToString}; use core::fmt; -use crate::{ - SourceMap, Span, - ast::{lex, toposort}, -}; +use crate::{SourceMap, Span, ast::lex}; #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] @@ -100,25 +97,3 @@ impl From for PackageParseErrors { PackageParseErrorKind::Lex(e).into() } } - -impl From for PackageParseErrors { - fn from(e: toposort::Error) -> Self { - let kind = match e { - toposort::Error::NonexistentDep { - span, - name, - kind, - hint, - } => PackageParseErrorKind::ItemNotFound { - span, - name, - kind, - hint, - }, - toposort::Error::Cycle { span, name, kind } => { - PackageParseErrorKind::TypeCycle { span, name, kind } - } - }; - kind.into() - } -} diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index cd6a05437b..ddad70c96e 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -926,9 +926,7 @@ impl<'a> Resolver<'a> { } } } - let order = toposort("type", &type_deps) - .map_err(attach_old_float_type_context) - .map_err(PackageParseErrors::from)?; + let order = toposort("type", &type_deps).map_err(attach_old_float_type_context)?; for ty in order { let def = match type_defs.swap_remove(&ty).unwrap() { Some(def) => def, @@ -949,8 +947,8 @@ impl<'a> Resolver<'a> { } return Ok(()); - fn attach_old_float_type_context(mut err: ast::toposort::Error) -> ast::toposort::Error { - if let ast::toposort::Error::NonexistentDep { name, hint, .. } = &mut err { + fn attach_old_float_type_context(mut err: PackageParseErrorKind) -> PackageParseErrorKind { + if let PackageParseErrorKind::ItemNotFound { name, hint, .. } = &mut err { let new = match name.as_str() { "float32" => "f32", "float64" => "f64", diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index 5dd36fb4a7..30810fd0f9 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,10 +1,10 @@ +use super::error::PackageParseErrorKind; use crate::IndexMap; -use crate::ast::{Id, Span}; +use crate::ast::Id; use alloc::collections::BinaryHeap; -use alloc::string::{String, ToString}; +use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; -use core::fmt; use core::mem; use core::result::Result; @@ -45,21 +45,21 @@ struct State { pub fn toposort<'a>( kind: &str, deps: &IndexMap<&'a str, Vec>>, -) -> Result, Error> { +) -> Result, PackageParseErrorKind> { // Initialize a `State` per-node with the number of outbound edges and // additionally filling out the `reverse_deps` array. let mut states = vec![State::default(); deps.len()]; for (i, (_, edges)) in deps.iter().enumerate() { states[i].outbound_remaining = edges.len(); for edge in edges { - let (j, _, _) = deps - .get_full(edge.name) - .ok_or_else(|| Error::NonexistentDep { - span: edge.span, - name: edge.name.to_string(), - kind: kind.to_string(), - hint: None, - })?; + let (j, _, _) = + deps.get_full(edge.name) + .ok_or_else(|| PackageParseErrorKind::ItemNotFound { + span: edge.span, + name: edge.name.to_string(), + kind: kind.to_string(), + hint: None, + })?; states[j].reverse_deps.push(i); } } @@ -115,7 +115,7 @@ pub fn toposort<'a>( if states[j].outbound_remaining == 0 { continue; } - return Err(Error::Cycle { + return Err(PackageParseErrorKind::TypeCycle { span: dep.span, name: dep.name.to_string(), kind: kind.to_string(), @@ -126,52 +126,6 @@ pub fn toposort<'a>( unreachable!() } -#[derive(Clone, Debug)] -pub enum Error { - NonexistentDep { - span: Span, - name: String, - kind: String, - /// Optional hint to display after the main error message, e.g. to - /// suggest a renamed type. - hint: Option, - }, - Cycle { - span: Span, - name: String, - kind: String, - }, -} - -impl Error { - pub fn span(&self) -> Span { - match self { - Error::NonexistentDep { span, .. } | Error::Cycle { span, .. } => *span, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::NonexistentDep { - kind, name, hint, .. - } => { - write!(f, "{kind} `{name}` does not exist")?; - if let Some(hint) = hint { - write!(f, "\n{hint}")?; - } - Ok(()) - } - Error::Cycle { kind, name, .. } => { - write!(f, "{kind} `{name}` depends on itself") - } - } - } -} - -impl core::error::Error for Error {} - #[cfg(test)] mod tests { use super::*; @@ -192,7 +146,7 @@ mod tests { nonexistent.insert("a", vec![id("b")]); assert!(matches!( toposort("", &nonexistent), - Err(Error::NonexistentDep { .. }) + Err(PackageParseErrorKind::ItemNotFound { .. }) )); let mut one = IndexMap::default(); @@ -214,13 +168,19 @@ mod tests { fn cycles() { let mut cycle = IndexMap::default(); cycle.insert("a", vec![id("a")]); - assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + assert!(matches!( + toposort("", &cycle), + Err(PackageParseErrorKind::TypeCycle { .. }) + )); let mut cycle = IndexMap::default(); cycle.insert("a", vec![id("b")]); cycle.insert("b", vec![id("c")]); cycle.insert("c", vec![id("a")]); - assert!(matches!(toposort("", &cycle), Err(Error::Cycle { .. }))); + assert!(matches!( + toposort("", &cycle), + Err(PackageParseErrorKind::TypeCycle { .. }) + )); } #[test] From e2c6c8464d9016b0a06c6e396b244c0298ded7e1 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:26:29 +0000 Subject: [PATCH 05/14] Remove useless comments --- crates/wit-parser/src/ast/error.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index caa732cabb..32148a92a8 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -13,15 +13,14 @@ pub enum PackageParseErrorKind { /// invalid attribute, etc.) Syntax { span: Span, message: String }, /// A type/interface/world references a name that does not exist within - /// the same package. Arises from within-package toposort. + /// the same package. ItemNotFound { span: Span, name: String, kind: String, hint: Option, }, - /// A type/interface/world depends on itself. Arises from within-package - /// toposort. + /// A type/interface/world depends on itself. TypeCycle { span: Span, name: String, From 0c713eae9b91522095d905b713f3d1873d47c3ea Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:17:26 +0000 Subject: [PATCH 06/14] Make highlight_span pub(crate) again --- crates/wit-parser/src/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index a4e5095337..2ee9bd6711 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1890,7 +1890,7 @@ impl SourceMap { Err(err) } - pub fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option { + pub(crate) fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option { if !span.is_known() { return None; } From 4cdf6170f1d3c06b7751b60e20235151dd417131 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Sun, 15 Mar 2026 13:58:33 +0000 Subject: [PATCH 07/14] Expose ast/parsing errors --- crates/wit-parser/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 123215eccf..83ef973b0e 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -46,6 +46,7 @@ pub use metadata::PackageMetadata; pub mod abi; mod ast; pub use ast::SourceMap; +pub use ast::error::*; pub use ast::lex::Span; pub use ast::{ParsedUsePath, parse_use_path}; mod sizealign; From 61619f6bcaa7b9141e84dad516a98169517557ca Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:00:52 +0000 Subject: [PATCH 08/14] Introduce new_syntax helper --- crates/wit-parser/src/ast.rs | 53 +++---- crates/wit-parser/src/ast/error.rs | 8 + crates/wit-parser/src/ast/resolve.rs | 229 +++++++++++++-------------- 3 files changed, 137 insertions(+), 153 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 2ee9bd6711..0f2adc2e46 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,4 +1,4 @@ -use crate::ast::error::{PackageParseErrorKind, PackageParseErrors}; +use crate::ast::error::PackageParseErrors; use crate::{UnresolvedPackage, UnresolvedPackageGroup}; use alloc::borrow::Cow; use alloc::boxed::Box; @@ -69,10 +69,10 @@ impl<'a> PackageFile<'a> { ) -> Result { let span = tokens.expect(Token::Package)?; if !attributes.is_empty() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: span, - message: format!("cannot place attributes on nested packages"), - })); + return Err(PackageParseErrors::new_syntax( + span, + format!("cannot place attributes on nested packages"), + )); } let package_id = PackageName::parse(tokens, docs)?; tokens.expect(Token::LeftBrace)?; @@ -1270,12 +1270,8 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version), PackageP eat_ids(tokens, Token::Minus, &mut span)?; eat_ids(tokens, Token::Plus, &mut span)?; let string = tokens.get_span(span); - let version = Version::parse(string).map_err(|e| { - PackageParseErrors::from(PackageParseErrorKind::Syntax { - span, - message: e.to_string(), - }) - })?; + let version = + Version::parse(string).map_err(|e| PackageParseErrors::new_syntax(span, e.to_string()))?; return Ok((span, version)); // According to `semver.org` this is what we're parsing: @@ -1425,10 +1421,7 @@ impl<'a> Type<'a> { let number = tokens.next()?; if let Some((span, Token::Integer)) = number { let size: u32 = tokens.get_span(span).parse().map_err(|e| { - PackageParseErrors::from(PackageParseErrorKind::Syntax { - span, - message: format!("invalid list size: {e}"), - }) + PackageParseErrors::new_syntax(span, format!("invalid list size: {e}")) })?; Some(size) } else { @@ -1637,14 +1630,14 @@ fn err_expected( found: Option<(Span, Token)>, ) -> PackageParseErrors { match found { - Some((span, token)) => PackageParseErrors::from(PackageParseErrorKind::Syntax { + Some((span, token)) => PackageParseErrors::new_syntax( span, - message: format!("expected {}, found {}", expected, token.describe()), - }), - None => PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: tokens.eof_span(), - message: format!("expected {expected}, found eof"), - }), + format!("expected {}, found {}", expected, token.describe()), + ), + None => PackageParseErrors::new_syntax( + tokens.eof_span(), + format!("expected {expected}, found eof"), + ), } } @@ -1694,10 +1687,10 @@ impl<'a> Attribute<'a> { } } other => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: id.span, - message: format!("unknown attribute `{other}`"), - })); + return Err(PackageParseErrors::new_syntax( + id.span, + format!("unknown attribute `{other}`"), + )); } }; ret.push(attr); @@ -1717,10 +1710,10 @@ impl<'a> Attribute<'a> { fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> Result { let id = parse_id(tokens)?; if id.name != expected { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: id.span, - message: format!("expected `{expected}`, found `{}`", id.name), - })); + return Err(PackageParseErrors::new_syntax( + id.span, + format!("expected `{expected}`, found `{}`", id.name), + )); } Ok(id.span) } diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 32148a92a8..eeeaed745a 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -64,6 +64,14 @@ impl fmt::Display for PackageParseErrorKind { pub struct PackageParseErrors(Box); impl PackageParseErrors { + pub fn new_syntax(span: Span, message: impl Into) -> Self { + PackageParseErrorKind::Syntax { + span, + message: message.into(), + } + .into() + } + pub fn kind(&self) -> &PackageParseErrorKind { &self.0 } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index ddad70c96e..7eaab8350a 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -115,13 +115,13 @@ impl<'a> Resolver<'a> { let cur_name = cur.package_name(); if let Some((prev, _)) = &self.package_name { if cur_name != *prev { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: cur.span, - message: format!( + return Err(PackageParseErrors::new_syntax( + cur.span, + format!( "package identifier `{cur_name}` does not match \ - previous package name of `{prev}`" + previous package name of `{prev}`" ), - })); + )); } } self.package_name = Some((cur_name, cur.span)); @@ -130,10 +130,10 @@ impl<'a> Resolver<'a> { let docs = self.docs(&cur.docs); if docs.contents.is_some() { if self.package_docs.contents.is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: cur.docs.span, - message: "found doc comments on multiple 'package' items".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + cur.docs.span, + "found doc comments on multiple 'package' items".to_owned(), + )); } self.package_docs = docs; } @@ -147,10 +147,10 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(pkg) => pkg.package_id.as_ref().unwrap().span, _ => continue, }; - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: span, - message: "nested packages must be placed at the top-level".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + span, + "nested packages must be placed at the top-level".to_owned(), + )); } self.decl_lists.push(file.decl_list); @@ -162,11 +162,10 @@ impl<'a> Resolver<'a> { let (name, package_name_span) = match &self.package_name { Some(name) => name.clone(), None => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: Span::default(), - message: "no `package` header was found in any WIT file for this package" - .to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + Span::default(), + "no `package` header was found in any WIT file for this package".to_owned(), + )); } }; @@ -356,10 +355,10 @@ impl<'a> Resolver<'a> { match item { ast::AstItem::Interface(i) => { if package_items.insert(i.name.name, i.name.span).is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: i.name.span, - message: format!("duplicate item named `{}`", i.name.name), - })); + return Err(PackageParseErrors::new_syntax( + i.name.span, + format!("duplicate item named `{}`", i.name.name), + )); } let prev = decl_list_ns.insert(i.name.name, ()); assert!(prev.is_none()); @@ -370,10 +369,10 @@ impl<'a> Resolver<'a> { } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: w.name.span, - message: format!("duplicate item named `{}`", w.name.name), - })); + return Err(PackageParseErrors::new_syntax( + w.name.span, + format!("duplicate item named `{}`", w.name.name), + )); } let prev = decl_list_ns.insert(w.name.name, ()); assert!(prev.is_none()); @@ -421,10 +420,10 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(_) => unreachable!(), }; if decl_list_ns.insert(name.name, (name.span, src)).is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.span, - message: format!("duplicate name `{}` in this file", name.name), - })); + return Err(PackageParseErrors::new_syntax( + name.span, + format!("duplicate name `{}` in this file", name.name), + )); } } @@ -501,10 +500,10 @@ impl<'a> Resolver<'a> { let (name, ast_item) = match item { ast::AstItem::Use(u) => { if !u.attributes.is_empty() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: u.span, - message: format!("attributes not allowed on top-level use"), - })); + return Err(PackageParseErrors::new_syntax( + 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 { @@ -640,12 +639,10 @@ impl<'a> Resolver<'a> { WorldItem::Type { id, span: *span }, ); if prev.is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: *span, - message: format!( - "import `{name}` conflicts with prior import of same name" - ), - })); + return Err(PackageParseErrors::new_syntax( + *span, + format!("import `{name}` conflicts with prior import of same name"), + )); } } TypeOrItem::Item(_) => unreachable!(), @@ -719,10 +716,10 @@ impl<'a> Resolver<'a> { }; if let WorldItem::Interface { id, .. } = world_item { if !interfaces.insert(id) { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: kind.span(), - message: format!("interface cannot be {desc}ed more than once"), - })); + return Err(PackageParseErrors::new_syntax( + kind.span(), + format!("interface cannot be {desc}ed more than once"), + )); } } let dst = if desc == "import" { @@ -741,10 +738,10 @@ impl<'a> Resolver<'a> { WorldKey::Name(name) => name, WorldKey::Interface(..) => unreachable!(), }; - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: kind.span(), - message: format!("{desc} `{name}` conflicts with prior {prev} of same name",), - })); + return Err(PackageParseErrors::new_syntax( + kind.span(), + format!("{desc} `{name}` conflicts with prior {prev} of same name",), + )); } } self.type_lookup.clear(); @@ -908,10 +905,10 @@ impl<'a> Resolver<'a> { TypeItem::Def(t) => { let prev = type_defs.insert(t.name.name, Some(t)); if prev.is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: t.name.span, - message: format!("name `{}` is defined more than once", t.name.name), - })); + return Err(PackageParseErrors::new_syntax( + t.name.span, + format!("name `{}` is defined more than once", t.name.name), + )); } let mut deps = Vec::new(); collect_deps(&t.ty, &mut deps); @@ -979,10 +976,10 @@ impl<'a> Resolver<'a> { let id = match lookup.get(name.name.name) { Some((TypeOrItem::Type(id), _)) => *id, Some((TypeOrItem::Item(s), _)) => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.name.span, - message: format!("cannot import {s} `{}`", name.name.name), - })); + return Err(PackageParseErrors::new_syntax( + name.name.span, + format!("cannot import {s} `{}`", name.name.name), + )); } None => { return Err(PackageParseErrors::from( @@ -1144,10 +1141,10 @@ impl<'a> Resolver<'a> { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + return Err(PackageParseErrors::new_syntax( span, - message: format!("name `{name}` is defined as a world, not an interface"), - })); + format!("name `{name}` is defined as a world, not an interface"), + )); } } } @@ -1161,10 +1158,10 @@ impl<'a> Resolver<'a> { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { + return Err(PackageParseErrors::new_syntax( span, - message: format!("name `{name}` is defined as an interface, not a world"), - })); + format!("name `{name}` is defined as an interface, not a world"), + )); } } } @@ -1176,10 +1173,10 @@ impl<'a> Resolver<'a> { ) -> Result<(), PackageParseErrors> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.span, - message: format!("name `{}` is defined more than once", name.name), - })); + return Err(PackageParseErrors::new_syntax( + name.span, + format!("name `{}` is defined more than once", name.name), + )); } else { Ok(()) } @@ -1230,10 +1227,7 @@ impl<'a> Resolver<'a> { | Type::Char | Type::String => {} _ => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: map.span, - message: "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned(), - })); + return Err(PackageParseErrors::new_syntax(map.span, "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned())); } } @@ -1258,25 +1252,18 @@ impl<'a> Resolver<'a> { match func { ast::ResourceFunc::Method(f) | ast::ResourceFunc::Static(f) => { if !names.insert(&f.name.name) { - return Err(PackageParseErrors::from( - PackageParseErrorKind::Syntax { - span: f.name.span, - message: format!( - "duplicate function name `{}`", - f.name.name - ), - }, + return Err(PackageParseErrors::new_syntax( + f.name.span, + format!("duplicate function name `{}`", f.name.name), )); } } ast::ResourceFunc::Constructor(f) => { ctors += 1; if ctors > 1 { - return Err(PackageParseErrors::from( - PackageParseErrorKind::Syntax { - span: f.name.span, - message: "duplicate constructors".to_owned(), - }, + return Err(PackageParseErrors::new_syntax( + f.name.span, + "duplicate constructors".to_owned(), )); } } @@ -1322,10 +1309,10 @@ impl<'a> Resolver<'a> { } ast::Type::Variant(variant) => { if variant.cases.is_empty() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: variant.span, - message: "empty variant".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + variant.span, + "empty variant".to_owned(), + )); } let cases = variant .cases @@ -1343,10 +1330,10 @@ impl<'a> Resolver<'a> { } ast::Type::Enum(e) => { if e.cases.is_empty() { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: e.span, - message: "empty enum".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + e.span, + "empty enum".to_owned(), + )); } let cases = e .cases @@ -1379,10 +1366,10 @@ impl<'a> Resolver<'a> { match self.type_lookup.get(name.name) { Some((TypeOrItem::Type(id), _)) => Ok(*id), Some((TypeOrItem::Item(s), _)) => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.span, - message: format!("cannot use {s} `{name}` as a type", name = name.name), - })); + return Err(PackageParseErrors::new_syntax( + name.span, + format!("cannot use {s} `{name}` as a type", name = name.name), + )); } None => { return Err(PackageParseErrors::from( @@ -1409,13 +1396,10 @@ impl<'a> Resolver<'a> { break Ok(id); } _ => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.span, - message: format!( - "type `{}` used in a handle must be a resource", - name.name - ), - })); + return Err(PackageParseErrors::new_syntax( + name.span, + format!("type `{}` used in a handle must be a resource", name.name), + )); } } } @@ -1668,16 +1652,16 @@ impl<'a> Resolver<'a> { deprecated: Some(version.clone()), }), [ast::Attribute::Deprecated { span, .. }] => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: *span, - message: "must pair @deprecated with either @since or @unstable".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + *span, + "must pair @deprecated with either @since or @unstable".to_owned(), + )); } [_, b, ..] => { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: b.span(), - message: "unsupported combination of attributes".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + b.span(), + "unsupported combination of attributes".to_owned(), + )); } } } @@ -1720,10 +1704,10 @@ impl<'a> Resolver<'a> { } for (name, ty) in params { if ret.iter().any(|p| p.name == name.name) { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: name.span, - message: format!("param `{}` is defined more than once", name.name), - })); + return Err(PackageParseErrors::new_syntax( + name.span, + format!("param `{}` is defined more than once", name.name), + )); } ret.push(Param { name: name.name.to_string(), @@ -1781,11 +1765,10 @@ impl<'a> Resolver<'a> { _ => None, }; let Some(ok_type) = ok_type else { - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: result_ast.span(), - message: "if a constructor return type is declared it must be a `result`" - .to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + result_ast.span(), + "if a constructor return type is declared it must be a `result`".to_owned(), + )); }; match ok_type { Some(Type::Id(ok_id)) if resource_id == ok_id => Ok(result), @@ -1796,10 +1779,10 @@ impl<'a> Resolver<'a> { } else { result_ast.span() }; - return Err(PackageParseErrors::from(PackageParseErrorKind::Syntax { - span: ok_span, - message: "the `ok` type must be the resource being constructed".to_owned(), - })); + return Err(PackageParseErrors::new_syntax( + ok_span, + "the `ok` type must be the resource being constructed".to_owned(), + )); } } } From 430f7064f2e25f16d83ff87cef3e70932459395d Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:05:13 +0000 Subject: [PATCH 09/14] PackageParseErrorKind -> PackageParseErrors --- crates/wit-parser/src/ast/error.rs | 4 +++ crates/wit-parser/src/ast/resolve.rs | 4 +-- crates/wit-parser/src/ast/toposort.rs | 37 ++++++++++++++------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index eeeaed745a..347f273927 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -76,6 +76,10 @@ impl PackageParseErrors { &self.0 } + pub fn kind_mut(&mut self) -> &mut PackageParseErrorKind { + &mut self.0 + } + /// Format this error with source context (file:line:col + snippet) pub fn highlight(&self, source_map: &SourceMap) -> String { let e = self.kind(); diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 7eaab8350a..3c414e95cc 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -944,8 +944,8 @@ impl<'a> Resolver<'a> { } return Ok(()); - fn attach_old_float_type_context(mut err: PackageParseErrorKind) -> PackageParseErrorKind { - if let PackageParseErrorKind::ItemNotFound { name, hint, .. } = &mut err { + fn attach_old_float_type_context(mut err: PackageParseErrors) -> PackageParseErrors { + if let PackageParseErrorKind::ItemNotFound { name, hint, .. } = err.kind_mut() { let new = match name.as_str() { "float32" => "f32", "float64" => "f64", diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index 30810fd0f9..f4f971f728 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,6 +1,6 @@ -use super::error::PackageParseErrorKind; -use crate::IndexMap; +use super::error::PackageParseErrors; use crate::ast::Id; +use crate::{IndexMap, PackageParseErrorKind}; use alloc::collections::BinaryHeap; use alloc::string::ToString; use alloc::vec; @@ -45,21 +45,21 @@ struct State { pub fn toposort<'a>( kind: &str, deps: &IndexMap<&'a str, Vec>>, -) -> Result, PackageParseErrorKind> { +) -> Result, PackageParseErrors> { // Initialize a `State` per-node with the number of outbound edges and // additionally filling out the `reverse_deps` array. let mut states = vec![State::default(); deps.len()]; for (i, (_, edges)) in deps.iter().enumerate() { states[i].outbound_remaining = edges.len(); for edge in edges { - let (j, _, _) = - deps.get_full(edge.name) - .ok_or_else(|| PackageParseErrorKind::ItemNotFound { - span: edge.span, - name: edge.name.to_string(), - kind: kind.to_string(), - hint: None, - })?; + let (j, _, _) = deps.get_full(edge.name).ok_or_else(|| { + PackageParseErrors::from(PackageParseErrorKind::ItemNotFound { + span: edge.span, + name: edge.name.to_string(), + kind: kind.to_string(), + hint: None, + }) + })?; states[j].reverse_deps.push(i); } } @@ -119,7 +119,8 @@ pub fn toposort<'a>( span: dep.span, name: dep.name.to_string(), kind: kind.to_string(), - }); + } + .into()); } } @@ -145,8 +146,8 @@ mod tests { let mut nonexistent = IndexMap::default(); nonexistent.insert("a", vec![id("b")]); assert!(matches!( - toposort("", &nonexistent), - Err(PackageParseErrorKind::ItemNotFound { .. }) + toposort("", &nonexistent).unwrap_err().kind(), + PackageParseErrorKind::ItemNotFound { .. } )); let mut one = IndexMap::default(); @@ -169,8 +170,8 @@ mod tests { let mut cycle = IndexMap::default(); cycle.insert("a", vec![id("a")]); assert!(matches!( - toposort("", &cycle), - Err(PackageParseErrorKind::TypeCycle { .. }) + toposort("", &cycle).unwrap_err().kind(), + PackageParseErrorKind::TypeCycle { .. } )); let mut cycle = IndexMap::default(); @@ -178,8 +179,8 @@ mod tests { cycle.insert("b", vec![id("c")]); cycle.insert("c", vec![id("a")]); assert!(matches!( - toposort("", &cycle), - Err(PackageParseErrorKind::TypeCycle { .. }) + toposort("", &cycle).unwrap_err().kind(), + PackageParseErrorKind::TypeCycle { .. } )); } From c685bad9ecd74526af43d3a037d1df08dfb5dc55 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:59:37 +0000 Subject: [PATCH 10/14] Introduce ParseResult --- crates/wit-parser/src/ast.rs | 102 +++++++++++--------------- crates/wit-parser/src/ast/error.rs | 2 + crates/wit-parser/src/ast/resolve.rs | 82 ++++++++------------- crates/wit-parser/src/ast/toposort.rs | 5 +- 4 files changed, 75 insertions(+), 116 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 0f2adc2e46..88819bc9cd 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,5 +1,5 @@ use crate::ast::error::PackageParseErrors; -use crate::{UnresolvedPackage, UnresolvedPackageGroup}; +use crate::{ParseResult, UnresolvedPackage, UnresolvedPackageGroup}; use alloc::borrow::Cow; use alloc::boxed::Box; use alloc::format; @@ -37,7 +37,7 @@ impl<'a> PackageFile<'a> { /// /// This will optionally start with `package foo:bar;` and then will have a /// list of ast items after it. - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> ParseResult { let mut package_name_tokens_peek = tokens.clone(); let docs = parse_docs(&mut package_name_tokens_peek)?; @@ -66,7 +66,7 @@ impl<'a> PackageFile<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { let span = tokens.expect(Token::Package)?; if !attributes.is_empty() { return Err(PackageParseErrors::new_syntax( @@ -125,10 +125,7 @@ pub struct DeclList<'a> { } impl<'a> DeclList<'a> { - fn parse_until( - tokens: &mut Tokenizer<'a>, - end: Option, - ) -> Result, PackageParseErrors> { + fn parse_until(tokens: &mut Tokenizer<'a>, end: Option) -> ParseResult> { let mut items = Vec::new(); let mut docs = parse_docs(tokens)?; loop { @@ -158,8 +155,8 @@ impl<'a> DeclList<'a> { &'b UsePath<'a>, Option<&'b [UseName<'a>]>, WorldOrInterface, - ) -> Result<(), PackageParseErrors>, - ) -> Result<(), PackageParseErrors> { + ) -> ParseResult<()>, + ) -> ParseResult<()> { for item in self.items.iter() { match item { AstItem::World(world) => { @@ -266,7 +263,7 @@ enum AstItem<'a> { } impl<'a> AstItem<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> ParseResult { let attributes = Attribute::parse_list(tokens)?; match tokens.clone().next()? { Some((_span, Token::Interface)) => { @@ -292,7 +289,7 @@ struct PackageName<'a> { } impl<'a> PackageName<'a> { - fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> ParseResult { let namespace = parse_id(tokens)?; tokens.expect(Token::Colon)?; let name = parse_id(tokens)?; @@ -329,10 +326,7 @@ struct ToplevelUse<'a> { } impl<'a> ToplevelUse<'a> { - fn parse( - tokens: &mut Tokenizer<'a>, - attributes: Vec>, - ) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> ParseResult { let span = tokens.expect(Token::Use)?; let item = UsePath::parse(tokens)?; let as_ = if tokens.eat(Token::As)? { @@ -362,7 +356,7 @@ impl<'a> World<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::World)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; @@ -374,7 +368,7 @@ impl<'a> World<'a> { }) } - fn parse_items(tokens: &mut Tokenizer<'a>) -> Result>, PackageParseErrors> { + fn parse_items(tokens: &mut Tokenizer<'a>) -> ParseResult>> { tokens.expect(Token::LeftBrace)?; let mut items = Vec::new(); loop { @@ -402,7 +396,7 @@ impl<'a> WorldItem<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { match tokens.clone().next()? { Some((_span, Token::Import)) => { Import::parse(tokens, docs, attributes).map(WorldItem::Import) @@ -453,7 +447,7 @@ impl<'a> Import<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { tokens.expect(Token::Import)?; let kind = ExternKind::parse(tokens)?; Ok(Import { @@ -475,7 +469,7 @@ impl<'a> Export<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { tokens.expect(Token::Export)?; let kind = ExternKind::parse(tokens)?; Ok(Export { @@ -493,7 +487,7 @@ enum ExternKind<'a> { } impl<'a> ExternKind<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { + fn parse(tokens: &mut Tokenizer<'a>) -> ParseResult> { // Create a copy of the token stream to test out if this is a function // or an interface import. In those situations the token stream gets // reset to the state of the clone and we continue down those paths. @@ -550,7 +544,7 @@ impl<'a> Interface<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Interface)?; let name = parse_id(tokens)?; let items = Self::parse_items(tokens)?; @@ -562,9 +556,7 @@ impl<'a> Interface<'a> { }) } - pub(super) fn parse_items( - tokens: &mut Tokenizer<'a>, - ) -> Result>, PackageParseErrors> { + pub(super) fn parse_items(tokens: &mut Tokenizer<'a>) -> ParseResult>> { tokens.expect(Token::LeftBrace)?; let mut items = Vec::new(); loop { @@ -605,7 +597,7 @@ enum UsePath<'a> { } impl<'a> UsePath<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> ParseResult { let id = parse_id(tokens)?; if tokens.eat(Token::Colon)? { // `foo:bar/baz@1.0` @@ -644,10 +636,7 @@ struct UseName<'a> { } impl<'a> Use<'a> { - fn parse( - tokens: &mut Tokenizer<'a>, - attributes: Vec>, - ) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> ParseResult { tokens.expect(Token::Use)?; let from = UsePath::parse(tokens)?; tokens.expect(Token::Period)?; @@ -689,10 +678,7 @@ struct IncludeName<'a> { } impl<'a> Include<'a> { - fn parse( - tokens: &mut Tokenizer<'a>, - attributes: Vec>, - ) -> Result { + fn parse(tokens: &mut Tokenizer<'a>, attributes: Vec>) -> ParseResult { tokens.expect(Token::Include)?; let from = UsePath::parse(tokens)?; @@ -819,7 +805,7 @@ impl<'a> ResourceFunc<'a> { docs: Docs<'a>, attributes: Vec>, tokens: &mut Tokenizer<'a>, - ) -> Result { + ) -> ParseResult { match tokens.clone().next()? { Some((span, Token::Constructor)) => { tokens.expect(Token::Constructor)?; @@ -983,11 +969,11 @@ struct Func<'a> { } impl<'a> Func<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { + fn parse(tokens: &mut Tokenizer<'a>) -> ParseResult> { fn parse_params<'a>( tokens: &mut Tokenizer<'a>, left_paren: bool, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { if left_paren { tokens.expect(Token::LeftParen)?; }; @@ -1022,7 +1008,7 @@ impl<'a> InterfaceItem<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { match tokens.clone().next()? { Some((_span, Token::Type)) => { TypeDef::parse(tokens, docs, attributes).map(InterfaceItem::TypeDef) @@ -1056,7 +1042,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Type)?; let name = parse_id(tokens)?; tokens.expect(Token::Equals)?; @@ -1074,7 +1060,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Flags)?; let name = parse_id(tokens)?; let ty = Type::Flags(Flags { @@ -1101,7 +1087,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Resource)?; let name = parse_id(tokens)?; let mut funcs = Vec::new(); @@ -1130,7 +1116,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Record)?; let name = parse_id(tokens)?; let ty = Type::Record(Record { @@ -1159,7 +1145,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Variant)?; let name = parse_id(tokens)?; let ty = Type::Variant(Variant { @@ -1193,7 +1179,7 @@ impl<'a> TypeDef<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { tokens.expect(Token::Enum)?; let name = parse_id(tokens)?; let ty = Type::Enum(Enum { @@ -1222,7 +1208,7 @@ impl<'a> NamedFunc<'a> { tokens: &mut Tokenizer<'a>, docs: Docs<'a>, attributes: Vec>, - ) -> Result { + ) -> ParseResult { let name = parse_id(tokens)?; tokens.expect(Token::Colon)?; let func = Func::parse(tokens)?; @@ -1236,7 +1222,7 @@ impl<'a> NamedFunc<'a> { } } -fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors> { +fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> ParseResult> { match tokens.next()? { Some((span, Token::Id)) => Ok(Id { name: tokens.parse_id(span)?, @@ -1250,9 +1236,7 @@ fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result, PackageParseErrors } } -fn parse_opt_version( - tokens: &mut Tokenizer<'_>, -) -> Result, PackageParseErrors> { +fn parse_opt_version(tokens: &mut Tokenizer<'_>) -> ParseResult> { if tokens.eat(Token::At)? { parse_version(tokens).map(Some) } else { @@ -1260,7 +1244,7 @@ fn parse_opt_version( } } -fn parse_version(tokens: &mut Tokenizer<'_>) -> Result<(Span, Version), PackageParseErrors> { +fn parse_version(tokens: &mut Tokenizer<'_>) -> ParseResult<(Span, Version)> { let start = tokens.expect(Token::Integer)?.start(); tokens.expect(Token::Period)?; tokens.expect(Token::Integer)?; @@ -1384,7 +1368,7 @@ fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result, lex::Error> { } impl<'a> Type<'a> { - fn parse(tokens: &mut Tokenizer<'a>) -> Result { + fn parse(tokens: &mut Tokenizer<'a>) -> ParseResult { match tokens.next()? { Some((span, Token::U8)) => Ok(Type::U8(span)), Some((span, Token::U16)) => Ok(Type::U16(span)), @@ -1590,8 +1574,8 @@ fn parse_list<'a, T>( tokens: &mut Tokenizer<'a>, start: Token, end: Token, - parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, -) -> Result, PackageParseErrors> { + parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> ParseResult, +) -> ParseResult> { tokens.expect(start)?; parse_list_trailer(tokens, end, parse) } @@ -1599,8 +1583,8 @@ fn parse_list<'a, T>( fn parse_list_trailer<'a, T>( tokens: &mut Tokenizer<'a>, end: Token, - mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, -) -> Result, PackageParseErrors> { + mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> ParseResult, +) -> ParseResult> { let mut items = Vec::new(); loop { // get docs before we skip them to try to eat the end token @@ -1648,7 +1632,7 @@ enum Attribute<'a> { } impl<'a> Attribute<'a> { - fn parse_list(tokens: &mut Tokenizer<'a>) -> Result>, PackageParseErrors> { + fn parse_list(tokens: &mut Tokenizer<'a>) -> ParseResult>> { let mut ret = Vec::new(); while tokens.eat(Token::At)? { let id = parse_id(tokens)?; @@ -1707,7 +1691,7 @@ impl<'a> Attribute<'a> { } } -fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> Result { +fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> ParseResult { let id = parse_id(tokens)?; if id.name != expected { return Err(PackageParseErrors::new_syntax( @@ -1818,9 +1802,7 @@ impl SourceMap { } } - fn parse_inner( - &self, - ) -> Result<(UnresolvedPackage, Vec), PackageParseErrors> { + fn parse_inner(&self) -> ParseResult<(UnresolvedPackage, Vec)> { let mut nested = Vec::new(); let mut resolver = Resolver::default(); let mut srcs = self.sources.iter().collect::>(); diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 347f273927..2c8c2bfb10 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -4,6 +4,8 @@ use core::fmt; use crate::{SourceMap, Span, ast::lex}; +pub type ParseResult = Result; + #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] pub enum PackageParseErrorKind { diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 3c414e95cc..d7298cadcf 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -7,7 +7,6 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::{format, vec}; use core::mem; -use core::result::Result; #[derive(Default)] pub struct Resolver<'a> { @@ -107,7 +106,7 @@ enum TypeOrItem { } impl<'a> Resolver<'a> { - pub(super) fn push(&mut self, file: ast::PackageFile<'a>) -> Result<(), PackageParseErrors> { + pub(super) fn push(&mut self, file: ast::PackageFile<'a>) -> ParseResult<()> { // As each WIT file is pushed into this resolver keep track of the // current package name assigned. Only one file needs to mention it, but // if multiple mention it then they must all match. @@ -157,7 +156,7 @@ impl<'a> Resolver<'a> { Ok(()) } - pub(crate) fn resolve(&mut self) -> Result { + pub(crate) fn resolve(&mut self) -> ParseResult { // At least one of the WIT files must have a `package` annotation. let (name, package_name_span) = match &self.package_name { Some(name) => name.clone(), @@ -341,7 +340,7 @@ impl<'a> Resolver<'a> { fn populate_ast_items( &mut self, decl_lists: &[ast::DeclList<'a>], - ) -> Result<(Vec, Vec), PackageParseErrors> { + ) -> ParseResult<(Vec, Vec)> { let mut package_items = IndexMap::default(); // Validate that all worlds and interfaces have unique names within this @@ -554,10 +553,7 @@ impl<'a> Resolver<'a> { /// This is done after all interfaces are generated so `self.resolve_path` /// can be used to determine if what's being imported from is a foreign /// interface or not. - fn populate_foreign_types( - &mut self, - decl_lists: &[ast::DeclList<'a>], - ) -> Result<(), PackageParseErrors> { + fn populate_foreign_types(&mut self, decl_lists: &[ast::DeclList<'a>]) -> ParseResult<()> { for (i, decl_list) in decl_lists.iter().enumerate() { self.cur_ast_index = i; decl_list.for_each_path(&mut |_, attrs, path, names, _| { @@ -601,11 +597,7 @@ impl<'a> Resolver<'a> { Ok(()) } - fn resolve_world( - &mut self, - world_id: WorldId, - world: &ast::World<'a>, - ) -> Result { + fn resolve_world(&mut self, world_id: WorldId, world: &ast::World<'a>) -> ParseResult { let docs = self.docs(&world.docs); self.worlds[world_id].docs = docs; let stability = self.stability(&world.attributes)?; @@ -754,7 +746,7 @@ impl<'a> Resolver<'a> { docs: &ast::Docs<'a>, attrs: &[ast::Attribute<'a>], kind: &ast::ExternKind<'a>, - ) -> Result { + ) -> ParseResult { match kind { ast::ExternKind::Interface(name, items) => { let prev = mem::take(&mut self.type_lookup); @@ -802,7 +794,7 @@ impl<'a> Resolver<'a> { fields: &[ast::InterfaceItem<'a>], docs: &ast::Docs<'a>, attrs: &[ast::Attribute<'a>], - ) -> Result<(), PackageParseErrors> { + ) -> ParseResult<()> { let docs = self.docs(docs); self.interfaces[interface_id].docs = docs; let stability = self.stability(attrs)?; @@ -878,7 +870,7 @@ impl<'a> Resolver<'a> { &mut self, owner: TypeOwner, fields: impl Iterator> + Clone, - ) -> Result<(), PackageParseErrors> + ) -> ParseResult<()> where 'a: 'b, { @@ -962,11 +954,7 @@ impl<'a> Resolver<'a> { } } - fn resolve_use( - &mut self, - owner: TypeOwner, - u: &ast::Use<'a>, - ) -> Result<(), PackageParseErrors> { + fn resolve_use(&mut self, owner: TypeOwner, u: &ast::Use<'a>) -> ParseResult<()> { 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)?; @@ -1008,11 +996,7 @@ 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<(), PackageParseErrors> { + fn resolve_include(&mut self, world_id: WorldId, i: &ast::Include<'a>) -> ParseResult<()> { 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)?; @@ -1036,7 +1020,7 @@ impl<'a> Resolver<'a> { &mut self, func: &ast::ResourceFunc<'_>, resource: &ast::Id<'_>, - ) -> Result { + ) -> ParseResult { let resource_id = match self.type_lookup.get(resource.name) { Some((TypeOrItem::Type(id), _)) => *id, _ => panic!("type lookup for resource failed"), @@ -1085,7 +1069,7 @@ impl<'a> Resolver<'a> { name_span: Span, func: &ast::Func, kind: FunctionKind, - ) -> Result { + ) -> ParseResult { let docs = self.docs(docs); let stability = self.stability(attrs)?; let params = self.resolve_params(&func.params, &kind, func.span)?; @@ -1104,7 +1088,7 @@ impl<'a> Resolver<'a> { fn resolve_ast_item_path( &self, path: &ast::UsePath<'a>, - ) -> Result<(AstItem, String, Span), PackageParseErrors> { + ) -> ParseResult<(AstItem, String, Span)> { match path { ast::UsePath::Id(id) => { let item = self.ast_items[self.cur_ast_index] @@ -1137,7 +1121,7 @@ impl<'a> Resolver<'a> { item: &AstItem, name: &str, span: Span, - ) -> Result { + ) -> ParseResult { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { @@ -1154,7 +1138,7 @@ impl<'a> Resolver<'a> { item: &AstItem, name: &str, span: Span, - ) -> Result { + ) -> ParseResult { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { @@ -1166,11 +1150,7 @@ impl<'a> Resolver<'a> { } } - fn define_interface_name( - &mut self, - name: &ast::Id<'a>, - item: TypeOrItem, - ) -> Result<(), PackageParseErrors> { + fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> ParseResult<()> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { return Err(PackageParseErrors::new_syntax( @@ -1186,7 +1166,7 @@ impl<'a> Resolver<'a> { &mut self, ty: &ast::Type<'_>, stability: &Stability, - ) -> Result { + ) -> ParseResult { Ok(match ty { ast::Type::Bool(_) => TypeDefKind::Type(Type::Bool), ast::Type::U8(_) => TypeDefKind::Type(Type::U8), @@ -1284,7 +1264,7 @@ impl<'a> Resolver<'a> { span: field.name.span, }) }) - .collect::, PackageParseErrors>>()?; + .collect::>>()?; TypeDefKind::Record(Record { fields }) } ast::Type::Flags(flags) => { @@ -1304,7 +1284,7 @@ impl<'a> Resolver<'a> { .types .iter() .map(|ty| self.resolve_type(ty, stability)) - .collect::, PackageParseErrors>>()?; + .collect::>>()?; TypeDefKind::Tuple(Tuple { types }) } ast::Type::Variant(variant) => { @@ -1325,7 +1305,7 @@ impl<'a> Resolver<'a> { span: case.name.span, }) }) - .collect::, PackageParseErrors>>()?; + .collect::>>()?; TypeDefKind::Variant(Variant { cases }) } ast::Type::Enum(e) => { @@ -1345,7 +1325,7 @@ impl<'a> Resolver<'a> { span: case.name.span, }) }) - .collect::, PackageParseErrors>>()?; + .collect::>>()?; TypeDefKind::Enum(Enum { cases }) } ast::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(&ty.ty, stability)?), @@ -1362,7 +1342,7 @@ impl<'a> Resolver<'a> { }) } - fn resolve_type_name(&mut self, name: &ast::Id<'_>) -> Result { + fn resolve_type_name(&mut self, name: &ast::Id<'_>) -> ParseResult { match self.type_lookup.get(name.name) { Some((TypeOrItem::Type(id), _)) => Ok(*id), Some((TypeOrItem::Item(s), _)) => { @@ -1384,7 +1364,7 @@ impl<'a> Resolver<'a> { } } - fn validate_resource(&mut self, name: &ast::Id<'_>) -> Result { + fn validate_resource(&mut self, name: &ast::Id<'_>) -> ParseResult { let id = self.resolve_type_name(name)?; let mut cur = id; loop { @@ -1474,11 +1454,7 @@ impl<'a> Resolver<'a> { } } - fn resolve_type( - &mut self, - ty: &super::Type<'_>, - stability: &Stability, - ) -> Result { + fn resolve_type(&mut self, ty: &super::Type<'_>, stability: &Stability) -> ParseResult { // 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. @@ -1502,7 +1478,7 @@ impl<'a> Resolver<'a> { &mut self, ty: Option<&super::Type<'_>>, stability: &Stability, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { match ty { Some(ty) => Ok(Some(self.resolve_type(ty, stability)?)), None => Ok(None), @@ -1608,7 +1584,7 @@ impl<'a> Resolver<'a> { Docs { contents } } - fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result { + fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> ParseResult { match attrs { [] => Ok(Stability::Unknown), @@ -1671,7 +1647,7 @@ impl<'a> Resolver<'a> { params: &ParamList<'_>, kind: &FunctionKind, span: Span, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { let mut ret = Vec::new(); match *kind { // These kinds of methods don't have any adjustments to the @@ -1723,7 +1699,7 @@ impl<'a> Resolver<'a> { result: &Option>, kind: &FunctionKind, _span: Span, - ) -> Result, PackageParseErrors> { + ) -> ParseResult> { match *kind { // These kinds of methods don't have any adjustments to the return // values, so plumb them through as-is. @@ -1755,7 +1731,7 @@ impl<'a> Resolver<'a> { &mut self, resource_id: TypeId, result_ast: &ast::Type<'_>, - ) -> Result { + ) -> ParseResult { let result = self.resolve_type(result_ast, &Stability::Unknown)?; let ok_type = match result { Type::Id(id) => match &self.types[id].kind { diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index f4f971f728..3e625a315c 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,12 +1,11 @@ use super::error::PackageParseErrors; use crate::ast::Id; -use crate::{IndexMap, PackageParseErrorKind}; +use crate::{IndexMap, PackageParseErrorKind, ParseResult}; use alloc::collections::BinaryHeap; use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::mem; -use core::result::Result; #[derive(Default, Clone)] struct State { @@ -45,7 +44,7 @@ struct State { pub fn toposort<'a>( kind: &str, deps: &IndexMap<&'a str, Vec>>, -) -> Result, PackageParseErrors> { +) -> ParseResult> { // Initialize a `State` per-node with the number of outbound edges and // additionally filling out the `reverse_deps` array. let mut states = vec![State::default(); deps.len()]; From 3ab3261325f93e7f8bb466460acb17cbd81f24db Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:03:53 +0000 Subject: [PATCH 11/14] Rename PackageParse -> Parse --- crates/wit-parser/src/ast.rs | 25 +++--- crates/wit-parser/src/ast/error.rs | 48 +++++----- crates/wit-parser/src/ast/resolve.rs | 123 ++++++++++++-------------- crates/wit-parser/src/ast/toposort.rs | 14 +-- 4 files changed, 99 insertions(+), 111 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 88819bc9cd..89398c0ba3 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,4 +1,4 @@ -use crate::ast::error::PackageParseErrors; +use crate::ast::error::ParseErrors; use crate::{ParseResult, UnresolvedPackage, UnresolvedPackageGroup}; use alloc::borrow::Cow; use alloc::boxed::Box; @@ -69,7 +69,7 @@ impl<'a> PackageFile<'a> { ) -> ParseResult { let span = tokens.expect(Token::Package)?; if !attributes.is_empty() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( span, format!("cannot place attributes on nested packages"), )); @@ -1255,7 +1255,7 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> ParseResult<(Span, Version)> { eat_ids(tokens, Token::Plus, &mut span)?; let string = tokens.get_span(span); let version = - Version::parse(string).map_err(|e| PackageParseErrors::new_syntax(span, e.to_string()))?; + Version::parse(string).map_err(|e| ParseErrors::new_syntax(span, e.to_string()))?; return Ok((span, version)); // According to `semver.org` this is what we're parsing: @@ -1405,7 +1405,7 @@ impl<'a> Type<'a> { let number = tokens.next()?; if let Some((span, Token::Integer)) = number { let size: u32 = tokens.get_span(span).parse().map_err(|e| { - PackageParseErrors::new_syntax(span, format!("invalid list size: {e}")) + ParseErrors::new_syntax(span, format!("invalid list size: {e}")) })?; Some(size) } else { @@ -1612,16 +1612,15 @@ fn err_expected( tokens: &Tokenizer<'_>, expected: &'static str, found: Option<(Span, Token)>, -) -> PackageParseErrors { +) -> ParseErrors { match found { - Some((span, token)) => PackageParseErrors::new_syntax( + Some((span, token)) => ParseErrors::new_syntax( span, format!("expected {}, found {}", expected, token.describe()), ), - None => PackageParseErrors::new_syntax( - tokens.eof_span(), - format!("expected {expected}, found eof"), - ), + None => { + ParseErrors::new_syntax(tokens.eof_span(), format!("expected {expected}, found eof")) + } } } @@ -1671,7 +1670,7 @@ impl<'a> Attribute<'a> { } } other => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( id.span, format!("unknown attribute `{other}`"), )); @@ -1694,7 +1693,7 @@ impl<'a> Attribute<'a> { fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> ParseResult { let id = parse_id(tokens)?; if id.name != expected { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( id.span, format!("expected `{expected}`, found `{}`", id.name), )); @@ -1791,7 +1790,7 @@ impl SourceMap { /// /// On failure returns `Err((self, e))` so the caller can use the source /// map for error formatting if needed. - pub fn parse(self) -> Result { + pub fn parse(self) -> Result { match self.parse_inner() { Ok((main, nested)) => Ok(UnresolvedPackageGroup { main, diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 2c8c2bfb10..4bcf149e9f 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -4,11 +4,11 @@ use core::fmt; use crate::{SourceMap, Span, ast::lex}; -pub type ParseResult = Result; +pub type ParseResult = Result; #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] -pub enum PackageParseErrorKind { +pub enum ParseErrorKind { /// Lexer error (invalid character, unterminated comment, etc.) Lex(lex::Error), /// Syntactic or semantic error within a single package (duplicate name, @@ -30,23 +30,23 @@ pub enum PackageParseErrorKind { }, } -impl PackageParseErrorKind { +impl ParseErrorKind { pub fn span(&self) -> Span { match self { - PackageParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1), - PackageParseErrorKind::Syntax { span, .. } - | PackageParseErrorKind::ItemNotFound { span, .. } - | PackageParseErrorKind::TypeCycle { span, .. } => *span, + ParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1), + ParseErrorKind::Syntax { span, .. } + | ParseErrorKind::ItemNotFound { span, .. } + | ParseErrorKind::TypeCycle { span, .. } => *span, } } } -impl fmt::Display for PackageParseErrorKind { +impl fmt::Display for ParseErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PackageParseErrorKind::Lex(e) => fmt::Display::fmt(e, f), - PackageParseErrorKind::Syntax { message, .. } => message.fmt(f), - PackageParseErrorKind::ItemNotFound { + ParseErrorKind::Lex(e) => fmt::Display::fmt(e, f), + ParseErrorKind::Syntax { message, .. } => message.fmt(f), + ParseErrorKind::ItemNotFound { kind, name, hint, .. } => { write!(f, "{kind} `{name}` does not exist")?; @@ -55,7 +55,7 @@ impl fmt::Display for PackageParseErrorKind { } Ok(()) } - PackageParseErrorKind::TypeCycle { kind, name, .. } => { + ParseErrorKind::TypeCycle { kind, name, .. } => { write!(f, "{kind} `{name}` depends on itself") } } @@ -63,22 +63,22 @@ impl fmt::Display for PackageParseErrorKind { } #[derive(Debug, PartialEq, Eq)] -pub struct PackageParseErrors(Box); +pub struct ParseErrors(Box); -impl PackageParseErrors { +impl ParseErrors { pub fn new_syntax(span: Span, message: impl Into) -> Self { - PackageParseErrorKind::Syntax { + ParseErrorKind::Syntax { span, message: message.into(), } .into() } - pub fn kind(&self) -> &PackageParseErrorKind { + pub fn kind(&self) -> &ParseErrorKind { &self.0 } - pub fn kind_mut(&mut self) -> &mut PackageParseErrorKind { + pub fn kind_mut(&mut self) -> &mut ParseErrorKind { &mut self.0 } @@ -91,22 +91,22 @@ impl PackageParseErrors { } } -impl fmt::Display for PackageParseErrors { +impl fmt::Display for ParseErrors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.kind(), f) } } -impl core::error::Error for PackageParseErrors {} +impl core::error::Error for ParseErrors {} -impl From for PackageParseErrors { - fn from(kind: PackageParseErrorKind) -> Self { - PackageParseErrors(Box::new(kind)) +impl From for ParseErrors { + fn from(kind: ParseErrorKind) -> Self { + ParseErrors(Box::new(kind)) } } -impl From for PackageParseErrors { +impl From for ParseErrors { fn from(e: lex::Error) -> Self { - PackageParseErrorKind::Lex(e).into() + ParseErrorKind::Lex(e).into() } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index d7298cadcf..2701e630f6 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,6 +1,6 @@ use super::{ParamList, WorldOrInterface}; use crate::alloc::borrow::ToOwned; -use crate::ast::error::{PackageParseErrorKind, PackageParseErrors}; +use crate::ast::error::{ParseErrorKind, ParseErrors}; use crate::ast::toposort::toposort; use crate::*; use alloc::string::{String, ToString}; @@ -114,7 +114,7 @@ impl<'a> Resolver<'a> { let cur_name = cur.package_name(); if let Some((prev, _)) = &self.package_name { if cur_name != *prev { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( cur.span, format!( "package identifier `{cur_name}` does not match \ @@ -129,7 +129,7 @@ impl<'a> Resolver<'a> { let docs = self.docs(&cur.docs); if docs.contents.is_some() { if self.package_docs.contents.is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( cur.docs.span, "found doc comments on multiple 'package' items".to_owned(), )); @@ -146,7 +146,7 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(pkg) => pkg.package_id.as_ref().unwrap().span, _ => continue, }; - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( span, "nested packages must be placed at the top-level".to_owned(), )); @@ -161,7 +161,7 @@ impl<'a> Resolver<'a> { let (name, package_name_span) = match &self.package_name { Some(name) => name.clone(), None => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( Span::default(), "no `package` header was found in any WIT file for this package".to_owned(), )); @@ -354,7 +354,7 @@ impl<'a> Resolver<'a> { match item { ast::AstItem::Interface(i) => { if package_items.insert(i.name.name, i.name.span).is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( i.name.span, format!("duplicate item named `{}`", i.name.name), )); @@ -368,7 +368,7 @@ impl<'a> Resolver<'a> { } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( w.name.span, format!("duplicate item named `{}`", w.name.name), )); @@ -419,7 +419,7 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(_) => unreachable!(), }; if decl_list_ns.insert(name.name, (name.span, src)).is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.span, format!("duplicate name `{}` in this file", name.name), )); @@ -450,14 +450,12 @@ impl<'a> Resolver<'a> { order[iface.name].push(used_name.clone()); } None => { - return Err(PackageParseErrors::from( - PackageParseErrorKind::ItemNotFound { - span: used_name.span, - name: used_name.name.to_string(), - kind: "interface or world".to_string(), - hint: None, - }, - )); + return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + span: used_name.span, + name: used_name.name.to_string(), + kind: "interface or world".to_string(), + hint: None, + })); } }, } @@ -499,7 +497,7 @@ impl<'a> Resolver<'a> { let (name, ast_item) = match item { ast::AstItem::Use(u) => { if !u.attributes.is_empty() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( u.span, format!("attributes not allowed on top-level use"), )); @@ -507,7 +505,7 @@ impl<'a> Resolver<'a> { 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(|| { - PackageParseErrors::from(PackageParseErrorKind::ItemNotFound { + ParseErrors::from(ParseErrorKind::ItemNotFound { span: name.span, name: name.name.to_string(), kind: "interface or world".to_owned(), @@ -631,7 +629,7 @@ impl<'a> Resolver<'a> { WorldItem::Type { id, span: *span }, ); if prev.is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( *span, format!("import `{name}` conflicts with prior import of same name"), )); @@ -708,7 +706,7 @@ impl<'a> Resolver<'a> { }; if let WorldItem::Interface { id, .. } = world_item { if !interfaces.insert(id) { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( kind.span(), format!("interface cannot be {desc}ed more than once"), )); @@ -730,7 +728,7 @@ impl<'a> Resolver<'a> { WorldKey::Name(name) => name, WorldKey::Interface(..) => unreachable!(), }; - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( kind.span(), format!("{desc} `{name}` conflicts with prior {prev} of same name",), )); @@ -897,7 +895,7 @@ impl<'a> Resolver<'a> { TypeItem::Def(t) => { let prev = type_defs.insert(t.name.name, Some(t)); if prev.is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( t.name.span, format!("name `{}` is defined more than once", t.name.name), )); @@ -936,8 +934,8 @@ impl<'a> Resolver<'a> { } return Ok(()); - fn attach_old_float_type_context(mut err: PackageParseErrors) -> PackageParseErrors { - if let PackageParseErrorKind::ItemNotFound { name, hint, .. } = err.kind_mut() { + fn attach_old_float_type_context(mut err: ParseErrors) -> ParseErrors { + if let ParseErrorKind::ItemNotFound { name, hint, .. } = err.kind_mut() { let new = match name.as_str() { "float32" => "f32", "float64" => "f64", @@ -964,20 +962,18 @@ impl<'a> Resolver<'a> { let id = match lookup.get(name.name.name) { Some((TypeOrItem::Type(id), _)) => *id, Some((TypeOrItem::Item(s), _)) => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.name.span, format!("cannot import {s} `{}`", name.name.name), )); } None => { - return Err(PackageParseErrors::from( - PackageParseErrorKind::ItemNotFound { - span: name.name.span, - name: name.name.name.to_string(), - kind: "name".to_string(), - hint: None, - }, - )); + return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + span: name.name.span, + name: name.name.name.to_string(), + kind: "name".to_string(), + hint: None, + })); } }; let span = name.name.span; @@ -1097,14 +1093,12 @@ impl<'a> Resolver<'a> { match item { Some(item) => Ok((*item, id.name.into(), id.span)), None => { - return Err(PackageParseErrors::from( - PackageParseErrorKind::ItemNotFound { - span: id.span, - name: id.name.to_string(), - kind: "interface or world".to_owned(), - hint: None, - }, - )); + return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + span: id.span, + name: id.name.to_string(), + kind: "interface or world".to_owned(), + hint: None, + })); } } } @@ -1125,7 +1119,7 @@ impl<'a> Resolver<'a> { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( span, format!("name `{name}` is defined as a world, not an interface"), )); @@ -1142,7 +1136,7 @@ impl<'a> Resolver<'a> { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( span, format!("name `{name}` is defined as an interface, not a world"), )); @@ -1153,7 +1147,7 @@ impl<'a> Resolver<'a> { fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> ParseResult<()> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.span, format!("name `{}` is defined more than once", name.name), )); @@ -1207,7 +1201,7 @@ impl<'a> Resolver<'a> { | Type::Char | Type::String => {} _ => { - return Err(PackageParseErrors::new_syntax(map.span, "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned())); + return Err(ParseErrors::new_syntax(map.span, "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned())); } } @@ -1232,7 +1226,7 @@ impl<'a> Resolver<'a> { match func { ast::ResourceFunc::Method(f) | ast::ResourceFunc::Static(f) => { if !names.insert(&f.name.name) { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( f.name.span, format!("duplicate function name `{}`", f.name.name), )); @@ -1241,7 +1235,7 @@ impl<'a> Resolver<'a> { ast::ResourceFunc::Constructor(f) => { ctors += 1; if ctors > 1 { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( f.name.span, "duplicate constructors".to_owned(), )); @@ -1289,7 +1283,7 @@ impl<'a> Resolver<'a> { } ast::Type::Variant(variant) => { if variant.cases.is_empty() { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( variant.span, "empty variant".to_owned(), )); @@ -1310,10 +1304,7 @@ impl<'a> Resolver<'a> { } ast::Type::Enum(e) => { if e.cases.is_empty() { - return Err(PackageParseErrors::new_syntax( - e.span, - "empty enum".to_owned(), - )); + return Err(ParseErrors::new_syntax(e.span, "empty enum".to_owned())); } let cases = e .cases @@ -1346,20 +1337,18 @@ impl<'a> Resolver<'a> { match self.type_lookup.get(name.name) { Some((TypeOrItem::Type(id), _)) => Ok(*id), Some((TypeOrItem::Item(s), _)) => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.span, format!("cannot use {s} `{name}` as a type", name = name.name), )); } None => { - return Err(PackageParseErrors::from( - PackageParseErrorKind::ItemNotFound { - span: name.span, - name: name.name.to_string(), - kind: "name".to_owned(), - hint: None, - }, - )); + return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + span: name.span, + name: name.name.to_string(), + kind: "name".to_owned(), + hint: None, + })); } } } @@ -1376,7 +1365,7 @@ impl<'a> Resolver<'a> { break Ok(id); } _ => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.span, format!("type `{}` used in a handle must be a resource", name.name), )); @@ -1628,13 +1617,13 @@ impl<'a> Resolver<'a> { deprecated: Some(version.clone()), }), [ast::Attribute::Deprecated { span, .. }] => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( *span, "must pair @deprecated with either @since or @unstable".to_owned(), )); } [_, b, ..] => { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( b.span(), "unsupported combination of attributes".to_owned(), )); @@ -1680,7 +1669,7 @@ impl<'a> Resolver<'a> { } for (name, ty) in params { if ret.iter().any(|p| p.name == name.name) { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( name.span, format!("param `{}` is defined more than once", name.name), )); @@ -1741,7 +1730,7 @@ impl<'a> Resolver<'a> { _ => None, }; let Some(ok_type) = ok_type else { - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( result_ast.span(), "if a constructor return type is declared it must be a `result`".to_owned(), )); @@ -1755,7 +1744,7 @@ impl<'a> Resolver<'a> { } else { result_ast.span() }; - return Err(PackageParseErrors::new_syntax( + return Err(ParseErrors::new_syntax( ok_span, "the `ok` type must be the resource being constructed".to_owned(), )); diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index 3e625a315c..4b9ebab8c0 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,6 +1,6 @@ -use super::error::PackageParseErrors; +use super::error::ParseErrors; use crate::ast::Id; -use crate::{IndexMap, PackageParseErrorKind, ParseResult}; +use crate::{IndexMap, ParseErrorKind, ParseResult}; use alloc::collections::BinaryHeap; use alloc::string::ToString; use alloc::vec; @@ -52,7 +52,7 @@ pub fn toposort<'a>( states[i].outbound_remaining = edges.len(); for edge in edges { let (j, _, _) = deps.get_full(edge.name).ok_or_else(|| { - PackageParseErrors::from(PackageParseErrorKind::ItemNotFound { + ParseErrors::from(ParseErrorKind::ItemNotFound { span: edge.span, name: edge.name.to_string(), kind: kind.to_string(), @@ -114,7 +114,7 @@ pub fn toposort<'a>( if states[j].outbound_remaining == 0 { continue; } - return Err(PackageParseErrorKind::TypeCycle { + return Err(ParseErrorKind::TypeCycle { span: dep.span, name: dep.name.to_string(), kind: kind.to_string(), @@ -146,7 +146,7 @@ mod tests { nonexistent.insert("a", vec![id("b")]); assert!(matches!( toposort("", &nonexistent).unwrap_err().kind(), - PackageParseErrorKind::ItemNotFound { .. } + ParseErrorKind::ItemNotFound { .. } )); let mut one = IndexMap::default(); @@ -170,7 +170,7 @@ mod tests { cycle.insert("a", vec![id("a")]); assert!(matches!( toposort("", &cycle).unwrap_err().kind(), - PackageParseErrorKind::TypeCycle { .. } + ParseErrorKind::TypeCycle { .. } )); let mut cycle = IndexMap::default(); @@ -179,7 +179,7 @@ mod tests { cycle.insert("c", vec![id("a")]); assert!(matches!( toposort("", &cycle).unwrap_err().kind(), - PackageParseErrorKind::TypeCycle { .. } + ParseErrorKind::TypeCycle { .. } )); } From 7211ee30ab2c073b077f569f69695dbe63328cd6 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:07:11 +0000 Subject: [PATCH 12/14] Comments --- crates/wit-parser/src/ast/error.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index 4bcf149e9f..c2b386de72 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -1,11 +1,19 @@ +//! Error types for WIT package parsing. +//! +//! The main types are [`ParseErrors`] (a single structured error) and +//! [`ParseErrorKind`] (the underlying variant). [`ParseResult`] is a +//! convenience alias for `Result`. + use alloc::boxed::Box; use alloc::string::{String, ToString}; use core::fmt; use crate::{SourceMap, Span, ast::lex}; +/// Convenience alias for a `Result` whose error type is [`ParseErrors`]. pub type ParseResult = Result; +/// The category of error that occurred while parsing a WIT package. #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] pub enum ParseErrorKind { @@ -31,6 +39,7 @@ pub enum ParseErrorKind { } impl ParseErrorKind { + /// Returns the source span associated with this error. pub fn span(&self) -> Span { match self { ParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1), @@ -62,6 +71,10 @@ impl fmt::Display for ParseErrorKind { } } +/// A single structured error from parsing a WIT package. +/// +/// Carries a [`ParseErrorKind`] and can be formatted with source context via +/// [`ParseErrors::highlight`]. #[derive(Debug, PartialEq, Eq)] pub struct ParseErrors(Box); @@ -74,10 +87,12 @@ impl ParseErrors { .into() } + /// Returns the underlying error kind. pub fn kind(&self) -> &ParseErrorKind { &self.0 } + /// Returns a mutable reference to the underlying error kind. pub fn kind_mut(&mut self) -> &mut ParseErrorKind { &mut self.0 } From 09c1a9016261074c86b9eb88487387f9fccccafd Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:09:17 +0000 Subject: [PATCH 13/14] Use singular ParseError --- crates/wit-parser/src/ast.rs | 20 ++++---- crates/wit-parser/src/ast/error.rs | 31 +++--------- crates/wit-parser/src/ast/resolve.rs | 70 +++++++++++++-------------- crates/wit-parser/src/ast/toposort.rs | 4 +- 4 files changed, 55 insertions(+), 70 deletions(-) diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 89398c0ba3..06543df063 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1,4 +1,4 @@ -use crate::ast::error::ParseErrors; +use crate::ast::error::ParseError; use crate::{ParseResult, UnresolvedPackage, UnresolvedPackageGroup}; use alloc::borrow::Cow; use alloc::boxed::Box; @@ -69,7 +69,7 @@ impl<'a> PackageFile<'a> { ) -> ParseResult { let span = tokens.expect(Token::Package)?; if !attributes.is_empty() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( span, format!("cannot place attributes on nested packages"), )); @@ -1255,7 +1255,7 @@ fn parse_version(tokens: &mut Tokenizer<'_>) -> ParseResult<(Span, Version)> { eat_ids(tokens, Token::Plus, &mut span)?; let string = tokens.get_span(span); let version = - Version::parse(string).map_err(|e| ParseErrors::new_syntax(span, e.to_string()))?; + Version::parse(string).map_err(|e| ParseError::new_syntax(span, e.to_string()))?; return Ok((span, version)); // According to `semver.org` this is what we're parsing: @@ -1405,7 +1405,7 @@ impl<'a> Type<'a> { let number = tokens.next()?; if let Some((span, Token::Integer)) = number { let size: u32 = tokens.get_span(span).parse().map_err(|e| { - ParseErrors::new_syntax(span, format!("invalid list size: {e}")) + ParseError::new_syntax(span, format!("invalid list size: {e}")) })?; Some(size) } else { @@ -1612,14 +1612,14 @@ fn err_expected( tokens: &Tokenizer<'_>, expected: &'static str, found: Option<(Span, Token)>, -) -> ParseErrors { +) -> ParseError { match found { - Some((span, token)) => ParseErrors::new_syntax( + Some((span, token)) => ParseError::new_syntax( span, format!("expected {}, found {}", expected, token.describe()), ), None => { - ParseErrors::new_syntax(tokens.eof_span(), format!("expected {expected}, found eof")) + ParseError::new_syntax(tokens.eof_span(), format!("expected {expected}, found eof")) } } } @@ -1670,7 +1670,7 @@ impl<'a> Attribute<'a> { } } other => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( id.span, format!("unknown attribute `{other}`"), )); @@ -1693,7 +1693,7 @@ impl<'a> Attribute<'a> { fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> ParseResult { let id = parse_id(tokens)?; if id.name != expected { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( id.span, format!("expected `{expected}`, found `{}`", id.name), )); @@ -1790,7 +1790,7 @@ impl SourceMap { /// /// On failure returns `Err((self, e))` so the caller can use the source /// map for error formatting if needed. - pub fn parse(self) -> Result { + pub fn parse(self) -> Result { match self.parse_inner() { Ok((main, nested)) => Ok(UnresolvedPackageGroup { main, diff --git a/crates/wit-parser/src/ast/error.rs b/crates/wit-parser/src/ast/error.rs index c2b386de72..55a3ffa959 100644 --- a/crates/wit-parser/src/ast/error.rs +++ b/crates/wit-parser/src/ast/error.rs @@ -1,19 +1,11 @@ -//! Error types for WIT package parsing. -//! -//! The main types are [`ParseErrors`] (a single structured error) and -//! [`ParseErrorKind`] (the underlying variant). [`ParseResult`] is a -//! convenience alias for `Result`. - use alloc::boxed::Box; use alloc::string::{String, ToString}; use core::fmt; use crate::{SourceMap, Span, ast::lex}; -/// Convenience alias for a `Result` whose error type is [`ParseErrors`]. -pub type ParseResult = Result; +pub type ParseResult = Result; -/// The category of error that occurred while parsing a WIT package. #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] pub enum ParseErrorKind { @@ -39,7 +31,6 @@ pub enum ParseErrorKind { } impl ParseErrorKind { - /// Returns the source span associated with this error. pub fn span(&self) -> Span { match self { ParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1), @@ -71,14 +62,10 @@ impl fmt::Display for ParseErrorKind { } } -/// A single structured error from parsing a WIT package. -/// -/// Carries a [`ParseErrorKind`] and can be formatted with source context via -/// [`ParseErrors::highlight`]. #[derive(Debug, PartialEq, Eq)] -pub struct ParseErrors(Box); +pub struct ParseError(Box); -impl ParseErrors { +impl ParseError { pub fn new_syntax(span: Span, message: impl Into) -> Self { ParseErrorKind::Syntax { span, @@ -87,12 +74,10 @@ impl ParseErrors { .into() } - /// Returns the underlying error kind. pub fn kind(&self) -> &ParseErrorKind { &self.0 } - /// Returns a mutable reference to the underlying error kind. pub fn kind_mut(&mut self) -> &mut ParseErrorKind { &mut self.0 } @@ -106,21 +91,21 @@ impl ParseErrors { } } -impl fmt::Display for ParseErrors { +impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.kind(), f) } } -impl core::error::Error for ParseErrors {} +impl core::error::Error for ParseError {} -impl From for ParseErrors { +impl From for ParseError { fn from(kind: ParseErrorKind) -> Self { - ParseErrors(Box::new(kind)) + ParseError(Box::new(kind)) } } -impl From for ParseErrors { +impl From for ParseError { fn from(e: lex::Error) -> Self { ParseErrorKind::Lex(e).into() } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 2701e630f6..7c7f617e37 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,6 +1,6 @@ use super::{ParamList, WorldOrInterface}; use crate::alloc::borrow::ToOwned; -use crate::ast::error::{ParseErrorKind, ParseErrors}; +use crate::ast::error::{ParseError, ParseErrorKind}; use crate::ast::toposort::toposort; use crate::*; use alloc::string::{String, ToString}; @@ -114,7 +114,7 @@ impl<'a> Resolver<'a> { let cur_name = cur.package_name(); if let Some((prev, _)) = &self.package_name { if cur_name != *prev { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( cur.span, format!( "package identifier `{cur_name}` does not match \ @@ -129,7 +129,7 @@ impl<'a> Resolver<'a> { let docs = self.docs(&cur.docs); if docs.contents.is_some() { if self.package_docs.contents.is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( cur.docs.span, "found doc comments on multiple 'package' items".to_owned(), )); @@ -146,7 +146,7 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(pkg) => pkg.package_id.as_ref().unwrap().span, _ => continue, }; - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( span, "nested packages must be placed at the top-level".to_owned(), )); @@ -161,7 +161,7 @@ impl<'a> Resolver<'a> { let (name, package_name_span) = match &self.package_name { Some(name) => name.clone(), None => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( Span::default(), "no `package` header was found in any WIT file for this package".to_owned(), )); @@ -354,7 +354,7 @@ impl<'a> Resolver<'a> { match item { ast::AstItem::Interface(i) => { if package_items.insert(i.name.name, i.name.span).is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( i.name.span, format!("duplicate item named `{}`", i.name.name), )); @@ -368,7 +368,7 @@ impl<'a> Resolver<'a> { } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( w.name.span, format!("duplicate item named `{}`", w.name.name), )); @@ -419,7 +419,7 @@ impl<'a> Resolver<'a> { ast::AstItem::Package(_) => unreachable!(), }; if decl_list_ns.insert(name.name, (name.span, src)).is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.span, format!("duplicate name `{}` in this file", name.name), )); @@ -450,7 +450,7 @@ impl<'a> Resolver<'a> { order[iface.name].push(used_name.clone()); } None => { - return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + return Err(ParseError::from(ParseErrorKind::ItemNotFound { span: used_name.span, name: used_name.name.to_string(), kind: "interface or world".to_string(), @@ -497,7 +497,7 @@ impl<'a> Resolver<'a> { let (name, ast_item) = match item { ast::AstItem::Use(u) => { if !u.attributes.is_empty() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( u.span, format!("attributes not allowed on top-level use"), )); @@ -505,7 +505,7 @@ impl<'a> Resolver<'a> { 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(|| { - ParseErrors::from(ParseErrorKind::ItemNotFound { + ParseError::from(ParseErrorKind::ItemNotFound { span: name.span, name: name.name.to_string(), kind: "interface or world".to_owned(), @@ -629,7 +629,7 @@ impl<'a> Resolver<'a> { WorldItem::Type { id, span: *span }, ); if prev.is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( *span, format!("import `{name}` conflicts with prior import of same name"), )); @@ -706,7 +706,7 @@ impl<'a> Resolver<'a> { }; if let WorldItem::Interface { id, .. } = world_item { if !interfaces.insert(id) { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( kind.span(), format!("interface cannot be {desc}ed more than once"), )); @@ -728,7 +728,7 @@ impl<'a> Resolver<'a> { WorldKey::Name(name) => name, WorldKey::Interface(..) => unreachable!(), }; - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( kind.span(), format!("{desc} `{name}` conflicts with prior {prev} of same name",), )); @@ -895,7 +895,7 @@ impl<'a> Resolver<'a> { TypeItem::Def(t) => { let prev = type_defs.insert(t.name.name, Some(t)); if prev.is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( t.name.span, format!("name `{}` is defined more than once", t.name.name), )); @@ -934,7 +934,7 @@ impl<'a> Resolver<'a> { } return Ok(()); - fn attach_old_float_type_context(mut err: ParseErrors) -> ParseErrors { + fn attach_old_float_type_context(mut err: ParseError) -> ParseError { if let ParseErrorKind::ItemNotFound { name, hint, .. } = err.kind_mut() { let new = match name.as_str() { "float32" => "f32", @@ -962,13 +962,13 @@ impl<'a> Resolver<'a> { let id = match lookup.get(name.name.name) { Some((TypeOrItem::Type(id), _)) => *id, Some((TypeOrItem::Item(s), _)) => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.name.span, format!("cannot import {s} `{}`", name.name.name), )); } None => { - return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + return Err(ParseError::from(ParseErrorKind::ItemNotFound { span: name.name.span, name: name.name.name.to_string(), kind: "name".to_string(), @@ -1093,7 +1093,7 @@ impl<'a> Resolver<'a> { match item { Some(item) => Ok((*item, id.name.into(), id.span)), None => { - return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + return Err(ParseError::from(ParseErrorKind::ItemNotFound { span: id.span, name: id.name.to_string(), kind: "interface or world".to_owned(), @@ -1119,7 +1119,7 @@ impl<'a> Resolver<'a> { match item { AstItem::Interface(id) => Ok(*id), AstItem::World(_) => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( span, format!("name `{name}` is defined as a world, not an interface"), )); @@ -1136,7 +1136,7 @@ impl<'a> Resolver<'a> { match item { AstItem::World(id) => Ok(*id), AstItem::Interface(_) => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( span, format!("name `{name}` is defined as an interface, not a world"), )); @@ -1147,7 +1147,7 @@ impl<'a> Resolver<'a> { fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> ParseResult<()> { let prev = self.type_lookup.insert(name.name, (item, name.span)); if prev.is_some() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.span, format!("name `{}` is defined more than once", name.name), )); @@ -1201,7 +1201,7 @@ impl<'a> Resolver<'a> { | Type::Char | Type::String => {} _ => { - return Err(ParseErrors::new_syntax(map.span, "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned())); + return Err(ParseError::new_syntax(map.span, "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string".to_owned())); } } @@ -1226,7 +1226,7 @@ impl<'a> Resolver<'a> { match func { ast::ResourceFunc::Method(f) | ast::ResourceFunc::Static(f) => { if !names.insert(&f.name.name) { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( f.name.span, format!("duplicate function name `{}`", f.name.name), )); @@ -1235,7 +1235,7 @@ impl<'a> Resolver<'a> { ast::ResourceFunc::Constructor(f) => { ctors += 1; if ctors > 1 { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( f.name.span, "duplicate constructors".to_owned(), )); @@ -1283,7 +1283,7 @@ impl<'a> Resolver<'a> { } ast::Type::Variant(variant) => { if variant.cases.is_empty() { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( variant.span, "empty variant".to_owned(), )); @@ -1304,7 +1304,7 @@ impl<'a> Resolver<'a> { } ast::Type::Enum(e) => { if e.cases.is_empty() { - return Err(ParseErrors::new_syntax(e.span, "empty enum".to_owned())); + return Err(ParseError::new_syntax(e.span, "empty enum".to_owned())); } let cases = e .cases @@ -1337,13 +1337,13 @@ impl<'a> Resolver<'a> { match self.type_lookup.get(name.name) { Some((TypeOrItem::Type(id), _)) => Ok(*id), Some((TypeOrItem::Item(s), _)) => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.span, format!("cannot use {s} `{name}` as a type", name = name.name), )); } None => { - return Err(ParseErrors::from(ParseErrorKind::ItemNotFound { + return Err(ParseError::from(ParseErrorKind::ItemNotFound { span: name.span, name: name.name.to_string(), kind: "name".to_owned(), @@ -1365,7 +1365,7 @@ impl<'a> Resolver<'a> { break Ok(id); } _ => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.span, format!("type `{}` used in a handle must be a resource", name.name), )); @@ -1617,13 +1617,13 @@ impl<'a> Resolver<'a> { deprecated: Some(version.clone()), }), [ast::Attribute::Deprecated { span, .. }] => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( *span, "must pair @deprecated with either @since or @unstable".to_owned(), )); } [_, b, ..] => { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( b.span(), "unsupported combination of attributes".to_owned(), )); @@ -1669,7 +1669,7 @@ impl<'a> Resolver<'a> { } for (name, ty) in params { if ret.iter().any(|p| p.name == name.name) { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( name.span, format!("param `{}` is defined more than once", name.name), )); @@ -1730,7 +1730,7 @@ impl<'a> Resolver<'a> { _ => None, }; let Some(ok_type) = ok_type else { - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( result_ast.span(), "if a constructor return type is declared it must be a `result`".to_owned(), )); @@ -1744,7 +1744,7 @@ impl<'a> Resolver<'a> { } else { result_ast.span() }; - return Err(ParseErrors::new_syntax( + return Err(ParseError::new_syntax( ok_span, "the `ok` type must be the resource being constructed".to_owned(), )); diff --git a/crates/wit-parser/src/ast/toposort.rs b/crates/wit-parser/src/ast/toposort.rs index 4b9ebab8c0..9122ff8711 100644 --- a/crates/wit-parser/src/ast/toposort.rs +++ b/crates/wit-parser/src/ast/toposort.rs @@ -1,4 +1,4 @@ -use super::error::ParseErrors; +use super::error::ParseError; use crate::ast::Id; use crate::{IndexMap, ParseErrorKind, ParseResult}; use alloc::collections::BinaryHeap; @@ -52,7 +52,7 @@ pub fn toposort<'a>( states[i].outbound_remaining = edges.len(); for edge in edges { let (j, _, _) = deps.get_full(edge.name).ok_or_else(|| { - ParseErrors::from(ParseErrorKind::ItemNotFound { + ParseError::from(ParseErrorKind::ItemNotFound { span: edge.span, name: edge.name.to_string(), kind: kind.to_string(), From ec5d8858c123018586008a3a44acaf1f1381ce35 Mon Sep 17 00:00:00 2001 From: Phoebe Szmucer <129869709+PhoebeSzmucer@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:50:55 +0000 Subject: [PATCH 14/14] Remove some methods --- crates/wit-parser/src/lib.rs | 36 ---------------------------- crates/wit-parser/src/resolve/mod.rs | 10 +++++--- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 83ef973b0e..76461df98c 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -391,48 +391,12 @@ impl UnresolvedPackageGroup { .as_ref() .to_str() .ok_or_else(|| anyhow::anyhow!("path is not valid utf-8: {:?}", path.as_ref()))?; - Self::parse_str(path, contents) - } - - /// Parses the given string as a wit document. - /// - /// The `path` argument is used for error reporting. The `contents` provided - /// are considered to be the contents of `path`. This function does not read - /// the filesystem. - pub fn parse_str(path: &str, contents: &str) -> Result { let mut map = SourceMap::default(); map.push_str(path, contents); map.parse() .map_err(|(map, e)| anyhow::anyhow!("{}", e.highlight(&map))) } - /// Parse a WIT package at the provided path. - /// - /// The path provided is inferred whether it's a file or a directory. A file - /// is parsed with [`UnresolvedPackageGroup::parse_file`] and a directory is - /// parsed with [`UnresolvedPackageGroup::parse_dir`]. - #[cfg(feature = "std")] - pub fn parse_path(path: impl AsRef) -> Result { - let path = path.as_ref(); - if path.is_dir() { - UnresolvedPackageGroup::parse_dir(path) - } else { - UnresolvedPackageGroup::parse_file(path) - } - } - - /// Parses a WIT package from the file provided. - /// - /// The return value represents all packages found in the WIT file which - /// might be either one or multiple depending on the syntax used. - #[cfg(feature = "std")] - pub fn parse_file(path: impl AsRef) -> Result { - let path = path.as_ref(); - let contents = std::fs::read_to_string(path) - .with_context(|| format!("failed to read file {path:?}"))?; - Self::parse(path, &contents) - } - /// Parses a WIT package from the directory provided. /// /// This method will look at all files under the `path` specified. All diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index f7d7b5ac92..0b8429481a 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -391,14 +391,18 @@ package {name} is defined in two different locations:\n\ Ok(pkg_id) } - /// Convenience method for combining [`UnresolvedPackageGroup::parse_str`] and - /// [`Resolve::push_group`]. + /// Convenience method for combining [`SourceMap`] and [`Resolve::push_group`]. /// /// The `path` provided is used for error messages but otherwise is not /// read. This method does not touch the filesystem. The `contents` provided /// are the contents of a WIT package. pub fn push_source(&mut self, path: &str, contents: &str) -> Result { - self.push_group(UnresolvedPackageGroup::parse_str(path, contents)?) + let mut map = SourceMap::default(); + map.push_str(path, contents); + self.push_group( + map.parse() + .map_err(|(map, e)| anyhow::anyhow!("{}", e.highlight(&map)))?, + ) } /// Renders a span as a human-readable location string (e.g., "file.wit:10:5").