|
| 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