|
| 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 a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) |
| 8 | + - [Dealing with macros](#dealing-with-macros) |
| 9 | + |
| 10 | +Useful Rustc dev guide links: |
| 11 | +- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) |
| 12 | +- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) |
| 13 | +- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) |
| 14 | + |
| 15 | +# Retrieving the type of an expression |
| 16 | + |
| 17 | +Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: |
| 18 | + |
| 19 | +- which type does this expression correspond to (using its [`TyKind`][TyKind])? |
| 20 | +- is it a sized type? |
| 21 | +- is it a primitive type? |
| 22 | +- does it implement a trait? |
| 23 | + |
| 24 | +This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckTables`][TypeckTables] struct, |
| 25 | +that gives you access to the underlying structure [`TyS`][TyS]. |
| 26 | + |
| 27 | +Example of use: |
| 28 | +```rust |
| 29 | +impl LateLintPass<'_, '_> for MyStructLint { |
| 30 | + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { |
| 31 | + // Get type of `expr` |
| 32 | + let ty = cx.tables.expr_ty(expr); |
| 33 | + // Match its kind to enter its type |
| 34 | + match ty.kind { |
| 35 | + ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), |
| 36 | + _ => () |
| 37 | + } |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +Similarly in [`TypeckTables`][TypeckTables] methods, you have the [`pat_ty()`][pat_ty] method |
| 43 | +to retrieve a type from a pattern. |
| 44 | + |
| 45 | +Two noticeable items here: |
| 46 | +- `cx` is the lint context [`LateContext`][LateContext]. |
| 47 | + The two most useful data structures in this context are `tcx` and `tables`, |
| 48 | + allowing us to jump to type definitions and other compilation stages such as HIR. |
| 49 | +- `tables` is [`TypeckTables`][TypeckTables] and is created by type checking step, |
| 50 | + it includes useful information such as types of expressions, ways to resolve methods and so on. |
| 51 | + |
| 52 | +# Checking if a type implements a specific trait |
| 53 | + |
| 54 | +There are two ways to do this, depending if the target trait is part of lang items. |
| 55 | + |
| 56 | +```rust |
| 57 | +use crate::utils::{implements_trait, match_trait_method, paths}; |
| 58 | + |
| 59 | +impl LateLintPass<'_, '_> for MyStructLint { |
| 60 | + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { |
| 61 | + // 1. Using expression and Clippy's convenient method |
| 62 | + // we use `match_trait_method` function from Clippy's toolbox |
| 63 | + if match_trait_method(cx, expr, &paths::INTO) { |
| 64 | + // `expr` implements `Into` trait |
| 65 | + } |
| 66 | + |
| 67 | + // 2. Using type context `TyCtxt` |
| 68 | + let ty = cx.tables.expr_ty(expr); |
| 69 | + if cx.tcx.lang_items() |
| 70 | + // we are looking for the `DefId` of `Drop` trait in lang items |
| 71 | + .drop_trait() |
| 72 | + // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils |
| 73 | + .map_or(false, |id| implements_trait(cx, ty, id, &[])) { |
| 74 | + // `expr` implements `Drop` trait |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +> Prefer using lang items, if the target trait is available there. |
| 81 | +
|
| 82 | +A list of defined paths for Clippy can be found in [paths.rs][paths] |
| 83 | + |
| 84 | +We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. |
| 85 | + |
| 86 | +# Dealing with macros |
| 87 | + |
| 88 | +There are several helpers in Clippy's utils to deal with macros: |
| 89 | + |
| 90 | +- `in_macro()`: detect if the given span is expanded by a macro |
| 91 | + |
| 92 | +You may want to use this for example to not start linting in any macro. |
| 93 | + |
| 94 | +```rust |
| 95 | +macro_rules! foo { |
| 96 | + ($param:expr) => { |
| 97 | + match $param { |
| 98 | + "bar" => println!("whatever"), |
| 99 | + _ => () |
| 100 | + } |
| 101 | + }; |
| 102 | +} |
| 103 | + |
| 104 | +foo!("bar"); |
| 105 | + |
| 106 | +// if we lint the `match` of `foo` call and test its span |
| 107 | +assert_eq!(in_macro(match_span), true); |
| 108 | +``` |
| 109 | + |
| 110 | +- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate |
| 111 | + |
| 112 | +You may want to use it for example to not start linting in macros from other crates |
| 113 | + |
| 114 | +```rust |
| 115 | +#[macro_use] |
| 116 | +extern crate a_crate_with_macros; |
| 117 | + |
| 118 | +// `foo` is defined in `a_crate_with_macros` |
| 119 | +foo!("bar"); |
| 120 | + |
| 121 | +// if we lint the `match` of `foo` call and test its span |
| 122 | +assert_eq!(in_external_macro(cx.sess(), match_span), true); |
| 123 | +``` |
| 124 | + |
| 125 | +- `differing_macro_contexts()`: returns true if the two given spans are not from the same context |
| 126 | + |
| 127 | +```rust |
| 128 | +macro_rules! m { |
| 129 | + ($a:expr, $b:expr) => { |
| 130 | + if $a.is_some() { |
| 131 | + $b; |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +let x: Option<u32> = Some(42); |
| 137 | +m!(x, x.unwrap()); |
| 138 | + |
| 139 | +// These spans are not from the same context |
| 140 | +// x.is_some() is from inside the macro |
| 141 | +// x.unwrap() is from outside the macro |
| 142 | +assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); |
| 143 | +``` |
| 144 | + |
| 145 | +[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html |
| 146 | +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html |
| 147 | +[TypeckTables]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckTables.html |
| 148 | +[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckTables.html#method.expr_ty |
| 149 | +[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html |
| 150 | +[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html |
| 151 | +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckTables.html#method.pat_ty |
| 152 | +[paths]: ../clippy_lints/src/utils/paths.rs |
0 commit comments