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.
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 easier error reporting whenever an error comes up.
A Span
can be looked up in a CodeMap
to get a "snippet" useful for
displaying errors with span_to_snippet
and other similar methods
on the CodeMap
.
The rustc_errors
crate defines most of the utilities used for
reporting errors.
Session
and ParseSess
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.
In general, there are two class 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
DiagnosticBuilder
.
DiagnosticBuilder
allows you to add related notes and suggestions to an error
before emitting it by calling the emit
method. See the
docs for more info on what you can do.
// 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 Some(snippet) = sess.codemap().span_to_snippet(sp) {
// Use the snippet to generate a suggested fix
err.span_suggestion(suggestion_sp, "try using a qux here", format!("qux {}", snip));
} 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();
We would like to make edition transitions as smooth as possible. To that end,
rustfix
can use compiler suggestions to automatically fix code. For example,
we could use rustfix
to mechanically apply the qux
suggestion from the
previous example. However, not all suggestions are mechanically applicable. We
use the span_suggestion_with_applicability
method of
DiagnosticBuilder
to inform the emitter of whether a suggestion is
mechanically applicable or not. This information, in turn, is outputed by
rustc when the error format is json
, which is used by rustfix
.
For example, to make our qux
suggestion machine-applicable, we would do:
let mut err = sess.struct_span_err(sp, "oh no! this is an error!");
if let Some(snippet) = sess.codemap().span_to_snippet(sp) {
// Add applicability info!
err.span_suggestion_with_applicability(
suggestion_sp,
"try using a qux here",
format!("qux {}", snip),
Applicability::MachineApplicable,
);
} else {
err.span_help(suggestion_sp, "you could use a qux here instead");
}
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`.
There are a few other Applicability
possibilities:
MachineApplicable
: Can be applied mechanically.HasPlaceholders
: Cannot be applied mechanically and 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.
The compiler linting infrastructure is defined in the rustc::lint
module.
The built-in compiler lints are defined in the rustc_lint
crate.
Each lint is defined as a struct
that implements the LintPass
trait
. The
trait implementation allows you to check certain syntactic constructs the
linter walks the source code. You can then choose to emit lints in a very
similar way to compile errors. Finally, you register the lint to actually get
it to be run by the compiler by using the declare_lint!
macro.
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 { }`"
}
// Define a struct and `impl LintPass` for it.
#[derive(Copy, Clone)]
pub struct WhileTrue;
impl LintPass for WhileTrue {
fn get_lints(&self) -> LintArray {
lint_array!(WHILE_TRUE)
}
}
// LateLintPass 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<'a, 'tcx> LateLintPass<'a, 'tcx> for WhileTrue {
fn check_expr(&mut self, cx: &LateContext, e: &hir::Expr) {
if let hir::ExprWhile(ref cond, ..) = e.node {
if let hir::ExprLit(ref lit) = cond.node {
if let ast::LitKind::Bool(true) = lit.node {
if lit.span.ctxt() == SyntaxContext::empty() {
let msg = "denote infinite loops with `loop { ... }`";
let condition_span = cx.tcx.sess.codemap().def_span(e.span);
let mut err = cx.struct_span_lint(WHILE_TRUE, condition_span, msg);
err.span_suggestion_short(condition_span, "use `loop`", "loop".to_owned());
err.emit();
}
}
}
}
}
}
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.
Lints that represent an incompatibility (i.e. error) in the upcoming edition
should also be registered as FutureIncompatibilityLint
s in
register_builtins
function in rustc_lint::lib
.
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 by using !#[warn(nonstandard_style)]
.