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:
parent
2273fee776
commit
17edb681ff
|
|
@ -38,3 +38,4 @@ warning-policy = "error"
|
|||
|
||||
[output.html.redirect]
|
||||
"/compiletest.html" = "tests/compiletest.html"
|
||||
"/diagnostics/sessiondiagnostic.html" = "diagnostics/diagnostic-structs.html"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue