blob: 714607ef25e5f726aa6910e2a52406da19f8be77 [file] [log] [blame] [view] [edit]
# Trait Checking
Besides [type checking](type_checking.md), we might want to examine if
a specific type `Ty` implements certain trait when implementing a lint.
There are three approaches to achieve this, depending on if the target trait
that we want to examine has a [diagnostic item][diagnostic_items],
[lang item][lang_items], or neither.
## Using Diagnostic Items
As explained in the [Rust Compiler Development Guide][rustc_dev_guide], diagnostic items
are introduced for identifying types via [Symbols][symbol].
For instance, if we want to examine whether an expression implements
the `Iterator` trait, we could simply write the following code,
providing the `LateContext` (`cx`), our expression at hand, and
the symbol of the trait in question:
```rust
use clippy_utils::sym;
use clippy_utils::ty::implements_trait;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckIteratorTraitLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let implements_iterator = (cx.tcx.get_diagnostic_item(sym::Iterator))
.is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[]));
if implements_iterator {
// [...]
}
}
}
```
> **Note**: Refer to [this index][symbol_index] for all the defined `Symbol`s.
## Using Lang Items
Besides diagnostic items, we can also use [`lang_items`][lang_items].
Take a look at the documentation to find that `LanguageItems` contains
all language items defined in the compiler.
Using one of its `*_trait` method, we could obtain the [DefId] of any
specific item, such as `Clone`, `Copy`, `Drop`, `Eq`, which are familiar
to many Rustaceans.
For instance, if we want to examine whether an expression `expr` implements
`Drop` trait, we could access `LanguageItems` via our `LateContext`'s
[TyCtxt], which provides a `lang_items` method that will return the id of
`Drop` trait to us. Then, by calling Clippy utils function `implements_trait`
we can check that the `Ty` of the `expr` implements the trait:
```rust
use clippy_utils::ty::implements_trait;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckDropTraitLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if cx.tcx.lang_items()
.drop_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[])) {
println!("`expr` implements `Drop` trait!");
}
}
}
```
## Using Type Path
If neither diagnostic item nor a language item is available, we can use
[`clippy_utils::paths`][paths] to determine get a trait's `DefId`.
> **Note**: This approach should be avoided if possible, the best thing to do would be to make a PR to [`rust-lang/rust`][rust] adding a diagnostic item.
Below, we check if the given `expr` implements [`core::iter::Step`](https://doc.rust-lang.org/std/iter/trait.Step.html):
```rust
use clippy_utils::paths;
use clippy_utils::ty::implements_trait;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckIterStep {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if let Some(trait_def_id) = paths::ITER_STEP.first(cx)
&& implements_trait(cx, ty, trait_def_id, &[])
{
println!("`expr` implements the `core::iter::Step` trait!");
}
}
}
```
## Creating Types Programmatically
Traits are often generic over a type parameter, e.g. `Borrow<T>` is generic
over `T`. Rust allows us to implement a trait for a specific type. For example,
we can implement `Borrow<[u8]>` for a hypothetical type `Foo`. Let's suppose
that we would like to find whether our type actually implements `Borrow<[u8]>`.
To do so, we can use the same `implements_trait` function as above, and supply
a type parameter that represents `[u8]`. Since `[u8]` is a specialization of
`[T]`, we can use the [`Ty::new_slice`][new_slice] method to create a type
that represents `[T]` and supply `u8` as a type parameter.
To create a `ty::Ty` programmatically, we rely on `Ty::new_*` methods. These
methods create a `TyKind` and then wrap it in a `Ty` struct. This means we
have access to all the primitive types, such as `Ty::new_char`,
`Ty::new_bool`, `Ty::new_int`, etc. We can also create more complex types,
such as slices, tuples, and references out of these basic building blocks.
For trait checking, it is not enough to create the types, we need to convert
them into [GenericArg]. In rustc, a generic is an entity that the compiler
understands and has three kinds, type, const and lifetime. By calling
`.into()` on a constructed [Ty], we wrap the type into a generic which can
then be used by the query system to decide whether the specialized trait
is implemented.
The following code demonstrates how to do this:
```rust
use rustc_middle::ty::Ty;
use clippy_utils::sym;
use clippy_utils::ty::implements_trait;
let ty = todo!("Get the `Foo` type to check for a trait implementation");
let borrow_id = cx.tcx.get_diagnostic_item(sym::Borrow).unwrap(); // avoid unwrap in real code
let slice_of_bytes_t = Ty::new_slice(cx.tcx, cx.tcx.types.u8);
let generic_param = slice_of_bytes_t.into();
if implements_trait(cx, ty, borrow_id, &[generic_param]) {
todo!("Rest of lint implementation")
}
```
In essence, the [Ty] struct allows us to create types programmatically in a
representation that can be used by the compiler and the query engine. We then
use the `rustc_middle::Ty` of the type we are interested in, and query the
compiler to see if it indeed implements the trait we are interested in.
[DefId]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def_id/struct.DefId.html
[diagnostic_items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
[lang_items]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/lang_items/struct.LanguageItems.html
[paths]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/paths.rs
[rustc_dev_guide]: https://rustc-dev-guide.rust-lang.org/
[symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html
[symbol_index]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/sym/index.html
[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
[rust]: https://github.com/rust-lang/rust
[new_slice]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.new_slice
[GenericArg]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.GenericArg.html