|
| 1 | +# Trait Checking |
| 2 | + |
| 3 | +Besides [type checking](type_checking.md), we might want to examine if |
| 4 | +a specific type `Ty` implements certain trait when implementing a lint. |
| 5 | +There are three approaches to achieve this, depending on if the target trait |
| 6 | +that we want to examine has a [diagnostic item][diagnostic_items], |
| 7 | +[lang item][lang_items], or neither. |
| 8 | + |
| 9 | +## Using Diagnostic Items |
| 10 | + |
| 11 | +As explained in the [Rust Compiler Development Guide][rustc_dev_guide], diagnostic items |
| 12 | +are introduced for identifying types via [Symbols][symbol]. |
| 13 | + |
| 14 | +While the Rust Compiler Development Guide has [a section][using_diagnostic_items] on |
| 15 | +how to check for a specific trait on a type `Ty`, Clippy provides |
| 16 | +a helper function `is_trait_method`, which simplifies the process for us. |
| 17 | + |
| 18 | +For instance, if we want to examine whether an expression implements |
| 19 | +the `Iterator` trait, we could simply write the following code, |
| 20 | +providing the `LateContext` (`cx`), our expression at hand, and |
| 21 | +the symbol of the trait in question: |
| 22 | + |
| 23 | +```rust |
| 24 | +use clippy_utils::is_trait_method; |
| 25 | +use rustc_hir::Expr; |
| 26 | +use rustc_lint::{LateContext, LateLintPass}; |
| 27 | +use rustc_span::symbol::sym; |
| 28 | + |
| 29 | +impl LateLintPass<'_> for CheckIteratorTraitLint { |
| 30 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { |
| 31 | + if is_trait_method(cx, expr, sym::Iterator) { |
| 32 | + println!("This expression implements `Iterator` trait!"); |
| 33 | + } |
| 34 | + } |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +> **Note**: Refer to [this index][symbol_index] for all the defined `Symbol`s. |
| 39 | +
|
| 40 | +## Using Lang Items |
| 41 | + |
| 42 | +Besides diagnostic items, we can also use [`lang_items`][lang_items]. |
| 43 | +Take a look at the documentation and we find that `LanguageItems` contains |
| 44 | +all language items both from the current crate or its |
| 45 | +dependencies. |
| 46 | + |
| 47 | +Using one of its `*_trait` method, we could obtain the [DefId] of any |
| 48 | +specific item, such as `Clone`, `Copy`, `Drop`, `Eq`, which are familiar |
| 49 | +to many Rustaceans. |
| 50 | + |
| 51 | +For instance, if we want to examine whether an expression `expr` implements |
| 52 | +`Drop` trait, we could access `LanguageItems` via our `LateContext`'s |
| 53 | +[TyCtxt], which provides a `lang_items` method that will return the id of |
| 54 | +`Drop` trait to us. Then, by calling Clippy utils function `implements_trait` |
| 55 | +we can check that the `Ty` of the `expr` implements the trait: |
| 56 | + |
| 57 | +```rust |
| 58 | +use clippy_utils::implements_trait; |
| 59 | +use rustc_hir::Expr; |
| 60 | +use rustc_lint::{LateContext, LateLintPass}; |
| 61 | + |
| 62 | +impl LateLintPass<'_> for CheckDropTraitLint { |
| 63 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { |
| 64 | + let ty = cx.typeck_results().expr_ty(expr); |
| 65 | + if cx.tcx.lang_items() |
| 66 | + .drop_trait() |
| 67 | + .map_or(false, |id| implements_trait(cx, ty, id, &[])) { |
| 68 | + println!("`expr` implements `Drop` trait!"); |
| 69 | + } |
| 70 | + } |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +## Using Type Path |
| 75 | + |
| 76 | +If neither diagnostic item or lang item is available, we can use |
| 77 | +[`clippy_utils::paths`][paths] with the `match_trait_method` to determine trait |
| 78 | +implementation. |
| 79 | + |
| 80 | +> **Note**: This approach should be avoided if possible. |
| 81 | +
|
| 82 | +Below, we check if the given `expr` implements `tokio`'s |
| 83 | +[`AsyncReadExt`][AsyncReadExt] trait: |
| 84 | + |
| 85 | +```rust |
| 86 | +use clippy_utils::{match_trait_method, paths}; |
| 87 | +use rustc_hir::Expr; |
| 88 | +use rustc_lint::{LateContext, LateLintPass}; |
| 89 | + |
| 90 | +impl LateLintPass<'_> for CheckTokioAsyncReadExtTrait { |
| 91 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { |
| 92 | + if match_trait_method(cx, expr, &paths::TOKIO_IO_ASYNCREADEXT) { |
| 93 | + println!("`expr` implements `TOKIO_IO_ASYNCREADEXT` trait!"); |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +> **Note**: Even though all the `clippy_utils` methods we have seen in this |
| 100 | +> chapter takes `expr` as a parameter, these methods are actually using |
| 101 | +> each expression's `HirId` under the hood. |
| 102 | +
|
| 103 | +[AsyncReadExt]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncReadExt.html |
| 104 | +[DefId]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def_id/struct.DefId.html |
| 105 | +[diagnostic_items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html |
| 106 | +[lang_items]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/lang_items/struct.LanguageItems.html |
| 107 | +[paths]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/paths.rs |
| 108 | +[rustc_dev_guide]: https://rustc-dev-guide.rust-lang.org/ |
| 109 | +[symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html |
| 110 | +[symbol_index]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/sym/index.html |
| 111 | +[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html |
| 112 | +[using_diagnostic_items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html#using-diagnostic-items |
0 commit comments