1 # Common tools for writing lints
3 You may need following tooltips to catch up with common operations.
5 - [Common tools for writing lints](#common-tools-for-writing-lints)
6 - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
7 - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
8 - [Checking for a specific type](#checking-for-a-specific-type)
9 - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
10 - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method)
11 - [Dealing with macros](#dealing-with-macros-and-expansions)
13 Useful Rustc dev guide links:
14 - [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
15 - [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
16 - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
17 - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
19 ## Retrieving the type of an expression
21 Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:
23 - which type does this expression correspond to (using its [`TyKind`][TyKind])?
25 - is it a primitive type?
26 - does it implement a trait?
28 This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct,
29 that gives you access to the underlying structure [`Ty`][Ty].
33 impl LateLintPass<'_> for MyStructLint {
34 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
36 let ty = cx.typeck_results().expr_ty(expr);
37 // Match its kind to enter its type
39 ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
46 Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
47 to retrieve a type from a pattern.
49 Two noticeable items here:
50 - `cx` is the lint context [`LateContext`][LateContext]. The two most useful
51 data structures in this context are `tcx` and the `TypeckResults` returned by
52 `LateContext::typeck_results`, allowing us to jump to type definitions and
53 other compilation stages such as HIR.
54 - `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is
55 created by type checking step, it includes useful information such as types
56 of expressions, ways to resolve methods and so on.
58 ## Checking if an expr is calling a specific method
60 Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
63 impl<'tcx> LateLintPass<'tcx> for MyStructLint {
64 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
65 // Check our expr is calling a method
66 if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind
67 // Check the name of this method is `some_method`
68 && path.ident.name == sym!(some_method)
69 // Optionally, check the type of the self argument.
70 // - See "Checking for a specific type"
78 ## Checking for a specific type
80 There are three ways to check if an expression type is a specific type we want to check for.
81 All of these methods only check for the base type, generic arguments have to be checked separately.
84 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
85 use clippy_utils::{paths, match_def_path};
86 use rustc_span::symbol::sym;
87 use rustc_hir::LangItem;
89 impl LateLintPass<'_> for MyStructLint {
90 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
91 // Getting the expression type
92 let ty = cx.typeck_results().expr_ty(expr);
94 // 1. Using diagnostic items
95 // The last argument is the diagnostic item to check for
96 if is_type_diagnostic_item(cx, ty, sym::Option) {
97 // The type is an `Option`
100 // 2. Using lang items
101 if is_type_lang_item(cx, ty, LangItem::RangeFull) {
102 // The type is a full range like `.drain(..)`
105 // 3. Using the type path
106 // This method should be avoided if possible
107 if match_def_path(cx, def_id, &paths::RESULT) {
108 // The type is a `core::result::Result`
114 Prefer using diagnostic items and lang items where possible.
116 ## Checking if a type implements a specific trait
118 There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
121 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
122 use rustc_span::symbol::sym;
124 impl LateLintPass<'_> for MyStructLint {
125 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
126 // 1. Using diagnostic items with the expression
127 // we use `is_trait_method` function from Clippy's utils
128 if is_trait_method(cx, expr, sym::Iterator) {
129 // method call in `expr` belongs to `Iterator` trait
132 // 2. Using lang items with the expression type
133 let ty = cx.typeck_results().expr_ty(expr);
134 if cx.tcx.lang_items()
135 // we are looking for the `DefId` of `Drop` trait in lang items
137 // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
138 .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
139 // `expr` implements `Drop` trait
142 // 3. Using the type path with the expression
143 // we use `match_trait_method` function from Clippy's utils
144 // (This method should be avoided if possible)
145 if match_trait_method(cx, expr, &paths::INTO) {
146 // `expr` implements `Into` trait
152 > Prefer using diagnostic and lang items, if the target trait has one.
154 We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
155 A list of defined paths for Clippy can be found in [paths.rs][paths]
157 ## Checking if a type defines a specific method
159 To check if our type defines a method called `some_method`:
162 use clippy_utils::{is_type_diagnostic_item, return_ty};
164 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
165 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
166 // Check if item is a method/function
167 if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
168 // Check the method is named `some_method`
169 && impl_item.ident.name == sym!(some_method)
170 // We can also check it has a parameter `self`
171 && signature.decl.implicit_self.has_implicit_self()
172 // We can go further and even check if its return type is `String`
173 && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
181 ## Dealing with macros and expansions
183 Keep in mind that macros are already expanded and desugaring is already applied
184 to the code representation that you are working with in Clippy. This unfortunately causes a lot of
185 false positives because macro expansions are "invisible" unless you actively check for them.
186 Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
187 dynamic in ways that are difficult or impossible to see.
188 Use the following functions to deal with macros:
190 - `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
191 Checking this is a common first step in a lint.
194 if expr.span.from_expansion() {
200 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
201 It is sometimes useful to check if the context of two spans are equal.
204 // expands to `1 + 0`, but don't lint
208 if left.span.ctxt() != right.span.ctxt() {
209 // the coder most likely cannot modify this expression
213 Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
214 be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
217 - `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
218 If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
219 not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
221 You may want to use it for example to not start linting in macros from other crates
225 extern crate a_crate_with_macros;
227 // `foo` is defined in `a_crate_with_macros`
230 // if we lint the `match` of `foo` call and test its span
231 assert_eq!(in_external_macro(cx.sess(), match_span), true);
234 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
236 One thing `SpanContext` is useful for is to check if two spans are in the same context. For example,
237 in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some
238 expression with a different context from `a`.
242 ($a:expr, $b:expr) => {
249 let x: Option<u32> = Some(42);
252 // These spans are not from the same context
253 // x.is_some() is from inside the macro
254 // x.unwrap() is from outside the macro
255 assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
258 [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
259 [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
260 [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
261 [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
262 [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
263 [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
264 [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
265 [paths]: ../clippy_utils/src/paths.rs