@@ -150,9 +150,85 @@ if foo_span.in_external_macro(cx.sess().source_map()) {
150
150
}
151
151
```
152
152
153
+ ### The ` is_from_proc_macro ` function
154
+ A common point of confusion is the existence of [ ` is_from_proc_macro ` ]
155
+ and how it differs from the other [ ` in_external_macro ` ] /[ ` from_expansion ` ] functions.
156
+
157
+ While [ ` in_external_macro ` ] and [ ` from_expansion ` ] both work perfectly fine for detecting expanded code
158
+ from * declarative* macros (i.e. ` macro_rules! ` and macros 2.0),
159
+ detecting * proc macro* -generated code is a bit more tricky, as proc macros can (and often do)
160
+ freely manipulate the span of returned tokens.
161
+
162
+ In practice, this often happens through the use of [ ` quote::quote_spanned! ` ] with a span from the input tokens.
163
+
164
+ In those cases, there is no * reliable* way for the compiler (and tools like Clippy)
165
+ to distinguish code that comes from such a proc macro from code that the user wrote directly,
166
+ and [ ` in_external_macro ` ] will return ` false ` .
167
+
168
+ This is usually not an issue for the compiler and actually helps proc macro authors create better error messages,
169
+ as it allows associating parts of the expansion with parts of the macro input and lets the compiler
170
+ point the user to the relevant code in case of a compile error.
171
+
172
+ However, for Clippy this is inconvenient, because most of the time * we don't* want
173
+ to lint proc macro-generated code and this makes it impossible to tell what is and isn't proc macro code.
174
+
175
+ > NOTE: this is specifically only an issue when a proc macro explicitly sets the span to that of an ** input span** .
176
+ >
177
+ > For example, other common ways of creating ` TokenStream ` s, such as ` "fn foo() {...}".parse::<TokenStream>() ` ,
178
+ > sets each token's span to ` Span::call_site() ` , which already marks the span as coming from a proc macro
179
+ > and the usual span methods have no problem detecting that as a macro span.
180
+
181
+ As such, Clippy has its own ` is_from_proc_macro ` function which tries to * approximate*
182
+ whether a span comes from a proc macro, by checking whether the source text at the given span
183
+ lines up with the given AST node.
184
+
185
+ This function is typically used in combination with the other mentioned macro span functions,
186
+ but is usually called much later into the condition chain as it's a bit heavier than most other conditions,
187
+ so that the other cheaper conditions can fail faster. For example, the ` borrow_deref_ref ` lint:
188
+ ``` rs
189
+ impl <'tcx > LateLintPass <'tcx > for BorrowDerefRef {
190
+ fn check_expr (& mut self , cx : & LateContext <'tcx >, e : & rustc_hir :: Expr <'tcx >) {
191
+ if let ... = ...
192
+ && ...
193
+ && ! e . span. from_expansion ()
194
+ && ...
195
+ && ...
196
+ && ! is_from_proc_macro (cx , e )
197
+ && ...
198
+ {
199
+ ...
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Testing lints with macro expansions
206
+ To test that all of these cases are handled correctly in your lint,
207
+ we have a helper auxiliary crate that exposes various macros, used by tests like so:
208
+ ``` rust
209
+ // @aux-build:proc_macros.rs
210
+
211
+ extern crate proc_macros;
212
+
213
+ fn main () {
214
+ proc_macros :: external! { code_that_should_trigger_your_lint }
215
+ proc_macros :: with_span! { span code_that_should_trigger_your_lint }
216
+ }
217
+ ```
218
+ This exercises two cases:
219
+ - ` proc_macros::external! ` is a simple proc macro that echos the input tokens back but with a macro span:
220
+ this represents the usual, common case where an external macro expands to code that your lint would trigger,
221
+ and is correctly handled by ` in_external_macro ` and ` Span::from_expansion ` .
222
+
223
+ - ` proc_macros::with_span! ` echos back the input tokens starting from the second token
224
+ with the span of the first token: this is where the other functions will fail and ` is_from_proc_macro ` is needed
225
+
226
+
153
227
[ `ctxt` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.ctxt
154
228
[ expansion ] : https://rustc-dev-guide.rust-lang.org/macro-expansion.html#expansion-and-ast-integration
155
229
[ `from_expansion` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
156
230
[ `in_external_macro` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.in_external_macro
157
231
[ Span ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
158
232
[ SyntaxContext ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html
233
+ [ `is_from_proc_macro` ] : https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.is_from_proc_macro.html
234
+ [ `quote::quote_spanned!` ] : https://docs.rs/quote/latest/quote/macro.quote_spanned.html
0 commit comments