Lint passes(Lint 传递)

在着手开发新的 lint 的逻辑之前,每个 Clippy 开发者都必须做出一个重要的决定:使用 EarlyLintPass 还是 LateLintPass

简而言之,LateLintPass 可以访问类型和符号信息,而 EarlyLintPass 则不能。如果您不需要访问类型信息,请使用 EarlyLintPass

让我们在下面更详细地介绍这两个 trait。

EarlyLintPass

如果您仔细查看 EarlyLintPass 的文档,您会看到为此 trait 定义的每个方法都使用 EarlyContext。在 EarlyContext 的文档中,它指出

AST 的 lint 检查上下文,在扩展之后,降级为 HIR 之前。

瞧。EarlyLintPass 仅在抽象语法树(AST)级别上工作。AST 是在代码编译的 词法分析和解析 阶段生成的。因此,它不知道符号的含义或有关类型的信息,如果 lint 仅处理语法相关的问题,它应该是我们为新 lint 选择的 trait。

虽然 lint 速度不是 Clippy 关注的问题,但 EarlyLintPass 更快,如果您确定某个 lint 不需要类型信息,它应该是您的选择。

提醒一下,运行以下命令以生成使用 EarlyLintPass 的 lint 的样板代码

$ cargo dev new_lint --name=<your_new_lint> --pass=early --category=<your_category_choice>

EarlyLintPass 的示例

看一下下面的代码

#![allow(unused)]
fn main() {
let x = OurUndefinedType;
x.non_existing_method();
}

从 AST 的角度来看,这两行代码在“语法上”都是正确的。赋值使用 let 并以分号结尾。方法的调用看起来也不错。作为程序员,我们可能已经提出了一些问题,但解析器可以接受它。这就是我们所说的 EarlyLintPass 仅在 AST 级别处理语法。

或者,考虑我们在 定义新 Lint 章节中提到的 foo_functions lint。

我们希望 foo_functions lint 检测名称为 foo 的函数。编写一个仅检查函数名称的 lint 意味着我们仅使用 AST,而无需访问类型系统(类型系统是 LateLintPass 发挥作用的地方)。

LateLintPass

EarlyLintPass 相比,LateLintPass 包含类型信息。

如果您仔细查看 LateLintPass 的文档,您会看到此 trait 中定义的每个方法都使用 LateContext

LateContext 的文档中,我们会发现处理类型检查的方法,这些方法在 EarlyContext 中不存在,例如

LateLintPass 的示例

让我们看下面的例子

#![allow(unused)]
fn main() {
let x = OurUndefinedType;
x.non_existing_method();
}

从 AST 的角度来看,这两行代码在语法上是正确的代码。我们有一个赋值,并在变量(具有类型)上调用方法。从语法上讲,解析器的一切都井井有条。

但是,深入一层并查看类型信息,编译器会注意到 OurUndefinedTypenon_existing_method() 都是未定义的

作为 Clippy 开发者,要访问此类类型信息,我们必须在我们的 lint 上实现 LateLintPass。当您浏览 Clippy 的 lint 时,您会注意到几乎每个 lint 都是在 LateLintPass 中实现的,特别是因为我们经常需要检查的不仅是语法问题,还有类型信息。

EarlyLintPass 的另一个限制是节点仅通过它们在 AST 中的位置来标识。这意味着您不能只获取一个 id 并请求某个节点。对于大多数 lint 来说,这没问题,但是我们有一些 lint 需要检查其他节点,这在 HIR 级别更容易。在这些情况下,LateLintPass 是更好的选择。

提醒一下,运行以下命令以生成使用 LateLintPass 的 lint 的样板代码

$ cargo dev new_lint --name=<your_new_lint> --pass=late --category=<your_category_choice>