-
Notifications
You must be signed in to change notification settings - Fork 327
Structured resolver errors #2482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b1bdd14
2aeff69
2bb93f0
9177bad
8bce150
f924443
0641505
2043c6c
89df4c4
0692157
6496897
d90a9f2
151cda0
9ab9544
69fe727
c44b8ba
d489aea
231996e
1a01c31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| //! Error types for WIT package resolution. | ||
|
|
||
| use alloc::boxed::Box; | ||
| use alloc::format; | ||
| use alloc::string::{String, ToString}; | ||
| use alloc::vec::Vec; | ||
| use core::fmt; | ||
|
|
||
| use crate::{PackageName, SourceMap, Span}; | ||
|
|
||
| /// Convenience alias for a `Result` whose error type is [`ResolveError`]. | ||
| pub type ResolveResult<T, E = ResolveError> = Result<T, E>; | ||
|
|
||
| /// The category of error that occurred while resolving a WIT package. | ||
| #[non_exhaustive] | ||
| #[derive(Debug, PartialEq, Eq)] | ||
| pub enum ResolveErrorKind { | ||
| /// A referenced package could not be found among the known packages. | ||
| PackageNotFound { | ||
| span: Span, | ||
| requested: PackageName, | ||
| known: Vec<PackageName>, | ||
| }, | ||
| /// An interface has a transitive dependency that creates an incompatible | ||
| /// import relationship. | ||
| InvalidTransitiveDependency { span: Span, name: String }, | ||
| /// The same package is defined in two different locations. | ||
| DuplicatePackage { | ||
| name: PackageName, | ||
| span1: Span, | ||
| span2: Span, | ||
| }, | ||
| /// Packages form a dependency cycle. | ||
| PackageCycle { package: PackageName, span: Span }, | ||
| /// A semantic error during resolution (type mismatch, invalid use, etc.) | ||
| Semantic { span: Span, message: String }, | ||
| } | ||
|
|
||
| impl ResolveErrorKind { | ||
| /// Returns the source span associated with this error. | ||
| pub fn span(&self) -> Span { | ||
| match self { | ||
| ResolveErrorKind::PackageNotFound { span, .. } | ||
| | ResolveErrorKind::InvalidTransitiveDependency { span, .. } | ||
| | ResolveErrorKind::PackageCycle { span, .. } | ||
| | ResolveErrorKind::Semantic { span, .. } => *span, | ||
| ResolveErrorKind::DuplicatePackage { span1, .. } => *span1, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Display for ResolveErrorKind { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| match self { | ||
| ResolveErrorKind::PackageNotFound { | ||
| requested, known, .. | ||
| } => { | ||
| if known.is_empty() { | ||
| write!(f, "package '{requested}' not found") | ||
| } else { | ||
| write!(f, "package '{requested}' not found. known packages:")?; | ||
| for k in known { | ||
| write!(f, "\n {k}")?; | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
| ResolveErrorKind::InvalidTransitiveDependency { name, .. } => write!( | ||
| f, | ||
| "interface `{name}` transitively depends on an interface in incompatible ways", | ||
| ), | ||
| ResolveErrorKind::DuplicatePackage { name, .. } => { | ||
| write!(f, "package `{name}` is defined in two different locations",) | ||
| } | ||
| ResolveErrorKind::PackageCycle { package, .. } => { | ||
| write!(f, "package `{package}` creates a dependency cycle") | ||
| } | ||
| ResolveErrorKind::Semantic { message, .. } => message.fmt(f), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A single structured error from resolving a WIT package. | ||
| #[derive(Debug, PartialEq, Eq)] | ||
| pub struct ResolveError(Box<ResolveErrorKind>); | ||
|
|
||
| impl ResolveError { | ||
| /// Creates a [`ResolveError`] with the [`ResolveErrorKind::Semantic`] variant. | ||
| pub fn new_semantic(span: Span, message: impl Into<String>) -> Self { | ||
| ResolveErrorKind::Semantic { | ||
| span, | ||
| message: message.into(), | ||
| } | ||
| .into() | ||
| } | ||
|
|
||
| /// Returns the underlying error kind. | ||
| pub fn kind(&self) -> &ResolveErrorKind { | ||
| &self.0 | ||
| } | ||
|
|
||
| /// Returns the underlying error kind (mutable). | ||
| pub fn kind_mut(&mut self) -> &mut ResolveErrorKind { | ||
| &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(); | ||
| let msg = e.to_string(); | ||
| match e { | ||
| ResolveErrorKind::DuplicatePackage { name, span1, span2 } => { | ||
| let loc1 = source_map.render_location(*span1); | ||
| let loc2 = source_map.render_location(*span2); | ||
| format!( | ||
| "package `{name}` is defined in two different locations:\n * {loc1}\n * {loc2}" | ||
| ) | ||
| } | ||
| _ => source_map.highlight_span(e.span(), &msg).unwrap_or(msg), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Display for ResolveError { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.kind(), f) | ||
| } | ||
| } | ||
|
|
||
| impl core::error::Error for ResolveError {} | ||
|
|
||
| impl From<ResolveErrorKind> for ResolveError { | ||
| fn from(kind: ResolveErrorKind) -> Self { | ||
| ResolveError(Box::new(kind)) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,7 +131,9 @@ impl Resolve { | |
| .parse_deps_dir(&deps) | ||
| .with_context(|| format!("failed to parse dependency directory: {}", deps.display()))?; | ||
|
|
||
| let (pkg_id, inner) = self.sort_unresolved_packages(top_pkg, deps)?; | ||
| let sort_result = self.sort_unresolved_packages(top_pkg, deps); | ||
| let (pkg_id, inner) = | ||
| sort_result.map_err(|e| anyhow::anyhow!("{}", e.highlight(&self.source_map)))?; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like I might have asked this already on the previous PR of yours, so I apologize if so, but here this feels kind of bad to stringify the error and forget about the original error. The main downside here is that the returned value of I forget, though, was this something where the Or, alternatively, one thing I thought of is that it's useful to have a decoupled error from the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You're right, I was a bit sloppy here. I think I can fix this by using
In this place it's not temporary. I personally don't think we should introduce any I/O error variants to
I generally prefer the errors to stay very lightweight - ie only contain the span info and the necessary metadata. Adding rendered strings to errors means that we lose a clean separation between what went wrong (spans/metadata), and how to display it (source map rendering). And if we mutate the error, the error now becomes dependent on when and whether you call that mutation (which increases the chance of bugs). I also plan to revisit the highlight naming and the overall error presentation in a follow-up - now that errors are structured, it should be much easier to reason about where and how rendering happens, and to improve individual error messages. |
||
| Ok((pkg_id, PackageSourceMap::from_inner(inner))) | ||
| } | ||
|
|
||
|
|
@@ -195,7 +197,9 @@ impl Resolve { | |
| match self._push_file(path.as_ref())? { | ||
| #[cfg(feature = "decoding")] | ||
| ParsedFile::Package(id) => Ok(id), | ||
| ParsedFile::Unresolved(pkg) => self.push_group(pkg), | ||
| ParsedFile::Unresolved(pkg) => self | ||
| .push_group(pkg) | ||
| .map_err(|e| anyhow::anyhow!("{}", e.highlight(&self.source_map))), | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.