类型检查

当我们开发一个新的 lint 或改进现有的 lint 时,我们可能需要出于各种原因检索表达式 Expr 的类型 Ty。这可以通过利用 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 时(请参阅特性检查)。

以下是如何为 u8 切片(即 [u8])创建 Ty 的示例:

#![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_* 方法。这些方法定义了类型系统和特性系统用于定义和理解编写代码的基本构建块。

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