Skip to content

Commit 5b1b6a2

Browse files
committed
Edit docs about macros
1 parent 14d54f0 commit 5b1b6a2

File tree

1 file changed

+57
-44
lines changed

1 file changed

+57
-44
lines changed

doc/common_tools_writing_lints.md

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ You may need following tooltips to catch up with common operations.
88
- [Checking for a specific type](#checking-for-a-specific-type)
99
- [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
1010
- [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method)
11-
- [Dealing with macros](#dealing-with-macros)
11+
- [Dealing with macros](#dealing-with-macros-and-expansions)
1212

1313
Useful Rustc dev guide links:
1414
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
@@ -182,64 +182,78 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
182182
}
183183
```
184184

185-
## Dealing with macros
185+
## Dealing with macros and expansions
186186

187-
There are several helpers in [`clippy_utils`][utils] to deal with macros:
187+
Keep in mind that macros are already expanded and desugaring is already applied
188+
to the code representation that you are working with in Clippy. This unfortunately causes a lot of
189+
false positives because macro expansions are "invisible" unless you actively check for them.
190+
Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
191+
dynamic in ways that are difficult or impossible to see.
192+
Use the following functions to deal with macros:
188193

189-
- `in_macro()`: detect if the given span is expanded by a macro
194+
- `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
195+
Checking this is a common first step in a lint.
190196

191-
You may want to use this for example to not start linting in any macro.
197+
```rust
198+
if expr.span.from_expansion() {
199+
// just forget it
200+
return;
201+
}
202+
```
192203

193-
```rust
194-
macro_rules! foo {
195-
($param:expr) => {
196-
match $param {
197-
"bar" => println!("whatever"),
198-
_ => ()
199-
}
200-
};
201-
}
204+
- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
205+
It is sometimes useful to check if the context of two spans are equal.
202206

203-
foo!("bar");
207+
```rust
208+
// expands to `1 + 0`, but don't lint
209+
1 + mac!()
210+
```
211+
```rust
212+
if left.span.ctxt() != right.span.ctxt() {
213+
// the coder most likely cannot modify this expression
214+
return;
215+
}
216+
```
217+
Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
218+
be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
204219

205-
// if we lint the `match` of `foo` call and test its span
206-
assert_eq!(in_macro(match_span), true);
207-
```
208220

209-
- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate
221+
- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
222+
If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
223+
not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
210224

211-
You may want to use it for example to not start linting in macros from other crates
225+
You may want to use it for example to not start linting in macros from other crates
212226

213-
```rust
214-
#[macro_use]
215-
extern crate a_crate_with_macros;
227+
```rust
228+
#[macro_use]
229+
extern crate a_crate_with_macros;
216230

217-
// `foo` is defined in `a_crate_with_macros`
218-
foo!("bar");
231+
// `foo` is defined in `a_crate_with_macros`
232+
foo!("bar");
219233

220-
// if we lint the `match` of `foo` call and test its span
221-
assert_eq!(in_external_macro(cx.sess(), match_span), true);
222-
```
234+
// if we lint the `match` of `foo` call and test its span
235+
assert_eq!(in_external_macro(cx.sess(), match_span), true);
236+
```
223237

224238
- `differing_macro_contexts()`: returns true if the two given spans are not from the same context
225239

226-
```rust
227-
macro_rules! m {
228-
($a:expr, $b:expr) => {
229-
if $a.is_some() {
230-
$b;
231-
}
232-
}
233-
}
240+
```rust
241+
macro_rules! m {
242+
($a:expr, $b:expr) => {
243+
if $a.is_some() {
244+
$b;
245+
}
246+
}
247+
}
234248

235-
let x: Option<u32> = Some(42);
236-
m!(x, x.unwrap());
249+
let x: Option<u32> = Some(42);
250+
m!(x, x.unwrap());
237251

238-
// These spans are not from the same context
239-
// x.is_some() is from inside the macro
240-
// x.unwrap() is from outside the macro
241-
assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
242-
```
252+
// These spans are not from the same context
253+
// x.is_some() is from inside the macro
254+
// x.unwrap() is from outside the macro
255+
assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
256+
```
243257

244258
[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
245259
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
@@ -249,4 +263,3 @@ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
249263
[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
250264
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
251265
[paths]: ../clippy_utils/src/paths.rs
252-
[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs

0 commit comments

Comments
 (0)