|
| 1 | +# Method Checking |
| 2 | + |
| 3 | +In some scenarios we might want to check for methods when developing |
| 4 | +a lint. There are two kinds of questions that we might be curious about: |
| 5 | + |
| 6 | +- Invocation: Does an expression call a specific method? |
| 7 | +- Definition: Does an `impl` define a method? |
| 8 | + |
| 9 | +## Checking if an `expr` is calling a specific method |
| 10 | + |
| 11 | +Suppose we have an `expr`, we can check whether it calls a specific |
| 12 | +method, e.g. `our_fancy_method`, by performing a pattern match on |
| 13 | +the [`ExprKind`] that we can access from `expr.kind`: |
| 14 | + |
| 15 | +```rust |
| 16 | +use rustc_hir as hir; |
| 17 | +use rustc_lint::{LateContext, LateLintPass}; |
| 18 | +use rustc_span::sym; |
| 19 | +use clippy_utils::is_trait_method; |
| 20 | + |
| 21 | +impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint { |
| 22 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { |
| 23 | + // Check our expr is calling a method with pattern matching |
| 24 | + if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind |
| 25 | + // Check if the name of this method is `our_fancy_method` |
| 26 | + && path.ident.name == sym!(our_fancy_method) |
| 27 | + // We can check the type of the self argument whenever necessary. |
| 28 | + // (It's necessary if we want to check that method is specifically belonging to a specific trait, |
| 29 | + // for example, a `map` method could belong to user-defined trait instead of to `Iterator`) |
| 30 | + // See the next section for more information. |
| 31 | + && is_trait_method(cx, self_arg, sym::OurFancyTrait) |
| 32 | + { |
| 33 | + println!("`expr` is a method call for `our_fancy_method`"); |
| 34 | + } |
| 35 | + } |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +Take a closer look at the `ExprKind` enum variant [`MethodCall`] for more |
| 40 | +information on the pattern matching. As mentioned in [Define |
| 41 | +Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern |
| 42 | +matching with `MethodCall` in case the reader wishes to explore more. |
| 43 | + |
| 44 | +Additionally, we use the [`clippy_utils::sym!`][sym] macro to conveniently |
| 45 | +convert an input `our_fancy_method` into a `Symbol` and compare that symbol to |
| 46 | +the [`Ident`]'s name in the [`PathSegment`] in the [`MethodCall`]. |
| 47 | + |
| 48 | +## Checking if a `impl` block implements a method |
| 49 | + |
| 50 | +While sometimes we want to check whether a method is being called or not, other |
| 51 | +times we want to know if our `Ty` defines a method. |
| 52 | + |
| 53 | +To check if our `impl` block defines a method `our_fancy_method`, we will |
| 54 | +utilize the [`check_impl_item`] method that is available in our beloved |
| 55 | +[`LateLintPass`] (for more information, refer to the ["Lint |
| 56 | +Passes"](lint_passes.md) chapter in the Clippy book). This method provides us |
| 57 | +with an [`ImplItem`] struct, which represents anything within an `impl` block. |
| 58 | + |
| 59 | +Let us take a look at how we might check for the implementation of |
| 60 | +`our_fancy_method` on a type: |
| 61 | + |
| 62 | +```rust |
| 63 | +use clippy_utils::ty::is_type_diagnostic_item; |
| 64 | +use clippy_utils::return_ty; |
| 65 | +use rustc_hir::{ImplItem, ImplItemKind}; |
| 66 | +use rustc_lint::{LateContext, LateLintPass}; |
| 67 | +use rustc_span::symbol::sym; |
| 68 | + |
| 69 | +impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { |
| 70 | + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { |
| 71 | + // Check if item is a method/function |
| 72 | + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind |
| 73 | + // Check the method is named `our_fancy_method` |
| 74 | + && impl_item.ident.name == sym!(our_fancy_method) |
| 75 | + // We can also check it has a parameter `self` |
| 76 | + && signature.decl.implicit_self.has_implicit_self() |
| 77 | + // We can go even further and even check if its return type is `String` |
| 78 | + && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym::String) |
| 79 | + { |
| 80 | + println!("`our_fancy_method` is implemented!"); |
| 81 | + } |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +[`check_impl_item`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_impl_item |
| 87 | +[`ExprKind`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html |
| 88 | +[`Ident`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/struct.Ident.html |
| 89 | +[`ImplItem`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_hir/hir/struct.ImplItem.html |
| 90 | +[`LateLintPass`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html |
| 91 | +[`MethodCall`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.MethodCall |
| 92 | +[`PathSegment`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/struct.PathSegment.html |
| 93 | +[sym]: https://doc.rust-lang.org/stable/nightly-rustc/clippy_utils/macro.sym.html |
0 commit comments