1 # Common tools for writing lints
3 You may need following tooltips to catch up with common operations.
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)
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)
19 ## Retrieving the type of an expression
21 Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:
23 - which type does this expression correspond to (using its [`TyKind`][TyKind])?
25 - is it a primitive type?
26 - does it implement a trait?
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].
33 impl LateLintPass<'_> for MyStructLint {
34 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
36 let ty = cx.typeck_results().expr_ty(expr);
37 // Match its kind to enter its type
39 ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
46 Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
47 to retrieve a type from a pattern.
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.
58 ## Checking if an expr is calling a specific method
60 Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
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"
78 ## Checking for a specific type
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.
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;
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);
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`
100 // 2. Using lang items
101 if is_type_lang_item(cx, ty, LangItem::RangeFull) {
102 // The type is a full range like `.drain(..)`
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`
114 Prefer using diagnostic items and lang items where possible.
116 ## Checking if a type implements a specific trait
118 There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
121 use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
122 use rustc_span::symbol::sym;
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
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
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
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
152 > Prefer using diagnostic and lang items, if the target trait has one.
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]
157 ## Checking if a type defines a specific method
159 To check if our type defines a method called `some_method`:
162 use clippy_utils::ty::is_type_diagnostic_item;
163 use clippy_utils::return_ty;
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))
182 ## Dealing with macros and expansions
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:
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.
195 if expr.span.from_expansion() {
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.
205 // expands to `1 + 0`, but don't lint
209 if left.span.ctxt() != right.span.ctxt() {
210 // the coder most likely cannot modify this expression
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.
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.
222 You may want to use it for example to not start linting in macros from other crates
226 extern crate a_crate_with_macros;
228 // `foo` is defined in `a_crate_with_macros`
231 // if we lint the `match` of `foo` call and test its span
232 assert_eq!(in_external_macro(cx.sess(), match_span), true);
235 - `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
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`.
243 ($a:expr, $b:expr) => {
250 let x: Option<u32> = Some(42);
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());
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