]> git.lizzy.rs Git - rust.git/blob - doc/common_tools_writing_lints.md
Auto merge of #8899 - botahamec:use-self-tuple-struct-variants, r=Alexendoo
[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::ty::is_type_diagnostic_item;
163 use clippy_utils::return_ty;
164
165 impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
166     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
167         // Check if item is a method/function
168         if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
169             // Check the method is named `some_method`
170             && impl_item.ident.name == sym!(some_method)
171             // We can also check it has a parameter `self`
172             && signature.decl.implicit_self.has_implicit_self()
173             // We can go further and even check if its return type is `String`
174             && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
175         {
176             // ...
177         }
178     }
179 }
180 ```
181
182 ## Dealing with macros and expansions
183
184 Keep in mind that macros are already expanded and desugaring is already applied
185 to the code representation that you are working with in Clippy. This unfortunately causes a lot of
186 false positives because macro expansions are "invisible" unless you actively check for them.
187 Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
188 dynamic in ways that are difficult or impossible to see.
189 Use the following functions to deal with macros:
190
191 - `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
192   Checking this is a common first step in a lint.
193
194    ```rust
195    if expr.span.from_expansion() {
196        // just forget it
197        return;
198    }
199    ```
200
201 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
202    It is sometimes useful to check if the context of two spans are equal.
203
204    ```rust
205    // expands to `1 + 0`, but don't lint
206    1 + mac!()
207    ```
208    ```rust
209    if left.span.ctxt() != right.span.ctxt() {
210        // the coder most likely cannot modify this expression
211        return;
212    }
213    ```
214   Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
215   be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
216
217
218 - `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
219    If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
220    not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
221
222    You may want to use it for example to not start linting in macros from other crates
223
224    ```rust
225    #[macro_use]
226    extern crate a_crate_with_macros;
227
228    // `foo` is defined in `a_crate_with_macros`
229    foo!("bar");
230
231    // if we lint the `match` of `foo` call and test its span
232    assert_eq!(in_external_macro(cx.sess(), match_span), true);
233    ```
234
235 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
236
237 One thing `SpanContext` is useful for is to check if two spans are in the same context. For example,
238 in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some
239 expression with a different context from `a`.
240
241    ```rust
242    macro_rules! m {
243        ($a:expr, $b:expr) => {
244            if $a.is_some() {
245                $b;
246            }
247        }
248    }
249
250    let x: Option<u32> = Some(42);
251    m!(x, x.unwrap());
252
253    // These spans are not from the same context
254    // x.is_some() is from inside the macro
255    // x.unwrap() is from outside the macro
256    assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
257    ```
258
259 [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
260 [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
261 [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
262 [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
263 [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
264 [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
265 [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
266 [paths]: ../clippy_utils/src/paths.rs