X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=doc%2Fcommon_tools_writing_lints.md;h=828bf4cbef9481526edc8840762be8421397dbe7;hb=764dd1360f1fb1540778592a36fefe71f713ca83;hp=1a6b7c8cb47a9db623f64fe5868d29d5f1dd5a40;hpb=f402edec1b0c89d58ce32734413d742a5006c27b;p=rust.git diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index 1a6b7c8cb47..828bf4cbef9 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -4,10 +4,11 @@ You may need following tooltips to catch up with common operations. - [Common tools for writing lints](#common-tools-for-writing-lints) - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) - - [Checking if an expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking for a specific type](#checking-for-a-specific-type) - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method) - - [Dealing with macros](#dealing-with-macros) + - [Dealing with macros](#dealing-with-macros-and-expansions) Useful Rustc dev guide links: - [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) @@ -15,7 +16,7 @@ Useful Rustc dev guide links: - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) -# Retrieving the type of an expression +## Retrieving the type of an expression Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: @@ -25,7 +26,7 @@ Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for ex - does it implement a trait? This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct, -that gives you access to the underlying structure [`TyS`][TyS]. +that gives you access to the underlying structure [`Ty`][Ty]. Example of use: ```rust @@ -54,18 +55,20 @@ Two noticeable items here: created by type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on. -# Checking if an expr is calling a specific method +## Checking if an expr is calling a specific method Starting with an `expr`, you can check whether it is calling a specific method `some_method`: ```rust -impl LateLintPass<'_> for MyStructLint { +impl<'tcx> LateLintPass<'tcx> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if_chain! { // Check our expr is calling a method - if let hir::ExprKind::MethodCall(path, _, _args, _) = &expr.kind; + if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind; // Check the name of this method is `some_method` if path.ident.name == sym!(some_method); + // Optionally, check the type of the self argument. + // - See "Checking for a specific type" then { // ... } @@ -74,7 +77,45 @@ impl LateLintPass<'_> for MyStructLint { } ``` -# Checking if a type implements a specific trait +## Checking for a specific type + +There are three ways to check if an expression type is a specific type we want to check for. +All of these methods only check for the base type, generic arguments have to be checked separately. + +```rust +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::{paths, match_def_path}; +use rustc_span::symbol::sym; +use rustc_hir::LangItem; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Getting the expression type + let ty = cx.typeck_results().expr_ty(expr); + + // 1. Using diagnostic items + // The last argument is the diagnostic item to check for + if is_type_diagnostic_item(cx, ty, sym::Option) { + // The type is an `Option` + } + + // 2. Using lang items + if is_type_lang_item(cx, ty, LangItem::RangeFull) { + // The type is a full range like `.drain(..)` + } + + // 3. Using the type path + // This method should be avoided if possible + if match_def_path(cx, def_id, &paths::RESULT) { + // The type is a `core::result::Result` + } + } +} +``` + +Prefer using diagnostic items and lang items where possible. + +## Checking if a type implements a specific trait There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither. @@ -102,6 +143,7 @@ impl LateLintPass<'_> for MyStructLint { // 3. Using the type path with the expression // we use `match_trait_method` function from Clippy's utils + // (This method should be avoided if possible) if match_trait_method(cx, expr, &paths::INTO) { // `expr` implements `Into` trait } @@ -114,7 +156,7 @@ impl LateLintPass<'_> for MyStructLint { We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined paths for Clippy can be found in [paths.rs][paths] -# Checking if a type defines a specific method +## Checking if a type defines a specific method To check if our type defines a method called `some_method`: @@ -140,66 +182,84 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { } ``` -# Dealing with macros - -There are several helpers in [`clippy_utils`][utils] to deal with macros: - -- `in_macro()`: detect if the given span is expanded by a macro - -You may want to use this for example to not start linting in any macro. - -```rust -macro_rules! foo { - ($param:expr) => { - match $param { - "bar" => println!("whatever"), - _ => () - } - }; -} - -foo!("bar"); - -// if we lint the `match` of `foo` call and test its span -assert_eq!(in_macro(match_span), true); -``` - -- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate - -You may want to use it for example to not start linting in macros from other crates - -```rust -#[macro_use] -extern crate a_crate_with_macros; - -// `foo` is defined in `a_crate_with_macros` -foo!("bar"); - -// if we lint the `match` of `foo` call and test its span -assert_eq!(in_external_macro(cx.sess(), match_span), true); -``` - -- `differing_macro_contexts()`: returns true if the two given spans are not from the same context - -```rust -macro_rules! m { - ($a:expr, $b:expr) => { - if $a.is_some() { - $b; - } - } -} - -let x: Option = Some(42); -m!(x, x.unwrap()); - -// These spans are not from the same context -// x.is_some() is from inside the macro -// x.unwrap() is from outside the macro -assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); -``` - -[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html +## Dealing with macros and expansions + +Keep in mind that macros are already expanded and desugaring is already applied +to the code representation that you are working with in Clippy. This unfortunately causes a lot of +false positives because macro expansions are "invisible" unless you actively check for them. +Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be +dynamic in ways that are difficult or impossible to see. +Use the following functions to deal with macros: + +- `span.from_expansion()`: detects if a span is from macro expansion or desugaring. + Checking this is a common first step in a lint. + + ```rust + if expr.span.from_expansion() { + // just forget it + return; + } + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it. + It is sometimes useful to check if the context of two spans are equal. + + ```rust + // expands to `1 + 0`, but don't lint + 1 + mac!() + ``` + ```rust + if left.span.ctxt() != right.span.ctxt() { + // the coder most likely cannot modify this expression + return; + } + ``` + Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can + be assumed to have the same context. And so just using `span.from_expansion()` is often good enough. + + +- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate. + If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros + not defined in the current crate. It doesn't make sense to lint code that the coder can't change. + + You may want to use it for example to not start linting in macros from other crates + + ```rust + #[macro_use] + extern crate a_crate_with_macros; + + // `foo` is defined in `a_crate_with_macros` + foo!("bar"); + + // if we lint the `match` of `foo` call and test its span + assert_eq!(in_external_macro(cx.sess(), match_span), true); + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it + +One thing `SpanContext` is useful for is to check if two spans are in the same context. For example, +in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some +expression with a different context from `a`. + + ```rust + macro_rules! m { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + } + } + + let x: Option = Some(42); + m!(x, x.unwrap()); + + // These spans are not from the same context + // x.is_some() is from inside the macro + // x.unwrap() is from outside the macro + assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt()); + ``` + +[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty @@ -207,4 +267,3 @@ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty [paths]: ../clippy_utils/src/paths.rs -[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs