Skip to content

Commit 75edd1e

Browse files
committed
sessiondiagnostic: translation
Updates documentation on `#[derive(SessionDiagnostic)]` now that it can be used for translatable diagnostics. Signed-off-by: David Wood <[email protected]>
1 parent 155126b commit 75edd1e

File tree

1 file changed

+178
-72
lines changed

1 file changed

+178
-72
lines changed

src/diagnostics/sessiondiagnostic.md

+178-72
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,203 @@
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.
26

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

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

1312
```rust,ignore
1413
#[derive(SessionDiagnostic)]
15-
#[error = "E0124"]
14+
#[error(code = "E0124", slug = "typeck-field-already-declared")]
1615
pub struct FieldAlreadyDeclared {
1716
pub field_name: Ident,
18-
#[message = "field `{field_name}` is already declared"]
19-
#[label = "field already declared"]
17+
#[primary_span]
18+
#[label]
2019
pub span: Span,
21-
#[label = "`{field_name}` first declared here"]
20+
#[label = "previous-decl-label"]
2221
pub prev_span: Span,
2322
}
24-
// ...
25-
tcx.sess.emit_err(FieldAlreadyDeclared {
26-
field_name: f.ident,
27-
span: f.span,
28-
prev_span,
29-
});
3023
```
3124

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

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
4949
```
5050

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

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

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

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`.
6974

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

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

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`.
8286

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).
8796

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+
}
96139
}
97140
```
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

Comments
 (0)