]> git.lizzy.rs Git - rust.git/blob - doc/common_tools_writing_lints.md
Auto merge of #5820 - ThibsG:FixSuspiciousArithmeticImpl, r=flip1995
[rust.git] / doc / 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 expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
8   - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
9   - [Checking if a type defines a method](#checking-if-a-type-defines-a-method)
10   - [Dealing with macros](#dealing-with-macros)
11
12 Useful Rustc dev guide links:
13 - [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
14 - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
15 - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
16
17 # Retrieving the type of an expression
18
19 Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:
20
21 - which type does this expression correspond to (using its [`TyKind`][TyKind])?
22 - is it a sized type?
23 - is it a primitive type?
24 - does it implement a trait?
25
26 This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct,
27 that gives you access to the underlying structure [`TyS`][TyS].
28
29 Example of use:
30 ```rust
31 impl LateLintPass<'_> for MyStructLint {
32     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
33         // Get type of `expr`
34         let ty = cx.typeck_results().expr_ty(expr);
35         // Match its kind to enter its type
36         match ty.kind {
37             ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
38             _ => ()
39         }
40     }
41 }
42 ```
43
44 Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
45 to retrieve a type from a pattern.
46
47 Two noticeable items here:
48 - `cx` is the lint context [`LateContext`][LateContext].
49   The two most useful data structures in this context are `tcx` and `tables`,
50   allowing us to jump to type definitions and other compilation stages such as HIR.
51 - `tables` is [`TypeckResults`][TypeckResults] and is created by type checking step,
52   it includes useful information such as types of expressions, ways to resolve methods and so on.
53
54 # Checking if an expr is calling a specific method
55
56 Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
57
58 ```rust
59 impl LateLintPass<'_> for MyStructLint {
60     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
61         if_chain! {
62             // Check our expr is calling a method
63             if let hir::ExprKind::MethodCall(path, _, _args) = &expr.kind;
64             // Check the name of this method is `some_method`
65             if path.ident.name == sym!(some_method);
66             then {
67                 // ...
68             }
69         }
70     }
71 }
72 ```
73
74 # Checking if a type implements a specific trait
75
76 There are two ways to do this, depending if the target trait is part of lang items.
77
78 ```rust
79 use crate::utils::{implements_trait, match_trait_method, paths};
80
81 impl LateLintPass<'_> for MyStructLint {
82     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
83         // 1. Using expression and Clippy's convenient method
84         // we use `match_trait_method` function from Clippy's toolbox
85         if match_trait_method(cx, expr, &paths::INTO) {
86             // `expr` implements `Into` trait
87         }
88
89         // 2. Using type context `TyCtxt`
90         let ty = cx.typeck_results().expr_ty(expr);
91         if cx.tcx.lang_items()
92             // we are looking for the `DefId` of `Drop` trait in lang items
93             .drop_trait()
94             // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
95             .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
96                 // `expr` implements `Drop` trait
97             }
98     }
99 }
100 ```
101
102 > Prefer using lang items, if the target trait is available there.
103
104 A list of defined paths for Clippy can be found in [paths.rs][paths]
105
106 We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
107
108 # Checking if a type defines a specific method
109
110 To check if our type defines a method called `some_method`:
111
112 ```rust
113 use crate::utils::{is_type_diagnostic_item, return_ty};
114
115 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
116     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
117         if_chain! {
118             // Check if item is a method/function
119             if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
120             // Check the method is named `some_method`
121             if impl_item.ident.name == sym!(some_method);
122             // We can also check it has a parameter `self`
123             if signature.decl.implicit_self.has_implicit_self();
124             // We can go further and even check if its return type is `String`
125             if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type));
126             then {
127                 // ...
128             }
129         }
130     }
131 }
132 ```
133
134 # Dealing with macros
135
136 There are several helpers in Clippy's utils to deal with macros:
137
138 - `in_macro()`: detect if the given span is expanded by a macro
139
140 You may want to use this for example to not start linting in any macro.
141
142 ```rust
143 macro_rules! foo {
144     ($param:expr) => {
145         match $param {
146             "bar" => println!("whatever"),
147             _ => ()
148         }
149     };
150 }
151
152 foo!("bar");
153
154 // if we lint the `match` of `foo` call and test its span
155 assert_eq!(in_macro(match_span), true);
156 ```
157
158 - `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate
159
160 You may want to use it for example to not start linting in macros from other crates
161
162 ```rust
163 #[macro_use]
164 extern crate a_crate_with_macros;
165
166 // `foo` is defined in `a_crate_with_macros`
167 foo!("bar");
168
169 // if we lint the `match` of `foo` call and test its span
170 assert_eq!(in_external_macro(cx.sess(), match_span), true);
171 ```
172
173 - `differing_macro_contexts()`: returns true if the two given spans are not from the same context
174
175 ```rust
176 macro_rules! m {
177     ($a:expr, $b:expr) => {
178         if $a.is_some() {
179             $b;
180         }
181     }
182 }
183
184 let x: Option<u32> = Some(42);
185 m!(x, x.unwrap());
186
187 // These spans are not from the same context
188 // x.is_some() is from inside the macro
189 // x.unwrap() is from outside the macro
190 assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
191 ```
192
193 [TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
194 [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
195 [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
196 [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
197 [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
198 [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
199 [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
200 [paths]: ../clippy_lints/src/utils/paths.rs