类型检查

当我们开发一个新的 lint 或者改进现有的 lint 时,我们可能需要检索表达式 Expr 的类型 Ty,这有很多原因。这可以通过利用 LateContext 来实现,LateContext 可用于 LateLintPass

LateContextTypeckResults

lint 上下文 LateContextTypeckResults (由 LateContext::typeck_results 返回) 是 LateLintPass 中两个最有用的数据结构。它们允许我们跳转到类型定义和其他编译阶段,例如 HIR。

注意:LateContext.typeck_results 的返回值是 TypeckResults,它是在类型检查步骤中创建的,它包含有用的信息,例如表达式的类型、解析方法的方式等等。

TypeckResults 包含有用的方法,例如 expr_ty,它使我们可以访问给定表达式的底层结构 Ty

#![allow(unused)] fn main() { pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx> }

作为旁注,除了 expr_ty 之外,TypeckResults 还包含一个 pat_ty() 方法,该方法对于从模式中检索类型很有用。

Ty

Ty 结构体包含表达式的类型信息。让我们看看 rustc_middleTy 结构体来检查这个结构体

#![allow(unused)] fn main() { pub struct Ty<'tcx>(Interned<'tcx, WithStableHash<TyS<'tcx>>>); }

乍一看,这个结构体看起来相当深奥。但仔细一看,我们会发现这个结构体包含许多用于类型检查的有用方法。

例如,is_char 检查给定的 Ty 结构体是否对应于原始字符类型。

is_* 用法

在某些情况下,我们只需要检查表达式的 Ty 是否是特定类型,例如 char 类型,因此我们可以编写以下代码

#![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); // Check if the `Ty` of this expression is of character type if ty.is_char() { println!("Our expression is a char!"); } } } }

此外,如果我们查看 is_char 的源代码,我们会发现一些非常有趣的东西

#![allow(unused)] fn main() { #[inline] pub fn is_char(self) -> bool { matches!(self.kind(), Char) } }

实际上,我们刚刚发现了 Tykind() 方法,它为我们提供了 TyTyKind

TyKind

TyKind 定义了 Rust 类型系统中类型的种类。查看 TyKind 文档,我们会看到它是一个包含超过 25 个变体的枚举,包括 BoolIntRef 等项。

kind 用法

TyTyKind 可以通过调用 Ty.kind() 方法 返回。我们经常使用此方法在 Clippy 中执行模式匹配。

例如,如果我们想检查一个 struct,我们可以检查 ty.kind 是否对应于 Adt (代数数据类型),以及它的 AdtDef 是否是一个结构体

#![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 the type match ty.kind() { ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), _ => () } } } }

hir::Tyty::Ty

我们一直在谈论 ty::Ty,但没有提及 hir::Ty,但后者对于理解也很重要。

hir::Ty 表示用户所写的内容,而 ty::Ty 是编译器看待类型的方式,并且包含更多信息。例如

#![allow(unused)] fn main() { fn foo(x: u32) -> u32 { x } }

在这里,HIR 查看类型时没有“思考”它们,它知道函数接受 u32 并返回 u32。就 hir::Ty 而言,这些可能是不同的类型。但在 ty::Ty 级别,编译器理解它们是相同的类型,深入的生命周期等等...

要从 hir::Ty 获取 ty::Ty,您可以在函数体外部使用 lower_ty 函数,或者在函数体内部使用 TypeckResults::node_type() 方法。

警告:不要在函数体内部使用 lower_ty,因为这可能会导致 ICE。

程序化地创建类型

程序化地创建类型的常见用例是当我们想要检查类型是否实现了 trait (参见 Trait 检查)。

这是一个如何为 u8 切片创建 Ty 的示例,即 [u8]

#![allow(unused)] fn main() { use rustc_middle::ty::Ty; // assume we have access to a LateContext let ty = Ty::new_slice(cx.tcx, Ty::new_u8()); }

通常,我们依赖于 Ty::new_* 方法。这些方法定义了类型系统和 trait 系统用于定义和理解编写代码的基本构建块。

以下是一些有用的链接,可用于进一步探索本章涵盖的概念