diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index 552b9b2587b8..8b70dae0b494 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -8,7 +8,7 @@ You may need following tooltips to catch up with common operations. - [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) @@ -182,64 +182,76 @@ 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); -``` +## 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); + ``` + + ```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 [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html @@ -249,4 +261,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