]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/book/src/development/common_tools_writing_lints.md
Auto merge of #101846 - chenyukang:fix-101793, r=davidtwco
[rust.git] / src / tools / clippy / book / src / development / common_tools_writing_lints.md
1 # Common tools for writing lints
2
3 You may need following tooltips to catch up with common operations.
4
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)
12
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)
18
19 ## Retrieving the type of an expression
20
21 Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for
22 example to answer following questions:
23
24 - which type does this expression correspond to (using its [`TyKind`][TyKind])?
25 - is it a sized type?
26 - is it a primitive type?
27 - does it implement a trait?
28
29 This operation is performed using the [`expr_ty()`][expr_ty] method from the
30 [`TypeckResults`][TypeckResults] struct, that gives you access to the underlying
31 structure [`Ty`][Ty].
32
33 Example of use:
34 ```rust
35 impl LateLintPass<'_> for MyStructLint {
36     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
37         // Get type of `expr`
38         let ty = cx.typeck_results().expr_ty(expr);
39         // Match its kind to enter its type
40         match ty.kind {
41             ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
42             _ => ()
43         }
44     }
45 }
46 ```
47
48 Similarly in [`TypeckResults`][TypeckResults] methods, you have the
49 [`pat_ty()`][pat_ty] method to retrieve a type from a pattern.
50
51 Two noticeable items here:
52 - `cx` is the lint context [`LateContext`][LateContext]. The two most useful
53   data structures in this context are `tcx` and the `TypeckResults` returned by
54   `LateContext::typeck_results`, allowing us to jump to type definitions and
55   other compilation stages such as HIR.
56 - `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is
57   created by type checking step, it includes useful information such as types of
58   expressions, ways to resolve methods and so on.
59
60 ## Checking if an expr is calling a specific method
61
62 Starting with an `expr`, you can check whether it is calling a specific method
63 `some_method`:
64
65 ```rust
66 impl<'tcx> LateLintPass<'tcx> for MyStructLint {
67     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
68         // Check our expr is calling a method
69         if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind
70             // Check the name of this method is `some_method`
71             && path.ident.name == sym!(some_method)
72             // Optionally, check the type of the self argument.
73             // - See "Checking for a specific type"
74         {
75                 // ...
76         }
77     }
78 }
79 ```
80
81 ## Checking for a specific type
82
83 There are three ways to check if an expression type is a specific type we want
84 to check for. All of these methods only check for the base type, generic
85 arguments have to be checked separately.
86
87 ```rust
88 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
89 use clippy_utils::{paths, match_def_path};
90 use rustc_span::symbol::sym;
91 use rustc_hir::LangItem;
92
93 impl LateLintPass<'_> for MyStructLint {
94     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
95         // Getting the expression type
96         let ty = cx.typeck_results().expr_ty(expr);
97
98         // 1. Using diagnostic items
99         // The last argument is the diagnostic item to check for
100         if is_type_diagnostic_item(cx, ty, sym::Option) {
101             // The type is an `Option`
102         }
103
104         // 2. Using lang items
105         if is_type_lang_item(cx, ty, LangItem::RangeFull) {
106             // The type is a full range like `.drain(..)`
107         }
108
109         // 3. Using the type path
110         // This method should be avoided if possible
111         if match_def_path(cx, def_id, &paths::RESULT) {
112             // The type is a `core::result::Result`
113         }
114     }
115 }
116 ```
117
118 Prefer using diagnostic items and lang items where possible.
119
120 ## Checking if a type implements a specific trait
121
122 There are three ways to do this, depending on if the target trait has a
123 diagnostic item, lang item or neither.
124
125 ```rust
126 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
127 use rustc_span::symbol::sym;
128
129 impl LateLintPass<'_> for MyStructLint {
130     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
131         // 1. Using diagnostic items with the expression
132         // we use `is_trait_method` function from Clippy's utils
133         if is_trait_method(cx, expr, sym::Iterator) {
134             // method call in `expr` belongs to `Iterator` trait
135         }
136
137         // 2. Using lang items with the expression type
138         let ty = cx.typeck_results().expr_ty(expr);
139         if cx.tcx.lang_items()
140             // we are looking for the `DefId` of `Drop` trait in lang items
141             .drop_trait()
142             // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
143             .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
144                 // `expr` implements `Drop` trait
145             }
146
147         // 3. Using the type path with the expression
148         // we use `match_trait_method` function from Clippy's utils
149         // (This method should be avoided if possible)
150         if match_trait_method(cx, expr, &paths::INTO) {
151             // `expr` implements `Into` trait
152         }
153     }
154 }
155 ```
156
157 > Prefer using diagnostic and lang items, if the target trait has one.
158
159 We access lang items through the type context `tcx`. `tcx` is of type
160 [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined
161 paths for Clippy can be found in [paths.rs][paths]
162
163 ## Checking if a type defines a specific method
164
165 To check if our type defines a method called `some_method`:
166
167 ```rust
168 use clippy_utils::ty::is_type_diagnostic_item;
169 use clippy_utils::return_ty;
170
171 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
172     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
173         // Check if item is a method/function
174         if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
175             // Check the method is named `some_method`
176             && impl_item.ident.name == sym!(some_method)
177             // We can also check it has a parameter `self`
178             && signature.decl.implicit_self.has_implicit_self()
179             // We can go further and even check if its return type is `String`
180             && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
181         {
182             // ...
183         }
184     }
185 }
186 ```
187
188 ## Dealing with macros and expansions
189
190 Keep in mind that macros are already expanded and desugaring is already applied
191 to the code representation that you are working with in Clippy. This
192 unfortunately causes a lot of false positives because macro expansions are
193 "invisible" unless you actively check for them. Generally speaking, code with
194 macro expansions should just be ignored by Clippy because that code can be
195 dynamic in ways that are difficult or impossible to see. Use the following
196 functions to deal with macros:
197
198 - `span.from_expansion()`: detects if a span is from macro expansion or
199   desugaring. Checking this is a common first step in a lint.
200
201    ```rust
202    if expr.span.from_expansion() {
203        // just forget it
204        return;
205    }
206    ```
207
208 - `span.ctxt()`: the span's context represents whether it is from expansion, and
209   if so, which macro call expanded it. It is sometimes useful to check if the
210   context of two spans are equal.
211
212   ```rust
213   // expands to `1 + 0`, but don't lint
214   1 + mac!()
215   ```
216   ```rust
217   if left.span.ctxt() != right.span.ctxt() {
218       // the coder most likely cannot modify this expression
219       return;
220   }
221   ```
222   > Note: Code that is not from expansion is in the "root" context. So any spans
223   > where `from_expansion` returns `true` can be assumed to have the same
224   > context. And so just using `span.from_expansion()` is often good enough.
225
226
227 - `in_external_macro(span)`: detect if the given span is from a macro defined in
228   a foreign crate. If you want the lint to work with macro-generated code, this
229   is the next line of defense to avoid macros not defined in the current crate.
230   It doesn't make sense to lint code that the coder can't change.
231
232   You may want to use it for example to not start linting in macros from other
233   crates
234
235   ```rust
236   #[macro_use]
237   extern crate a_crate_with_macros;
238
239   // `foo` is defined in `a_crate_with_macros`
240   foo!("bar");
241
242   // if we lint the `match` of `foo` call and test its span
243   assert_eq!(in_external_macro(cx.sess(), match_span), true);
244   ```
245
246 - `span.ctxt()`: the span's context represents whether it is from expansion, and
247   if so, what expanded it
248
249   One thing `SpanContext` is useful for is to check if two spans are in the same
250   context. For example, in `a == b`, `a` and `b` have the same context. In a
251   `macro_rules!` with `a == $b`, `$b` is expanded to some expression with a
252   different context from `a`.
253
254    ```rust
255    macro_rules! m {
256        ($a:expr, $b:expr) => {
257            if $a.is_some() {
258                $b;
259            }
260        }
261    }
262
263    let x: Option<u32> = Some(42);
264    m!(x, x.unwrap());
265
266    // These spans are not from the same context
267    // x.is_some() is from inside the macro
268    // x.unwrap() is from outside the macro
269    assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
270    ```
271
272 [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
273 [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
274 [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
275 [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
276 [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
277 [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
278 [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
279 [paths]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/paths/index.html