Skip to main content

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 as ast;
17use rustc_ast::AttrStyle;
18use rustc_ast::ast::{
19    AttrKind, Attribute, BindingMode, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy,
20};
21use rustc_ast::token::CommentKind;
22use rustc_hir::intravisit::FnKind;
23use rustc_hir::{
24    Block, BlockCheckMode, Body, BoundConstness, BoundPolarity, Closure, Destination, Expr, ExprKind, FieldDef,
25    FnHeader, FnRetTy, HirId, Impl, ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource,
26    MatchSource, MutTy, Node, PatExpr, PatExprKind, PatKind, Path, PolyTraitRef, QPath, Safety, TraitBoundModifiers,
27    TraitImplHeader, TraitItem, TraitItemKind, TraitRef, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData,
28    YieldSource,
29};
30use rustc_lint::{EarlyContext, LateContext, LintContext};
31use rustc_middle::ty::TyCtxt;
32use rustc_session::Session;
33use rustc_span::symbol::{Ident, kw};
34use rustc_span::{Span, Symbol, sym};
35
36/// The search pattern to look for. Used by `span_matches_pat`
37#[derive(Clone)]
38pub enum Pat {
39    /// A single string.
40    Str(&'static str),
41    /// Any of the given strings.
42    MultiStr(&'static [&'static str]),
43    /// Any of the given strings.
44    OwnedMultiStr(Vec<String>),
45    /// The string representation of the symbol.
46    Sym(Symbol),
47    /// Any decimal or hexadecimal digit depending on the location.
48    Num,
49    /// An attribute.
50    Attr(Symbol),
51}
52
53/// Checks if the start and the end of the span's text matches the patterns. This will return false
54/// if the span crosses multiple files or if source is not available.
55fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
56    let pos = sess.source_map().lookup_byte_offset(span.lo());
57    let Some(ref src) = pos.sf.src else {
58        return false;
59    };
60    let end = span.hi() - pos.sf.start_pos;
61    src.get(pos.pos.0 as usize..end.0 as usize).is_some_and(|s| {
62        // Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas.
63        let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
64        let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
65        (match start_pat {
66            Pat::Str(text) => start_str.starts_with(text),
67            Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
68            Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
69            Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
70            Pat::Num => start_str.as_bytes().first().is_some_and(u8::is_ascii_digit),
71            Pat::Attr(sym) => {
72                let start_str = start_str
73                    .strip_prefix("#[")
74                    .or_else(|| start_str.strip_prefix("#!["))
75                    .unwrap_or(start_str);
76                start_str.trim_start().starts_with(sym.as_str())
77            },
78        } && match end_pat {
79            Pat::Str(text) => end_str.ends_with(text),
80            Pat::MultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
81            Pat::OwnedMultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
82            Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
83            Pat::Num => end_str.as_bytes().last().is_some_and(u8::is_ascii_hexdigit),
84            Pat::Attr(_) => false,
85        })
86    })
87}
88
89/// Get the search patterns to use for the given literal
90fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
91    match lit {
92        LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
93        LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
94        LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
95        LitKind::ByteStr(_, StrStyle::Cooked) => (Pat::Str("b\""), Pat::Str("\"")),
96        LitKind::ByteStr(_, StrStyle::Raw(0)) => (Pat::Str("br\""), Pat::Str("\"")),
97        LitKind::ByteStr(_, StrStyle::Raw(_)) => (Pat::Str("br#\""), Pat::Str("#")),
98        LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
99        LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
100        LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
101        LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
102        LitKind::Int(..) => (Pat::Num, Pat::Num),
103        LitKind::Float(..) => (Pat::Num, Pat::Str("")),
104        LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
105        LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
106        _ => (Pat::Str(""), Pat::Str("")),
107    }
108}
109
110/// Get the search patterns to use for the given path
111fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
112    match path {
113        QPath::Resolved(ty, path) => {
114            let start = if ty.is_some() {
115                Pat::Str("<")
116            } else {
117                path.segments.first().map_or(Pat::Str(""), |seg| {
118                    if seg.ident.name == kw::PathRoot {
119                        Pat::Str("::")
120                    } else {
121                        Pat::Sym(seg.ident.name)
122                    }
123                })
124            };
125            let end = path.segments.last().map_or(Pat::Str(""), |seg| {
126                if seg.args.is_some() {
127                    Pat::Str(">")
128                } else {
129                    Pat::Sym(seg.ident.name)
130                }
131            });
132            (start, end)
133        },
134        QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
135    }
136}
137
138fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) {
139    let (head, tail) = match path.segments {
140        [] => return (Pat::Str(""), Pat::Str("")),
141        [p] => (Pat::Sym(p.ident.name), p),
142        // QPath::Resolved can have a path that looks like `<Foo as Bar>::baz` where
143        // the path (`Bar::baz`) has it's span covering the whole QPath.
144        [.., tail] => (Pat::Str(""), tail),
145    };
146    (
147        head,
148        if tail.args.is_some() {
149            Pat::Str(">")
150        } else {
151            Pat::Sym(tail.ident.name)
152        },
153    )
154}
155
156/// Get the search patterns to use for the given expression
157fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
158    fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) {
159        // The expression can have subexpressions in different contexts, in which case
160        // building up a search pattern from the macro expansion would lead to false positives;
161        // e.g. `return format!(..)` would be considered to be from a proc macro
162        // if we build up a pattern for the macro expansion and compare it to the invocation `format!()`.
163        // So instead we return an empty pattern such that `span_matches_pat` always returns true.
164        if !e.span.eq_ctxt(outer_span) {
165            return (Pat::Str(""), Pat::Str(""));
166        }
167
168        match e.kind {
169            ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
170            // Parenthesis are trimmed from the text before the search patterns are matched.
171            // See: `span_matches_pat`
172            ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
173            ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1),
174            ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1),
175            ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1),
176            ExprKind::Lit(lit) => lit_search_pat(&lit.node),
177            ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
178            ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => {
179                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("("))
180            },
181            ExprKind::Call(first, [.., last])
182            | ExprKind::MethodCall(_, first, [.., last], _)
183            | ExprKind::Binary(_, first, last)
184            | ExprKind::Tup([first, .., last])
185            | ExprKind::Assign(first, last, _)
186            | ExprKind::AssignOp(_, first, last) => (
187                expr_search_pat_inner(tcx, first, outer_span).0,
188                expr_search_pat_inner(tcx, last, outer_span).1,
189            ),
190            ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span),
191            ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")),
192            ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1),
193            ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
194            ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
195            ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
196            ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
197            ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
198                (Pat::Str("for"), Pat::Str("}"))
199            },
200            ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
201            ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => {
202                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?"))
203            },
204            ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
205                (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await"))
206            },
207            ExprKind::Closure(&Closure { body, .. }) => (
208                Pat::Str(""),
209                expr_search_pat_inner(tcx, tcx.hir_body(body).value, outer_span).1,
210            ),
211            ExprKind::Block(
212                Block {
213                    rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
214                    ..
215                },
216                None,
217            ) => (Pat::Str("unsafe"), Pat::Str("}")),
218            ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
219            ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)),
220            ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")),
221            ExprKind::Path(ref path) => qpath_search_pat(path),
222            ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1),
223            ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
224            ExprKind::Break(Destination { label: Some(name), .. }, None) => {
225                (Pat::Str("break"), Pat::Sym(name.ident.name))
226            },
227            ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1),
228            ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
229            ExprKind::Continue(Destination { label: Some(name), .. }) => {
230                (Pat::Str("continue"), Pat::Sym(name.ident.name))
231            },
232            ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
233            ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1),
234            ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
235            ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1),
236            _ => (Pat::Str(""), Pat::Str("")),
237        }
238    }
239
240    expr_search_pat_inner(tcx, e, e.span)
241}
242
243fn fn_header_search_pat(header: FnHeader) -> Pat {
244    if header.is_async() {
245        Pat::Str("async")
246    } else if matches!(header.constness, rustc_hir::Constness::Const { always: false }) {
247        Pat::Str("const")
248    } else if header.is_unsafe() {
249        Pat::Str("unsafe")
250    } else if header.abi != ExternAbi::Rust {
251        Pat::Str("extern")
252    } else {
253        Pat::MultiStr(&["fn", "extern"])
254    }
255}
256
257fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
258    let (start_pat, end_pat) = match &item.kind {
259        ItemKind::ExternCrate(..) => (Pat::Str("extern"), Pat::Str(";")),
260        ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
261        ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
262        ItemKind::Fn { sig, .. } => (fn_header_search_pat(sig.header), Pat::Str("")),
263        ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
264        ItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
265        ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
266        ItemKind::Struct(_, _, VariantData::Struct { .. }) => (Pat::Str("struct"), Pat::Str("}")),
267        ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
268        ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
269        ItemKind::Trait {
270            safety: Safety::Unsafe, ..
271        }
272        | ItemKind::Impl(Impl {
273            of_trait: Some(TraitImplHeader {
274                safety: Safety::Unsafe, ..
275            }),
276            ..
277        }) => (Pat::Str("unsafe"), Pat::Str("}")),
278        ItemKind::Trait {
279            is_auto: IsAuto::Yes, ..
280        } => (Pat::Str("auto"), Pat::Str("}")),
281        ItemKind::Trait { .. } => (Pat::Str("trait"), Pat::Str("}")),
282        ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
283        ItemKind::Mod(..) => (Pat::Str("mod"), Pat::Str("")),
284        ItemKind::Macro(_, def, _) => (
285            Pat::Str(if def.macro_rules { "macro_rules" } else { "macro" }),
286            Pat::Str(""),
287        ),
288        ItemKind::TraitAlias(..) => (Pat::Str("trait"), Pat::Str(";")),
289        ItemKind::GlobalAsm { .. } => return (Pat::Str("global_asm"), Pat::Str("")),
290        ItemKind::Use(..) => return (Pat::Str(""), Pat::Str("")),
291        ItemKind::TestBinderConstraints { .. } => return (Pat::Str(""), Pat::Str("")),
292    };
293    if item.vis_span.is_empty() {
294        (start_pat, end_pat)
295    } else {
296        (Pat::Str("pub"), end_pat)
297    }
298}
299
300fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
301    match &item.kind {
302        TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
303        TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
304        TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
305    }
306}
307
308fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
309    let (mut start_pat, end_pat) = match &item.kind {
310        ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
311        ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
312        ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
313    };
314    if let ImplItemImplKind::Inherent { vis_span, .. } = item.impl_kind
315        && !vis_span.is_empty()
316    {
317        start_pat = Pat::Str("pub");
318    }
319    (start_pat, end_pat)
320}
321
322fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
323    if def.vis_span.is_empty() {
324        if def.is_positional() {
325            (Pat::Str(""), Pat::Str(""))
326        } else {
327            (Pat::Sym(def.ident.name), Pat::Str(""))
328        }
329    } else {
330        (Pat::Str("pub"), Pat::Str(""))
331    }
332}
333
334fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
335    match v.data {
336        VariantData::Struct { .. } => (Pat::Sym(v.ident.name), Pat::Str("}")),
337        VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
338        VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
339    }
340}
341
342fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
343    let (mut start_pat, end_pat) = match kind {
344        FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
345        FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
346        FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
347    };
348    match tcx.hir_node(hir_id) {
349        Node::Item(Item { vis_span, .. })
350        | Node::ImplItem(ImplItem {
351            impl_kind: ImplItemImplKind::Inherent { vis_span, .. },
352            ..
353        }) => {
354            if !vis_span.is_empty() {
355                start_pat = Pat::Str("pub");
356            }
357        },
358        Node::ImplItem(_) | Node::TraitItem(_) => {},
359        _ => start_pat = Pat::Str(""),
360    }
361    (start_pat, end_pat)
362}
363
364fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) {
365    match attr.kind {
366        AttrKind::Normal(..) => {
367            if let Some(name) = attr.name() {
368                // NOTE: This will likely have false positives, like `allow = 1`
369                (Pat::Attr(name), Pat::Str(""))
370            } else {
371                (Pat::Str("#"), Pat::Str("]"))
372            }
373        },
374        AttrKind::Synthetic(..) => unreachable!(),
375        AttrKind::DocComment(_kind @ CommentKind::Line, ..) => {
376            if attr.style == AttrStyle::Outer {
377                (Pat::Str("///"), Pat::Str(""))
378            } else {
379                (Pat::Str("//!"), Pat::Str(""))
380            }
381        },
382        AttrKind::DocComment(_kind @ CommentKind::Block, ..) => {
383            if attr.style == AttrStyle::Outer {
384                (Pat::Str("/**"), Pat::Str("*/"))
385            } else {
386                (Pat::Str("/*!"), Pat::Str("*/"))
387            }
388        },
389    }
390}
391
392fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
393    match ty.kind {
394        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
395        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1),
396        TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1),
397        TyKind::FnPtr(fn_ptr) => (
398            if fn_ptr.safety.is_unsafe() {
399                Pat::Str("unsafe")
400            } else if fn_ptr.abi != ExternAbi::Rust {
401                Pat::Str("extern")
402            } else {
403                Pat::MultiStr(&["fn", "extern"])
404            },
405            match fn_ptr.decl.output {
406                FnRetTy::DefaultReturn(_) => {
407                    if let [.., ty] = fn_ptr.decl.inputs {
408                        ty_search_pat(ty).1
409                    } else {
410                        Pat::Str("(")
411                    }
412                },
413                FnRetTy::Return(ty) => ty_search_pat(ty).1,
414            },
415        ),
416        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
417        // Parenthesis are trimmed from the text before the search patterns are matched.
418        // See: `span_matches_pat`
419        TyKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
420        TyKind::Tup([ty]) => ty_search_pat(ty),
421        TyKind::Tup([head, .., tail]) => (ty_search_pat(head).0, ty_search_pat(tail).1),
422        TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
423        TyKind::Path(qpath) => qpath_search_pat(&qpath),
424        TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
425        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1),
426        TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
427            (Pat::Str("dyn"), Pat::Str(""))
428        },
429        // NOTE: `TraitObject` is incomplete. It will always return true then.
430        _ => (Pat::Str(""), Pat::Str("")),
431    }
432}
433
434fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) {
435    use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind};
436
437    match &ty.kind {
438        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
439        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1),
440        TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => {
441            (Pat::Str("&"), ast_ty_search_pat(ty).1)
442        },
443        TyKind::FnPtr(fn_ptr) => (
444            if let Safety::Unsafe(_) = fn_ptr.safety {
445                Pat::Str("unsafe")
446            } else if let Extern::Explicit(strlit, _) = fn_ptr.ext
447                && strlit.symbol == sym::rust
448            {
449                Pat::MultiStr(&["fn", "extern"])
450            } else {
451                Pat::Str("extern")
452            },
453            match &fn_ptr.decl.output {
454                FnRetTy::Default(_) => {
455                    if let [.., param] = &*fn_ptr.decl.inputs {
456                        ast_ty_search_pat(&param.ty).1
457                    } else {
458                        Pat::Str("(")
459                    }
460                },
461                FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
462            },
463        ),
464        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
465        // Parenthesis are trimmed from the text before the search patterns are matched.
466        // See: `span_matches_pat`
467        TyKind::Tup(tup) => match &**tup {
468            [] => (Pat::Str(")"), Pat::Str("(")),
469            [ty] => ast_ty_search_pat(ty),
470            [head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1),
471        },
472        TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")),
473        TyKind::Path(qself_path, path) => {
474            let start = if qself_path.is_some() {
475                Pat::Str("<")
476            } else if let Some(first) = path.segments.first() {
477                ident_search_pat(first.ident).0
478            } else {
479                // this shouldn't be possible, but sure
480                Pat::Str("")
481            };
482            let end = if let Some(last) = path.segments.last() {
483                match last.args.as_deref() {
484                    // last `>` in `std::foo::Bar<T>`
485                    Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"),
486                    Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output {
487                        FnRetTy::Default(_) => {
488                            if let Some(last) = par_args.inputs.last() {
489                                // `B` in `(A, B)` -- `)` gets stripped
490                                ast_ty_search_pat(&last.ty).1
491                            } else {
492                                // `(` in `()` -- `)` gets stripped
493                                Pat::Str("(")
494                            }
495                        },
496                        // `C` in `(A, B) -> C`
497                        FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
498                    },
499                    // last `..` in `(..)` -- `)` gets stripped
500                    Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."),
501                    // `bar` in `std::foo::bar`
502                    None => ident_search_pat(last.ident).1,
503                }
504            } else {
505                // this shouldn't be possible
506                Pat::Str(
507                    if qself_path.is_some() {
508                        ">"  // last `>` in `<Vec as IntoIterator>`
509                    } else {
510                        ""
511                    }
512                )
513            };
514            (start, end)
515        },
516        TyKind::Infer => (Pat::Str("_"), Pat::Str("_")),
517        TyKind::Paren(ty) => ast_ty_search_pat(ty),
518        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1),
519        TyKind::TraitObject(_, trait_obj_syntax) => {
520            if let TraitObjectSyntax::Dyn = trait_obj_syntax {
521                (Pat::Str("dyn"), Pat::Str(""))
522            } else {
523                // NOTE: `TraitObject` is incomplete. It will always return true then.
524                (Pat::Str(""), Pat::Str(""))
525            }
526        },
527        TyKind::MacCall(mac_call) => {
528            let start = if let Some(first) = mac_call.path.segments.first() {
529                ident_search_pat(first.ident).0
530            } else {
531                Pat::Str("")
532            };
533            (start, Pat::Str(""))
534        },
535
536        // implicit, so has no contents to match against
537        TyKind::ImplicitSelf
538
539        // experimental
540        | TyKind::Pat(..)
541        | TyKind::FieldOf(..)
542        | TyKind::View(..)
543        | TyKind::DirectConstArg(..)
544
545        // unused
546        | TyKind::CVarArgs
547
548        // placeholder
549        | TyKind::Dummy
550        | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")),
551    }
552}
553
554// NOTE: can't `impl WithSearchPat for TraitRef`, because `TraitRef` doesn't have a `span` field
555// (nor a method)
556fn trait_ref_search_pat(trait_ref: &TraitRef<'_>) -> (Pat, Pat) {
557    path_search_pat(trait_ref.path)
558}
559
560fn poly_trait_ref_search_pat(poly_trait_ref: &PolyTraitRef<'_>) -> (Pat, Pat) {
561    // NOTE: unfortunately we can't use `bound_generic_params` to see whether the pattern starts with
562    // `for<..>`, because if it's empty, we could have either `for<>` (nothing bound), or
563    // no `for` at all
564    let PolyTraitRef {
565        modifiers: TraitBoundModifiers { constness, polarity },
566        trait_ref,
567        ..
568    } = poly_trait_ref;
569
570    let trait_ref_search_pat = trait_ref_search_pat(trait_ref);
571
572    #[expect(
573        clippy::unnecessary_lazy_evaluations,
574        reason = "the closure in `or_else` has `match polarity`, which isn't free"
575    )]
576    let start = match constness {
577        BoundConstness::Never => None,
578        BoundConstness::Maybe(_) => Some(Pat::Str("[const]")),
579        BoundConstness::Always(_) => Some(Pat::Str("const")),
580    }
581    .or_else(|| match polarity {
582        BoundPolarity::Negative(_) => Some(Pat::Str("!")),
583        BoundPolarity::Maybe(_) => Some(Pat::Str("?")),
584        BoundPolarity::Positive => None,
585    })
586    .unwrap_or(trait_ref_search_pat.0);
587    let end = trait_ref_search_pat.1;
588
589    (start, end)
590}
591
592fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
593    (Pat::Sym(ident.name), Pat::Sym(ident.name))
594}
595
596fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) {
597    match pat.kind {
598        // Tuple patterns cannot show up in proc-macro checks
599        PatKind::Missing | PatKind::Err(_) | PatKind::Tuple(_, _) => (Pat::Str(""), Pat::Str("")),
600        PatKind::Wild => (Pat::Sym(kw::Underscore), Pat::Sym(kw::Underscore)),
601        PatKind::Binding(binding_mode, _, ident, Some(end_pat)) => {
602            let start = if binding_mode == BindingMode::NONE {
603                ident_search_pat(ident).0
604            } else {
605                Pat::Str(binding_mode.prefix_str())
606            };
607
608            let (_, end) = pat_search_pat(tcx, end_pat);
609            (start, end)
610        },
611        PatKind::Binding(binding_mode, _, ident, None) => {
612            let (s, end) = ident_search_pat(ident);
613            let start = if binding_mode == BindingMode::NONE {
614                s
615            } else {
616                Pat::Str(binding_mode.prefix_str())
617            };
618
619            (start, end)
620        },
621        PatKind::Struct(path, _, _) => {
622            let (start, _) = qpath_search_pat(&path);
623            (start, Pat::Str("}"))
624        },
625        PatKind::TupleStruct(path, _, _) => {
626            let (start, _) = qpath_search_pat(&path);
627            // This pattern cannot show up in proc-macro checks
628            (start, Pat::Str(""))
629        },
630        PatKind::Or(plist) => {
631            // documented invariant
632            debug_assert!(plist.len() >= 2);
633            let (start, _) = pat_search_pat(tcx, plist.first().unwrap());
634            let (_, end) = pat_search_pat(tcx, plist.last().unwrap());
635            (start, end)
636        },
637        PatKind::Never => (Pat::Str("!"), Pat::Str("")),
638        PatKind::Deref(_) => (Pat::Str("deref!"), Pat::Str("")),
639        PatKind::Ref(p, _, _) => {
640            let (_, end) = pat_search_pat(tcx, p);
641            (Pat::Str("&"), end)
642        },
643        PatKind::Expr(expr) => pat_expr_search_pat(expr),
644        PatKind::Guard(pat, guard) => {
645            let (start, _) = pat_search_pat(tcx, pat);
646            let (_, end) = expr_search_pat(tcx, guard);
647            (start, end)
648        },
649        PatKind::Range(None, None, range) => match range {
650            rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")),
651            rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")),
652        },
653        PatKind::Range(r_start, r_end, range) => {
654            let start = match r_start {
655                Some(e) => pat_expr_search_pat(e).0,
656                None => match range {
657                    rustc_hir::RangeEnd::Included => Pat::Str("..="),
658                    rustc_hir::RangeEnd::Excluded => Pat::Str(".."),
659                },
660            };
661
662            let end = match r_end {
663                Some(e) => pat_expr_search_pat(e).1,
664                None => match range {
665                    rustc_hir::RangeEnd::Included => Pat::Str("..="),
666                    rustc_hir::RangeEnd::Excluded => Pat::Str(".."),
667                },
668            };
669            (start, end)
670        },
671        PatKind::Slice(_, _, _) => (Pat::Str("["), Pat::Str("]")),
672    }
673}
674
675fn pat_expr_search_pat(expr: &PatExpr<'_>) -> (Pat, Pat) {
676    match expr.kind {
677        PatExprKind::Lit { lit, negated } => {
678            let (start, end) = lit_search_pat(&lit.node);
679            if negated { (Pat::Str("!"), end) } else { (start, end) }
680        },
681        PatExprKind::Path(path) => qpath_search_pat(&path),
682    }
683}
684
685pub trait WithSearchPat<'cx> {
686    type Context: LintContext;
687    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
688    fn span(&self) -> Span;
689}
690macro_rules! impl_with_search_pat {
691    (($cx_ident:ident: $cx_ty:ident<$cx_lt:lifetime>, $self:tt: $ty:ty) => $fn:ident($($args:tt)*)) => {
692        impl<$cx_lt> WithSearchPat<$cx_lt> for $ty {
693            type Context = $cx_ty<$cx_lt>;
694            fn search_pat(&$self, $cx_ident: &Self::Context) -> (Pat, Pat) {
695                $fn($($args)*)
696            }
697            fn span(&self) -> Span {
698                self.span
699            }
700        }
701    };
702}
703impl_with_search_pat!((cx: LateContext<'tcx>, self: Expr<'tcx>) => expr_search_pat(cx.tcx, self));
704impl_with_search_pat!((_cx: LateContext<'tcx>, self: Item<'_>) => item_search_pat(self));
705impl_with_search_pat!((_cx: LateContext<'tcx>, self: TraitItem<'_>) => trait_item_search_pat(self));
706impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_search_pat(self));
707impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self));
708impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self));
709impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self));
710impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self));
711impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node));
712impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
713impl_with_search_pat!((_cx: LateContext<'tcx>, self: PolyTraitRef<'_>) => poly_trait_ref_search_pat(self));
714impl_with_search_pat!((cx: LateContext<'tcx>, self: rustc_hir::Pat<'_>) => pat_search_pat(cx.tcx, self));
715
716impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
717impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self));
718
719impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
720    type Context = LateContext<'cx>;
721
722    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
723        fn_kind_pat(cx.tcx, self.0, self.1, self.2)
724    }
725
726    fn span(&self) -> Span {
727        self.3
728    }
729}
730
731/// Checks if the item likely came from a proc-macro.
732///
733/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
734/// it is significantly slower than both of those.
735pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
736    let (start_pat, end_pat) = item.search_pat(cx);
737    !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
738}
739
740/// Checks if the span actually refers to a match expression
741pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
742    span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
743}
744
745/// Checks if the span actually refers to an if expression
746pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
747    span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
748}