Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new chapter with examples of diagnostic translation PRs #1621

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
- [Error codes](./diagnostics/error-codes.md)
- [Diagnostic items](./diagnostics/diagnostic-items.md)
- [`ErrorGuaranteed`](./diagnostics/error-guaranteed.md)
- [Making diagnostics translatable](diagnostics/making-diagnostics-translatable.md)

# MIR to Binaries

Expand Down
197 changes: 197 additions & 0 deletions src/diagnostics/making-diagnostics-translatable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Making diagnostics translatable

<!-- toc -->

There is an ongoing effort to make diagnostics translatable,
and the coordination happens [on GitHub].

This is intended to be a gentle guide to help with this effort,
with the use of examples.

> A more detailed explanation can be found in [the announcement of the initiative].
> However,
> note that some of the important details are now outdated.
>
> Reference documentation can be found in these chapters:
> - [Diagnostic structs](diagnostic-structs.md)
> - [Translation system](translation.md)

## General comments

For a single diagnostic, 3 files need to be modified:

- One file where the diagnostic is emitted,
typically by calling [Session::struct_span_err].
- One file where the type representing the diagnostic,
typically a `struct`, would be added.
tshepang marked this conversation as resolved.
Show resolved Hide resolved
This would be in the `errors` module of the relevant rustc crate.
tshepang marked this conversation as resolved.
Show resolved Hide resolved
- One file where the actual text of the diagnostic is located,
located in a file named `locales/en-US.ftl`,
relative to the root path of the relevant rustc crate.

## Simple example

*This uses [issue #108727] for demonstration*

Suppose you have the following code:

```rust
ecx.struct_span_err(span, "proc-macro derive produced unparseable tokens").emit();
```

> Note that `ecx.struct_span_err` is the [Session::struct_span_err].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Note that `ecx.struct_span_err` is the [Session::struct_span_err].
> Note that `ecx.struct_span_err` calls [Session::struct_span_err].

This should also probably link to ecx.struct_span_err (https://doc.rust-lang.org/nightly/nightly-rustc/rustc_expand/base/struct.ExtCtxt.html#method.struct_span_err) so that users can attest this relationship themselves.

Copy link
Member Author

@tshepang tshepang Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that should have been ultimately calls...


Referring to the [general comments](#general-comments) section above,
follow these changes:

- Replace the code above with this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Replace the code above with this:
- Replace the calls to `struct_span_err` and `emit` with:


```rust
ecx.sess.emit_err(errors::ProcMacroDeriveTokens { span });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should point to emit_err in rustdocs. Also, useful to point out that if the user wants to make a diagnostic, but not emit it, they can use create_err.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tough balance between adding useful info and avoiding the overwhelm... I should find a way to fit create_err in the reference

```

- Create the above type, `errors::ProcMacroDeriveTokens`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Create the above type, `errors::ProcMacroDeriveTokens`,
- Add a diagnostic struct, `errors::ProcMacroDeriveTokens`,

in `src/errors.rs` (relative to crate directory root):

```rust
#[derive(Diagnostic)]
#[diag(expand_proc_macro_derive_tokens)]
pub struct ProcMacroDeriveTokens {
#[primary_span]
Comment on lines +57 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be useful to motivate the three important parts of this:

  • derive(Diagnostic)
  • diag(FLUENT_SLUG)
  • and primary_span

Maybe why we need these three components, what other components are common, etc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I more wanted to have people see the pattern, than do wall-of-text which can overwhelm. The more complete details are in the reference chapters (earlier in the guide).

pub span: Span,
}
```

- Create the actual text of the diagnostic in `locales/en-US.ftl`
(also relative to crate directory root):

```fluent
expand_proc_macro_derive_tokens =
proc-macro derive produced unparseable tokens
```

Once that is done, a PR may be submitted,
and the following comment on the PR page will label the PR accordingly,
as well as alert the right people to review it:

```
@rustbot label +A-translation
r? rust-lang/diagnostics
```

## Example with variable interpolation

*This uses [issue #108436] for demonstration*

Suppose you have the following code:

```rust
let mut err = ecx.struct_span_err(span, "proc macro panicked");
if let Some(s) = e.as_str() {
err.help(&format!("message: {}", s));
}
err.emit()
```

> Note that `ecx.struct_span_err` indirectly calls [Session::struct_span_err].

- Replace the code above with this:

```rust
ecx.sess.emit_err(errors::ProcMacroPanicked {
span,
message: e
.as_str()
.map(|message| errors::ProcMacroPanickedHelp { message: message.into() }),
})
```

- Create the above type, `errors::ProcMacroPanickedHelp`,
in `src/errors.rs` (relative to crate directory root):

```rust
#[derive(Diagnostic)]
#[diag(expand_proc_macro_panicked)]
pub(crate) struct ProcMacroPanicked {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub message: Option<ProcMacroPanickedHelp>,
}

#[derive(Subdiagnostic)]
#[help(expand_help)]
pub(crate) struct ProcMacroPanickedHelp {
pub message: String,
}
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
```

- Create the actual text of the diagnostic in `locales/en-US.ftl`
(also relative to crate directory root):

```fluent
expand_proc_macro_panicked =
proc macro panicked
.help = message: {$message}
```

## Example with a macro, `struct_span_err!`

*This uses [issue #108373] for demonstration*

Suppose you have the following code:

```rust
let mut diag = struct_span_err!(
tcx.sess,
generics_where_clauses_span.unwrap_or(main_span),
E0646,
"`main` function is not allowed to have a `where` clause"
);
if let Some(generics_where_clauses_span) = generics_where_clauses_span {
diag.span_label(generics_where_clauses_span, "`main` cannot have a `where` clause");
}
diag.emit();
```

> Note that `struct_span_err!` ultimately calls [Session::struct_span_err_with_code].

- Replace the code above with this:

```rust
tcx.sess.emit_err(errors::WhereClauseOnMain {
span: generics_where_clauses_span.unwrap_or(main_span),
generics_span: generics_where_clauses_span,
});
```

- Create the above type, `errors::WhereClauseOnMain`,
in `src/errors.rs` (relative to crate directory root):

```rust
#[derive(Diagnostic)]
#[diag(hir_analysis_where_clause_on_main, code = "E0646")]
pub(crate) struct WhereClauseOnMain {
#[primary_span]
pub span: Span,
#[label]
pub generics_span: Option<Span>,
}
```

- Create the actual text of the diagnostic in `locales/en-US.ftl`
(also relative to crate directory root):

```fluent
hir_analysis_where_clause_on_main =
`main` function is not allowed to have a `where` clause
.label = `main` cannot have a `where` clause
```

[Session::struct_span_err]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_session/session/struct.Session.html#method.struct_span_err
[Session::struct_span_err_with_code]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_session/session/struct.Session.html#method.struct_span_err_with_code
[the announcement of the initiative]: https://blog.rust-lang.org/inside-rust/2022/08/16/diagnostic-effort.html#manually-implementing-sessiondiagnostic
[on GitHub]: https://github.com/rust-lang/rust/issues/100717
[issue #108373]: https://github.com/rust-lang/rust/pull/108373
[issue #108436]: https://github.com/rust-lang/rust/pull/108436
[issue #108727]: https://github.com/rust-lang/rust/pull/108727