]> git.lizzy.rs Git - rust.git/blob - doc/common_tools_writing_lints.md
Auto merge of #8807 - Jarcho:cmp_owned, r=giraffate
[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 [`Ty`][Ty].
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<'tcx> LateLintPass<'tcx> for MyStructLint {
64     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
65         // Check our expr is calling a method
66         if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind
67             // Check the name of this method is `some_method`
68             && path.ident.name == sym!(some_method)
69             // Optionally, check the type of the self argument.
70             // - See "Checking for a specific type"
71         {
72                 // ...
73         }
74     }
75 }
76 ```
77
78 ## Checking for a specific type
79
80 There are three ways to check if an expression type is a specific type we want to check for.
81 All of these methods only check for the base type, generic arguments have to be checked separately.
82
83 ```rust
84 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
85 use clippy_utils::{paths, match_def_path};
86 use rustc_span::symbol::sym;
87 use rustc_hir::LangItem;
88
89 impl LateLintPass<'_> for MyStructLint {
90     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
91         // Getting the expression type
92         let ty = cx.typeck_results().expr_ty(expr);
93
94         // 1. Using diagnostic items
95         // The last argument is the diagnostic item to check for
96         if is_type_diagnostic_item(cx, ty, sym::Option) {
97             // The type is an `Option`
98         }
99
100         // 2. Using lang items
101         if is_type_lang_item(cx, ty, LangItem::RangeFull) {
102             // The type is a full range like `.drain(..)`
103         }
104
105         // 3. Using the type path
106         // This method should be avoided if possible
107         if match_def_path(cx, def_id, &paths::RESULT) {
108             // The type is a `core::result::Result`
109         }
110     }
111 }
112 ```
113
114 Prefer using diagnostic items and lang items where possible.
115
116 ## Checking if a type implements a specific trait
117
118 There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
119
120 ```rust
121 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
122 use rustc_span::symbol::sym;
123
124 impl LateLintPass<'_> for MyStructLint {
125     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
126         // 1. Using diagnostic items with the expression
127         // we use `is_trait_method` function from Clippy's utils
128         if is_trait_method(cx, expr, sym::Iterator) {
129             // method call in `expr` belongs to `Iterator` trait
130         }
131
132         // 2. Using lang items with the expression type
133         let ty = cx.typeck_results().expr_ty(expr);
134         if cx.tcx.lang_items()
135             // we are looking for the `DefId` of `Drop` trait in lang items
136             .drop_trait()
137             // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
138             .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
139                 // `expr` implements `Drop` trait
140             }
141
142         // 3. Using the type path with the expression
143         // we use `match_trait_method` function from Clippy's utils
144         // (This method should be avoided if possible)
145         if match_trait_method(cx, expr, &paths::INTO) {
146             // `expr` implements `Into` trait
147         }
148     }
149 }
150 ```
151
152 > Prefer using diagnostic and lang items, if the target trait has one.
153
154 We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
155 A list of defined paths for Clippy can be found in [paths.rs][paths]
156
157 ## Checking if a type defines a specific method
158
159 To check if our type defines a method called `some_method`:
160
161 ```rust
162 use clippy_utils::{is_type_diagnostic_item, return_ty};
163
164 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
165     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
166         // Check if item is a method/function
167         if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
168             // Check the method is named `some_method`
169             && impl_item.ident.name == sym!(some_method)
170             // We can also check it has a parameter `self`
171             && signature.decl.implicit_self.has_implicit_self()
172             // We can go further and even check if its return type is `String`
173             && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
174         {
175             // ...
176         }
177     }
178 }
179 ```
180
181 ## Dealing with macros and expansions
182
183 Keep in mind that macros are already expanded and desugaring is already applied
184 to the code representation that you are working with in Clippy. This unfortunately causes a lot of
185 false positives because macro expansions are "invisible" unless you actively check for them.
186 Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
187 dynamic in ways that are difficult or impossible to see.
188 Use the following functions to deal with macros:
189
190 - `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
191   Checking this is a common first step in a lint.
192
193    ```rust
194    if expr.span.from_expansion() {
195        // just forget it
196        return;
197    }
198    ```
199
200 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
201    It is sometimes useful to check if the context of two spans are equal.
202
203    ```rust
204    // expands to `1 + 0`, but don't lint
205    1 + mac!()
206    ```
207    ```rust
208    if left.span.ctxt() != right.span.ctxt() {
209        // the coder most likely cannot modify this expression
210        return;
211    }
212    ```
213   Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
214   be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
215
216
217 - `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
218    If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
219    not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
220
221    You may want to use it for example to not start linting in macros from other crates
222
223    ```rust
224    #[macro_use]
225    extern crate a_crate_with_macros;
226
227    // `foo` is defined in `a_crate_with_macros`
228    foo!("bar");
229
230    // if we lint the `match` of `foo` call and test its span
231    assert_eq!(in_external_macro(cx.sess(), match_span), true);
232    ```
233
234 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
235
236 One thing `SpanContext` is useful for is to check if two spans are in the same context. For example,
237 in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some
238 expression with a different context from `a`.
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!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
256    ```
257
258 [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.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