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 [`TyS`][TyS].
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 LateLintPass<'_> for MyStructLint {
64 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
66 // Check our expr is calling a method
67 if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..], _) = &expr.kind;
68 // Check the name of this method is `some_method`
69 if path.ident.name == sym!(some_method);
70 // Optionally, check the type of the self argument.
71 // - See "Checking for a specific type"
80 ## Checking for a specific type
82 There are three ways to check if an expression type is a specific type we want to check for.
83 All of these methods only check for the base type, generic arguments have to be checked separately.
86 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
87 use clippy_utils::{paths, match_def_path};
88 use rustc_span::symbol::sym;
89 use rustc_hir::LangItem;
91 impl LateLintPass<'_> for MyStructLint {
92 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
93 // Getting the expression type
94 let ty = cx.typeck_results().expr_ty(expr);
96 // 1. Using diagnostic items
97 // The last argument is the diagnostic item to check for
98 if is_type_diagnostic_item(cx, ty, sym::Option) {
99 // The type is an `Option`
102 // 2. Using lang items
103 if is_type_lang_item(cx, ty, LangItem::RangeFull) {
104 // The type is a full range like `.drain(..)`
107 // 3. Using the type path
108 // This method should be avoided if possible
109 if match_def_path(cx, def_id, &paths::RESULT) {
110 // The type is a `core::result::Result`
116 Prefer using diagnostic items and lang items where possible.
118 ## Checking if a type implements a specific trait
120 There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
123 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
124 use rustc_span::symbol::sym;
126 impl LateLintPass<'_> for MyStructLint {
127 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
128 // 1. Using diagnostic items with the expression
129 // we use `is_trait_method` function from Clippy's utils
130 if is_trait_method(cx, expr, sym::Iterator) {
131 // method call in `expr` belongs to `Iterator` trait
134 // 2. Using lang items with the expression type
135 let ty = cx.typeck_results().expr_ty(expr);
136 if cx.tcx.lang_items()
137 // we are looking for the `DefId` of `Drop` trait in lang items
139 // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
140 .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
141 // `expr` implements `Drop` trait
144 // 3. Using the type path with the expression
145 // we use `match_trait_method` function from Clippy's utils
146 // (This method should be avoided if possible)
147 if match_trait_method(cx, expr, &paths::INTO) {
148 // `expr` implements `Into` trait
154 > Prefer using diagnostic and lang items, if the target trait has one.
156 We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
157 A list of defined paths for Clippy can be found in [paths.rs][paths]
159 ## Checking if a type defines a specific method
161 To check if our type defines a method called `some_method`:
164 use clippy_utils::{is_type_diagnostic_item, return_ty};
166 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
167 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
169 // Check if item is a method/function
170 if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
171 // Check the method is named `some_method`
172 if impl_item.ident.name == sym!(some_method);
173 // We can also check it has a parameter `self`
174 if signature.decl.implicit_self.has_implicit_self();
175 // We can go further and even check if its return type is `String`
176 if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type));
185 ## Dealing with macros and expansions
187 Keep in mind that macros are already expanded and desugaring is already applied
188 to the code representation that you are working with in Clippy. This unfortunately causes a lot of
189 false positives because macro expansions are "invisible" unless you actively check for them.
190 Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
191 dynamic in ways that are difficult or impossible to see.
192 Use the following functions to deal with macros:
194 - `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
195 Checking this is a common first step in a lint.
198 if expr.span.from_expansion() {
204 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
205 It is sometimes useful to check if the context of two spans are equal.
208 // expands to `1 + 0`, but don't lint
212 if left.span.ctxt() != right.span.ctxt() {
213 // the coder most likely cannot modify this expression
217 Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
218 be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
221 - `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
222 If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
223 not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
225 You may want to use it for example to not start linting in macros from other crates
229 extern crate a_crate_with_macros;
231 // `foo` is defined in `a_crate_with_macros`
234 // if we lint the `match` of `foo` call and test its span
235 assert_eq!(in_external_macro(cx.sess(), match_span), true);
238 - `differing_macro_contexts()`: returns true if the two given spans are not from the same context
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!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
258 [TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.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