用于编写 lint 的常用工具
您可能需要以下工具提示来掌握常用操作。
有用的 Rustc 开发指南链接
检索表达式的类型
有时您可能想检索表达式 Expr
的类型 Ty
,例如回答以下问题
- 此表达式对应于哪种类型(使用其
TyKind
)? - 它是一个有大小的类型吗?
- 它是一个原始类型吗?
- 它是否实现了某个 trait?
此操作使用 expr_ty()
方法执行,该方法来自 TypeckResults
结构,使您可以访问底层结构 Ty
。
使用示例
#![allow(unused)] fn main() { 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!"), _ => () } } } }
类似地,在 TypeckResults
方法中,您可以使用 pat_ty()
方法从模式中检索类型。
此处有两项值得注意的地方
cx
是 lint 上下文LateContext
。此上下文中两个最有用的数据结构是tcx
和LateContext::typeck_results
返回的TypeckResults
,允许我们跳转到类型定义和其他编译阶段(例如 HIR)。typeck_results
的返回值是TypeckResults
,它由类型检查步骤创建,其中包含有用的信息,例如表达式的类型、解析方法的方式等等。
检查 expr 是否在调用特定方法
从 expr
开始,您可以检查它是否在调用特定的方法 some_method
#![allow(unused)] fn main() { impl<'tcx> LateLintPass<'tcx> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { // Check our expr is calling a method if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind // Check the name of this method is `some_method` && path.ident.name.as_str() == "some_method" // Optionally, check the type of the self argument. // - See "Checking for a specific type" { // ... } } } }
检查特定类型
有三种方法可以检查表达式类型是否是我们想要检查的特定类型。所有这些方法都只检查基本类型,泛型参数必须单独检查。
#![allow(unused)] fn main() { use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::{paths, match_def_path}; use rustc_span::symbol::sym; use rustc_hir::LangItem; impl LateLintPass<'_> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { // Getting the expression type let ty = cx.typeck_results().expr_ty(expr); // 1. Using diagnostic items // The last argument is the diagnostic item to check for if is_type_diagnostic_item(cx, ty, sym::Option) { // The type is an `Option` } // 2. Using lang items if is_type_lang_item(cx, ty, LangItem::RangeFull) { // The type is a full range like `.drain(..)` } // 3. Using the type path // This method should be avoided if possible if match_def_path(cx, def_id, &paths::RESULT) { // The type is a `core::result::Result` } } } }
尽可能优先使用诊断项和语言项。
检查类型是否实现了特定 trait
有三种方法可以做到这一点,具体取决于目标 trait 是否具有诊断项、语言项或两者都没有。
#![allow(unused)] fn main() { use clippy_utils::ty::implements_trait; use clippy_utils::is_trait_method; 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 } } } }
如果目标 trait 具有诊断项和语言项,则优先使用它们。
我们通过类型上下文 tcx
访问语言项。 tcx
的类型为 TyCtxt
,并在 rustc_middle
crate 中定义。可以在 paths.rs 中找到 Clippy 定义的路径列表
检查类型是否定义了特定方法
要检查我们的类型是否定义了一个名为 some_method
的方法
#![allow(unused)] fn main() { use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::return_ty; impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { // Check if item is a method/function if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // Check the method is named `some_method` && impl_item.ident.name.as_str() == "some_method" // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go further and even check if its return type is `String` && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)) { // ... } } } }
处理宏和展开
请记住,宏已经展开,并且去糖化已经应用于您在 Clippy 中使用的代码表示形式。不幸的是,这会导致很多误报,因为宏展开是“不可见的”,除非您主动检查它们。一般来说,带有宏展开的代码应该被 Clippy 忽略,因为该代码可能会以难以或无法看到的方式动态化。使用以下函数来处理宏
-
span.from_expansion()
:检测 span 是否来自宏展开或去糖化。检查此项是 lint 中的常见第一步。if expr.span.from_expansion() { // just forget it return; }
-
span.ctxt()
:span 的上下文表示它是否来自展开,如果是,则表示哪个宏调用展开了它。有时检查两个 span 的上下文是否相等很有用。// expands to `1 + 0`, but don't lint 1 + mac!()
if left.span.ctxt() != right.span.ctxt() { // the coder most likely cannot modify this expression return; }
注意:非展开的代码位于“根”上下文中。因此,任何
from_expansion
返回true
的 span 都可以假定具有相同的上下文。因此,仅使用span.from_expansion()
通常就足够了。 -
in_external_macro(span)
:检测给定的 span 是否来自在外部 crate 中定义的宏。如果您希望 lint 与宏生成的代码一起使用,这是避免当前 crate 中未定义的宏的下一道防线。 lint 程序员无法更改的代码是没有意义的。例如,您可能希望使用它来避免在其他 crate 的宏中开始 linting
#![allow(unused)] fn main() { use rustc_middle::lint::in_external_macro; use a_crate_with_macros::foo; // `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); }
-
span.ctxt()
:span 的上下文表示它是否来自展开,如果是,则表示展开它的内容SpanContext
的一个有用之处是检查两个 span 是否在同一个上下文中。例如,在a == b
中,a
和b
具有相同的上下文。在带有a == $b
的macro_rules!
中,$b
展开为某个与a
上下文不同的表达式。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!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());