]> git.lizzy.rs Git - rust.git/blob - doc/common_tools_writing_lints.md
Edit docs about macros
[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 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 example to answer following questions:
22
23 - which type does this expression correspond to (using its [`TyKind`][TyKind])?
24 - is it a sized type?
25 - is it a primitive type?
26 - does it implement a trait?
27
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].
30
31 Example of use:
32 ```rust
33 impl LateLintPass<'_> for MyStructLint {
34     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
35         // Get type of `expr`
36         let ty = cx.typeck_results().expr_ty(expr);
37         // Match its kind to enter its type
38         match ty.kind {
39             ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
40             _ => ()
41         }
42     }
43 }
44 ```
45
46 Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
47 to retrieve a type from a pattern.
48
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.
57
58 ## Checking if an expr is calling a specific method
59
60 Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
61
62 ```rust
63 impl LateLintPass<'_> for MyStructLint {
64     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
65         if_chain! {
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"
72             then {
73                 // ...
74             }
75         }
76     }
77 }
78 ```
79
80 ## Checking for a specific type
81
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.
84
85 ```rust
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;
90
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);
95
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`
100         }
101
102         // 2. Using lang items
103         if is_type_lang_item(cx, ty, LangItem::RangeFull) {
104             // The type is a full range like `.drain(..)`
105         }
106
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`
111         }
112     }
113 }
114 ```
115
116 Prefer using diagnostic items and lang items where possible.
117
118 ## Checking if a type implements a specific trait
119
120 There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
121
122 ```rust
123 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
124 use rustc_span::symbol::sym;
125
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
132         }
133
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
138             .drop_trait()
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
142             }
143
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
149         }
150     }
151 }
152 ```
153
154 > Prefer using diagnostic and lang items, if the target trait has one.
155
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]
158
159 ## Checking if a type defines a specific method
160
161 To check if our type defines a method called `some_method`:
162
163 ```rust
164 use clippy_utils::{is_type_diagnostic_item, return_ty};
165
166 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
167     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
168         if_chain! {
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));
177             then {
178                 // ...
179             }
180         }
181     }
182 }
183 ```
184
185 ## Dealing with macros and expansions
186
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:
193
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.
196
197    ```rust
198    if expr.span.from_expansion() {
199        // just forget it
200        return;
201    }
202    ```
203
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.
206
207    ```rust
208    // expands to `1 + 0`, but don't lint
209    1 + mac!()
210    ```
211    ```rust
212    if left.span.ctxt() != right.span.ctxt() {
213        // the coder most likely cannot modify this expression
214        return;
215    }
216    ```
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.
219
220
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.
224
225    You may want to use it for example to not start linting in macros from other crates
226
227    ```rust
228    #[macro_use]
229    extern crate a_crate_with_macros;
230
231    // `foo` is defined in `a_crate_with_macros`
232    foo!("bar");
233
234    // if we lint the `match` of `foo` call and test its span
235    assert_eq!(in_external_macro(cx.sess(), match_span), true);
236    ```
237
238 - `differing_macro_contexts()`: returns true if the two given spans are not from the same context
239
240    ```rust
241    macro_rules! m {
242        ($a:expr, $b:expr) => {
243            if $a.is_some() {
244                $b;
245            }
246        }
247    }
248
249    let x: Option<u32> = Some(42);
250    m!(x, x.unwrap());
251
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);
256    ```
257
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