850 lines
37 KiB
Markdown
850 lines
37 KiB
Markdown
# Errors and Lints
|
||
|
||
A lot of effort has been put into making `rustc` have great error messages.
|
||
This chapter is about how to emit compile errors and lints from the compiler.
|
||
|
||
## Diagnostic structure
|
||
|
||
The main parts of a diagnostic error are the following:
|
||
|
||
```
|
||
error[E0000]: main error message
|
||
--> file.rs:LL:CC
|
||
|
|
||
LL | <code>
|
||
| -^^^^- secondary label
|
||
| |
|
||
| primary label
|
||
|
|
||
= note: note without a `Span`, created with `.note`
|
||
note: sub-diagnostic message for `.span_note`
|
||
--> file.rs:LL:CC
|
||
|
|
||
LL | more code
|
||
| ^^^^
|
||
```
|
||
|
||
- Description (`error`, `warning`, etc.).
|
||
- Code (for example, for "mismatched types", it is `E0308`). It helps
|
||
users get more information about the current error through an extended
|
||
description of the problem in the error code index.
|
||
- Message. It is the main description of the problem. It should be general and
|
||
able to stand on its own, so that it can make sense even in isolation.
|
||
- Diagnostic window. This contains several things:
|
||
- The path, line number and column of the beginning of the primary span.
|
||
- The users' affected code and its surroundings.
|
||
- Primary and secondary spans underlying the users' code. These spans can
|
||
optionally contain one or more labels.
|
||
- Primary spans should have enough text to describe the problem in such a
|
||
way that if it where the only thing being displayed (for example, in an
|
||
IDE) it would still make sense. Because it is "spatially aware" (it
|
||
points at the code), it can generally be more succinct than the error
|
||
message.
|
||
- If cluttered output can be foreseen in cases when multiple span labels
|
||
overlap, it is a good idea to tweak the output appropriately. For
|
||
example, the `if/else arms have incompatible types` error uses different
|
||
spans depending on whether the arms are all in the same line, if one of
|
||
the arms is empty and if none of those cases applies.
|
||
- Sub-diagnostics. Any error can have multiple sub-diagnostics that look
|
||
similar to the main part of the error. These are used for cases where the
|
||
order of the explanation might not correspond with the order of the code. If
|
||
the order of the explanation can be "order free", leveraging secondary labels
|
||
in the main diagnostic is preferred, as it is typically less verbose.
|
||
|
||
The text should be matter of fact and avoid capitalization and periods, unless
|
||
multiple sentences are _needed_:
|
||
|
||
```txt
|
||
error: the fobrulator needs to be krontrificated
|
||
```
|
||
|
||
When code or an identifier must appear in a message or label, it should be
|
||
surrounded with single acute accents \`.
|
||
|
||
### Error explanations
|
||
|
||
Some errors include long form descriptions. They may be viewed with the
|
||
`--explain` flag, or via the [error index]. Each explanation comes with an
|
||
example of how to trigger it and advice on how to fix it.
|
||
|
||
Please read [RFC 1567] for details on how to format and write long error
|
||
codes.
|
||
|
||
The descriptions are written in Markdown, and all of them are linked in the
|
||
[`rustc_error_codes`] crate.
|
||
|
||
As a general rule, give an error a code (with an associated explanation) if the
|
||
explanation would give more information than the error itself. A lot of the time
|
||
it's better to put all the information in the emitted error itself. However,
|
||
sometimes that would make the error verbose or there are too many possible
|
||
triggers to include useful information for all cases in the error, in which case
|
||
it's a good idea to add an explanation.[^estebank]
|
||
As always, if you are not sure, just ask your reviewer!
|
||
|
||
[^estebank]: This rule of thumb was suggested by **@estebank** [here][estebank-comment].
|
||
|
||
[`rustc_error_codes`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_error_codes/error_codes/index.html
|
||
[error index]: https://doc.rust-lang.org/error-index.html
|
||
[RFC 1567]: https://github.com/rust-lang/rfcs/blob/master/text/1567-long-error-codes-explanation-normalization.md
|
||
[estebank-comment]: https://github.com/rust-lang/rustc-dev-guide/pull/967#issuecomment-733218283
|
||
|
||
### Lints versus fixed diagnostics
|
||
|
||
Some messages are emitted via [lints](#lints), where the user can control the
|
||
level. Most diagnostics are hard-coded such that the user cannot control the
|
||
level.
|
||
|
||
Usually it is obvious whether a diagnostic should be "fixed" or a lint, but
|
||
there are some grey areas.
|
||
|
||
Here are a few examples:
|
||
|
||
- Borrow checker errors: these are fixed errors. The user cannot adjust the
|
||
level of these diagnostics to silence the borrow checker.
|
||
- Dead code: this is a lint. While the user probably doesn't want dead code in
|
||
their crate, making this a hard error would make refactoring and development
|
||
very painful.
|
||
- [safe_packed_borrows future compatibility warning][safe_packed_borrows]:
|
||
this is a silencable lint related to safety. It was judged that the making
|
||
this a hard (fixed) error would cause too much breakage, so instead a
|
||
warning is emitted that eventually will be turned into a hard error.
|
||
|
||
Hard-coded warnings (those using the `span_warn` methods) should be avoided
|
||
for normal code, preferring to use lints instead. Some cases, such as warnings
|
||
with CLI flags, will require the use of hard-coded warnings.
|
||
|
||
See the `deny` [lint level](#diagnostic-levels) below for guidelines when to
|
||
use an error-level lint instead of a fixed error.
|
||
|
||
[safe_packed_borrows]: https://github.com/rust-lang/rust/issues/46043
|
||
|
||
## Diagnostic output style guide
|
||
|
||
- Write in plain simple English. If your message, when shown on a – possibly
|
||
small – screen (which hasn't been cleaned for a while), cannot be understood
|
||
by a normal programmer, who just came out of bed after a night partying,
|
||
it's too complex.
|
||
- `Error`, `Warning`, `Note`, and `Help` messages start with a lowercase
|
||
letter and do not end with punctuation.
|
||
- Error messages should be succinct. Users will see these error messages many
|
||
times, and more verbose descriptions can be viewed with the `--explain`
|
||
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.
|
||
- 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
|
||
detecting duplicates.
|
||
- When the compiler has too little information for a specific error message,
|
||
consult with the compiler team to add new attributes for library code that
|
||
allow adding more information. For example see
|
||
[`#[rustc_on_unimplemented]`](#rustc_on_unimplemented). Use these
|
||
annotations when available!
|
||
- Keep in mind that Rust's learning curve is rather steep, and that the
|
||
compiler messages are an important learning tool.
|
||
- When talking about the compiler, call it `the compiler`, not `Rust` or
|
||
`rustc`.
|
||
|
||
### Lint naming
|
||
|
||
From [RFC 0344], lint names should be consistent, with the following
|
||
guidelines:
|
||
|
||
The basic rule is: the lint name should make sense when read as "allow
|
||
*lint-name*" or "allow *lint-name* items". For example, "allow
|
||
`deprecated` items" and "allow `dead_code`" makes sense, while "allow
|
||
`unsafe_block`" is ungrammatical (should be plural).
|
||
|
||
- Lint names should state the bad thing being checked for, e.g. `deprecated`,
|
||
so that `#[allow(deprecated)]` (items) reads correctly. Thus `ctypes` is not
|
||
an appropriate name; `improper_ctypes` is.
|
||
|
||
- Lints that apply to arbitrary items (like the stability lints) should just
|
||
mention what they check for: use `deprecated` rather than
|
||
`deprecated_items`. This keeps lint names short. (Again, think "allow
|
||
*lint-name* items".)
|
||
|
||
- If a lint applies to a specific grammatical class, mention that class and
|
||
use the plural form: use `unused_variables` rather than `unused_variable`.
|
||
This makes `#[allow(unused_variables)]` read correctly.
|
||
|
||
- Lints that catch unnecessary, unused, or useless aspects of code should use
|
||
the term `unused`, e.g. `unused_imports`, `unused_typecasts`.
|
||
|
||
- Use snake case in the same way you would for function names.
|
||
|
||
[RFC 0344]: https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md#lints
|
||
|
||
### Diagnostic levels
|
||
|
||
Guidelines for different diagnostic levels:
|
||
|
||
- `error`: emitted when the compiler detects a problem that makes it unable to
|
||
compile the program, either because the program is invalid or the programmer
|
||
has decided to make a specific `warning` into an error.
|
||
|
||
- `warning`: emitted when the compiler detects something odd about a program.
|
||
Care should be taken when adding warnings to avoid warning fatigue, and
|
||
avoid false-positives where there really isn't a problem with the code. Some
|
||
examples of when it is appropriate to issue a warning:
|
||
|
||
- A situation where the user *should* take action, such as swap out a
|
||
deprecated item, or use a `Result`, but otherwise doesn't prevent
|
||
compilation.
|
||
- Unnecessary syntax that can be removed without affecting the semantics of
|
||
the code. For example, unused code, or unnecessary `unsafe`.
|
||
- Code that is very likely to be incorrect, dangerous, or confusing, but the
|
||
language technically allows, and is not ready or confident enough to make
|
||
an error. For example `unused_comparisons` (out of bounds comparisons) or
|
||
`bindings_with_variant_name` (the user likely did not intend to create a
|
||
binding in a pattern).
|
||
- [Future-incompatible lints](#future-incompatible), where something was
|
||
accidentally or erroneously accepted in the past, but rejecting would
|
||
cause excessive breakage in the ecosystem.
|
||
- Stylistic choices. For example, camel or snake case, or the `dyn` trait
|
||
warning in the 2018 edition. These have a high bar to be added, and should
|
||
only be used in exceptional circumstances. Other stylistic choices should
|
||
either be allow-by-default lints, or part of other tools like Clippy or
|
||
rustfmt.
|
||
|
||
- `help`: emitted following an `error` or `warning` to give additional
|
||
information to the user about how to solve their problem. These messages
|
||
often include a suggestion string and [`rustc_errors::Applicability`]
|
||
confidence level to guide automated source fixes by tools. See the
|
||
[Suggestions](#suggestions) section for more details.
|
||
|
||
The error or warning portion should *not* suggest how to fix the problem,
|
||
only the "help" sub-diagnostic should.
|
||
|
||
- `note`: emitted to identify additional circumstances and parts of the code
|
||
that caused the warning or error. For example, the borrow checker will note
|
||
any previous conflicting borrows.
|
||
|
||
Not to be confused with *lint levels*, whose guidelines are:
|
||
|
||
- `forbid`: Lints should never default to `forbid`.
|
||
- `deny`: Equivalent to `error` diagnostic level. Some examples:
|
||
|
||
- A future-incompatible or edition-based lint that has graduated from the
|
||
warning level.
|
||
- Something that has an extremely high confidence that is incorrect, but
|
||
still want an escape hatch to allow it to pass.
|
||
|
||
- `warn`: Equivalent to the `warning` diagnostic level. See `warning` above
|
||
for guidelines.
|
||
- `allow`: Examples of the kinds of lints that should default to `allow`:
|
||
|
||
- The lint has a too high false positive rate.
|
||
- The lint is too opinionated.
|
||
- The lint is experimental.
|
||
- The lint is used for enforcing something that is not normally enforced.
|
||
For example, the `unsafe_code` lint can be used to prevent usage of unsafe
|
||
code.
|
||
|
||
More information about lint levels can be found in the [rustc
|
||
book][rustc-lint-levels] and the [reference][reference-diagnostics].
|
||
|
||
[`rustc_errors::Applicability`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html
|
||
[reference-diagnostics]: https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#lint-check-attributes
|
||
[rustc-lint-levels]: https://doc.rust-lang.org/nightly/rustc/lints/levels.html
|
||
|
||
## Helpful tips and options
|
||
|
||
### Finding the source of errors
|
||
|
||
There are two main ways to find where a given error is emitted:
|
||
|
||
- `grep` for either a sub-part of the error message/label or error code. This
|
||
usually works well and is straightforward, but there are some cases where
|
||
the error emitting code is removed from the code where the error is
|
||
constructed behind a relatively deep call-stack. Even then, it is a good way
|
||
to get your bearings.
|
||
- Invoking `rustc` with the nightly-only flag `-Ztreat-err-as-bug=1`, which
|
||
will treat the first error being emitted as an Internal Compiler Error, which
|
||
allows you to use the environment variable `RUST_BACKTRACE=full` to get a
|
||
stack trace at the point the error has been emitted. Change the `1` to
|
||
something else if you whish to trigger on a later error. Some limitations
|
||
with this approach is that some calls get elided from the stack trace because
|
||
they get inlined in the compiled `rustc`, and the same problem we faced with
|
||
the prior approach, where the _construction_ of the error is far away from
|
||
where it is _emitted_. In some cases we buffer multiple errors in order to
|
||
emit them in order.
|
||
|
||
The regular development practices apply: judicious use of `debug!()` statements
|
||
and use of a debugger to trigger break points in order to figure out in what
|
||
order things are happening.
|
||
|
||
## `Span`
|
||
|
||
[`Span`][span] is the primary data structure in `rustc` used to represent a
|
||
location in the code being compiled. `Span`s are attached to most constructs in
|
||
HIR and MIR, allowing for more informative error reporting.
|
||
|
||
[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html
|
||
|
||
A `Span` can be looked up in a [`SourceMap`][sourcemap] to get a "snippet"
|
||
useful for displaying errors with [`span_to_snippet`][sptosnip] and other
|
||
similar methods on the `SourceMap`.
|
||
|
||
[sourcemap]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/source_map/struct.SourceMap.html
|
||
[sptosnip]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/source_map/struct.SourceMap.html#method.span_to_snippet
|
||
|
||
## Error messages
|
||
|
||
The [`rustc_errors`][errors] crate defines most of the utilities used for
|
||
reporting errors.
|
||
|
||
[errors]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/index.html
|
||
|
||
[`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...
|
||
There are lots of them; they emit different types of "errors", such as
|
||
warnings, errors, fatal errors, suggestions, etc.
|
||
|
||
[parsesses]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/parse/struct.ParseSess.html
|
||
[session]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html
|
||
|
||
In general, there are two classes of such methods: ones that emit an error
|
||
directly and ones that allow finer control over what to emit. For example,
|
||
[`span_err`][spanerr] emits the given error message at the given `Span`, but
|
||
[`struct_span_err`][strspanerr] instead returns a
|
||
[`DiagnosticBuilder`][diagbuild].
|
||
|
||
`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
|
||
[docs][diagbuild] for more info on what you can do.
|
||
|
||
[spanerr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.span_err
|
||
[strspanerr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.struct_span_err
|
||
[diagbuild]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/diagnostic_builder/struct.DiagnosticBuilder.html
|
||
[emit]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/diagnostic_builder/struct.DiagnosticBuilder.html#method.emit
|
||
[cancel]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.Diagnostic.html#method.cancel
|
||
|
||
```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!");
|
||
|
||
// 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));
|
||
} 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");
|
||
}
|
||
|
||
// emit the error
|
||
err.emit();
|
||
```
|
||
|
||
## Suggestions
|
||
|
||
In addition to telling the user exactly _why_ their code is wrong, it's
|
||
oftentimes furthermore possible to tell them how to fix it. To this end,
|
||
`DiagnosticBuilder` offers a structured suggestions API, which formats code
|
||
suggestions pleasingly in the terminal, or (when the `--error-format json` flag
|
||
is passed) as JSON for consumption by tools, most notably the [Rust Language
|
||
Server][rls] and [`rustfix`][rustfix].
|
||
|
||
[rls]: https://github.com/rust-lang/rls
|
||
[rustfix]: https://github.com/rust-lang/rustfix
|
||
|
||
Not all suggestions should be applied mechanically, they have a degree of
|
||
confidence in the suggested code, from high
|
||
(`Applicability::MachineApplicable`) to low (`Applicability::MaybeIncorrect`).
|
||
Be conservative when choosing the level. Use the
|
||
[`span_suggestion`][span_suggestion] method of `DiagnosticBuilder` to
|
||
make a suggestion. The last argument provides a hint to tools whether
|
||
the suggestion is mechanically applicable or not.
|
||
|
||
Suggestions point to one or more spans with corresponding code that will
|
||
replace their current content.
|
||
|
||
The message that accompanies them should be understandable in the following
|
||
contexts:
|
||
|
||
- shown as an independent sub-diagnostic (this is the default output)
|
||
- shown as a label pointing at the affected span (this is done automatically if
|
||
some heuristics for verbosity are met)
|
||
- shown as a `help` sub-diagnostic with no content (used for cases where the
|
||
suggestion is obvious from the text, but we still want to let tools to apply
|
||
them))
|
||
- not shown (used for _very_ obvious cases, but we still want to allow tools to
|
||
apply them)
|
||
|
||
[span_suggestion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.DiagnosticBuilder.html#method.span_suggestion
|
||
|
||
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!");
|
||
|
||
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
|
||
err.span_suggestion(
|
||
suggestion_sp,
|
||
"try using a qux here",
|
||
format!("qux {}", snippet),
|
||
Applicability::MachineApplicable,
|
||
);
|
||
} else {
|
||
err.span_help(suggestion_sp, "you could use a qux here instead");
|
||
}
|
||
|
||
err.emit();
|
||
```
|
||
|
||
This might emit an error like
|
||
|
||
```console
|
||
$ rustc mycode.rs
|
||
error[E0999]: oh no! this is an error!
|
||
--> mycode.rs:3:5
|
||
|
|
||
3 | sad()
|
||
| ^ help: try using a qux here: `qux sad()`
|
||
|
||
error: aborting due to previous error
|
||
|
||
For more information about this error, try `rustc --explain E0999`.
|
||
```
|
||
|
||
In some cases, like when the suggestion spans multiple lines or when there are
|
||
multiple suggestions, the suggestions are displayed on their own:
|
||
|
||
```console
|
||
error[E0999]: oh no! this is an error!
|
||
--> mycode.rs:3:5
|
||
|
|
||
3 | sad()
|
||
| ^
|
||
help: try using a qux here:
|
||
|
|
||
3 | qux sad()
|
||
| ^^^
|
||
|
||
error: aborting due to previous error
|
||
|
||
For more information about this error, try `rustc --explain E0999`.
|
||
```
|
||
|
||
The possible values of [`Applicability`][appl] are:
|
||
|
||
- `MachineApplicable`: Can be applied mechanically.
|
||
- `HasPlaceholders`: Cannot be applied mechanically because it has placeholder
|
||
text in the suggestions. For example, "Try adding a type: \`let x:
|
||
\<type\>\`".
|
||
- `MaybeIncorrect`: Cannot be applied mechanically because the suggestion may
|
||
or may not be a good one.
|
||
- `Unspecified`: Cannot be applied mechanically because we don't know which
|
||
of the above cases it falls into.
|
||
|
||
[appl]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html
|
||
|
||
### Suggestion Style Guide
|
||
|
||
- Suggestions should not be a question. In particular, language like "did you
|
||
mean" should be avoided. Sometimes, it's unclear why a particular suggestion
|
||
is being made. In these cases, it's better to be upfront about what the
|
||
suggestion is.
|
||
|
||
Compare "did you mean: `Foo`" vs. "there is a struct with a similar name: `Foo`".
|
||
|
||
- The message should not contain any phrases like "the following", "as shown",
|
||
etc. Use the span to convey what is being talked about.
|
||
- The message may contain further instruction such as "to do xyz, use" or "to do
|
||
xyz, use abc".
|
||
- The message may contain a name of a function, variable, or type, but avoid
|
||
whole expressions.
|
||
|
||
## Lints
|
||
|
||
The compiler linting infrastructure is defined in the [`rustc_middle::lint`][rlint]
|
||
module.
|
||
|
||
[rlint]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/index.html
|
||
|
||
### Declaring a lint
|
||
|
||
The built-in compiler lints are defined in the [`rustc_lint`][builtin]
|
||
crate.
|
||
|
||
[builtin]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/index.html
|
||
|
||
Every lint is implemented via a `struct` that implements the `LintPass` `trait`
|
||
(you also implement one of the more specific lint pass traits, either
|
||
`EarlyLintPass` or `LateLintPass`). The trait implementation allows you to
|
||
check certain syntactic constructs as the linter walks the source code. 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!`
|
||
macro. This includes the name, the default level, a short description, and some
|
||
more details.
|
||
|
||
Note that the lint and the lint pass must be registered with the compiler.
|
||
|
||
For example, the following lint checks for uses
|
||
of `while true { ... }` and suggests using `loop { ... }` instead.
|
||
|
||
```rust,ignore
|
||
// Declare a lint called `WHILE_TRUE`
|
||
declare_lint! {
|
||
WHILE_TRUE,
|
||
|
||
// warn-by-default
|
||
Warn,
|
||
|
||
// This string is the lint description
|
||
"suggest using `loop { }` instead of `while true { }`"
|
||
}
|
||
|
||
// This declares a struct and a lint pass, providing a list of associated lints. The
|
||
// compiler currently doesn't use the associated lints directly (e.g., to not
|
||
// run the pass or otherwise check that the pass emits the appropriate set of
|
||
// lints). However, it's good to be accurate here as it's possible that we're
|
||
// going to register the lints via the get_lints method on our lint pass (that
|
||
// this macro generates).
|
||
declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
|
||
|
||
// Helper function for `WhileTrue` lint.
|
||
// Traverse through any amount of parenthesis and return the first non-parens expression.
|
||
fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
|
||
while let ast::ExprKind::Paren(sub) = &expr.kind {
|
||
expr = sub;
|
||
}
|
||
expr
|
||
}
|
||
|
||
// `EarlyLintPass` has lots of methods. We only override the definition of
|
||
// `check_expr` for this lint because that's all we need, but you could
|
||
// override other methods for your own lint. See the rustc docs for a full
|
||
// list of methods.
|
||
impl EarlyLintPass for WhileTrue {
|
||
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
|
||
if let ast::ExprKind::While(cond, ..) = &e.kind {
|
||
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)
|
||
.span_suggestion_short(
|
||
condition_span,
|
||
"use `loop`",
|
||
"loop".to_owned(),
|
||
Applicability::MachineApplicable,
|
||
)
|
||
.emit();
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Edition-gated Lints
|
||
|
||
Sometimes we want to change the behavior of a lint in a new edition. To do this,
|
||
we just add the transition to our invocation of `declare_lint!`:
|
||
|
||
```rust,ignore
|
||
declare_lint! {
|
||
pub ANONYMOUS_PARAMETERS,
|
||
Allow,
|
||
"detects anonymous parameters",
|
||
Edition::Edition2018 => Warn,
|
||
}
|
||
```
|
||
|
||
This makes the `ANONYMOUS_PARAMETERS` lint allow-by-default in the 2015 edition
|
||
but warn-by-default in the 2018 edition.
|
||
|
||
A future-incompatible lint should be declared with the `@future_incompatible`
|
||
additional "field":
|
||
|
||
```rust,ignore
|
||
declare_lint! {
|
||
pub ANONYMOUS_PARAMETERS,
|
||
Allow,
|
||
"detects anonymous parameters",
|
||
@future_incompatible = FutureIncompatibleInfo {
|
||
reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
|
||
edition: Some(Edition::Edition2018),
|
||
};
|
||
}
|
||
```
|
||
|
||
If you need a combination of options that's not supported by the
|
||
`declare_lint!` macro, you can always define your own static with a type of
|
||
`&Lint` but this is currently linted against in the compiler tree.
|
||
|
||
<a id="future-incompatible"></a>
|
||
#### Guidelines for creating a future incompatibility lint
|
||
|
||
- Create a lint defaulting to warn as normal, with ideally the same error
|
||
message you would normally give.
|
||
- Add a suitable reference, typically an RFC or tracking issue. Go ahead
|
||
and include the full URL, sort items in ascending order of issue numbers.
|
||
- Later, change lint to error.
|
||
- Eventually, remove lint.
|
||
|
||
### Renaming or removing a lint
|
||
|
||
A lint can be renamed or removed, 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
|
||
[`register_builtins`] function.
|
||
|
||
```rust,ignore
|
||
store.register_renamed("single_use_lifetime", "single_use_lifetimes");
|
||
```
|
||
|
||
[`store.register_renamed`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_renamed
|
||
[`store.register_removed`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_removed
|
||
[`register_builtins`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/fn.register_builtins.html
|
||
|
||
### Lint Groups
|
||
|
||
Lints can be turned on in groups. These groups are declared in the
|
||
[`register_builtins`][rbuiltins] function in [`rustc_lint::lib`][builtin]. The
|
||
`add_lint_group!` macro is used to declare a new group.
|
||
|
||
[rbuiltins]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/fn.register_builtins.html
|
||
|
||
For example,
|
||
|
||
```rust,ignore
|
||
add_lint_group!(sess,
|
||
"nonstandard_style",
|
||
NON_CAMEL_CASE_TYPES,
|
||
NON_SNAKE_CASE,
|
||
NON_UPPER_CASE_GLOBALS);
|
||
```
|
||
|
||
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.
|
||
|
||
### Linting early in the compiler
|
||
|
||
On occasion, you may need to define a lint that runs before the linting system
|
||
has been initialized (e.g. during parsing or macro expansion). This is
|
||
problematic because we need to have computed lint levels to know whether we
|
||
should emit a warning or an error or nothing at all.
|
||
|
||
To solve this problem, we buffer the lints until the linting system is
|
||
processed. [`Session`][sessbl] and [`ParseSess`][parsebl] both have
|
||
`buffer_lint` methods that allow you to buffer a lint for later. The linting
|
||
system automatically takes care of handling buffered lints later.
|
||
|
||
[sessbl]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.buffer_lint
|
||
[parsebl]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/parse/struct.ParseSess.html#method.buffer_lint
|
||
|
||
Thus, to define a lint that runs early in the compilation, one defines a lint
|
||
like normal but invokes the lint with `buffer_lint`.
|
||
|
||
#### Linting even earlier in the compiler
|
||
|
||
The parser (`rustc_ast`) is interesting in that it cannot have dependencies on
|
||
any of the other `rustc*` crates. In particular, it cannot depend on
|
||
`rustc_middle::lint` or `rustc_lint`, where all of the compiler linting
|
||
infrastructure is defined. That's troublesome!
|
||
|
||
To solve this, `rustc_ast` defines its own buffered lint type, which
|
||
`ParseSess::buffer_lint` uses. After macro expansion, these buffered lints are
|
||
then dumped into the `Session::buffered_lints` used by the rest of the compiler.
|
||
|
||
## JSON diagnostic output
|
||
|
||
The compiler accepts an `--error-format json` flag to output
|
||
diagnostics as JSON objects (for the benefit of tools such as `cargo
|
||
fix` or the RLS). It looks like this—
|
||
|
||
```console
|
||
$ rustc json_error_demo.rs --error-format json
|
||
{"message":"cannot add `&str` to `{integer}`","code":{"code":"E0277","explanation":"\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"},"level":"error","spans":[{"file_name":"json_error_demo.rs","byte_start":50,"byte_end":51,"line_start":4,"line_end":4,"column_start":7,"column_end":8,"is_primary":true,"text":[{"text":" a + b","highlight_start":7,"highlight_end":8}],"label":"no implementation for `{integer} + &str`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the trait `std::ops::Add<&str>` is not implemented for `{integer}`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error[E0277]: cannot add `&str` to `{integer}`\n --> json_error_demo.rs:4:7\n |\n4 | a + b\n | ^ no implementation for `{integer} + &str`\n |\n = help: the trait `std::ops::Add<&str>` is not implemented for `{integer}`\n\n"}
|
||
{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error\n\n"}
|
||
{"message":"For more information about this error, try `rustc --explain E0277`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0277`.\n"}
|
||
```
|
||
|
||
Note that the output is a series of lines, each of which is a JSON
|
||
object, but the series of lines taken together is, unfortunately, not
|
||
valid JSON, thwarting tools and tricks (such as [piping to `python3 -m
|
||
json.tool`](https://docs.python.org/3/library/json.html#module-json.tool))
|
||
that require such. (One speculates that this was intentional for LSP
|
||
performance purposes, so that each line/object can be sent to RLS as
|
||
it is flushed?)
|
||
|
||
Also note the "rendered" field, which contains the "human" output as a
|
||
string; this was introduced so that UI tests could both make use of
|
||
the structured JSON and see the "human" output (well, _sans_ colors)
|
||
without having to compile everything twice.
|
||
|
||
The "human" readable and the json format emitter can be found under
|
||
`rustc_errors`, both were moved from the `rustc_ast` crate to the
|
||
[rustc_errors crate](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/index.html).
|
||
|
||
The JSON emitter defines [its own `Diagnostic`
|
||
struct](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/json/struct.Diagnostic.html)
|
||
(and sub-structs) for the JSON serialization. Don't confuse this with
|
||
[`errors::Diagnostic`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.Diagnostic.html)!
|
||
|
||
## `#[rustc_on_unimplemented(...)]`
|
||
|
||
The `#[rustc_on_unimplemented]` attribute allows trait definitions to add specialized
|
||
notes to error messages when an implementation was expected but not found.
|
||
You can refer to the trait's generic arguments by name and to the resolved type using `Self`.
|
||
|
||
For example:
|
||
|
||
```rust,ignore
|
||
#![feature(rustc_attrs)]
|
||
|
||
#[rustc_on_unimplemented="an iterator over elements of type `{A}` \
|
||
cannot be built from a collection of type `{Self}`"]
|
||
trait MyIterator<A> {
|
||
fn next(&mut self) -> A;
|
||
}
|
||
|
||
fn iterate_chars<I: MyIterator<char>>(i: I) {
|
||
// ...
|
||
}
|
||
|
||
fn main() {
|
||
iterate_chars(&[1, 2, 3][..]);
|
||
}
|
||
```
|
||
|
||
When the user compiles this, they will see the following;
|
||
|
||
```txt
|
||
error[E0277]: the trait bound `&[{integer}]: MyIterator<char>` is not satisfied
|
||
--> <anon>:14:5
|
||
|
|
||
14 | iterate_chars(&[1, 2, 3][..]);
|
||
| ^^^^^^^^^^^^^ an iterator over elements of type `char` cannot be built from a collection of type `&[{integer}]`
|
||
|
|
||
= help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
|
||
= note: required by `iterate_chars`
|
||
```
|
||
|
||
`rustc_on_unimplemented` also supports advanced filtering for better targeting
|
||
of messages, as well as modifying specific parts of the error message. You
|
||
target the text of:
|
||
|
||
- the main error message (`message`)
|
||
- the label (`label`)
|
||
- an extra note (`note`)
|
||
|
||
For example, the following attribute
|
||
|
||
```rust,ignore
|
||
#[rustc_on_unimplemented(
|
||
message="message",
|
||
label="label",
|
||
note="note"
|
||
)]
|
||
trait MyIterator<A> {
|
||
fn next(&mut self) -> A;
|
||
}
|
||
```
|
||
|
||
Would generate the following output:
|
||
|
||
```text
|
||
error[E0277]: message
|
||
--> <anon>:14:5
|
||
|
|
||
14 | iterate_chars(&[1, 2, 3][..]);
|
||
| ^^^^^^^^^^^^^ label
|
||
|
|
||
= note: note
|
||
= help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
|
||
= note: required by `iterate_chars`
|
||
```
|
||
|
||
To allow more targeted error messages, it is possible to filter the
|
||
application of these fields based on a variety of attributes when using
|
||
`on`:
|
||
|
||
- `crate_local`: whether the code causing the trait bound to not be
|
||
fulfilled is part of the user's crate. This is used to avoid suggesting
|
||
code changes that would require modifying a dependency.
|
||
- Any of the generic arguments that can be substituted in the text can be
|
||
referred by name as well for filtering, like `Rhs="i32"`, except for
|
||
`Self`.
|
||
- `_Self`: to filter only on a particular calculated trait resolution, like
|
||
`Self="std::iter::Iterator<char>"`. This is needed because `Self` is a
|
||
keyword which cannot appear in attributes.
|
||
- `direct`: user-specified rather than derived obligation.
|
||
- `from_method`: usable both as boolean (whether the flag is present, like
|
||
`crate_local`) or matching against a particular method. Currently used
|
||
for `try`.
|
||
- `from_desugaring`: usable both as boolean (whether the flag is present)
|
||
or matching against a particular desugaring. The desugaring is identified
|
||
with its variant name in the `DesugaringKind` enum.
|
||
|
||
For example, the `Iterator` trait can be annotated in the following way:
|
||
|
||
```rust,ignore
|
||
#[rustc_on_unimplemented(
|
||
on(
|
||
_Self="&str",
|
||
note="call `.chars()` or `.as_bytes()` on `{Self}"
|
||
),
|
||
message="`{Self}` is not an iterator",
|
||
label="`{Self}` is not an iterator",
|
||
note="maybe try calling `.iter()` or a similar method"
|
||
)]
|
||
pub trait Iterator {}
|
||
```
|
||
|
||
Which would produce the following outputs:
|
||
|
||
```text
|
||
error[E0277]: `Foo` is not an iterator
|
||
--> src/main.rs:4:16
|
||
|
|
||
4 | for foo in Foo {}
|
||
| ^^^ `Foo` is not an iterator
|
||
|
|
||
= note: maybe try calling `.iter()` or a similar method
|
||
= help: the trait `std::iter::Iterator` is not implemented for `Foo`
|
||
= note: required by `std::iter::IntoIterator::into_iter`
|
||
|
||
error[E0277]: `&str` is not an iterator
|
||
--> src/main.rs:5:16
|
||
|
|
||
5 | for foo in "" {}
|
||
| ^^ `&str` is not an iterator
|
||
|
|
||
= note: call `.chars()` or `.bytes() on `&str`
|
||
= help: the trait `std::iter::Iterator` is not implemented for `&str`
|
||
= note: required by `std::iter::IntoIterator::into_iter`
|
||
```
|
||
|
||
If you need to filter on multiple attributes, you can use `all`, `any` or
|
||
`not` in the following way:
|
||
|
||
```rust,ignore
|
||
#[rustc_on_unimplemented(
|
||
on(
|
||
all(_Self="&str", T="std::string::String"),
|
||
note="you can coerce a `{T}` into a `{Self}` by writing `&*variable`"
|
||
)
|
||
)]
|
||
pub trait From<T>: Sized { /* ... */ }
|
||
```
|