Skip to content

Commit d19bbee

Browse files
Structured resolver errors (#2482)
* [wit-parser] Migrate to structured errors in the resolver * Rename structs * Remove unneeded methods * Remove dangling doc * Doc improvements * Update snapshot * Don't reference a non-existent method in docs * Fix bad merge * sm_idx -> *source_maps_index * Revert "sm_idx -> *source_maps_index" This reverts commit 0692157. * Remove push_groups, push_group returns a structured error * Remove stale reference * Use fewer abbreviations * Introduce ResolveError::new_semantic * Bring back push_groups because it makes it easier to test things * Rename for consistency * Fix typo * Preserve downcasting * Add new variants to ResolveError * Pass a more specific span * Bless the snapshots * Mention function name * fmt * bin fmt * Trim down comment * Tighter downcasting tests
1 parent 1a2b915 commit d19bbee

45 files changed

Lines changed: 777 additions & 554 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/wit-parser/src/ast.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,25 +1845,6 @@ impl SourceMap {
18451845
Ok((resolver.resolve()?, nested))
18461846
}
18471847

1848-
/// Runs `f` and, on error, attempts to add source highlighting to resolver
1849-
/// error types that still use `anyhow`. Only needed until the resolver is
1850-
/// migrated to structured errors.
1851-
pub(crate) fn rewrite_error<F, T>(&self, f: F) -> anyhow::Result<T>
1852-
where
1853-
F: FnOnce() -> anyhow::Result<T>,
1854-
{
1855-
let mut err = match f() {
1856-
Ok(t) => return Ok(t),
1857-
Err(e) => e,
1858-
};
1859-
if let Some(e) = err.downcast_mut::<crate::Error>() {
1860-
e.highlight(self);
1861-
} else if let Some(e) = err.downcast_mut::<crate::PackageNotFoundError>() {
1862-
e.highlight(self);
1863-
}
1864-
Err(err)
1865-
}
1866-
18671848
pub(crate) fn highlight_span(&self, span: Span, err: impl fmt::Display) -> Option<String> {
18681849
if !span.is_known() {
18691850
return None;

crates/wit-parser/src/ast/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
//! Error types for WIT parsing.
2+
13
use alloc::boxed::Box;
24
use alloc::string::{String, ToString};
35
use core::fmt;
46

57
use crate::{SourceMap, Span, ast::lex};
68

9+
/// Convenience alias for a `Result` whose error type is [`ParseError`].
710
pub type ParseResult<T, E = ParseError> = Result<T, E>;
811

12+
/// The category of error that occurred while parsing a WIT package.
913
#[non_exhaustive]
1014
#[derive(Debug, PartialEq, Eq)]
1115
pub enum ParseErrorKind {
@@ -31,6 +35,7 @@ pub enum ParseErrorKind {
3135
}
3236

3337
impl ParseErrorKind {
38+
/// Returns the source span associated with this error.
3439
pub fn span(&self) -> Span {
3540
match self {
3641
ParseErrorKind::Lex(e) => Span::new(e.position(), e.position() + 1),
@@ -62,6 +67,7 @@ impl fmt::Display for ParseErrorKind {
6267
}
6368
}
6469

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

@@ -74,10 +80,12 @@ impl ParseError {
7480
.into()
7581
}
7682

83+
/// Returns the underlying error kind
7784
pub fn kind(&self) -> &ParseErrorKind {
7885
&self.0
7986
}
8087

88+
/// Returns the underlying error kind (mutable).
8189
pub fn kind_mut(&mut self) -> &mut ParseErrorKind {
8290
&mut self.0
8391
}

crates/wit-parser/src/decoding.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::*;
22
use alloc::string::{String, ToString};
33
use alloc::vec;
44
use alloc::vec::Vec;
5+
use anyhow::Result;
56
use anyhow::{Context, anyhow, bail};
67
use core::mem;
78
use std::io::Read;

crates/wit-parser/src/lib.rs

Lines changed: 34 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use alloc::format;
1010
use alloc::string::{String, ToString};
1111
use alloc::vec::Vec;
1212
#[cfg(feature = "std")]
13-
use anyhow::Context;
14-
use anyhow::{Result, bail};
13+
use anyhow::Context as _;
1514
use id_arena::{Arena, Id};
1615
use semver::Version;
1716

@@ -52,6 +51,7 @@ pub use ast::{ParsedUsePath, parse_use_path};
5251
mod sizealign;
5352
pub use sizealign::*;
5453
mod resolve;
54+
pub use resolve::error::*;
5555
pub use resolve::*;
5656
mod live;
5757
pub use live::{LiveTypes, TypeIdVisitor};
@@ -64,11 +64,38 @@ mod serde_;
6464
use serde_::*;
6565

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

72+
/// Renders an [`anyhow::Error`] chain produced by this crate, substituting
73+
/// snippet-bearing output for any [`ResolveError`] or [`ParseError`] layers.
74+
///
75+
/// For each layer in the chain, this calls [`ResolveError::highlight`] or
76+
/// [`ParseError::highlight`] to format typed errors with file/line/column and
77+
/// a source snippet. Other layers are formatted via their [`fmt::Display`]
78+
/// impl. Layers are joined with `": "`, matching `format!("{err:#}")`.
79+
///
80+
/// `source_map` must be the [`SourceMap`] in which every typed error's spans
81+
/// are valid; combining typed errors from different source maps in one chain
82+
/// is unsupported.
83+
#[cfg(feature = "std")]
84+
pub fn render_anyhow_error(err: &anyhow::Error, source_map: &SourceMap) -> String {
85+
err.chain()
86+
.map(|layer| {
87+
if let Some(re) = layer.downcast_ref::<ResolveError>() {
88+
re.highlight(source_map)
89+
} else if let Some(pe) = layer.downcast_ref::<ParseError>() {
90+
pe.highlight(source_map)
91+
} else {
92+
layer.to_string()
93+
}
94+
})
95+
.collect::<Vec<_>>()
96+
.join(": ")
97+
}
98+
7299
pub type WorldId = Id<World>;
73100
pub type InterfaceId = Id<Interface>;
74101
pub type TypeId = Id<TypeDef>;
@@ -294,99 +321,14 @@ impl fmt::Display for PackageName {
294321
}
295322
}
296323

297-
#[derive(Debug)]
298-
struct Error {
299-
span: Span,
300-
msg: String,
301-
highlighted: Option<String>,
302-
}
303-
304-
impl Error {
305-
fn new(span: Span, msg: impl Into<String>) -> Error {
306-
Error {
307-
span,
308-
msg: msg.into(),
309-
highlighted: None,
310-
}
311-
}
312-
313-
/// Highlights this error using the given source map, if the span is known.
314-
fn highlight(&mut self, source_map: &ast::SourceMap) {
315-
if self.highlighted.is_none() {
316-
self.highlighted = source_map.highlight_span(self.span, &self.msg);
317-
}
318-
}
319-
}
320-
321-
impl fmt::Display for Error {
322-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323-
self.highlighted.as_ref().unwrap_or(&self.msg).fmt(f)
324-
}
325-
}
326-
327-
impl core::error::Error for Error {}
328-
329-
#[derive(Debug)]
330-
struct PackageNotFoundError {
331-
span: Span,
332-
requested: PackageName,
333-
known: Vec<PackageName>,
334-
highlighted: Option<String>,
335-
}
336-
337-
impl PackageNotFoundError {
338-
pub fn new(span: Span, requested: PackageName, known: Vec<PackageName>) -> Self {
339-
Self {
340-
span,
341-
requested,
342-
known,
343-
highlighted: None,
344-
}
345-
}
346-
347-
/// Highlights this error using the given source map, if the span is known.
348-
fn highlight(&mut self, source_map: &ast::SourceMap) {
349-
if self.highlighted.is_none() {
350-
self.highlighted = source_map.highlight_span(self.span, &format!("{self}"));
351-
}
352-
}
353-
}
354-
355-
impl fmt::Display for PackageNotFoundError {
356-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357-
if let Some(highlighted) = &self.highlighted {
358-
return highlighted.fmt(f);
359-
}
360-
if self.known.is_empty() {
361-
write!(
362-
f,
363-
"package '{}' not found. no known packages.",
364-
self.requested
365-
)?;
366-
} else {
367-
write!(
368-
f,
369-
"package '{}' not found. known packages:\n",
370-
self.requested
371-
)?;
372-
for known in self.known.iter() {
373-
write!(f, " {known}\n")?;
374-
}
375-
}
376-
Ok(())
377-
}
378-
}
379-
380-
impl core::error::Error for PackageNotFoundError {}
381-
382324
impl UnresolvedPackageGroup {
383325
/// Parses the given string as a wit document.
384326
///
385327
/// The `path` argument is used for error reporting. The `contents` provided
386328
/// are considered to be the contents of `path`. This function does not read
387329
/// the filesystem.
388330
#[cfg(feature = "std")]
389-
pub fn parse(path: impl AsRef<Path>, contents: &str) -> Result<UnresolvedPackageGroup> {
331+
pub fn parse(path: impl AsRef<Path>, contents: &str) -> anyhow::Result<UnresolvedPackageGroup> {
390332
let path = path
391333
.as_ref()
392334
.to_str()
@@ -404,7 +346,7 @@ impl UnresolvedPackageGroup {
404346
/// grouping. This is useful when a WIT package is split across multiple
405347
/// files.
406348
#[cfg(feature = "std")]
407-
pub fn parse_dir(path: impl AsRef<Path>) -> Result<UnresolvedPackageGroup> {
349+
pub fn parse_dir(path: impl AsRef<Path>) -> anyhow::Result<UnresolvedPackageGroup> {
408350
let path = path.as_ref();
409351
let mut map = SourceMap::default();
410352
let cx = || format!("failed to read directory {path:?}");
@@ -1162,12 +1104,12 @@ pub enum Mangling {
11621104
impl core::str::FromStr for Mangling {
11631105
type Err = anyhow::Error;
11641106

1165-
fn from_str(s: &str) -> Result<Mangling> {
1107+
fn from_str(s: &str) -> anyhow::Result<Mangling> {
11661108
match s {
11671109
"legacy" => Ok(Mangling::Legacy),
11681110
"standard32" => Ok(Mangling::Standard32),
11691111
_ => {
1170-
bail!(
1112+
anyhow::bail!(
11711113
"unknown name mangling `{s}`, \
11721114
supported values are `legacy` or `standard32`"
11731115
)

0 commit comments

Comments
 (0)