diagnostics: add translation documentation

- Add documentation on translation infrastructure and use of
  `SessionSubdiagnostic`.
- Update diagnostic examples on other pages to be translatable since
  this is preferred.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-06-06 14:07:55 +01:00 committed by Tshepang Mbambo
parent 2273fee776
commit 17edb681ff
7 changed files with 659 additions and 246 deletions

View File

@ -38,3 +38,4 @@ warning-policy = "error"
[output.html.redirect]
"/compiletest.html" = "tests/compiletest.html"
"/diagnostics/sessiondiagnostic.html" = "diagnostics/diagnostic-structs.html"

View File

@ -138,10 +138,11 @@
- [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)
- [Diagnostic and subdiagnostic structs](./diagnostics/diagnostic-structs.md)
- [Translation](./diagnostics/translation.md)
- [`LintStore`](./diagnostics/lintstore.md)
- [Diagnostic Codes](./diagnostics/diagnostic-codes.md)
- [Diagnostic Items](./diagnostics/diagnostic-items.md)
- [Diagnostic codes](./diagnostics/diagnostic-codes.md)
- [Diagnostic items](./diagnostics/diagnostic-items.md)
- [`ErrorGuaranteed`](./diagnostics/error-guaranteed.md)
# MIR to Binaries

View File

