1 unstable release
new 0.12.1 | Feb 3, 2025 |
---|
#2 in #emitter
105KB
609 lines
This crate is all about constructing structured Diagnostic
s and emitting
them in some specified format.
At the time of writing we are in the process of porting old diagnostics to these structured diagnostics, which is tracked in spade#190 on GitLab.
Diagnostics
Diagnostic
s are created using builders. The simplest compiler error looks like this:
let code = CodeBundle::new("hello ocean!".to_string());
// Spans are never created manually like this. They are created by the lexer
// and need to be combined with each other to form bigger spans.
let span = (spade_codespan::Span::from(6..11), 0);
let diag = Diagnostic::error(span, "something's fishy :spadesquint:");
emitter.emit_diagnostic(&diag, &mut buffer, &code);
error: something's fishy :spadesquint:
┌─ <str>:1:7
│
1 │ hello ocean!
│ ^^^^^
As mentioned, spans shouldn't be created manually. They are passed on from
earlier stages in the compiler, all the way from the tokenizer, usually as a
Loc<T>
. Additional Locs can then be created by combining earlier Locs, for
example using between_locs
. The rest of this documentation will assume that
Locs and code exist, and instead focus on creating diagnostics. The examples
will be inspired by diagnostics currently emitted by spade.
Secondary labels
fn foo() {}
^^^ ------------- first_foo
fn bar() {}
fn foo() {}
^^^ ------------- second_foo
#
#
Diagnostic::error(second_foo, "Duplicate definition of item")
.primary_label("Second definition here")
.secondary_label(first_foo, "First definition here")
error: Duplicate definition of item
┌─ <str>:5:4
│
1 │ fn foo() {}
│ --- First definition here
·
5 │ fn foo() {}
│ ^^^ Second definition here
Note that labels are sorted by location in the code automatically.
Notes and spanned notes
Notes are additional snippets of text that are shown after the labels. They can be used to give additional information or help notices.
fn foo(port: &int<10>) {}
^^^^^^^^ ------ port_ty
Diagnostic::error(port_ty, "Port argument in function")
.primary_label("This is a port")
.note("Only entities and pipelines can take ports as arguments")
error: Port argument in function
┌─ <str>:1:14
│
1 │ fn foo(port: &int<10>) {}
│ ^^^^^^ This is a port
│
= note: Only entities and pipelines can take ports as arguments
The spanned versions are rendered like labels, but compared to the secondary labels they are always rendered separately.
Suggestions
Suggestions can be used to format a change suggestion to the user. There are a bunch of convenience functions depending on what kind of suggestion is needed. Try to use them since they show the intent behind the suggestion.
struct port S {
^^^^ --------------- port_kw
field1: &bool,
field2: bool,
^^^^^^ ^^^^ ---------- field_ty
|----------------- field
}
Diagnostic::error(field_ty, "Non-port in port struct")
.primary_label("This is not a port type")
.secondary_label(port_kw, "This is a port struct")
.note("All members of a port struct must be ports")
.span_suggest_insert_before(
format!("Consider making {field} a wire"),
field_ty,
"&",
)
error: Non-port in port struct
┌─ <str>:3:13
│
1 │ struct port S {
│ ---- This is a port struct
2 │ field1: &bool,
3 │ field2: bool,
│ ^^^^ This is not a port type
│
= note: All members of a port struct must be ports
= Consider making field2 a wire
│
3 │ field2: &bool,
│ +
The convenience functions start with span_suggest
and include
span_suggest_insert_before
(above), span_suggest_replace
and
span_suggest_remove
.
Multipart suggestions are basically multiple single part suggestions. Use them when the suggestion needs to include changes that are separated in the code.
enum E {
VariantA(a: bool),
^ ^ ---- close_paren
|------------- open_paren
}
Diagnostic::error(open_paren, "Expected '{', '}' or ','")
.span_suggest_multipart(
"Use '{' if you want to add items to this enum variant",
SuggestionParts::new()
.part(open_paren, "{")
.part(close_paren, "}"),
)
error: Expected '{', '}' or ','
┌─ <str>:2:13
│
2 │ VariantA(a: bool),
│ ^
│
= Use '{' if you want to add items to this enum variant
│
2 │ VariantA{a: bool},
│ ~ ~
Emitters
We also need some way to show these diagnostics to the user. For this we have
Emitter
s which abstract away the details of formatting the diagnostic. The
default emitter is the CodespanEmitter
which formats the diagnostics using
a forked codespan-reporting
.
If you use the compiler as a library (like we do for the Spade Language Server) you can define your own emitter that formats the diagnostics. The language server, for example, has its own emitter that sends LSP-friendly diagnostics to the connected Language Server Client.
When writing diagnostics in the compiler you usually don't have to care about
the emitter. Almost everywhere, the diagnostics are returned and handled by
someone else. (In Spade, that someone else is spade-compiler
.)
Dependencies
~5–12MB
~105K SLoC