about summary refs log tree commit diff
path: root/src/tools/clippy/doc/common_tools_writing_lints.md
blob: 1a6b7c8cb47a9db623f64fe5868d29d5f1dd5a40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# Common tools for writing lints

You may need following tooltips to catch up with common operations.

- [Common tools for writing lints](#common-tools-for-writing-lints)
  - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
  - [Checking if an expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
  - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
  - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method)
  - [Dealing with macros](#dealing-with-macros)

Useful Rustc dev guide links:
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)

# Retrieving the type of an expression

Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:

- which type does this expression correspond to (using its [`TyKind`][TyKind])?
- is it a sized type?
- is it a primitive type?
- does it implement a trait?

This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct,
that gives you access to the underlying structure [`TyS`][TyS].

Example of use:
```rust
impl LateLintPass<'_> for MyStructLint {
    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
        // Get type of `expr`
        let ty = cx.typeck_results().expr_ty(expr);
        // Match its kind to enter its type
        match ty.kind {
            ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
            _ => ()
        }
    }
}
```

Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
to retrieve a type from a pattern.

Two noticeable items here:
- `cx` is the lint context [`LateContext`][LateContext]. The two most useful
  data structures in this context are `tcx` and the `TypeckResults` returned by
  `LateContext::typeck_results`, allowing us to jump to type definitions and
  other compilation stages such as HIR.
- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is
  created by type checking step, it includes useful information such as types
  of expressions, ways to resolve methods and so on.

# Checking if an expr is calling a specific method

Starting with an `expr`, you can check whether it is calling a specific method `some_method`:

```rust
impl LateLintPass<'_> for MyStructLint {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
        if_chain! {
            // Check our expr is calling a method
            if let hir::ExprKind::MethodCall(path, _, _args, _) = &expr.kind;
            // Check the name of this method is `some_method`
            if path.ident.name == sym!(some_method);
            then {
                // ...
            }
        }
    }
}
```

# Checking if a type implements a specific trait

There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.

```rust
use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
use rustc_span::symbol::sym;

impl LateLintPass<'_> for MyStructLint {
    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
        // 1. Using diagnostic items with the expression
        // we use `is_trait_method` function from Clippy's utils
        if is_trait_method(cx, expr, sym::Iterator) {
            // method call in `expr` belongs to `Iterator` trait
        }

        // 2. Using lang items with the expression type
        let ty = cx.typeck_results().expr_ty(expr);
        if cx.tcx.lang_items()
            // we are looking for the `DefId` of `Drop` trait in lang items
            .drop_trait()
            // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
            .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
                // `expr` implements `Drop` trait
            }

        // 3. Using the type path with the expression
        // we use `match_trait_method` function from Clippy's utils
        if match_trait_method(cx, expr, &paths::INTO) {
            // `expr` implements `Into` trait
        }
    }
}
```

> Prefer using diagnostic and lang items, if the target trait has one.

We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
A list of defined paths for Clippy can be found in [paths.rs][paths]

# Checking if a type defines a specific method

To check if our type defines a method called `some_method`:

```rust
use clippy_utils::{is_type_diagnostic_item, return_ty};

impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
        if_chain! {
            // Check if item is a method/function
            if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
            // Check the method is named `some_method`
            if impl_item.ident.name == sym!(some_method);
            // We can also check it has a parameter `self`
            if signature.decl.implicit_self.has_implicit_self();
            // We can go further and even check if its return type is `String`
            if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type));
            then {
                // ...
            }
        }
    }
}
```

# Dealing with macros

There are several helpers in [`clippy_utils`][utils] to deal with macros:

- `in_macro()`: detect if the given span is expanded by a macro

You may want to use this for example to not start linting in any macro.

```rust
macro_rules! foo {
    ($param:expr) => {
        match $param {
            "bar" => println!("whatever"),
            _ => ()
        }
    };
}

foo!("bar");

// if we lint the `match` of `foo` call and test its span
assert_eq!(in_macro(match_span), true);
```

- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate

You may want to use it for example to not start linting in macros from other crates

```rust
#[macro_use]
extern crate a_crate_with_macros;

// `foo` is defined in `a_crate_with_macros`
foo!("bar");

// if we lint the `match` of `foo` call and test its span
assert_eq!(in_external_macro(cx.sess(), match_span), true);
```

- `differing_macro_contexts()`: returns true if the two given spans are not from the same context

```rust
macro_rules! m {
    ($a:expr, $b:expr) => {
        if $a.is_some() {
            $b;
        }
    }
}

let x: Option<u32> = Some(42);
m!(x, x.unwrap());

// These spans are not from the same context
// x.is_some() is from inside the macro
// x.unwrap() is from outside the macro
assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
```

[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
[paths]: ../clippy_utils/src/paths.rs
[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs