Errors and Lints
- Diagnostic structure
- Diagnostic output style guide
- Helpful tips and options
Span
- Error messages
- Suggestions
- Lints
- JSON diagnostic output
#[rustc_on_unimplemented(...)]
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
| ^^^^
- Level (
error
,warning
, etc.). It indicates the severity of the message. (See diagnostic levels) - 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. Not all diagnostic have a code. For example, diagnostics created by lints don't have one. - 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 were 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:
error: the fobrulator needs to be krontrificated
When code or an identifier must appear in a message or label, it should be surrounded with backticks:
error: the identifier `foo.bar` is invalid
Error codes and explanations
Most errors have an associated error code. Error codes are linked to long-form
explanations which contains an example of how to trigger the error and in-depth
details about the error. They may be viewed with the --explain
flag, or via
the error index.
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.1 As always, if you are not sure, just ask your reviewer!
If you decide to add a new error with an associated error code, please read this section for a guide and important details about the process.
This rule of thumb was suggested by @estebank here.
Lints versus fixed diagnostics
Some messages are emitted via 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.
- future-incompatible lints: these are silencable lints. It was decided that making them fixed errors would cause too much breakage, so warnings are instead emitted, and will eventually be turned into fixed (hard) errors.
Hard-coded warnings (those using methods like span_warn
) 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 below for guidelines when to
use an error-level lint instead of a fixed error.
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
, andHelp
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 (use
rustc_errors::DiagCtxt
'sspan_*
methods or a diagnostic struct's#[primary_span]
to easily do this). Alsonote
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]
. 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
, notRust
orrustc
. - Use the Oxford comma when writing lists of items.
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. Thusctypes
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 thandeprecated_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 thanunused_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.
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 specificwarning
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) orbindings_with_variant_name
(the user likely did not intend to create a binding in a pattern). - Future-incompatible lints, 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.
- A situation where the user should take action, such as swap out a
deprecated item, or use a
-
help
: emitted following anerror
orwarning
to give additional information to the user about how to solve their problem. These messages often include a suggestion string andrustc_errors::Applicability
confidence level to guide automated source fixes by tools. See the 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 given more context and 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.help
vsnote
:help
should be used to show changes the user can possibly make to fix the problem.note
should be used for everything else, such as other context, information and facts, online resources to read, etc.
Not to be confused with lint levels, whose guidelines are:
-
forbid
: Lints should never default toforbid
. -
deny
: Equivalent toerror
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 thewarning
diagnostic level. Seewarning
above for guidelines. -
allow
: Examples of the kinds of lints that should default toallow
:- 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 and the reference.
Helpful tips and options
Finding the source of errors
There are three 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 code emitting the error 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-Z treat-err-as-bug=1
will treat the first error being emitted as an Internal Compiler Error, which allows you to get a stack trace at the point the error has been emitted. Change the1
to something else if you wish to trigger on a later error.There are limitations with this approach:
- Some calls get elided from the stack trace because they get inlined in the compiled
rustc
. - The construction of the error is far away from where it is emitted,
a problem similar to the one we faced with the
grep
approach. In some cases, we buffer multiple errors in order to emit them in order.
- Some calls get elided from the stack trace because they get inlined in the compiled
-
Invoking
rustc
with-Z track-diagnostics
will print error creation locations alongside the error.
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
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.
A Span
can be looked up in a SourceMap
to get a "snippet"
useful for displaying errors with span_to_snippet
and other
similar methods on the SourceMap
.
Error messages
The rustc_errors
crate defines most of the utilities used for
reporting errors.
Diagnostics can be implemented as types which implement the Diagnostic
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 Diagnostic
trait can be derived -- see Diagnostic
structs. Within the trait implementation, the APIs
described below can be used as normal.
DiagCtxt
has methods that create and emit 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.
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
emits the given error message at the given Span
, but
struct_span_err
instead returns a
Diag
.
Most of these methods will accept strings, but it is recommended that typed identifiers for translatable diagnostics be used for new diagnostics (see Translation).
Diag
allows you to add related notes and suggestions to an error
before emitting it by calling the emit
method. (Failing to either
emit or cancel a Diag
will result in an ICE.) See the
docs for more info on what you can do.
// Get a `Diag`. This does _not_ emit an error yet.
let mut err = sess.dcx.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, 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, fluent::example::qux_suggestion);
}
// emit the error
err.emit();
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
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,
Diag
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 like 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
method of Diag
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)
For example, to make our qux
suggestion machine-applicable, we would do:
let mut err = sess.dcx.struct_span_err(sp, fluent::example::message);
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
err.span_suggestion(
suggestion_sp,
fluent::example::try_qux_suggestion,
format!("qux {}", snippet),
Applicability::MachineApplicable,
);
} else {
err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}
err.emit();
This might emit an error like
$ 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:
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
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.
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
module.
When do lints run?
Different lints will run at different times based on what information the lint needs to do its job. Some lints get grouped into passes where the lints within a pass are processed together via a single visitor. Some of the passes are:
-
Pre-expansion pass: Works on AST nodes before macro expansion. This should generally be avoided.
- Example:
keyword_idents
checks for identifiers that will become keywords in future editions, but is sensitive to identifiers used in macros.
- Example:
-
Early lint pass: Works on AST nodes after macro expansion and name resolution, just before AST lowering. These lints are for purely syntactical lints.
- Example: The
unused_parens
lint checks for parenthesized-expressions in situations where they are not needed, like anif
condition.
- Example: The
-
Late lint pass: Works on HIR nodes, towards the end of analysis (after borrow checking, etc.). These lints have full type information available. Most lints are late.
- Example: The
invalid_value
lint (which checks for obviously invalid uninitialized values) is a late lint because it needs type information to figure out whether a type allows being left uninitialized.
- Example: The
-
MIR pass: Works on MIR nodes. This isn't quite the same as other passes; lints that work on MIR nodes have their own methods for running.
- Example: The
arithmetic_overflow
lint is emitted when it detects a constant value that may overflow.
- Example: The
Most lints work well via the pass systems, and they have a fairly
straightforward interface and easy way to integrate (mostly just implementing
a specific check
function). However, some lints are easier to write when
they live on a specific code path anywhere in the compiler. For example, the
unused_mut
lint is implemented in the borrow checker as it requires some
information and state in the borrow checker.
Some of these inline lints fire before the linting system is ready. Those lints will be buffered where they are held until later phases of the compiler when the linting system is ready. See Linting early in the compiler.
Lint definition terms
Lints are managed via the LintStore
and get registered in
various ways. The following terms refer to the different classes of lints
generally based on how they are registered.
- Built-in lints are defined inside the compiler source.
- Driver-registered lints are registered when the compiler driver is created by an external driver. This is the mechanism used by Clippy, for example.
- Tool lints are lints with a path prefix like
clippy::
orrustdoc::
. - Internal lints are the
rustc::
scoped tool lints that only run on the rustc source tree itself and are defined in the compiler source like a regular built-in lint.
More information about lint registration can be found in the LintStore chapter.
Declaring a lint
The built-in compiler lints are defined in the rustc_lint
crate. Lints that need to be implemented in other crates are defined in
rustc_lint_defs
. You should prefer to place lints in rustc_lint
if
possible. One benefit is that it is close to the dependency root, so it can be
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
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.
// 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
&& let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind
&& let ast::LitKind::Bool(true) = lit.kind
&& !lit.span.from_expansion()
{
let condition_span = cx.sess.source_map().guess_head_span(e.span);
cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
lint.build(fluent::example::use_loop)
.span_suggestion_short(
condition_span,
fluent::example::suggestion,
"loop".to_owned(),
Applicability::MachineApplicable,
)
.emit();
})
}
}
}
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,
we just add the transition to our invocation of declare_lint!
:
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.
See Edition-specific lints for more information.
Feature-gated lints
Lints belonging to a feature should only be usable if the feature is enabled in the crate. To support this, lint declarations can contain a feature gate like so:
declare_lint! {
pub SOME_LINT_NAME,
Warn,
"a new and useful, but feature gated lint",
@feature_gate = sym::feature_name;
}
Future-incompatible lints
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 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 meaning that rustc exclusively exposes to users as "future incompatible".
- 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. See migration lints for more details.
A future-incompatible lint should be declared with the @future_incompatible
additional "field":
declare_lint! {
pub ANONYMOUS_PARAMETERS,
Allow,
"detects anonymous parameters",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
};
}
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 its "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, 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.
If you need a combination of options that's not supported by the
declare_lint!
macro, you can always change the declare_lint!
macro
to support this.
Renaming or removing a lint
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
rustc_lint::register_builtins
function.
store.register_renamed("single_use_lifetime", "single_use_lifetimes");
Lint Groups
Lints can be turned on in groups. These groups are declared in the
register_builtins
function in rustc_lint::lib
. The
add_lint_group!
macro is used to declare a new group.
For example,
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.
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
(e.g., rust_2021_compatibility
) automatically generated for any lints signaling
future-incompatible code that will break in the specified edition.
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
and ParseSess
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.
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
). It looks like this:
$ 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
)
that require such. (One speculates that this was intentional for LSP
performance purposes, so that each line/object can be sent 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.
The JSON emitter defines its own Diagnostic
struct
(and sub-structs) for the JSON serialization. Don't confuse this with
errors::Diag
!
#[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:
#![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;
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
#[rustc_on_unimplemented(
message="message",
label="label",
note="note"
)]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
Would generate the following output:
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 forSelf
. _Self
: to filter only on a particular calculated trait resolution, likeSelf="std::iter::Iterator<char>"
. This is needed becauseSelf
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, likecrate_local
) or matching against a particular method. Currently used fortry
.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 theDesugaringKind
enum.
For example, the Iterator
trait can be annotated in the following way:
#[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:
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:
#[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 { /* ... */ }