Type Checking
When we work on a new lint or improve an existing lint, we might want
to retrieve the type Ty of an expression Expr for a variety of
reasons. This can be achieved by utilizing the LateContext
that is available for LateLintPass.
LateContext and TypeckResults
The lint context LateContext and TypeckResults
(returned by LateContext::typeck_results) are the two most useful data structures
in LateLintPass. They allow us to jump to type definitions and other compilation
stages such as HIR.
Note:
LateContext.typeck_results's return value isTypeckResultsand is created in the type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on.
TypeckResults contains useful methods such as expr_ty,
which gives us access to the underlying structure Ty of a given expression.
#![allow(unused)] fn main() { pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx> }
As a side note, besides expr_ty, TypeckResults contains a
pat_ty() method that is useful for retrieving a type from a pattern.
Ty
Ty struct contains the type information of an expression.
Let's take a look at rustc_middle's Ty struct to examine this struct:
#![allow(unused)] fn main() { pub struct Ty<'tcx>(Interned<'tcx, WithStableHash<TyS<'tcx>>>); }
At a first glance, this struct looks quite esoteric. But at a closer look, we will see that this struct contains many useful methods for type checking.
For instance, is_char checks if the given Ty struct corresponds
to the primitive character type.
is_* Usage
In some scenarios, all we need to do is check if the Ty of an expression
is a specific type, such as char type, so we could write the following:
#![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!"); } } } }
Furthermore, if we examine the source code for is_char,
we find something very interesting:
#![allow(unused)] fn main() { #[inline] pub fn is_char(self) -> bool { matches!(self.kind(), Char) } }
Indeed, we just discovered Ty's kind method, which provides us
with TyKind of a Ty.
TyKind
TyKind defines the kinds of types in Rust's type system.
Peeking into TyKind documentation, we will see that it is an
enum of 27 variants, including items such as Bool, Int, Ref, etc.
kind Usage
The TyKind of Ty can be returned by calling Ty.kind method.
We often use this method to perform pattern matching in Clippy.
For instance, if we want to check for a struct, we could examine if the
ty.kind corresponds to an Adt (algebraic data type) and if its
AdtDef is a struct:
#![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::Ty and ty::Ty
We've been talking about ty::Ty this whole time without addressing hir::Ty, but the latter
is also important to understand.
hir::Ty would represent what an user wrote, while ty::Ty would understand the meaning of it (because it has more
information).
Example: fn foo(x: u32) -> u32 { x }
Here the HIR sees the types without "thinking" about them, it knows that the function takes an u32 and returns
an u32. But at the ty::Ty level the compiler understands that they're the same type, in-depth lifetimes, etc...
you can use the hir_ty_to_ty function to convert from a hir::Ty to a ty::Ty
Useful Links
Below are some useful links to further explore the concepts covered in this chapter: