]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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]. The two most useful | |
49 | data structures in this context are `tcx` and the `TypeckResults` returned by | |
50 | `LateContext::typeck_results`, allowing us to jump to type definitions and | |
51 | other compilation stages such as HIR. | |
52 | - `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is | |
53 | created by type checking step, it includes useful information such as types | |
54 | of expressions, ways to resolve methods and so on. | |
55 | ||
56 | # Checking if an expr is calling a specific method | |
57 | ||
58 | Starting with an `expr`, you can check whether it is calling a specific method `some_method`: | |
59 | ||
60 | ```rust | |
61 | impl LateLintPass<'_> for MyStructLint { | |
62 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { | |
63 | if_chain! { | |
64 | // Check our expr is calling a method | |
65 | if let hir::ExprKind::MethodCall(path, _, _args, _) = &expr.kind; | |
66 | // Check the name of this method is `some_method` | |
67 | if path.ident.name == sym!(some_method); | |
68 | then { | |
69 | // ... | |
70 | } | |
71 | } | |
72 | } | |
73 | } | |
74 | ``` | |
75 | ||
76 | # Checking if a type implements a specific trait | |
77 | ||
78 | There are two ways to do this, depending if the target trait is part of lang items. | |
79 | ||
80 | ```rust | |
81 | use clippy_utils::{implements_trait, match_trait_method, paths}; | |
82 | ||
83 | impl LateLintPass<'_> for MyStructLint { | |
84 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { | |
85 | // 1. Using expression and Clippy's convenient method | |
86 | // we use `match_trait_method` function from Clippy's toolbox | |
87 | if match_trait_method(cx, expr, &paths::INTO) { | |
88 | // `expr` implements `Into` trait | |
89 | } | |
90 | ||
91 | // 2. Using type context `TyCtxt` | |
92 | let ty = cx.typeck_results().expr_ty(expr); | |
93 | if cx.tcx.lang_items() | |
94 | // we are looking for the `DefId` of `Drop` trait in lang items | |
95 | .drop_trait() | |
96 | // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils | |
97 | .map_or(false, |id| implements_trait(cx, ty, id, &[])) { | |
98 | // `expr` implements `Drop` trait | |
99 | } | |
100 | } | |
101 | } | |
102 | ``` | |
103 | ||
104 | > Prefer using lang items, if the target trait is available there. | |
105 | ||
106 | A list of defined paths for Clippy can be found in [paths.rs][paths] | |
107 | ||
108 | We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. | |
109 | ||
110 | # Checking if a type defines a specific method | |
111 | ||
112 | To check if our type defines a method called `some_method`: | |
113 | ||
114 | ```rust | |
115 | use clippy_utils::{is_type_diagnostic_item, return_ty}; | |
116 | ||
117 | impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { | |
118 | fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { | |
119 | if_chain! { | |
120 | // Check if item is a method/function | |
121 | if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; | |
122 | // Check the method is named `some_method` | |
123 | if impl_item.ident.name == sym!(some_method); | |
124 | // We can also check it has a parameter `self` | |
125 | if signature.decl.implicit_self.has_implicit_self(); | |
126 | // We can go further and even check if its return type is `String` | |
127 | if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)); | |
128 | then { | |
129 | // ... | |
130 | } | |
131 | } | |
132 | } | |
133 | } | |
134 | ``` | |
135 | ||
136 | # Dealing with macros | |
137 | ||
138 | There are several helpers in [`clippy_utils`][utils] to deal with macros: | |
139 | ||
140 | - `in_macro()`: detect if the given span is expanded by a macro | |
141 | ||
142 | You may want to use this for example to not start linting in any macro. | |
143 | ||
144 | ```rust | |
145 | macro_rules! foo { | |
146 | ($param:expr) => { | |
147 | match $param { | |
148 | "bar" => println!("whatever"), | |
149 | _ => () | |
150 | } | |
151 | }; | |
152 | } | |
153 | ||
154 | foo!("bar"); | |
155 | ||
156 | // if we lint the `match` of `foo` call and test its span | |
157 | assert_eq!(in_macro(match_span), true); | |
158 | ``` | |
159 | ||
160 | - `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate | |
161 | ||
162 | You may want to use it for example to not start linting in macros from other crates | |
163 | ||
164 | ```rust | |
165 | #[macro_use] | |
166 | extern crate a_crate_with_macros; | |
167 | ||
168 | // `foo` is defined in `a_crate_with_macros` | |
169 | foo!("bar"); | |
170 | ||
171 | // if we lint the `match` of `foo` call and test its span | |
172 | assert_eq!(in_external_macro(cx.sess(), match_span), true); | |
173 | ``` | |
174 | ||
175 | - `differing_macro_contexts()`: returns true if the two given spans are not from the same context | |
176 | ||
177 | ```rust | |
178 | macro_rules! m { | |
179 | ($a:expr, $b:expr) => { | |
180 | if $a.is_some() { | |
181 | $b; | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | let x: Option<u32> = Some(42); | |
187 | m!(x, x.unwrap()); | |
188 | ||
189 | // These spans are not from the same context | |
190 | // x.is_some() is from inside the macro | |
191 | // x.unwrap() is from outside the macro | |
192 | assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); | |
193 | ``` | |
194 | ||
195 | [TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html | |
196 | [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html | |
197 | [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html | |
198 | [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty | |
199 | [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html | |
200 | [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html | |
201 | [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty | |
202 | [paths]: ../clippy_utils/src/paths.rs | |
203 | [utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs |