Skip to content

Commit 33cf2e5

Browse files
committed
Document SessionDiagnostic
1 parent 2777dee commit 33cf2e5

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
- [Two-phase-borrows](./borrow_check/two_phase_borrows.md)
128128
- [Parameter Environments](./param_env.md)
129129
- [Errors and Lints](diagnostics.md)
130+
- [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md)
130131
- [`LintStore`](./diagnostics/lintstore.md)
131132
- [Diagnostic Codes](./diagnostics/diagnostic-codes.md)
132133

src/diagnostics.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
338338
err.emit();
339339
```
340340

341+
Alternatively, for less-complex diagnostics, the `SessionDiagnostic` derive
342+
macro can be used -- see [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md).
343+
344+
341345
## Suggestions
342346

343347
In addition to telling the user exactly _why_ their code is wrong, it's

src/diagnostics/sessiondiagnostic.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Creating Errors With SessionDiagnostic
2+
3+
The SessionDiagnostic derive macro gives an alternate way to the DiagnosticBuilder API for defining
4+
and emitting errors. It allows a struct to be annotated with information which allows it to be
5+
transformed and emitted as a Diagnostic.
6+
7+
As an example, we'll take a look at how the "field already declared" diagnostic is actually defined
8+
in the compiler (see the definition
9+
[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/errors.rs#L65-L74)
10+
and usage
11+
[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/collect.rs#L863-L867)):
12+
13+
```rust,ignore
14+
#[derive(SessionDiagnostic)]
15+
#[error = "E0124"]
16+
pub struct FieldAlreadyDeclared {
17+
pub field_name: Ident,
18+
#[message = "field `{field_name}` is already declared"]
19+
#[label = "field already declared"]
20+
pub span: Span,
21+
#[label = "`{field_name}` first declared here"]
22+
pub prev_span: Span,
23+
}
24+
// ...
25+
tcx.sess.emit_err(FieldAlreadyDeclared {
26+
field_name: f.ident,
27+
span: f.span,
28+
prev_span,
29+
});
30+
```
31+
32+
We see that using `SessionDiagnostic` is relatively straight forward. The `#[error = "..."]`
33+
attribute is used to supply the error code for the diagnostic. We are then annotate fields in the
34+
struct with various information of how to convert an instance of the struct into a proper
35+
diagnostic. The attributes above produce code which is roughly equivalent to the following (in
36+
pseudo-Rust):
37+
38+
```rust,ignore
39+
impl SessionDiagnostic for FieldAlreadyDeclared {
40+
fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> {
41+
let mut diag = sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error("E0124"));
42+
diag.set_span(self.span);
43+
diag.set_primary_message(format!("field `{field_name}` is already declared", field_name = self.field_name));
44+
diag.span_label(self.span, "field already declared");
45+
diag.span_label(self.prev_span, format!("`{field_name}` first declared here", field_name = self.field_name));
46+
diag
47+
}
48+
}
49+
```
50+
51+
The generated code draws attention to a number of features. First, we see that within the strings
52+
passed to each attribute, we see that field names can be referenced without needing to be passed
53+
explicitly into the format string -- in this example here, `#[message = "field {field_name} is
54+
already declared"]` produces a call to `format!` with the appropriate arguments to format
55+
`self.field_name` into the string. This applies to strings passed to all attributes.
56+
57+
We also see that labelling `Span` fields in the struct produces calls which pass that `Span` to the
58+
produced diagnostic. In the example above, we see that putting the `#[message = "..."]` attribute
59+
on a `Span` leads to the primary span of the diagnostic being set to that `Span`, while applying the
60+
`#[label = "..."]` attribute on a Span will simply set the span for that label.
61+
Each attribute has different requirements for what they can be applied on, differing on position
62+
(on the struct, or on a specific field), type (if it's applied on a field), and whether or not the
63+
attribute is optional.
64+
65+
## Attributes Listing
66+
67+
Below is a listing of all the currently-available attributes that `#[derive(SessionDiagnostic)]`
68+
understands:
69+
70+
Attribute | Applied to | Mandatory | Behaviour
71+
:-------------- | :-------------------- |:--------- | :---------
72+
`#[code = "..."]` | Struct | Yes | Sets the Diagnostic's error code
73+
`#[message = "..."]` | Struct / `Span` fields | Yes | Sets the Diagnostic's primary message. If on `Span` field, also sets the Diagnostic's span.
74+
`#[label = "..."]` | `Span` fields | No | Equivalent to calling `span_label` with that Span and message.
75+
`#[suggestion(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion`. Note `code` is optional.
76+
`#[suggestion_short(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_short`. Note `code` is optional.
77+
`#[suggestion_hidden(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_hidden`. Note `code` is optional.
78+
`#[suggestion_verbose(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_verbose`. Note `code` is optional.
79+
80+
81+
## Optional Diagnostic Attributes
82+
83+
There may be some cases where you want one of the decoration attributes to be applied optionally;
84+
for example, if a suggestion can only be generated sometimes. In this case, simply wrap the field's
85+
type in an `Option`. At runtime, if the field is set to `None`, the attribute for that field won't
86+
be used in creating the diagnostic. For example:
87+
88+
```rust,ignored
89+
#[derive(SessionDiagnostic)]
90+
#[code = "E0123"]
91+
struct SomeKindOfError {
92+
...
93+
#[suggestion(message = "informative error message")]
94+
opt_sugg: Option<(Span, Applicability)>
95+
...
96+
}
97+
```

0 commit comments

Comments
 (0)