@ -139,10 +139,11 @@ use an error-level lint instead of a fixed error.
flag. That said, don't make it so terse that it's hard to understand.
- The word "illegal" is illegal. Prefer "invalid" or a more specific word
instead.
- Errors should document the span of code where they occur the
[`rustc_errors::diagnostic_builder::DiagnosticBuilder`][diagbuild] `span_*`
methods allow to easily do this. Also `note` other spans that have
contributed to the error if the span isn't too large.
- Errors should document the span of code where they occur (use
[`rustc_errors::diagnostic_builder::DiagnosticBuilder`][diagbuild]'s
`span_*` methods or a diagnostic struct's `#[primary_span]` to easily do
this). Also `note` other spans that have contributed to the error if the span
isn't too large.
- When emitting a message with span, try to reduce the span to the smallest
amount possible that still signifies the issue
- Try not to emit multiple error messages for the same error. This may require
@ -312,6 +313,15 @@ reporting errors.
[errors]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/index.html
Diagnostics can be implemented as types which implement the `SessionDiagnostic`
trait. This is preferred for new diagnostics as it enforces a separation
between diagnostic emitting logic and the main code paths. For less-complex
diagnostics, the `SessionDiagnostic` trait can be derived -- see [Diagnostic
structs][diagnostic-structs]. Within the trait implementation, the APIs
described below can be used as normal.
[diagnostic-structs]: ./diagnostics/diagnostic-structs.md
[`Session`][session] and [`ParseSess`][parsesses] have
methods (or fields with methods) that allow reporting errors. These methods
usually have names like `span_err` or `struct_span_err` or `span_warn`, etc...
@ -327,6 +337,12 @@ directly and ones that allow finer control over what to emit. For example,
[`struct_span_err`][strspanerr] instead returns a
[`DiagnosticBuilder`][diagbuild].
Most of these methods will accept strings, but it is recommended that typed
identifiers for translatable diagnostics be used for new diagnostics (see
[Translation][translation]).
[translation]: ./diagnostics/translation.md
`DiagnosticBuilder` allows you to add related notes and suggestions to an error
before emitting it by calling the [`emit`][emit] method. (Failing to either
emit or [cancel][cancel] a `DiagnosticBuilder` will result in an ICE.) See the
@ -340,30 +356,30 @@ emit or [cancel][cancel] a `DiagnosticBuilder` will result in an ICE.) See the
```rust,ignore
// Get a DiagnosticBuilder. This does _not_ emit an error yet.
let mut err = sess.struct_span_err(sp, "oh no! this is an error!");
let mut err = sess.struct_span_err(sp, fluent::example::example_error);
// In some cases, you might need to check if `sp` is generated by a macro to
// avoid printing weird errors about macro-generated code.
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
// Use the snippet to generate a suggested fix
err.span_suggestion(suggestion_sp, "try using a qux here", format!("qux {}", snippet));
err.span_suggestion(suggestion_sp, fluent::example::try_qux_suggestion, format!("qux {}", snippet));
} else {
// If we weren't able to generate a snippet, then emit a "help" message
// instead of a concrete "suggestion". In practice this is unlikely to be
// reached.
err.span_help(suggestion_sp, "you could use a qux here instead");
err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}
// emit the error
err.emit();
```
Alternatively, for less-complex diagnostics, the `SessionDiagnostic` derive
macro can be used -- see [Creating Errors With SessionDiagnostic][sessiondiagnostic].
[sessiondiagnostic]: ./diagnostics/sessiondiagnostic.md
```fluent
example-example-error = oh no! this is an error!
.try-qux-suggestion = try using a qux here
.qux-suggestion = you could use a qux here instead
```
## Suggestions
@ -405,17 +421,17 @@ apply them)
For example, to make our `qux` suggestion machine-applicable, we would do:
```rust,ignore
let mut err = sess.struct_span_err(sp, "oh no! this is an error!");
let mut err = sess.struct_span_err(sp, fluent::example::message);
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
err.span_suggestion(
suggestion_sp,
"try using a qux here",
fluent::example::try_qux_suggestion,
format!("qux {}", snippet),
Applicability::MachineApplicable,
);
} else {
err.span_help(suggestion_sp, "you could use a qux here instead");
err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}
err.emit();
@ -504,9 +520,9 @@ much faster to work on.
Every lint is implemented via a `struct` that implements the `LintPass` `trait`
(you can also implement one of the more specific lint pass traits, either
`EarlyLintPass` or `LateLintPass` depending on when is best for your lint to run).
The trait implementation allows you to check certain syntactic constructs
as the linter walks the AST. You can then choose to emit lints in a
`EarlyLintPass` or `LateLintPass` depending on when is best for your lint to run).
The trait implementation allows you to check certain syntactic constructs
as the linter walks the AST. You can then choose to emit lints in a
very similar way to compile errors.
You also declare the metadata of a particular lint via the `declare_lint!`
@ -557,13 +573,12 @@ impl EarlyLintPass for WhileTrue {
if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind {
if let ast::LitKind::Bool(true) = lit.kind {
if !lit.span.from_expansion() {
let msg = "denote infinite loops with `loop { ... }`";
let condition_span = cx.sess.source_map().guess_head_span(e.span);
cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
lint.build(msg)
lint.build(fluent::example::use_loop)
.span_suggestion_short(
condition_span,
"use `loop`",
fluent::example::suggestion,
"loop".to_owned(),
Applicability::MachineApplicable,
)
@ -577,6 +592,11 @@ impl EarlyLintPass for WhileTrue {
}
```
```fluent
example-use-loop = denote infinite loops with `loop {"{"} ... {"}"}`
.suggestion = use `loop`
```
### Edition-gated lints
Sometimes we want to change the behavior of a lint in a new edition. To do this,
@ -613,15 +633,15 @@ declare_lint! {
The use of the term `future-incompatible` within the compiler has a slightly
broader meaning than what rustc exposes to users of the compiler.
Inside rustc, future-incompatible lints are for signalling to the user that code they have
Inside rustc, future-incompatible lints are for signalling to the user that code they have
written may not compile in the future. In general, future-incompatible code
exists for two reasons:
* the user has written unsound code that the compiler mistakenly accepted. While
it is within Rust's backwards compatibility guarantees to fix the soundness hole
(breaking the user's code), the lint is there to warn the user that this will happen
in some upcoming version of rustc *regardless of which edition the code uses*. This is the
* the user has written unsound code that the compiler mistakenly accepted. While
it is within Rust's backwards compatibility guarantees to fix the soundness hole
(breaking the user's code), the lint is there to warn the user that this will happen
in some upcoming version of rustc *regardless of which edition the code uses*. This is the
meaning that rustc exclusively exposes to users as "future incompatible".
* the user has written code that will either no longer compiler *or* will change
* the user has written code that will either no longer compiler *or* will change
meaning in an upcoming *edition*. These are often called "edition lints" and can be
typically seen in the various "edition compatibility" lint groups (e.g., `rust_2021_compatibility`)
that are used to lint against code that will break if the user updates the crate's edition.
@ -644,11 +664,11 @@ declare_lint! {
Notice the `reason` field which describes why the future incompatible change is happening.
This will change the diagnostic message the user receives as well as determine which
lint groups the lint is added to. In the example above, the lint is an "edition lint"
(since it's "reason" is `EditionError`) signifying to the user that the use of anonymous
(since it's "reason" is `EditionError`) signifying to the user that the use of anonymous
parameters will no longer compile in Rust 2018 and beyond.
Inside [LintStore::register_lints][fi-lint-groupings], lints with `future_incompatible`
fields get placed into either edition-based lint groups (if their `reason` is tied to
Inside [LintStore::register_lints][fi-lint-groupings], lints with `future_incompatible`
fields get placed into either edition-based lint groups (if their `reason` is tied to
an edition) or into the `future_incompatibility` lint group.
[fi-lint-groupings]: https://github.com/rust-lang/rust/blob/51fd129ac12d5bfeca7d216c47b0e337bf13e0c2/compiler/rustc_lint/src/context.rs#L212-L237
@ -659,7 +679,7 @@ to support this.
### Renaming or removing a lint
If it is determined that a lint is either improperly named or no longer needed,
If it is determined that a lint is either improperly named or no longer needed,
the lint must be registered for renaming or removal, which will trigger a warning if a user tries
to use the old lint name. To declare a rename/remove, add a line with
[`store.register_renamed`] or [`store.register_removed`] to the code of the
@ -695,11 +715,11 @@ This defines the `nonstandard_style` group which turns on the listed lints. A
user can turn on these lints with a `!#[warn(nonstandard_style)]` attribute in
the source code, or by passing `-W nonstandard-style` on the command line.
Some lint groups are created automatically in `LintStore::register_lints`. For instance,
any lint declared with `FutureIncompatibleInfo` where the reason is
`FutureIncompatibilityReason::FutureReleaseError` (the default when
Some lint groups are created automatically in `LintStore::register_lints`. For instance,
any lint declared with `FutureIncompatibleInfo` where the reason is
`FutureIncompatibilityReason::FutureReleaseError` (the default when
`@future_incompatible` is used in `declare_lint!`), will be added to
the `future_incompatible` lint group. Editions also have their own lint groups
the `future_incompatible` lint group. Editions also have their own lint groups
(e.g., `rust_2021_compatibility`) automatically generated for any lints signaling
future-incompatible code that will break in the specified edition.

View File

@ -76,7 +76,7 @@ In rustc, diagnostic items are looked up via [`Symbol`]s from inside the
[`rustc_span::symbol::sym`] module. These can then be mapped to [`DefId`]s
using [`TyCtxt::get_diagnostic_item()`] or checked if they match a [`DefId`]
using [`TyCtxt::is_diagnostic_item()`]. When mapping from a diagnostic item to
a [`DefId`] the method will return a `Option<DefId>`. This can be `None` if
a [`DefId`], the method will return a `Option<DefId>`. This can be `None` if
either the symbol isn't a diagnostic item or the type is not registered, for
instance when compiling with `#[no_std]`. All following examples are based on
[`DefId`]s and their usage.

View File

@ -0,0 +1,359 @@
# Diagnostic and subdiagnostic structs
rustc has two diagnostic derives that can be used to create simple diagnostics,
which are recommended to be used when they are applicable:
`#[derive(SessionDiagnostic)]` and `#[derive(SessionSubdiagnostic)]`.
Diagnostics created with the derive macros can be translated into different
languages and each has a slug that uniquely identifies the diagnostic.
## `#[derive(SessionDiagnostic)]`
Instead of using the `DiagnosticBuilder` API to create and emit diagnostics,
the `SessionDiagnostic` derive can be used. `#[derive(SessionDiagnostic)]` is
only applicable for simple diagnostics that don't require much logic in
deciding whether or not to add additional subdiagnostics.
Consider the [definition][defn] of the "field already declared" diagnostic
shown below:
```rust,ignore
#[derive(SessionDiagnostic)]
#[error(code = "E0124", slug = "typeck-field-already-declared")]
pub struct FieldAlreadyDeclared {
pub field_name: Ident,
#[primary_span]
#[label]
pub span: Span,
#[label = "previous-decl-label"]
pub prev_span: Span,
}
```
`SessionDiagnostic` can only be applied to structs. Every `SessionDiagnostic`
has to have one attribute applied to the struct itself: either `#[error(..)]`
for defining errors, or `#[warning(..)]` for defining warnings.
If an error has an error code (e.g. "E0624"), then that can be specified using
the `code` sub-attribute. Specifying a `code` isn't mandatory, but if you are
porting a diagnostic that uses `DiagnosticBuilder` to use `SessionDiagnostic`
then you should keep the code if there was one.
Both `#[error(..)]` and `#[warning(..)]` must set a value for the `slug`
sub-attribute. `slug` uniquely identifies the diagnostic and is also how the
compiler knows what error message to emit (in the default locale of the
compiler, or in the locale requested by the user). See [translation
documentation](./translation.md) to learn more about how translatable error
messages are written.
In our example, the Fluent message for the "field already declared" diagnostic
looks like this:
```fluent
typeck-field-already-declared =
field `{$field_name}` is already declared
.label = field already declared
.previous-decl-label = `{$field_name}` first declared here
```
`typeck-field-already-declared` is the `slug` from our example and is followed
by the diagnostic message.
Every field of the `SessionDiagnostic` which does not have an annotation is
available in Fluent messages as a variable, like `field_name` in the example
above. Fields can be annotated `#[skip_arg]` if this is undesired.
Using the `#[primary_span]` attribute on a field (that has type `Span`)
indicates the primary span of the diagnostic which will have the main message
of the diagnostic.
Diagnostics are more than just their primary message, they often include
labels, notes, help messages and suggestions, all of which can also be
specified on a `SessionDiagnostic`.
`#[label]`, `#[help]` and `#[note]` can all be applied to fields which have the
type `Span`. Applying any of these attributes will create the corresponding
subdiagnostic with that `Span`. These attributes will look for their
diagnostic message in a Fluent attribute attached to the primary Fluent
message. In our example, `#[label]` will look for
`typeck-field-already-declared.label` (which has the message "field already
declared"). If there is more than one subdiagnostic of the same type, then
these attributes can also take a value that is the attribute name to look for
(e.g. `previous-decl-label` in our example).
Other types have special behavior when used in a `SessionDiagnostic` derive:
- Any attribute applied to an `Option<T>` and will only emit a
subdiagnostic if the option is `Some(..)`.
- Any attribute applied to a `Vec<T>` will be repeated for each element of the
vector.
`#[help]` and `#[note]` can also be applied to the struct itself, in which case
they work exactly like when applied to fields except the subdiagnostic won't
have a `Span`. These attributes can also be applied to fields of type `()` for
the same effect, which when combined with the `Option` type can be used to
represent optional `#[note]`/`#[help]` subdiagnostics.
Suggestions can be emitted using one of four field attributes:
- `#[suggestion(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_hidden(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_short(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_verbose(message = "...", code = "...", applicability = "...")]`
Suggestions must be applied on either a `Span` field or a `(Span,
MachineApplicability)` field. Similarly to other field attributes, `message`
specifies the Fluent attribute with the message and defaults to `.suggestion`.
`code` specifies the code that should be suggested as a replacement and is a
format string (e.g. `{field_name}` would be replaced by the value of the
`field_name` field of the struct), not a Fluent identifier. `applicability` can
be used to specify the applicability in the attribute, it cannot be used when
the field's type contains an `Applicability`.
In the end, the `SessionDiagnostic` derive will generate an implementation of
`SessionDiagnostic` that looks like the following:
```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::DiagnosticMessage::fluent("typeck-field-already-declared"),
rustc_errors::DiagnosticId::Error("E0124")
);
diag.set_span(self.span);
diag.span_label(
self.span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "label")
);
diag.span_label(
self.prev_span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "previous-decl-label")
);
diag
}
}
```
Now that we've defined our diagnostic, how do we [use it][use]? It's quite
straightforward, just create an instance of the struct and pass it to
`emit_err` (or `emit_warning`):
```rust,ignore
tcx.sess.emit_err(FieldAlreadyDeclared {
field_name: f.ident,
span: f.span,
prev_span,
});
```
### Reference
`#[derive(SessionDiagnostic)]` supports the following attributes:
- `#[error(code = "...", slug = "...")]` or `#[warning(code = "...", slug = "...")]`
- _Applied to struct._
- _Mandatory_
- Defines the struct to be representing an error or a warning.
- `code = "..."` (_Optional_)
- Specifies the error code.
- `slug = "..."` (_Mandatory_)
- Uniquely identifies the diagnostic and corresponds to its Fluent message,
mandatory.
- `#[note]` or `#[note = "..."]` (_Optional_)
- _Applied to struct or `Span`/`()` fields._
- Adds a note subdiagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the note's message
- Defaults to `note`.
- If applied to a `Span` field, creates a spanned note.
- `#[help]` or `#[help = "..."]` (_Optional_)
- _Applied to struct or `Span`/`()` fields._
- Adds a help subdiagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the help's message.
- Defaults to `help`.
- If applied to a `Span` field, creates a spanned help.
- `#[label]` or `#[label = "..."]` (_Optional_)
- _Applied to `Span` fields._
- Adds a label subdiagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the label's message.
- Defaults to `label`.
- `#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...", applicability = "...")]`
(_Optional_)
- _Applied to `(Span, MachineApplicability)` or `Span` fields._
- Adds a suggestion subdiagnostic.
- `message = "..."` (_Mandatory_)
- Value is the Fluent attribute (relative to the Fluent message specified
by `slug`) for the suggestion's message.
- Defaults to `suggestion`.
- `code = "..."` (_Mandatory_)
- Value is a format string indicating the code to be suggested as a
replacement.
- `applicability = "..."` (_Optional_)
- String which must be one of `machine-applicable`, `maybe-incorrect`,
`has-placeholders` or `unspecified`.
- `#[subdiagnostic]`
- _Applied to a type that implements `AddToDiagnostic` (from
`#[derive(SessionSubdiagnostic)]`)._
- Adds the subdiagnostic represented by the subdiagnostic struct.
- `#[primary_span]` (_Optional_)
- _Applied to `Span` fields._
- Indicates the primary span of the diagnostic.
- `#[skip_arg]` (_Optional_)
- _Applied to any field._
- Prevents the field from being provided as a diagnostic argument.
## `#[derive(SessionSubdiagnostic)]`
It is common in the compiler to write a function that conditionally adds a
specific subdiagnostic to an error if it is applicable. Oftentimes these
subdiagnostics could be represented using a diagnostic struct even if the
overall diagnostic could not. In this circumstance, the `SessionSubdiagnostic`
derive can be used to represent a partial diagnostic (e.g a note, label, help or
suggestion) as a struct.
Consider the [definition][subdiag_defn] of the "expected return type" label
shown below:
```rust
#[derive(SessionSubdiagnostic)]
pub enum ExpectedReturnTypeLabel<'tcx> {
#[label(slug = "typeck-expected-default-return-type")]
Unit {
#[primary_span]
span: Span,
},
#[label(slug = "typeck-expected-return-type")]
Other {
#[primary_span]
span: Span,
expected: Ty<'tcx>,
},
}
```
Unlike `SessionDiagnostic`, `SessionSubdiagnostic` can be applied to structs or
enums. Attributes that are placed on the type for structs are placed on each
variants for enums (or vice versa). Each `SessionSubdiagnostic` should have one
attribute applied to the struct or each variant, one of:
- `#[label(..)]` for defining a label
- `#[note(..)]` for defining a note
- `#[help(..)]` for defining a help
- `#[suggestion{,_hidden,_short,_verbose}(..)]` for defining a suggestion
All of the above must have a value set for the `slug` sub-attribute. `slug`
uniquely identifies the diagnostic and is also how the compiler knows what
error message to emit (in the default locale of the compiler, or in the locale
requested by the user). See [translation documentation](./translation.md) to
learn more about how translatable error messages are written.
In our example, the Fluent message for the "expected return type" label
looks like this:
```fluent
typeck-expected-default-return-type = expected `()` because of default return type
typeck-expected-return-type = expected `{$expected}` because of return type
```
Using the `#[primary_span]` attribute on a field (with type `Span`) will denote
the primary span of the subdiagnostic. A primary span is only necessary for a
label or suggestion, which can not be spanless.
Every field of the type/variant which does not have an annotation is available
in Fluent messages as a variable. Fields can be annotated `#[skip_arg]` if this
is undesired.
Like `SessionDiagnostic`, `SessionSubdiagnostic` supports `Option<T>` and
`Vec<T>` fields.
Suggestions can be emitted using one of four attributes on the type/variant:
- `#[suggestion(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_hidden(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_short(message = "...", code = "...", applicability = "...")]`
- `#[suggestion_verbose(message = "...", code = "...", applicability = "...")]`
Suggestions require `#[primary_span]` be set on a field and can have the
following sub-attributes:
- `message` specifies the Fluent attribute with the message and defaults to
`.suggestion`.
- `code` specifies the code that should be suggested as a replacement and is a
format string (e.g. `{field_name}` would be replaced by the value of the
`field_name` field of the struct), not a Fluent identifier.
- `applicability` can be used to specify the applicability in the attribute, it
cannot be used when the field's type contains an `Applicability`.
Applicabilities can also be specified as a field (of type `Applicability`)
using the `#[applicability]` attribute.
In the end, the `SessionSubdiagnostic` derive will generate an implementation
of `SessionSubdiagnostic` that looks like the following:
```rust
impl<'tcx> AddToDiagnostic for ExpectedReturnTypeLabel<'tcx> {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
use rustc_errors::{Applicability, IntoDiagnosticArg};
match self {
ExpectedReturnTypeLabel::Unit { span } => {
diag.span_label(span, DiagnosticMessage::fluent("typeck-expected-default-return-type"))
}
ExpectedReturnTypeLabel::Other { span, expected } => {
diag.set_arg("expected", expected);
diag.span_label(span, DiagnosticMessage::fluent("typeck-expected-return-type"))
}
}
}
}
```
Once defined, a subdiagnostic can be used by passing it to the `subdiagnostic`
function ([example][subdiag_use_1] and [example][subdiag_use_2]) on a
diagnostic or by assigning it to a `#[subdiagnostic]`-annotated field of a
diagnostic struct.
### Reference
`#[derive(SessionSubdiagnostic)]` supports the following attributes:
- `#[label(slug = "...")]`, `#[help(slug = "...")]` or `#[note(slug = "...")]`
- _Applied to struct or enum variant. Mutually exclusive with struct/enum variant attributes._
- _Mandatory_
- Defines the type to be representing a label, help or note.
- `slug = "..."` (_Mandatory_)
- Uniquely identifies the diagnostic and corresponds to its Fluent message,
mandatory.
- `#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...", applicability = "...")]`
- _Applied to struct or enum variant. Mutually exclusive with struct/enum variant attributes._
- _Mandatory_
- Defines the type to be representing a suggestion.
- `message = "..."` (_Mandatory_)
- Value is the Fluent attribute (relative to the Fluent message specified
by `slug`) for the suggestion's message.
- Defaults to `suggestion`.
- `code = "..."` (_Mandatory_)
- Value is a format string indicating the code to be suggested as a
replacement.
- `applicability = "..."` (_Optional_)
- _Mutually exclusive with `#[applicability]` on a field._
- Value is the applicability of the suggestion.
- String which must be one of:
- `machine-applicable`
- `maybe-incorrect`
- `has-placeholders`
- `unspecified`
- `#[primary_span]` (_Mandatory_ for labels and suggestions; _optional_ otherwise)
- _Applied to `Span` fields._
- Indicates the primary span of the subdiagnostic.
- `#[applicability]` (_Optional_; only applicable to suggestions)
- _Applied to `Applicability` fields._
- Indicates the applicability of the suggestion.
- `#[skip_arg]` (_Optional_)
- _Applied to any field._
- Prevents the field from being provided as a diagnostic argument.
[defn]: https://github.com/rust-lang/rust/blob/bbe9d27b8ff36da56638aa43d6d0cdfdf89a4e57/compiler/rustc_typeck/src/errors.rs#L65-L74
[use]: https://github.com/rust-lang/rust/blob/eb82facb1626166188d49599a3313fc95201f556/compiler/rustc_typeck/src/collect.rs#L981-L985
[subdiag_defn]: https://github.com/rust-lang/rust/blob/e70c60d34b9783a2fd3171d88d248c2e0ec8ecdd/compiler/rustc_typeck/src/errors.rs#L220-L233
[subdiag_use_1]: https://github.com/rust-lang/rust/blob/e70c60d34b9783a2fd3171d88d248c2e0ec8ecdd/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs#L556-L560
[subdiag_use_2]: https://github.com/rust-lang/rust/blob/e70c60d34b9783a2fd3171d88d248c2e0ec8ecdd/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs#L575-L579

View File

@ -1,207 +0,0 @@
# Creating translatable errors using `SessionDiagnostic`
The `SessionDiagnostic` derive macro is the recommended way to create
diagnostics. Diagnostics created with the derive macro can be translated into
different languages and each have a slug that uniquely identifies the
diagnostic.
Instead of using the `DiagnosticBuilder` API to create and emit diagnostics,
the `SessionDiagnostic` derive macro is applied to structs.
The [definition][defn] of the "field already declared" diagnostic is shown
below.
```rust,ignore
#[derive(SessionDiagnostic)]
#[error(code = "E0124", slug = "typeck-field-already-declared")]
pub struct FieldAlreadyDeclared {
pub field_name: Ident,
#[primary_span]
#[label]
pub span: Span,
#[label = "previous-decl-label"]
pub prev_span: Span,
}
```
Every `SessionDiagnostic` has to have one attribute applied to the struct
itself: either `#[error(..)]` for defining errors, or `#[warning(..)]` for
defining warnings.
If an error has an error code (e.g. "E0624"), then that can be specified using
the `code` sub-attribute. Specifying a `code` isn't mandatory, but if you are
porting a diagnostic that uses `DiagnosticBuilder` to use `SessionDiagnostic`
then you should keep the code if there was one.
Both `#[error(..)]` and `#[warning(..)]` must set a value for the `slug`
sub-attribute. `slug` uniquely identifies the diagnostic and is also how the
compiler knows what error message to emit (in the default locale of the
compiler, or in the locale requested by the user).
rustc uses [Fluent](https://projectfluent.org) to handle the intricacies of
translation. Each diagnostic's `slug` is actually an identifier for a *Fluent
message*. Let's take a look at what the Fluent message for the "field already
declared" diagnostic looks like:
```fluent
typeck-field-already-declared =
field `{$field_name}` is already declared
.label = field already declared
.previous-decl-label = `{$field_name}` first declared here
```
`typeck-field-already-declared` is the `slug` from our example and is followed
by the diagnostic message.
Fluent is built around the idea of "asymmetric localization", which aims to
decouple the expressiveness of translations from the grammar of the source
language (English in rustc's case). Prior to translation, rustc's diagnostics
relied heavily on interpolation to build the messages shown to the users.
Interpolated strings are hard to translate because writing a natural-sounding
translation might require more, less, or just different interpolation than the
English string, all of which would require changes to the compiler's source
code to support.
As the compiler team gain more experience creating `SessionDiagnostic` structs
that have all of the information necessary to be translated into different
languages, this page will be updated with more guidance. For now, the [Project
Fluent](https://projectfluent.org) documentation has excellent examples of
translating messages into different locales and the information that needs to
be provided by the code to do so.
When adding or changing a diagnostic, you don't need to worry about the
translations, only updating the original English message. All of rustc's
English Fluent messages can be found in
`/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl`.
Every field of the `SessionDiagnostic` which does not have an annotation is
available in Fluent messages as a variable, like `field_name` in the example
above.
Using the `#[primary_span]` attribute on a field (that has type `Span`)
indicates the primary span of the diagnostic which will have the main message
of the diagnostic.
Diagnostics are more than just their primary message, they often include
labels, notes, help messages and suggestions, all of which can also be
specified on a `SessionDiagnostic`.
`#[label]`, `#[help]` and `#[note]` can all be applied to fields which have the
type `Span`. Applying any of these attributes will create the corresponding
sub-diagnostic with that `Span`. These attributes will look for their
diagnostic message in a Fluent attribute attached to the primary Fluent
message. In our example, `#[label]` will look for
`typeck-field-already-declared.label` (which has the message "field already
declared"). If there is more than one sub-diagnostic of the same type, then
these attributes can also take a value that is the attribute name to look for
(e.g. `previous-decl-label` in our example).
`#[help]` and `#[note]` can also be applied to the struct itself, in which case
they work exactly like when applied to fields except the sub-diagnostic won't
have a `Span`.
Any attribute can also be applied to an `Option<Span>` and will only emit a
sub-diagnostic if the option is `Some(..)`.
Suggestions can be emitted using one of four field attributes:
- `#[suggestion(message = "...", code = "...")]`
- `#[suggestion_hidden(message = "...", code = "...")]`
- `#[suggestion_short(message = "...", code = "...")]`
- `#[suggestion_verbose(message = "...", code = "...")]`
Suggestions must be applied on either a `Span` field or a
`(Span, MachineApplicability)` field. Similarly to other field attributes,
`message` specifies the Fluent attribute with the message and defaults to
`.suggestion`. `code` specifies the code that should be suggested as a
replacement and is a format string (e.g. `{field_name}` would be replaced by
the value of the `field_name` field of the struct), not a Fluent identifier.
In the end, the `SessionDiagnostic` derive will generate an implementation of
`SessionDiagnostic` that looks like the following:
```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::DiagnosticMessage::fluent("typeck-field-already-declared"),
rustc_errors::DiagnosticId::Error("E0124")
);
diag.set_span(self.span);
diag.span_label(
self.span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "label")
);
diag.span_label(
self.prev_span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "previous-decl-label")
);
diag
}
}
```
Now that we've defined our diagnostic, how do we [use it][use]? It's quite
straightforward, just create an instance of the struct and pass it to
`emit_err` (or `emit_warning`):
```rust,ignore
tcx.sess.emit_err(FieldAlreadyDeclared {
field_name: f.ident,
span: f.span,
prev_span,
});
```
## Reference
`#[derive(SessionDiagnostic)]` supports the following attributes:
- `#[error(code = "...", slug = "...")]` or `#[warning(code = "...", slug = "...")]`
- _Applied to struct._
- _Mandatory_
- Defines the struct to be representing an error or a warning.
- `code = "..."`
- _Optional_
- Specifies the error code.
- `slug = "..."`
- _Mandatory_
- Uniquely identifies the diagnostic and corresponds to its Fluent message,
mandatory.
- `#[note]` or `#[note = "..."]`
- _Applied to struct or `Span` fields._
- _Optional_
- Adds a note sub-diagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the note's message
- Defaults to `note`.
- If applied to a `Span` field, creates a spanned note.
- `#[help]` or `#[help = "..."]`
- _Applied to struct or `Span` fields._
- _Optional_
- Adds a help sub-diagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the help's message
- Defaults to `help`.
- If applied to a `Span` field, creates a spanned help.
- `#[label]` or `#[label = "..."]`
- _Applied to `Span` fields._
- _Optional_
- Adds a label sub-diagnostic.
- Value is the Fluent attribute (relative to the Fluent message specified by
`slug`) for the label's message
- Defaults to `label`.
- `#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...")]`
- _Applied to `(Span, MachineApplicability)` or `Span` fields._
- _Optional_
- Adds a suggestion sub-diagnostic.
- `message = "..."`
- _Mandatory_
- Value is the Fluent attribute (relative to the Fluent message specified
by `slug`) for the suggestion's message
- Defaults to `suggestion`.
- `code = "..."`
- _Optional_
- Value is a format string indicating the code to be suggested as a
replacement.
[defn]: https://github.com/rust-lang/rust/blob/bbe9d27b8ff36da56638aa43d6d0cdfdf89a4e57/compiler/rustc_typeck/src/errors.rs#L65-L74
[use]: https://github.com/rust-lang/rust/blob/eb82facb1626166188d49599a3313fc95201f556/compiler/rustc_typeck/src/collect.rs#L981-L985

View File

@ -0,0 +1,239 @@
# Translation
rustc's diagnostic infrastructure supports translatable diagnostics using
[Fluent].
## Writing translatable diagnostics
There are two ways of writing translatable diagnostics:
1. For simple diagnostics, using a diagnostic (or subdiagnostic) derive
("simple" diagnostics being those that don't require a lot of logic in
deciding to emit subdiagnostics and can therefore be represented as
diagnostic structs). See [the diagnostic and subdiagnostic structs
documentation](./diagnostic-structs.md).
2. Using typed identifiers with `DiagnosticBuilder` APIs (in
`SessionDiagnostic` implementations).
When adding or changing a translatable diagnostic, you don't need to worry
about the translations, only updating the original English message. Currently,
each crate which defines translatable diagnostics has its own Fluent resource,
such as `parser.ftl` or `typeck.ftl`.
## Fluent
Fluent is built around the idea of "asymmetric localization", which aims to
decouple the expressiveness of translations from the grammar of the source
language (English in rustc's case). Prior to translation, rustc's diagnostics
relied heavily on interpolation to build the messages shown to the users.
Interpolated strings are hard to translate because writing a natural-sounding
translation might require more, less, or just different interpolation than the
English string, all of which would require changes to the compiler's source
code to support.
Diagnostic messages are defined in Fluent resources. A combined set of Fluent
resources for a given locale (e.g. `en-US`) is known as Fluent bundle.
```fluent
typeck-address-of-temporary-taken = cannot take address of a temporary
```
In the above example, `typeck-address-of-temporary-taken` is the identifier for
a Fluent message and corresponds to the diagnostic message in English. Other
Fluent resources can be written which would correspond to a message in another
language. Each diagnostic therefore has at least one Fluent message.
```fluent
typeck-address-of-temporary-taken = cannot take address of a temporary
.label = temporary value
```
By convention, diagnostic messages for subdiagnostics are specified as
"attributes" on Fluent messages (additional related messages, denoted by the
`.<attribute-name>` syntax). In the above example, `label` is an attribute of
`typeck-address-of-temporary-taken` which corresponds to the message for the
label added to this diagnostic.
Diagnostic messages often interpolate additional context into the message shown
to the user, such as the name of a type or of a variable. Additional context to
Fluent messages is provided as an "argument" to the diagnostic.
```fluent
typeck-struct-expr-non-exhaustive =
cannot create non-exhaustive {$what} using struct expression
```
In the above example, the Fluent message refers to an argument named `what`
which is expected to exist (how arguments are provided to diagnostics is
discussed in detail later).
You can consult the [Fluent] documentation for other usage examples of Fluent
and its syntax.
### Guidelines for writing translatable messages
For a message to be translatable into different languages, all of the
information required by any language must be provided to the diagnostic as an
argument (not just the information required in the English message).
As the compiler team gain more experience writing diagnostics that have all of
the information necessary to be translated into different languages, this page
will be updated with more guidance. For now, the [Fluent] documentation has
excellent examples of translating messages into different locales and the
information that needs to be provided by the code to do so.
### Compile-time validation and typed identifiers
rustc's Fluent resources for the default locale (`en-US`) are in the
[`compiler/rustc_error_messages/locales/en-US`] directory. Currently, each crate
which defines translatable diagnostics has its own Fluent resource, such as
`parser.ftl` or `typeck.ftl`.
rustc's `fluent_messages` macro performs compile-time validation of Fluent
resources and generates code to make it easier to refer to Fluent messages in
diagnostics.
Compile-time validation of Fluent resources will emit any parsing errors
from Fluent resources while building the compiler, preventing invalid Fluent
resources from causing panics in the compiler. Compile-time validation also
emits an error if multiple Fluent messages have the same identifier.
In `rustc_error_messages`, `fluent_messages` also generates a constant for each
Fluent message which can be used to refer to messages when emitting
diagnostics and guarantee that the message exists.
```rust
fluent_messages! {
typeck => "../locales/en-US/typeck.ftl",
}
```
For example, given the following Fluent...
```fluent
typeck-field-multiply-specified-in-initializer =
field `{$ident}` specified more than once
.label = used more than once
.label-previous-use = first use of `{$ident}`
```
...then the `fluent_messages` macro will generate:
```rust
pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[
include_str!("../locales/en-US/typeck.ftl"),
];
mod fluent_generated {
mod typeck {
pub const field_multiply_specified_in_initializer: DiagnosticMessage =
DiagnosticMessage::new("typeck-field-multiply-specified-in-initializer");
pub const label: SubdiagnosticMessage =
SubdiagnosticMessage::attr("label");
pub const label_previous_use: SubdiagnosticMessage =
SubdiagnosticMessage::attr("previous-use-label");
}
}
```
`rustc_error_messages::fluent_generated` is re-exported and primarily used as
`rustc_errors::fluent`.
```rust
use rustc_errors::fluent;
let mut err = sess.struct_span_err(span, fluent::typeck::field_multiply_specified_in_initializer);
err.span_label(span, fluent::typeck::label);
err.span_label(previous_use_span, fluent::typeck::previous_use_label);
err.emit();
```
When emitting a diagnostic, these constants can be used like shown above.
## Internals
Various parts of rustc's diagnostic internals are modified in order to support
translation.
### Messages
All of rustc's traditional diagnostic APIs (e.g. `struct_span_err` or `note`)
take any message that can be converted into a `DiagnosticMessage` (or
`SubdiagnosticMessage`).
[`rustc_error_messages::DiagnosticMessage`] can represent legacy non-translatable
diagnostic messages and translatable messages. Non-translatable messages are
just `String`s. Translatable messages are just a `&'static str` with the
identifier of the Fluent message (sometimes with an additional `&'static str`
with an attribute).
`DiagnosticMessage` never needs to be interacted with directly:
`DiagnosticMessage` constants are created for each diagnostic message in a
Fluent resource (described in more detail below), or `DiagnosticMessage`s will
either be created in the macro-generated code of a diagnostic derive.
`rustc_error_messages::SubdiagnosticMessage` is similar, it can correspond to a
legacy non-translatable diagnostic message or the name of an attribute to a
Fluent message. Translatable `SubdiagnosticMessage`s must be combined with a
`DiagnosticMessage` (using `DiagnosticMessage::with_subdiagnostic_message`) to
be emitted (an attribute name on its own is meaningless without a corresponding
message identifier, which is what `DiagnosticMessage` provides).
Both `DiagnosticMessage` and `SubdiagnosticMessage` implement `Into` for any
type that can be converted into a string, and converts these into
non-translatable diagnostics - this keeps all existing diagnostic calls
working.
### Arguments
Additional context for Fluent messages which are interpolated into message
contents needs to be provided to translatable diagnostics.
Diagnostics have a `set_arg` function that can be used to provide this
additional context to a diagnostic.
Arguments have both a name (e.g. "what" in the earlier example) and a value.
Argument values are represented using the `DiagnosticArgValue` type, which is
just a string or a number. rustc types can implement `IntoDiagnosticArg` with
conversion into a string or a number, common types like `Ty<'tcx>` already
have such implementations.
`set_arg` calls are handled transparently by diagnostic derives but need to be
added manually when using diagnostic builder APIs.
### Loading
rustc makes a distinction between the "fallback bundle" for `en-US` that is used
by default and when another locale is missing a message; and the primary fluent
bundle which is requested by the user.
Diagnostic emitters implement the `Emitter` trait which has two functions for
accessing the fallback and primary fluent bundles (`fallback_fluent_bundle` and
`fluent_bundle` respectively).
`Emitter` also has member functions with default implementations for performing
translation of a `DiagnosticMessage` using the results of
`fallback_fluent_bundle` and `fluent_bundle`.
All of the emitters in rustc load the fallback Fluent bundle lazily, only
reading Fluent resources and parsing them when an error message is first being
translated (for performance reasons - it doesn't make sense to do this if no
error is being emitted). `rustc_error_messages::fallback_fluent_bundle` returns
a `std::lazy::Lazy<FluentBundle>` which is provided to emitters and evaluated
in the first call to `Emitter::fallback_fluent_bundle`.
The primary Fluent bundle (for the user's desired locale) is expected to be
returned by `Emitter::fluent_bundle`. This bundle is used preferentially when
translating messages, the fallback bundle is only used if the primary bundle is
missing a message or not provided.
As of <!-- date: 2022-06 --> June 2022, there are no locale bundles
distributed with the compiler, but mechanisms are implemented for loading
bundles.
- `-Ztranslate-additional-ftl` can be used to load a specific resource as the
primary bundle for testing purposes.
- `-Ztranslate-lang` can be provided a language identifier (something like
`en-US`) and will load any Fluent resources found in
`$sysroot/share/locale/$locale/` directory (both the user provided
sysroot and any sysroot candidates).
Primary bundles are not currently loaded lazily and if requested will be loaded
at the start of compilation regardless of whether an error occurs. Lazily
loading primary bundles is possible if it can be assumed that loading a bundle
won't fail. Bundle loading can fail if a requested locale is missing, Fluent
files are malformed, or a message is duplicated in multiple resources.
[Fluent]: https://projectfluent.org
[`compiler/rustc_error_messages/locales/en-US`]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_error_messages/locales/en-US
[`rustc_error_messages::DiagnosticMessage`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_error_messages/enum.DiagnosticMessage.html