pub trait PhaseError {
// Required method
fn message(&self) -> String;
// Provided methods
fn notes(&self) -> Vec<String> { ... }
fn labels(&self) -> Vec<SpanLabel> { ... }
fn message_and_notes(&self) -> Vec<String> { ... }
}Expand description
What a compiler phase owes the diagnostic reporter.
Each phase keeps its own error type — one enum for the whole compiler would make
every phase depend on the vocabulary of every other. What they have to share is
the ability to describe themselves in the user’s terms, because
CompilationError::as_diagnostic is the only place a Diagnostic is ever
built and it has no phase-specific knowledge to fall back on. Dumping
format!("{:?}", e) into a note is exactly what this trait replaces: a Debug
dump names Rust types, not source constructs.
§How much a span can say, and where that stops
Every production in grammar.lalrpop that builds a node captures @L/@R, so
declarations, expressions, patterns and types all know where they were written,
and canonicalization copies that onto the node it builds. An error raised from one
of those construction sites returns a SpanLabel from
labels and gets a caret under the text it is about — an
unresolvable name is underlined at the identifier, not across the declaration
containing it.
Not every error can. labels defaults to empty and that is a real answer, not a
stub: an error raised while walking a node the grammar does not span — an
import‘s exposing list, say — has nowhere to point, and renders as message-plus-notes
with no caret, exactly as every phase after parsing used to. An error that groups
others (canonical::Error::Many, EnvironmentErrors) has no position of its own
and flattens its members’ labels instead, the way it already flattens their
messages.
The typer is the phase that has to work for this, and it does: it does not check
the canonical AST but a term language of its own, so it carries each canonical
node’s span into that language, each constraint records the term and the reason
it came from, and unify reports the origin of the constraint it failed on. A
type error therefore underlines the sub-expression that disagrees and adds a
secondary label under the annotation that says what was expected (ERR-4).
parser::Error is still the one phase error that does not go through this trait
at all — it builds its own labelled Diagnostic through parser::Error::diagnostic.
§The two ways a label learns its SourceFileId
A phase still never knows the id of the file the module it is checking came
from — that half is unchanged, and stays attached by compile_package, the only
place that knows it, the way CompilationError::Source already works. A
SpanLabel with file: None — everything built before ERR-5 — means
exactly that: “in the module under check”, resolved at render time.
That is right for an error entirely about one module and cannot cover a
diagnostic that also names something written elsewhere — “the annotation you
are contradicting is over there, in Basics”. For that, file carries its own
SourceFileId instead, taken from a SourceSpan cloned out of an
Interface via Interface::source_span. An Interface can offer that
because it is not a phase: it is built by driver code
(dependencies::ModuleWalker::check_in_order) that already has the file the
module it just checked came from, and stamps it onto the Interface before
handing it to whoever imports next. canonical::Type still carries no span of
its own — seeing why is worth a look at that type’s own documentation.
Required Methods§
Provided Methods§
Sourcefn notes(&self) -> Vec<String>
fn notes(&self) -> Vec<String>
Supporting detail, one string per rendered note. Empty by default.
Sourcefn labels(&self) -> Vec<SpanLabel>
fn labels(&self) -> Vec<SpanLabel>
The regions of the user’s source this error is about, if it knows any.
Empty — the default — means this error has no position to point at, and it renders as message-plus-notes with no caret. See the trait’s documentation.
Sourcefn message_and_notes(&self) -> Vec<String>
fn message_and_notes(&self) -> Vec<String>
This error’s message followed by its notes.
A Diagnostic has one headline, so an error that ends up inside a group —
several errors from one phase, or a variant like canonical::Error::Many
that wraps others — has to give up its headline and become notes. This is
that demotion, in one place, so a group cannot silently drop the message of
a member it swallowed.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl PhaseError for zelkova_compiler::canonical::Error
Canonicalization errors name source constructs — a value, a type, an operator — so their messages can be written in the same words the user wrote.
The variants whose construction site had a declaration in hand also point at it; see the enum’s own documentation for why the others do not.
impl PhaseError for zelkova_compiler::dependencies::Error
A dependency cycle belongs to the package, not to any one module, so its message
names the modules involved rather than being prefixed with one of them, and its
labels — one per import that forms an edge — each carry their own file rather
than relying on a fallback the way a single-module phase error can
(CompilationError::DependenciesError renders this without one, see there).
impl PhaseError for zelkova_compiler::exhaustiveness::Error
impl PhaseError for zelkova_compiler::ir::SpecialiseError
impl PhaseError for zelkova_compiler::program::Error
impl PhaseError for zelkova_compiler::resolve::Error
impl PhaseError for zelkova_compiler::typer::Error
Type errors are about types, and Type’s Display writes them the way the
source does — so the message can name both sides of a mismatch instead of
dumping the typer’s internal representation.
impl PhaseError for ManifestError
impl PhaseError for SourceFileError
Loading is the one phase that has no module and no source text to point into —
it failed before either existed — so its errors name the path instead. It goes
through PhaseError anyway, so that as_diagnostic has exactly one way to
turn a phase error into text.