Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b1bdd14
[wit-parser] Migrate to structured errors in the resolver
PhoebeSzmucer Mar 15, 2026
2aeff69
Rename structs
PhoebeSzmucer Mar 25, 2026
2bb93f0
Remove unneeded methods
PhoebeSzmucer Apr 1, 2026
9177bad
Remove dangling doc
PhoebeSzmucer Apr 1, 2026
8bce150
Merge branch 'main' into structured-resolver-errors
PhoebeSzmucer Apr 7, 2026
f924443
Doc improvements
PhoebeSzmucer Apr 7, 2026
0641505
Update snapshot
PhoebeSzmucer Apr 7, 2026
2043c6c
Don't reference a non-existent method in docs
PhoebeSzmucer Apr 7, 2026
89df4c4
Fix bad merge
PhoebeSzmucer Apr 8, 2026
0692157
sm_idx -> *source_maps_index
PhoebeSzmucer Apr 8, 2026
6496897
Revert "sm_idx -> *source_maps_index"
PhoebeSzmucer Apr 8, 2026
d90a9f2
Remove push_groups, push_group returns a structured error
PhoebeSzmucer Apr 8, 2026
151cda0
Remove stale reference
PhoebeSzmucer Apr 8, 2026
9ab9544
Use fewer abbreviations
PhoebeSzmucer Apr 9, 2026
69fe727
Introduce ResolveError::new_semantic
PhoebeSzmucer Apr 9, 2026
c44b8ba
Bring back push_groups because it makes it easier to test things
PhoebeSzmucer Apr 10, 2026
d489aea
Rename for consistency
PhoebeSzmucer Apr 10, 2026
231996e
Fix typo
PhoebeSzmucer Apr 10, 2026
1a01c31
Merge branch 'main' into structured-resolver-errors
PhoebeSzmucer Apr 10, 2026
a8d40f7
Preserve downcasting
PhoebeSzmucer Apr 29, 2026
317f704
Add new variants to ResolveError
PhoebeSzmucer Apr 29, 2026
eaa481b
Pass a more specific span
PhoebeSzmucer Apr 30, 2026
5641adf
Merge branch 'main' into structured-resolver-errors
PhoebeSzmucer Apr 30, 2026
8a28ad7
Bless the snapshots
PhoebeSzmucer Apr 30, 2026
8bd2e4c
Mention function name
PhoebeSzmucer May 2, 2026
1050e4a
fmt
PhoebeSzmucer May 4, 2026
0f2787b
bin fmt
PhoebeSzmucer May 4, 2026
bc4e225
Trim down comment
PhoebeSzmucer May 4, 2026
a027750
Tighter downcasting tests
PhoebeSzmucer May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions crates/wit-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1845,25 +1845,6 @@ impl SourceMap {
Ok((resolver.resolve()?, nested))
}

/// 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<F, T>(&self, f: F) -> anyhow::Result<T>
where
F: FnOnce() -> anyhow::Result<T>,
{
let mut err = match f() {
Ok(t) => return Ok(t),
Err(e) => e,
};
if let Some(e) = err.downcast_mut::<crate::Error>() {
e.highlight(self);
} else if let Some(e) = err.downcast_mut::<crate::PackageNotFoundError>() {
e.highlight(self);
}
Err(err)
}

pub(crate) fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option<String> {
if !span.is_known() {
return None;
Expand Down
8 changes: 8 additions & 0 deletions crates/wit-parser/src/ast/error.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Error types for WIT parsing.
Comment thread
PhoebeSzmucer marked this conversation as resolved.

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 [`ParseError`].
pub type ParseResult<T, E = ParseError> = Result<T, E>;

/// The category of error that occurred while parsing a WIT package.
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq)]
pub enum ParseErrorKind {
Expand All @@ -31,6 +35,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),
Expand Down Expand Up @@ -62,6 +67,7 @@ impl fmt::Display for ParseErrorKind {
}
}

/// A single structured error from parsing a WIT package.
#[derive(Debug, PartialEq, Eq)]
pub struct ParseError(Box<ParseErrorKind>);

Expand All @@ -74,10 +80,12 @@ impl ParseError {
.into()
}

/// Returns the underlying error kind
pub fn kind(&self) -> &ParseErrorKind {
&self.0
}

/// Returns the underlying error kind (mutable).
pub fn kind_mut(&mut self) -> &mut ParseErrorKind {
&mut self.0
}
Expand Down
1 change: 1 addition & 0 deletions crates/wit-parser/src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::*;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use anyhow::Result;
use anyhow::{Context, anyhow, bail};
use core::mem;
use std::io::Read;
Expand Down
126 changes: 34 additions & 92 deletions crates/wit-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[cfg(feature = "std")]
use anyhow::Context;
use anyhow::{Result, bail};
use anyhow::Context as _;
use id_arena::{Arena, Id};
use semver::Version;

