@@ -150,9 +150,85 @@ if foo_span.in_external_macro(cx.sess().source_map()) {
150150}
151151```
152152
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+
153227[ `ctxt` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.ctxt
154228[ expansion ] : https://rustc-dev-guide.rust-lang.org/macro-expansion.html#expansion-and-ast-integration
155229[ `from_expansion` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
156230[ `in_external_macro` ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.in_external_macro
157231[ Span ] : https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
158232[ 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