Common tools for writing lints
You may need following tooltips to catch up with common operations.
Useful Rustc dev guide links:
Retrieving the type of expression
Sometimes you may want to retrieve the type Ty
of an expression Expr
, for
example to answer following questions:
- which type does this expression correspond to (using its
TyKind
)? - is it a sized type?
- is it a primitive type?
- does it implement a trait?
This operation is performed using the expr_ty()
method from the
TypeckResults
struct, that gives you access to the underlying
structure Ty
.
Example of use:
Similarly, in TypeckResults
methods, you have the
pat_ty()
method to retrieve a type from a pattern.
Two noticeable items here:
cx
is the lint contextLateContext
. The two most useful data structures in this context aretcx
and theTypeckResults
returned byLateContext::typeck_results
, allowing us to jump to type definitions and other compilation stages such as HIR.typeck_results
's return value isTypeckResults
and is created by type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on.
Checking if an expr is calling a specific method
Starting with an expr
, you can check whether it is calling a specific method
some_method
:
Checking for a specific type
There are three ways to check if an expression type is a specific type we want to check for. All of these methods only check for the base type, generic arguments have to be checked separately.
Prefer using diagnostic items and lang items where possible.
Checking if a type implements a specific trait
There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
Prefer using diagnostic and lang items, if the target trait has one.
We access lang items through the type context tcx
. tcx
is of type
TyCtxt
and is defined in the rustc_middle
crate. A list of defined
paths for Clippy can be found in paths.rs
Checking if a type defines a specific method
To check if our type defines a method called some_method
:
Dealing with macros and expansions
Keep in mind that macros are already expanded and desugaring is already applied to the code representation that you are working with in Clippy. This unfortunately causes a lot of false positives because macro expansions are "invisible" unless you actively check for them. Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be dynamic in ways that are difficult or impossible to see. Use the following functions to deal with macros:
-
span.from_expansion()
: detects if a span is from macro expansion or desugaring. Checking this is a common first step in a lint.if expr.span.from_expansion() { // just forget it return; }
-
span.ctxt()
: the span's context represents whether it is from expansion, and if so, which macro call expanded it. It is sometimes useful to check if the context of two spans are equal.// expands to `1 + 0`, but don't lint 1 + mac!()
if left.span.ctxt() != right.span.ctxt() { // the coder most likely cannot modify this expression return; }
Note: Code that is not from expansion is in the "root" context. So any spans where
from_expansion
returnstrue
can be assumed to have the same context. And so just usingspan.from_expansion()
is often good enough. -
in_external_macro(span)
: detect if the given span is from a macro defined in a foreign crate. If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros not defined in the current crate. It doesn't make sense to lint code that the coder can't change.You may want to use it for example to not start linting in macros from other crates
-
span.ctxt()
: the span's context represents whether it is from expansion, and if so, what expanded itOne thing
SpanContext
is useful for is to check if two spans are in the same context. For example, ina == b
,a
andb
have the same context. In amacro_rules!
witha == $b
,$b
is expanded to some expression with a different context froma
.macro_rules! m { ($a:expr, $b:expr) => { if $a.is_some() { $b; } } } let x: Option<u32> = Some(42); m!(x, x.unwrap()); // These spans are not from the same context // x.is_some() is from inside the macro // x.unwrap() is from outside the macro assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());