Expand Down Expand Up @@ -52,6 +51,7 @@ pub use ast::{ParsedUsePath, parse_use_path};
mod sizealign;
pub use sizealign::*;
mod resolve;
pub use resolve::error::*;
pub use resolve::*;
mod live;
pub use live::{LiveTypes, TypeIdVisitor};
Expand All @@ -64,11 +64,38 @@ mod serde_;
use serde_::*;

/// Checks if the given string is a legal identifier in wit.
pub fn validate_id(s: &str) -> Result<()> {
pub fn validate_id(s: &str) -> anyhow::Result<()> {
ast::validate_id(0, s)?;
Ok(())
}

/// Renders an [`anyhow::Error`] chain produced by this crate, substituting
/// snippet-bearing output for any [`ResolveError`] or [`ParseError`] layers.
///
/// For each layer in the chain, this calls [`ResolveError::highlight`] or
/// [`ParseError::highlight`] to format typed errors with file/line/column and
/// a source snippet. Other layers are formatted via their [`fmt::Display`]
/// impl. Layers are joined with `": "`, matching `format!("{err:#}")`.
///
/// `source_map` must be the [`SourceMap`] in which every typed error's spans
/// are valid; combining typed errors from different source maps in one chain
/// is unsupported.
#[cfg(feature = "std")]
pub fn render_anyhow_error(err: &anyhow::Error, source_map: &SourceMap) -> String {
err.chain()
.map(|layer| {
if let Some(re) = layer.downcast_ref::<ResolveError>() {
re.highlight(source_map)
} else if let Some(pe) = layer.downcast_ref::<ParseError>() {
pe.highlight(source_map)
} else {
layer.to_string()
}
})
.collect::<Vec<_>>()
.join(": ")
}

pub type WorldId = Id<World>;
pub type InterfaceId = Id<Interface>;
pub type TypeId = Id<TypeDef>;
Expand Down Expand Up @@ -294,99 +321,14 @@ impl fmt::Display for PackageName {
}
}

#[derive(Debug)]
struct Error {
span: Span,
msg: String,
highlighted: Option<String>,
}

impl Error {
fn new(span: Span, msg: impl Into<String>) -> Error {
Error {
span,
msg: msg.into(),
highlighted: None,
}
}

/// Highlights this error using the given source map, if the span is known.
fn highlight(&mut self, source_map: &ast::SourceMap) {
if self.highlighted.is_none() {
self.highlighted = source_map.highlight_span(self.span, &self.msg);
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.highlighted.as_ref().unwrap_or(&self.msg).fmt(f)
}
}

impl core::error::Error for Error {}

#[derive(Debug)]
struct PackageNotFoundError {
span: Span,
requested: PackageName,
known: Vec<PackageName>,
highlighted: Option<String>,
}

impl PackageNotFoundError {
pub fn new(span: Span, requested: PackageName, known: Vec<PackageName>) -> Self {
Self {
span,
requested,
known,
highlighted: None,
}
}

/// Highlights this error using the given source map, if the span is known.
fn highlight(&mut self, source_map: &ast::SourceMap) {
if self.highlighted.is_none() {
self.highlighted = source_map.highlight_span(self.span, &format!("{self}"));
}
}
}

impl fmt::Display for PackageNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(highlighted) = &self.highlighted {
return highlighted.fmt(f);
}
if self.known.is_empty() {
write!(
f,
"package '{}' not found. no known packages.",
self.requested
)?;
} else {
write!(
f,
"package '{}' not found. known packages:\n",
self.requested
)?;
for known in self.known.iter() {
write!(f, " {known}\n")?;
}
}
Ok(())
}
}

impl core::error::Error for PackageNotFoundError {}

impl UnresolvedPackageGroup {
/// 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.
#[cfg(feature = "std")]
pub fn parse(path: impl AsRef<Path>, contents: &str) -> Result<UnresolvedPackageGroup> {
pub fn parse(path: impl AsRef<Path>, contents: &str) -> anyhow::Result<UnresolvedPackageGroup> {
let path = path
.as_ref()
.to_str()
Expand All @@ -404,7 +346,7 @@ impl UnresolvedPackageGroup {
/// grouping. This is useful when a WIT package is split across multiple
/// files.
#[cfg(feature = "std")]
pub fn parse_dir(path: impl AsRef<Path>) -> Result<UnresolvedPackageGroup> {
pub fn parse_dir(path: impl AsRef<Path>) -> anyhow::Result<UnresolvedPackageGroup> {
let path = path.as_ref();
let mut map = SourceMap::default();
let cx = || format!("failed to read directory {path:?}");
Expand Down Expand Up @@ -1162,12 +1104,12 @@ pub enum Mangling {
impl core::str::FromStr for Mangling {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Mangling> {
fn from_str(s: &str) -> anyhow::Result<Mangling> {
match s {
"legacy" => Ok(Mangling::Legacy),
"standard32" => Ok(Mangling::Standard32),
_ => {
bail!(
anyhow::bail!(
"unknown name mangling `{s}`, \
supported values are `legacy` or `standard32`"
)
Expand Down
Loading
Loading