|
1 |
| -# Creating Errors With SessionDiagnostic |
| 1 | +# Creating translatable errors using `SessionDiagnostic` |
| 2 | +The `SessionDiagnostic` derive macro is the recommended way to create |
| 3 | +diagnostics. Diagnostics created with the derive macro can be translated into |
| 4 | +different languages and each have a slug that uniquely identifies the |
| 5 | +diagnostic. |
2 | 6 |
|
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. |
| 7 | +Instead of using the `DiagnosticBuilder` API to create and emit diagnostics, |
| 8 | +the `SessionDiagnostic` derive macro is applied to structs. |
6 | 9 |
|
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)): |
| 10 | +The [definition]() of the "field already declared" diagnostic is shown below. |
12 | 11 |
|
13 | 12 | ```rust,ignore
|
14 | 13 | #[derive(SessionDiagnostic)]
|
15 |
| -#[error = "E0124"] |
| 14 | +#[error(code = "E0124", slug = "typeck-field-already-declared")] |
16 | 15 | pub struct FieldAlreadyDeclared {
|
17 | 16 | pub field_name: Ident,
|
18 |
| - #[message = "field `{field_name}` is already declared"] |
19 |
| - #[label = "field already declared"] |
| 17 | + #[primary_span] |
| 18 | + #[label] |
20 | 19 | pub span: Span,
|
21 |
| - #[label = "`{field_name}` first declared here"] |
| 20 | + #[label = "previous-decl-label"] |
22 | 21 | pub prev_span: Span,
|
23 | 22 | }
|
24 |
| -// ... |
25 |
| -tcx.sess.emit_err(FieldAlreadyDeclared { |
26 |
| - field_name: f.ident, |
27 |
| - span: f.span, |
28 |
| - prev_span, |
29 |
| -}); |
30 | 23 | ```
|
31 | 24 |
|
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 then annotate fields in the |
34 |
| -struct with various information on how to convert an instance of the struct into a rendered |
35 |
| -diagnostic. The attributes above produce code which is roughly equivalent to the following (in |
36 |
| -pseudo-Rust): |
| 25 | +Every `SessionDiagnostic` has to have one attribute applied to the struct |
| 26 | +itself: either `#[error(..)]` for defining errors, or `#[warning(..)]` for |
| 27 | +defining warnings. |
37 | 28 |
|
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 |
| -} |
| 29 | +If an error has an error code (e.g. "E0624"), then that can be specified using |
| 30 | +the `code` sub-attribute. Specifying a `code` isn't mandatory, but if you are |
| 31 | +porting a diagnostic that uses `DiagnosticBuilder` to use `SessionDiagnostic` |
| 32 | +then you should keep the code if there was one. |
| 33 | + |
| 34 | +Both `#[error(..)]` and `#[warning(..)]` must set a value for the `slug` |
| 35 | +sub-attribute. `slug` uniquely identifies the diagnostic and is also how the |
| 36 | +compiler knows what error message to emit (in the default locale of the |
| 37 | +compiler, or in the locale requested by the user). |
| 38 | + |
| 39 | +rustc uses [Fluent](https://projectfluent.org) to handle the intricacies of |
| 40 | +translation. Each diagnostic's `slug` is actually an identifier for a *Fluent |
| 41 | +message*. Let's take a look at what the Fluent message for the "field already |
| 42 | +declared" diagnostic looks like: |
| 43 | + |
| 44 | +```fluent |
| 45 | +typeck-field-already-declared = |
| 46 | + field `{$field_name}` is already declared |
| 47 | + .label = field already declared |
| 48 | + .previous-decl-label = `{$field_name}` first declared here |
49 | 49 | ```
|
50 | 50 |
|
51 |
| -The generated code draws attention to a number of features. First, we see that within the strings |
52 |
| -passed to each attribute, 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. |
| 51 | +`typeck-field-already-declared` is the `slug` from our example and is followed |
| 52 | +by the diagnostic message. |
56 | 53 |
|
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. |
| 54 | +Fluent is built around the idea of "asymmetric localization", which aims to |
| 55 | +decouple the expressiveness of translations from the grammar of the source |
| 56 | +language (English in rustc's case). Prior to translation, rustc's diagnostics |
| 57 | +relied heavily on interpolation to build the messages shown to the users. |
| 58 | +Interpolated strings are hard to translate because writing a natural-sounding |
| 59 | +translation might require more, less, or just different interpolation than the |
| 60 | +English string, all of which would require changes to the compiler's source |
| 61 | +code to support. |
64 | 62 |
|
65 |
| -## Attributes Listing |
| 63 | +As the compiler team gain more experience creating `SessionDiagnostic` structs |
| 64 | +that have all of the information necessary to be translated into different |
| 65 | +languages, this page will be updated with more guidance. For now, the [Project |
| 66 | +Fluent](https://projectfluent.org) documentation has excellent examples of |
| 67 | +translating messages into different locales and the information that needs to |
| 68 | +be provided by the code to do so. |
66 | 69 |
|
67 |
| -Below is a listing of all the currently-available attributes that `#[derive(SessionDiagnostic)]` |
68 |
| -understands: |
| 70 | +When adding or changing a diagnostic, you don't need to worry about the |
| 71 | +translations, only updating the original English message. All of rustc's |
| 72 | +English Fluent messages can be found in |
| 73 | +`/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl`. |
69 | 74 |
|
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. |
| 75 | +Every field of the `SessionDiagnostic` which does not have an annotation is |
| 76 | +available in Fluent messages as a variable, like `field_name` in the example |
| 77 | +above. |
79 | 78 |
|
| 79 | +Using the `#[primary_span]` attribute on a field (that has type `Span`) |
| 80 | +indicates the primary span of the diagnostic which will have the main message |
| 81 | +of the diagnostic. |
80 | 82 |
|
81 |
| -## Optional Diagnostic Attributes |
| 83 | +Diagnostics are more than just their primary message, they often include |
| 84 | +labels, notes, help messages and suggestions, all of which can also be |
| 85 | +specified on a `SessionDiagnostic`. |
82 | 86 |
|
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 | +`#[label]`, `#[help]` and `#[note]` can all be applied to fields which have the |
| 88 | +type `Span`. Applying any of these attributes will create the corresponding |
| 89 | +sub-diagnostic with that `Span`. These attributes will look for their |
| 90 | +diagnostic message in a Fluent attribute attached to the primary Fluent |
| 91 | +message. In our example, `#[label]` will look for |
| 92 | +`typeck-field-already-declared.label` (which has the message "field already |
| 93 | +declared"). If there is more than one sub-diagnostic of the same type, then |
| 94 | +these attributes can also take a value that is the attribute name to look for |
| 95 | +(e.g. `previous-decl-label` in our example). |
87 | 96 |
|
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 |
| - ... |
| 97 | +`#[help]` and `#[note]` can also be applied to the struct itself, in which case |
| 98 | +they work exactly like when applied to fields except the sub-diagnostic won't |
| 99 | +have a `Span`. |
| 100 | + |
| 101 | +Any attribute can also applied to an `Option<Span>` and will only emit a |
| 102 | +sub-diagnostic if the option is `Some(..)`. |
| 103 | + |
| 104 | +Suggestions can be emitted using one of four field attributes: |
| 105 | + |
| 106 | +- `#[suggestion(message = "...", code = "...")]` |
| 107 | +- `#[suggestion_hidden(message = "...", code = "...")]` |
| 108 | +- `#[suggestion_short(message = "...", code = "...")]` |
| 109 | +- `#[suggestion_verbose(message = "...", code = "...")]` |
| 110 | + |
| 111 | +Suggestions must be applied on either a `Span` field or a |
| 112 | +`(Span, MachineApplicability)` field. Similarly to other field attributes, |
| 113 | +`message` specifies the Fluent attribute with the message and defaults to |
| 114 | +`.suggestion`. `code` specifies the code that should be suggested as a |
| 115 | +replacement and is a format string (e.g. `{field_name}` would be replaced by |
| 116 | +the value of the `field_name` field of the struct), not a Fluent identifier. |
| 117 | + |
| 118 | +In the end, the `SessionDiagnostic` derive will generate an implementation of |
| 119 | +`SessionDiagnostic` that looks like the following: |
| 120 | + |
| 121 | +```rust,ignore |
| 122 | +impl SessionDiagnostic for FieldAlreadyDeclared { |
| 123 | + fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> { |
| 124 | + let mut diag = sess.struct_err_with_code( |
| 125 | + rustc_errors::DiagnosticMessage::fluent("typeck-field-already-declared"), |
| 126 | + rustc_errors::DiagnosticId::Error("E0124") |
| 127 | + ); |
| 128 | + diag.set_span(self.span); |
| 129 | + diag.span_label( |
| 130 | + self.span, |
| 131 | + rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "label") |
| 132 | + ); |
| 133 | + diag.span_label( |
| 134 | + self.prev_span, |
| 135 | + rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "previous-decl-label") |
| 136 | + ); |
| 137 | + diag |
| 138 | + } |
96 | 139 | }
|
97 | 140 | ```
|
| 141 | + |
| 142 | +Now that we've defined our diagnostic, how do we [use it]()? It's quite |
| 143 | +straightforward, just create an instance of the struct and pass it to |
| 144 | +`emit_err` (or `emit_warning`): |
| 145 | + |
| 146 | +```rust,ignore |
| 147 | +tcx.sess.emit_err(FieldAlreadyDeclared { |
| 148 | + field_name: f.ident, |
| 149 | + span: f.span, |
| 150 | + prev_span, |
| 151 | +}); |
| 152 | +``` |
| 153 | + |
| 154 | +## Reference |
| 155 | +`#[derive(SessionDiagnostic)]` supports the following attributes: |
| 156 | + |
| 157 | +- `#[error(code = "...", slug = "...")]` or `#[warning(code = "...", slug = "...")]` |
| 158 | + - _Applied to struct._ |
| 159 | + - _Mandatory_ |
| 160 | + - Defines the struct to be representing an error or a warning. |
| 161 | + - `code = "..."` |
| 162 | + - _Optional_ |
| 163 | + - Specifies the error code. |
| 164 | + - `slug = "..."` |
| 165 | + - _Mandatory_ |
| 166 | + - Uniquely identifies the diagnostic and corresponds to its Fluent message, |
| 167 | + mandatory. |
| 168 | +- `#[note]` or `#[note = "..."]` |
| 169 | + - _Applied to struct or `Span` fields._ |
| 170 | + - _Optional_ |
| 171 | + - Adds a note sub-diagnostic. |
| 172 | + - Value is the Fluent attribute (relative to the Fluent message specified by |
| 173 | + `slug`) for the note's message |
| 174 | + - Defaults to `note`. |
| 175 | + - If applied to a `Span` field, creates a spanned note. |
| 176 | +- `#[help]` or `#[help = "..."]` |
| 177 | + - _Applied to struct or `Span` fields._ |
| 178 | + - _Optional_ |
| 179 | + - Adds a help sub-diagnostic. |
| 180 | + - Value is the Fluent attribute (relative to the Fluent message specified by |
| 181 | + `slug`) for the help's message |
| 182 | + - Defaults to `help`. |
| 183 | + - If applied to a `Span` field, creates a spanned help. |
| 184 | +- `#[label]` or `#[label = "..."]` |
| 185 | + - _Applied to `Span` fields._ |
| 186 | + - _Optional_ |
| 187 | + - Adds a label sub-diagnostic. |
| 188 | + - Value is the Fluent attribute (relative to the Fluent message specified by |
| 189 | + `slug`) for the label's message |
| 190 | + - Defaults to `label`. |
| 191 | +- `#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...")]` |
| 192 | + - _Applied to `(Span, MachineApplicability)` or `Span` fields._ |
| 193 | + - _Optional_ |
| 194 | + - Adds a suggestion sub-diagnostic. |
| 195 | + - `message = "..."` |
| 196 | + - _Mandatory_ |
| 197 | + - Value is the Fluent attribute (relative to the Fluent message specified |
| 198 | + by `slug`) for the suggestion's message |
| 199 | + - Defaults to `suggestion`. |
| 200 | + - `code = "..."` |
| 201 | + - _Optional_ |
| 202 | + - Value is a format string indicating the code to be suggested as a |
| 203 | + replacement. |
0 commit comments