Document SessionDiagnostic
This commit is contained in:
parent
d89703684b
commit
0e7f5c08b1
|
|
@ -128,6 +128,7 @@
|
|||
- [Two-phase-borrows](./borrow_check/two_phase_borrows.md)
|
||||
- [Parameter Environments](./param_env.md)
|
||||
- [Errors and Lints](diagnostics.md)
|
||||
- [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md)
|
||||
- [`LintStore`](./diagnostics/lintstore.md)
|
||||
- [Diagnostic Codes](./diagnostics/diagnostic-codes.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -347,6 +347,10 @@ if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
|
|||
err.emit();
|
||||
```
|
||||
|
||||
Alternatively, for less-complex diagnostics, the `SessionDiagnostic` derive
|
||||
macro can be used -- see [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md).
|
||||
|
||||
|
||||
## Suggestions
|
||||
|
||||
In addition to telling the user exactly _why_ their code is wrong, it's
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
# Creating Errors With SessionDiagnostic
|
||||
|
||||
The SessionDiagnostic derive macro gives an alternate way to the DiagnosticBuilder API for defining
|
||||
and emitting errors. It allows a struct to be annotated with information which allows it to be
|
||||
transformed and emitted as a Diagnostic.
|
||||
|
||||
As an example, we'll take a look at how the "field already declared" diagnostic is actually defined
|
||||
in the compiler (see the definition
|
||||
[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/errors.rs#L65-L74)
|
||||
and usage
|
||||
[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/collect.rs#L863-L867)):
|
||||
|
||||
```rust,ignore
|
||||
#[derive(SessionDiagnostic)]
|
||||
#[error = "E0124"]
|
||||
pub struct FieldAlreadyDeclared {
|
||||
pub field_name: Ident,
|
||||
#[message = "field `{field_name}` is already declared"]
|
||||
#[label = "field already declared"]
|
||||
pub span: Span,
|
||||
#[label = "`{field_name}` first declared here"]
|
||||
pub prev_span: Span,
|
||||
}
|
||||
// ...
|
||||
tcx.sess.emit_err(FieldAlreadyDeclared {
|
||||
field_name: f.ident,
|
||||
span: f.span,
|
||||
prev_span,
|
||||
});
|
||||
```
|
||||
|
||||
We see that using `SessionDiagnostic` is relatively straight forward. The `#[error = "..."]`
|
||||
attribute is used to supply the error code for the diagnostic. We are then annotate fields in the
|
||||
struct with various information of how to convert an instance of the struct into a proper
|
||||
diagnostic. The attributes above produce code which is roughly equivalent to the following (in
|
||||
pseudo-Rust):
|
||||
|
||||
```rust,ignore
|
||||
impl SessionDiagnostic for FieldAlreadyDeclared {
|
||||
fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> {
|
||||
let mut diag = sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error("E0124"));
|
||||
diag.set_span(self.span);
|
||||
diag.set_primary_message(format!("field `{field_name}` is already declared", field_name = self.field_name));
|
||||
diag.span_label(self.span, "field already declared");
|
||||
diag.span_label(self.prev_span, format!("`{field_name}` first declared here", field_name = self.field_name));
|
||||
diag
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated code draws attention to a number of features. First, we see that within the strings
|
||||
passed to each attribute, we see that field names can be referenced without needing to be passed
|
||||
explicitly into the format string -- in this example here, `#[message = "field {field_name} is
|
||||
already declared"]` produces a call to `format!` with the appropriate arguments to format
|
||||
`self.field_name` into the string. This applies to strings passed to all attributes.
|
||||
|
||||
We also see that labelling `Span` fields in the struct produces calls which pass that `Span` to the
|
||||
produced diagnostic. In the example above, we see that putting the `#[message = "..."]` attribute
|
||||
on a `Span` leads to the primary span of the diagnostic being set to that `Span`, while applying the
|
||||
`#[label = "..."]` attribute on a Span will simply set the span for that label.
|
||||
Each attribute has different requirements for what they can be applied on, differing on position
|
||||
(on the struct, or on a specific field), type (if it's applied on a field), and whether or not the
|
||||
attribute is optional.
|
||||
|
||||
## Attributes Listing
|
||||
|
||||
Below is a listing of all the currently-available attributes that `#[derive(SessionDiagnostic)]`
|
||||
understands:
|
||||
|
||||
Attribute | Applied to | Mandatory | Behaviour
|
||||
:-------------- | :-------------------- |:--------- | :---------
|
||||
`#[code = "..."]` | Struct | Yes | Sets the Diagnostic's error code
|
||||
`#[message = "..."]` | Struct / `Span` fields | Yes | Sets the Diagnostic's primary message. If on `Span` field, also sets the Diagnostic's span.
|
||||
`#[label = "..."]` | `Span` fields | No | Equivalent to calling `span_label` with that Span and message.
|
||||
`#[suggestion(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion`. Note `code` is optional.
|
||||
`#[suggestion_short(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_short`. Note `code` is optional.
|
||||
`#[suggestion_hidden(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_hidden`. Note `code` is optional.
|
||||
`#[suggestion_verbose(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_verbose`. Note `code` is optional.
|
||||
|
||||
|
||||
## Optional Diagnostic Attributes
|
||||
|
||||
There may be some cases where you want one of the decoration attributes to be applied optionally;
|
||||
for example, if a suggestion can only be generated sometimes. In this case, simply wrap the field's
|
||||
type in an `Option`. At runtime, if the field is set to `None`, the attribute for that field won't
|
||||
be used in creating the diagnostic. For example:
|
||||
|
||||
```rust,ignored
|
||||
#[derive(SessionDiagnostic)]
|
||||
#[code = "E0123"]
|
||||
struct SomeKindOfError {
|
||||
...
|
||||
#[suggestion(message = "informative error message")]
|
||||
opt_sugg: Option<(Span, Applicability)>
|
||||
...
|
||||
}
|
||||
```
|
||||
Loading…
Reference in New Issue