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 isTypeckResults
and 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.
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:
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_*
UsageIn 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:
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:
#[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 over 25 variants, including items such as Bool
, Int
, Ref
, etc.
kind
UsageThe 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:
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 the user wrote, while ty::Ty
is how the compiler sees the type and 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
. As far as hir::Ty
is concerned those might be different types. But at the ty::Ty
level the compiler understands that they're the same type, in-depth lifetimes, etc...
To get from a hir::Ty
to a ty::Ty
, you can use the hir_ty_to_ty
function outside of bodies or the TypeckResults::node_type()
method inside of bodies.
Warning: Don't use
hir_ty_to_ty
inside of bodies, because this can cause ICEs.
Below are some useful links to further explore the concepts covered in this chapter: