Skip to content

Commit 9b4d239

Browse files
committed
Edit docs about macros
1 parent a8fee73 commit 9b4d239

File tree

1 file changed

+71
-59
lines changed

1 file changed

+71
-59
lines changed

doc/common_tools_writing_lints.md

Lines changed: 71 additions & 59 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,76 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
182182
}
183183
```
184184

185-
## Dealing with macros
186-
187-
There are several helpers in [`clippy_utils`][utils] to deal with macros:
188-
189-
- `in_macro()`: detect if the given span is expanded by a macro
190-
191-
You may want to use this for example to not start linting in any macro.
192-
193-
```rust
194-
macro_rules! foo {
195-
($param:expr) => {
196-
match $param {
197-
"bar" => println!("whatever"),
198-
_ => ()
199-
}
200-
};
201-
}
202-
203-
foo!("bar");
204-
205-
// if we lint the `match` of `foo` call and test its span
206-
assert_eq!(in_macro(match_span), true);
207-
```
208-
209-
- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate
210-
211-
You may want to use it for example to not start linting in macros from other crates
212-
213-
```rust
214-
#[macro_use]
215-
extern crate a_crate_with_macros;
216-
217-
// `foo` is defined in `a_crate_with_macros`
218-
foo!("bar");
219-
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-
```
223-
224-
- `differing_macro_contexts()`: returns true if the two given spans are not from the same context
225-
226-
```rust
227-
macro_rules! m {
228-
($a:expr, $b:expr) => {
229-
if $a.is_some() {
230-
$b;
231-
}
232-
}
233-
}
234-
235-
let x: Option<u32> = Some(42);
236-
m!(x, x.unwrap());
237-
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-
```
185+
## Dealing with macros and expansions
186+
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:
193+
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.
196+
197+
```rust
198+
if expr.span.from_expansion() {
199+
// just forget it
200+
return;
201+
}
202+
```
203+
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.
206+
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.
219+
220+
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.
224+
225+
You may want to use it for example to not start linting in macros from other crates
226+
227+
```rust
228+
#[macro_use]
229+
extern crate a_crate_with_macros;
230+
231+
// `foo` is defined in `a_crate_with_macros`
232+
foo!("bar");
233+
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+
```
237+
238+
```rust
239+
macro_rules! m {
240+
($a:expr, $b:expr) => {
241+
if $a.is_some() {
242+
$b;
243+
}
244+
}
245+
}
246+
247+
let x: Option<u32> = Some(42);
248+
m!(x, x.unwrap());
249+
250+
// These spans are not from the same context
251+
// x.is_some() is from inside the macro
252+
// x.unwrap() is from outside the macro
253+
assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
254+
```
243255

244256
[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
245257
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html

0 commit comments

Comments
 (0)