clippy_utils/
check_proc_macro.rs

1//! This module handles checking if the span given is from a proc-macro or not.
2//!
3//! Proc-macros are capable of setting the span of every token they output to a few possible spans.
4//! This includes spans we can detect easily as coming from a proc-macro (e.g. the call site
5//! or the def site), and spans we can't easily detect as such (e.g. the span of any token
6//! passed into the proc macro). This capability means proc-macros are capable of generating code
7//! with a span that looks like it was written by the user, but which should not be linted by clippy
8//! as it was generated by an external macro.
9//!
10//! That brings us to this module. The current approach is to determine a small bit of text which
11//! must exist at both the start and the end of an item (e.g. an expression or a path) assuming the
12//! code was written, and check if the span contains that text. Note this will only work correctly
13//! if the span is not from a `macro_rules` based macro.
14
15use rustc_abi::ExternAbi;
16use rustc_ast::AttrStyle;
17use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy};
18use rustc_ast::token::CommentKind;
19use rustc_hir::intravisit::FnKind;
20use rustc_hir::{
21    Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl,
22    ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
23    TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
24};
25use rustc_lint::{EarlyContext, LateContext, LintContext};
26use rustc_middle::ty::TyCtxt;
27use rustc_session::Session;
28use rustc_span::symbol::{Ident, kw};
29use rustc_span::{Span, Symbol};
30
31/// The search pattern to look for. Used by `span_matches_pat`
32#[derive(Clone)]
33pub enum Pat {
34    /// A single string.
35    Str(&'static str),
36    /// Any of the given strings.
37    MultiStr(&'static [&'static str]),
38    /// Any of the given strings.
39    OwnedMultiStr(Vec<String>),
40    /// The string representation of the symbol.
41    Sym(Symbol),
42    /// Any decimal or hexadecimal digit depending on the location.
43    Num,
44}
45
46/// Checks if the start and the end of the span's text matches the patterns. This will return false
47/// if the span crosses multiple files or if source is not available.
48fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
49    let pos = sess.source_map().lookup_byte_offset(span.lo());
50    let Some(ref src) = pos.sf.src else {
51        return false;
52    };
53    let end = span.hi() - pos.sf.start_pos;
54    src.get(pos.pos.0 as usize..end.0 as usize).is_some_and(|s| {
55        // Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas.
56        let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
57        let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
58        (match start_pat {
59            Pat::Str(text) => start_str.starts_with(text),
60            Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
61            Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
62            Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
63            Pat::Num => start_str.as_bytes().first().is_some_and(u8::is_ascii_digit),
64        } && match end_pat {
65            Pat::Str(text) => end_str.ends_with(text),
66            Pat::MultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
67            Pat::OwnedMultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
68            Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
69            Pat::Num => end_str.as_bytes().last().is_some_and(u8::is_ascii_hexdigit),
70        })
71    })
72}
73
74/// Get the search patterns to use for the given literal
75fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
76    match lit {
77        LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
78        LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
79        LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
80        LitKind::ByteStr(_, StrStyle::Cooked) => (Pat::Str("b\""), Pat::Str("\"")),
81        LitKind::ByteStr(_, StrStyle::Raw(0)) => (Pat::Str("br\""), Pat::Str("\"")),
82        LitKind::ByteStr(_, StrStyle::Raw(_)) => (Pat::Str("br#\""), Pat::Str("#")),
83        LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
84        LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
85        LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
86        LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
87        LitKind::Int(..) => (Pat::Num, Pat::Num),
88        LitKind::Float(..) => (Pat::Num, Pat::Str("")),
89        LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
90        LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
91        _ => (Pat::Str(""), Pat::Str("")),
92    }
93}
94
95/// Get the search patterns to use for the given path
96fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
97    match path {
98        QPath::Resolved(ty, path) => {
99            let start = if ty.is_some() {
100                Pat::Str("<")
101            } else {
102                path.segments.first().map_or(Pat::Str(""), |seg| {
103                    if seg.ident.name == kw::PathRoot {
104                        Pat::Str("::")
105                    } else {
106                        Pat::Sym(seg.ident.name)
107                    }
108                })
109            };
110            let end = path.segments.last().map_or(Pat::Str(""), |seg| {
111                if seg.args.is_some() {
112                    Pat::Str(">")
113                } else {
114                    Pat::Sym(seg.ident.name)
115                }
116            });
117            (start, end)
118        },
119        QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
120        QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")),
121    }
122}
123
124fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) {
125    let (head, tail) = match path.segments {
126        [] => return (Pat::Str(""), Pat::Str("")),
127        [p] => (Pat::Sym(p.ident.name), p),
128        // QPath::Resolved can have a path that looks like `<Foo as Bar>::baz` where
129        // the path (`Bar::baz`) has it's span covering the whole QPath.
130        [.., tail] => (Pat::Str(""), tail),
131    };
132    (
133        head,
134        if tail.args.is_some() {
135            Pat::Str(">")
136        } else {
137            Pat::Sym(tail.ident.name)
138        },
139    )
140}
141
142/// Get the search patterns to use for the given expression
143fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
144    fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) {
145        // The expression can have subexpressions in different contexts, in which case
146        // building up a search pattern from the macro expansion would lead to false positives;
147        // e.g. `return format!(..)` would be considered to be from a proc macro
148        // if we build up a pattern for the macro expansion and compare it to the invocation `format!()`.
149        // So instead we return an empty pattern such that `span_matches_pat` always returns true.
150        if !e.span.eq_ctxt(outer_span) {
151            return (Pat::Str(""), Pat::Str(""));
152        }
153
154        match e.kind {
155            ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
156            // Parenthesis are trimmed from the text before the search patterns are matched.
157            // See: `span_matches_pat`
158            ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
159            ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1),
160            ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1),
161            ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1),
162            ExprKind::Lit(lit) => lit_search_pat(&lit.node),
163            ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
164            ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => {
165                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("("))
166            },
167            ExprKind::Call(first, [.., last])
168            | ExprKind::MethodCall(_, first, [.., last], _)
169            | ExprKind::Binary(_, first, last)
170            | ExprKind::Tup([first, .., last])
171            | ExprKind::Assign(first, last, _)
172            | ExprKind::AssignOp(_, first, last) => (
173                expr_search_pat_inner(tcx, first, outer_span).0,
174                expr_search_pat_inner(tcx, last, outer_span).1,
175            ),
176            ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span),
177            ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")),
178            ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1),
179            ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
180            ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
181            ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
182            ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
183            ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
184                (Pat::Str("for"), Pat::Str("}"))
185            },
186            ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
187            ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => {
188                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?"))
189            },
190            ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
191                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await"))
192            },
193            ExprKind::Closure(&Closure { body, .. }) => (
194                Pat::Str(""),
195                expr_search_pat_inner(tcx, tcx.hir().body(body).value, outer_span).1,
196            ),
197            ExprKind::Block(
198                Block {
199                    rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
200                    ..
201                },
202                None,
203            ) => (Pat::Str("unsafe"), Pat::Str("}")),
204            ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
205            ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)),
206            ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")),
207            ExprKind::Path(ref path) => qpath_search_pat(path),
208            ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1),
209            ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
210            ExprKind::Break(Destination { label: Some(name), .. }, None) => {
211                (Pat::Str("break"), Pat::Sym(name.ident.name))
212            },
213            ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1),
214            ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
215            ExprKind::Continue(Destination { label: Some(name), .. }) => {
216                (Pat::Str("continue"), Pat::Sym(name.ident.name))
217            },
218            ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
219            ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1),
220            ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
221            ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1),
222            _ => (Pat::Str(""), Pat::Str("")),
223        }
224    }
225
226    expr_search_pat_inner(tcx, e, e.span)
227}
228
229fn fn_header_search_pat(header: FnHeader) -> Pat {
230    if header.is_async() {
231        Pat::Str("async")
232    } else if header.is_const() {
233        Pat::Str("const")
234    } else if header.is_unsafe() {
235        Pat::Str("unsafe")
236    } else if header.abi != ExternAbi::Rust {
237        Pat::Str("extern")
238    } else {
239        Pat::MultiStr(&["fn", "extern"])
240    }
241}
242
243fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
244    let (start_pat, end_pat) = match &item.kind {
245        ItemKind::ExternCrate(_) => (Pat::Str("extern"), Pat::Str(";")),
246        ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
247        ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
248        ItemKind::Fn { sig, .. } => (fn_header_search_pat(sig.header), Pat::Str("")),
249        ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
250        ItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
251        ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
252        ItemKind::Struct(VariantData::Struct { .. }, _) => (Pat::Str("struct"), Pat::Str("}")),
253        ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
254        ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
255        ItemKind::Trait(_, Safety::Unsafe, ..)
256        | ItemKind::Impl(Impl {
257            safety: Safety::Unsafe, ..
258        }) => (Pat::Str("unsafe"), Pat::Str("}")),
259        ItemKind::Trait(IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
260        ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
261        ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
262        _ => return (Pat::Str(""), Pat::Str("")),
263    };
264    if item.vis_span.is_empty() {
265        (start_pat, end_pat)
266    } else {
267        (Pat::Str("pub"), end_pat)
268    }
269}
270
271fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
272    match &item.kind {
273        TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
274        TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
275        TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
276    }
277}
278
279fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
280    let (start_pat, end_pat) = match &item.kind {
281        ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
282        ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
283        ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
284    };
285    if item.vis_span.is_empty() {
286        (start_pat, end_pat)
287    } else {
288        (Pat::Str("pub"), end_pat)
289    }
290}
291
292fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
293    if def.vis_span.is_empty() {
294        if def.is_positional() {
295            (Pat::Str(""), Pat::Str(""))
296        } else {
297            (Pat::Sym(def.ident.name), Pat::Str(""))
298        }
299    } else {
300        (Pat::Str("pub"), Pat::Str(""))
301    }
302}
303
304fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
305    match v.data {
306        VariantData::Struct { .. } => (Pat::Sym(v.ident.name), Pat::Str("}")),
307        VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
308        VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
309    }
310}
311
312fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
313    let (start_pat, end_pat) = match kind {
314        FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
315        FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
316        FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
317    };
318    let start_pat = match tcx.hir_node(hir_id) {
319        Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => {
320            if vis_span.is_empty() {
321                start_pat
322            } else {
323                Pat::Str("pub")
324            }
325        },
326        Node::TraitItem(_) => start_pat,
327        _ => Pat::Str(""),
328    };
329    (start_pat, end_pat)
330}
331
332fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) {
333    match attr.kind {
334        AttrKind::Normal(..) => {
335            if let Some(ident) = attr.ident() {
336                // NOTE: This will likely have false positives, like `allow = 1`
337                let ident_string = ident.to_string();
338                if attr.style == AttrStyle::Outer {
339                    (
340                        Pat::OwnedMultiStr(vec!["#[".to_owned() + &ident_string, ident_string]),
341                        Pat::Str(""),
342                    )
343                } else {
344                    (
345                        Pat::OwnedMultiStr(vec!["#![".to_owned() + &ident_string, ident_string]),
346                        Pat::Str(""),
347                    )
348                }
349            } else {
350                (Pat::Str("#"), Pat::Str("]"))
351            }
352        },
353        AttrKind::DocComment(_kind @ CommentKind::Line, ..) => {
354            if attr.style == AttrStyle::Outer {
355                (Pat::Str("///"), Pat::Str(""))
356            } else {
357                (Pat::Str("//!"), Pat::Str(""))
358            }
359        },
360        AttrKind::DocComment(_kind @ CommentKind::Block, ..) => {
361            if attr.style == AttrStyle::Outer {
362                (Pat::Str("/**"), Pat::Str("*/"))
363            } else {
364                (Pat::Str("/*!"), Pat::Str("*/"))
365            }
366        },
367    }
368}
369
370fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
371    match ty.kind {
372        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
373        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1),
374        TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1),
375        TyKind::BareFn(bare_fn) => (
376            if bare_fn.safety.is_unsafe() {
377                Pat::Str("unsafe")
378            } else if bare_fn.abi != ExternAbi::Rust {
379                Pat::Str("extern")
380            } else {
381                Pat::MultiStr(&["fn", "extern"])
382            },
383            match bare_fn.decl.output {
384                FnRetTy::DefaultReturn(_) => {
385                    if let [.., ty] = bare_fn.decl.inputs {
386                        ty_search_pat(ty).1
387                    } else {
388                        Pat::Str("(")
389                    }
390                },
391                FnRetTy::Return(ty) => ty_search_pat(ty).1,
392            },
393        ),
394        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
395        // Parenthesis are trimmed from the text before the search patterns are matched.
396        // See: `span_matches_pat`
397        TyKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
398        TyKind::Tup([ty]) => ty_search_pat(ty),
399        TyKind::Tup([head, .., tail]) => (ty_search_pat(head).0, ty_search_pat(tail).1),
400        TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
401        TyKind::Path(qpath) => qpath_search_pat(&qpath),
402        TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
403        TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
404            (Pat::Str("dyn"), Pat::Str(""))
405        },
406        // NOTE: `TraitObject` is incomplete. It will always return true then.
407        _ => (Pat::Str(""), Pat::Str("")),
408    }
409}
410
411fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
412    (Pat::Sym(ident.name), Pat::Sym(ident.name))
413}
414
415pub trait WithSearchPat<'cx> {
416    type Context: LintContext;
417    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
418    fn span(&self) -> Span;
419}
420macro_rules! impl_with_search_pat {
421    (($cx_ident:ident: $cx_ty:ident<$cx_lt:lifetime>, $self:tt: $ty:ty) => $fn:ident($($args:tt)*)) => {
422        impl<$cx_lt> WithSearchPat<$cx_lt> for $ty {
423            type Context = $cx_ty<$cx_lt>;
424            fn search_pat(&$self, $cx_ident: &Self::Context) -> (Pat, Pat) {
425                $fn($($args)*)
426            }
427            fn span(&self) -> Span {
428                self.span
429            }
430        }
431    };
432}
433impl_with_search_pat!((cx: LateContext<'tcx>, self: Expr<'tcx>) => expr_search_pat(cx.tcx, self));
434impl_with_search_pat!((_cx: LateContext<'tcx>, self: Item<'_>) => item_search_pat(self));
435impl_with_search_pat!((_cx: LateContext<'tcx>, self: TraitItem<'_>) => trait_item_search_pat(self));
436impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_search_pat(self));
437impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self));
438impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self));
439impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self));
440impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self));
441impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node));
442impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
443
444impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
445
446impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
447    type Context = LateContext<'cx>;
448
449    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
450        fn_kind_pat(cx.tcx, self.0, self.1, self.2)
451    }
452
453    fn span(&self) -> Span {
454        self.3
455    }
456}
457
458/// Checks if the item likely came from a proc-macro.
459///
460/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
461/// it is significantly slower than both of those.
462pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
463    let (start_pat, end_pat) = item.search_pat(cx);
464    !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
465}
466
467/// Checks if the span actually refers to a match expression
468pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
469    span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
470}
471
472/// Checks if the span actually refers to an if expression
473pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
474    span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
475}