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    };
292    if item.vis_span.is_empty() {
293        (start_pat, end_pat)
294    } else {
295        (Pat::Str("pub"), end_pat)
296    }
297}
298
299fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
300    match &item.kind {
301        TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
302        TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
303        TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
304    }
305}
306
307fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
308    let (mut start_pat, end_pat) = match &item.kind {
309        ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
310        ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
311        ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
312    };
313    if let ImplItemImplKind::Inherent { vis_span, .. } = item.impl_kind
314        && !vis_span.is_empty()
315    {
316        start_pat = Pat::Str("pub");
317    }
318    (start_pat, end_pat)
319}
320
321fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
322    if def.vis_span.is_empty() {
323        if def.is_positional() {
324            (Pat::Str(""), Pat::Str(""))
325        } else {
326            (Pat::Sym(def.ident.name), Pat::Str(""))
327        }
328    } else {
329        (Pat::Str("pub"), Pat::Str(""))
330    }
331}
332
333fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
334    match v.data {
335        VariantData::Struct { .. } => (Pat::Sym(v.ident.name), Pat::Str("}")),
336        VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
337        VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
338    }
339}
340
341fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
342    let (mut start_pat, end_pat) = match kind {
343        FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
344        FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
345        FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
346    };
347    match tcx.hir_node(hir_id) {
348        Node::Item(Item { vis_span, .. })
349        | Node::ImplItem(ImplItem {
350            impl_kind: ImplItemImplKind::Inherent { vis_span, .. },
351            ..
352        }) => {
353            if !vis_span.is_empty() {
354                start_pat = Pat::Str("pub");
355            }
356        },
357        Node::ImplItem(_) | Node::TraitItem(_) => {},
358        _ => start_pat = Pat::Str(""),
359    }
360    (start_pat, end_pat)
361}
362
363fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) {
364    match attr.kind {
365        AttrKind::Normal(..) => {
366            if let Some(name) = attr.name() {
367                // NOTE: This will likely have false positives, like `allow = 1`
368                (Pat::Attr(name), Pat::Str(""))
369            } else {
370                (Pat::Str("#"), Pat::Str("]"))
371            }
372        },
373        AttrKind::Synthetic(..) => unreachable!(),
374        AttrKind::DocComment(_kind @ CommentKind::Line, ..) => {
375            if attr.style == AttrStyle::Outer {
376                (Pat::Str("///"), Pat::Str(""))
377            } else {
378                (Pat::Str("//!"), Pat::Str(""))
379            }
380        },
381        AttrKind::DocComment(_kind @ CommentKind::Block, ..) => {
382            if attr.style == AttrStyle::Outer {
383                (Pat::Str("/**"), Pat::Str("*/"))
384            } else {
385                (Pat::Str("/*!"), Pat::Str("*/"))
386            }
387        },
388    }
389}
390
391fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
392    match ty.kind {
393        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
394        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1),
395        TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1),
396        TyKind::FnPtr(fn_ptr) => (
397            if fn_ptr.safety.is_unsafe() {
398                Pat::Str("unsafe")
399            } else if fn_ptr.abi != ExternAbi::Rust {
400                Pat::Str("extern")
401            } else {
402                Pat::MultiStr(&["fn", "extern"])
403            },
404            match fn_ptr.decl.output {
405                FnRetTy::DefaultReturn(_) => {
406                    if let [.., ty] = fn_ptr.decl.inputs {
407                        ty_search_pat(ty).1
408                    } else {
409                        Pat::Str("(")
410                    }
411                },
412                FnRetTy::Return(ty) => ty_search_pat(ty).1,
413            },
414        ),
415        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
416        // Parenthesis are trimmed from the text before the search patterns are matched.
417        // See: `span_matches_pat`
418        TyKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
419        TyKind::Tup([ty]) => ty_search_pat(ty),
420        TyKind::Tup([head, .., tail]) => (ty_search_pat(head).0, ty_search_pat(tail).1),
421        TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
422        TyKind::Path(qpath) => qpath_search_pat(&qpath),
423        TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
424        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1),
425        TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
426            (Pat::Str("dyn"), Pat::Str(""))
427        },
428        // NOTE: `TraitObject` is incomplete. It will always return true then.
429        _ => (Pat::Str(""), Pat::Str("")),
430    }
431}
432
433fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) {
434    use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind};
435
436    match &ty.kind {
437        TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
438        TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1),
439        TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => {
440            (Pat::Str("&"), ast_ty_search_pat(ty).1)
441        },
442        TyKind::FnPtr(fn_ptr) => (
443            if let Safety::Unsafe(_) = fn_ptr.safety {
444                Pat::Str("unsafe")
445            } else if let Extern::Explicit(strlit, _) = fn_ptr.ext
446                && strlit.symbol == sym::rust
447            {
448                Pat::MultiStr(&["fn", "extern"])
449            } else {
450                Pat::Str("extern")
451            },
452            match &fn_ptr.decl.output {
453                FnRetTy::Default(_) => {
454                    if let [.., param] = &*fn_ptr.decl.inputs {
455                        ast_ty_search_pat(&param.ty).1
456                    } else {
457                        Pat::Str("(")
458                    }
459                },
460                FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
461            },
462        ),
463        TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
464        // Parenthesis are trimmed from the text before the search patterns are matched.
465        // See: `span_matches_pat`
466        TyKind::Tup(tup) => match &**tup {
467            [] => (Pat::Str(")"), Pat::Str("(")),
468            [ty] => ast_ty_search_pat(ty),
469            [head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1),
470        },
471        TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")),
472        TyKind::Path(qself_path, path) => {
473            let start = if qself_path.is_some() {
474                Pat::Str("<")
475            } else if let Some(first) = path.segments.first() {
476                ident_search_pat(first.ident).0
477            } else {
478                // this shouldn't be possible, but sure
479                Pat::Str("")
480            };
481            let end = if let Some(last) = path.segments.last() {
482                match last.args.as_deref() {
483                    // last `>` in `std::foo::Bar<T>`
484                    Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"),
485                    Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output {
486                        FnRetTy::Default(_) => {
487                            if let Some(last) = par_args.inputs.last() {
488                                // `B` in `(A, B)` -- `)` gets stripped
489                                ast_ty_search_pat(last).1
490                            } else {
491                                // `(` in `()` -- `)` gets stripped
492                                Pat::Str("(")
493                            }
494                        },
495                        // `C` in `(A, B) -> C`
496                        FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
497                    },
498                    // last `..` in `(..)` -- `)` gets stripped
499                    Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."),
500                    // `bar` in `std::foo::bar`
501                    None => ident_search_pat(last.ident).1,
502                }
503            } else {
504                // this shouldn't be possible
505                Pat::Str(
506                    if qself_path.is_some() {
507                        ">"  // last `>` in `<Vec as IntoIterator>`
508                    } else {
509                        ""
510                    }
511                )
512            };
513            (start, end)
514        },
515        TyKind::Infer => (Pat::Str("_"), Pat::Str("_")),
516        TyKind::Paren(ty) => ast_ty_search_pat(ty),
517        TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1),
518        TyKind::TraitObject(_, trait_obj_syntax) => {
519            if let TraitObjectSyntax::Dyn = trait_obj_syntax {
520                (Pat::Str("dyn"), Pat::Str(""))
521            } else {
522                // NOTE: `TraitObject` is incomplete. It will always return true then.
523                (Pat::Str(""), Pat::Str(""))
524            }
525        },
526        TyKind::MacCall(mac_call) => {
527            let start = if let Some(first) = mac_call.path.segments.first() {
528                ident_search_pat(first.ident).0
529            } else {
530                Pat::Str("")
531            };
532            (start, Pat::Str(""))
533        },
534
535        // implicit, so has no contents to match against
536        TyKind::ImplicitSelf
537
538        // experimental
539        | TyKind::Pat(..)
540        | TyKind::FieldOf(..)
541        | TyKind::View(..)
542        | TyKind::DirectConstArg(..)
543
544        // unused
545        | TyKind::CVarArgs
546
547        // placeholder
548        | TyKind::Dummy
549        | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")),
550    }
551}
552
553// NOTE: can't `impl WithSearchPat for TraitRef`, because `TraitRef` doesn't have a `span` field
554// (nor a method)
555fn trait_ref_search_pat(trait_ref: &TraitRef<'_>) -> (Pat, Pat) {
556    path_search_pat(trait_ref.path)
557}
558
559fn poly_trait_ref_search_pat(poly_trait_ref: &PolyTraitRef<'_>) -> (Pat, Pat) {
560    // NOTE: unfortunately we can't use `bound_generic_params` to see whether the pattern starts with
561    // `for<..>`, because if it's empty, we could have either `for<>` (nothing bound), or
562    // no `for` at all
563    let PolyTraitRef {
564        modifiers: TraitBoundModifiers { constness, polarity },
565        trait_ref,
566        ..
567    } = poly_trait_ref;
568
569    let trait_ref_search_pat = trait_ref_search_pat(trait_ref);
570
571    #[expect(
572        clippy::unnecessary_lazy_evaluations,
573        reason = "the closure in `or_else` has `match polarity`, which isn't free"
574    )]
575    let start = match constness {
576        BoundConstness::Never => None,
577        BoundConstness::Maybe(_) => Some(Pat::Str("[const]")),
578        BoundConstness::Always(_) => Some(Pat::Str("const")),
579    }
580    .or_else(|| match polarity {
581        BoundPolarity::Negative(_) => Some(Pat::Str("!")),
582        BoundPolarity::Maybe(_) => Some(Pat::Str("?")),
583        BoundPolarity::Positive => None,
584    })
585    .unwrap_or(trait_ref_search_pat.0);
586    let end = trait_ref_search_pat.1;
587
588    (start, end)
589}
590
591fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
592    (Pat::Sym(ident.name), Pat::Sym(ident.name))
593}
594
595fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) {
596    match pat.kind {
597        // Tuple patterns cannot show up in proc-macro checks
598        PatKind::Missing | PatKind::Err(_) | PatKind::Tuple(_, _) => (Pat::Str(""), Pat::Str("")),
599        PatKind::Wild => (Pat::Sym(kw::Underscore), Pat::Sym(kw::Underscore)),
600        PatKind::Binding(binding_mode, _, ident, Some(end_pat)) => {
601            let start = if binding_mode == BindingMode::NONE {
602                ident_search_pat(ident).0
603            } else {
604                Pat::Str(binding_mode.prefix_str())
605            };
606
607            let (_, end) = pat_search_pat(tcx, end_pat);
608            (start, end)
609        },
610        PatKind::Binding(binding_mode, _, ident, None) => {
611            let (s, end) = ident_search_pat(ident);
612            let start = if binding_mode == BindingMode::NONE {
613                s
614            } else {
615                Pat::Str(binding_mode.prefix_str())
616            };
617
618            (start, end)
619        },
620        PatKind::Struct(path, _, _) => {
621            let (start, _) = qpath_search_pat(&path);
622            (start, Pat::Str("}"))
623        },
624        PatKind::TupleStruct(path, _, _) => {
625            let (start, _) = qpath_search_pat(&path);
626            // This pattern cannot show up in proc-macro checks
627            (start, Pat::Str(""))
628        },
629        PatKind::Or(plist) => {
630            // documented invariant
631            debug_assert!(plist.len() >= 2);
632            let (start, _) = pat_search_pat(tcx, plist.first().unwrap());
633            let (_, end) = pat_search_pat(tcx, plist.last().unwrap());
634            (start, end)
635        },
636        PatKind::Never => (Pat::Str("!"), Pat::Str("")),
637        PatKind::Box(p) => {
638            let (_, end) = pat_search_pat(tcx, p);
639            (Pat::Str("box"), end)
640        },
641        PatKind::Deref(_) => (Pat::Str("deref!"), Pat::Str("")),
642        PatKind::Ref(p, _, _) => {
643            let (_, end) = pat_search_pat(tcx, p);
644            (Pat::Str("&"), end)
645        },
646        PatKind::Expr(expr) => pat_expr_search_pat(expr),
647        PatKind::Guard(pat, guard) => {
648            let (start, _) = pat_search_pat(tcx, pat);
649            let (_, end) = expr_search_pat(tcx, guard);
650            (start, end)
651        },
652        PatKind::Range(None, None, range) => match range {
653            rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")),
654            rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")),
655        },
656        PatKind::Range(r_start, r_end, range) => {
657            let start = match r_start {
658                Some(e) => pat_expr_search_pat(e).0,
659                None => match range {
660                    rustc_hir::RangeEnd::Included => Pat::Str("..="),
661                    rustc_hir::RangeEnd::Excluded => Pat::Str(".."),
662                },
663            };
664
665            let end = match r_end {
666                Some(e) => pat_expr_search_pat(e).1,
667                None => match range {
668                    rustc_hir::RangeEnd::Included => Pat::Str("..="),
669                    rustc_hir::RangeEnd::Excluded => Pat::Str(".."),
670                },
671            };
672            (start, end)
673        },
674        PatKind::Slice(_, _, _) => (Pat::Str("["), Pat::Str("]")),
675    }
676}
677
678fn pat_expr_search_pat(expr: &PatExpr<'_>) -> (Pat, Pat) {
679    match expr.kind {
680        PatExprKind::Lit { lit, negated } => {
681            let (start, end) = lit_search_pat(&lit.node);
682            if negated { (Pat::Str("!"), end) } else { (start, end) }
683        },
684        PatExprKind::Path(path) => qpath_search_pat(&path),
685    }
686}
687
688pub trait WithSearchPat<'cx> {
689    type Context: LintContext;
690    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
691    fn span(&self) -> Span;
692}
693macro_rules! impl_with_search_pat {
694    (($cx_ident:ident: $cx_ty:ident<$cx_lt:lifetime>, $self:tt: $ty:ty) => $fn:ident($($args:tt)*)) => {
695        impl<$cx_lt> WithSearchPat<$cx_lt> for $ty {
696            type Context = $cx_ty<$cx_lt>;
697            fn search_pat(&$self, $cx_ident: &Self::Context) -> (Pat, Pat) {
698                $fn($($args)*)
699            }
700            fn span(&self) -> Span {
701                self.span
702            }
703        }
704    };
705}
706impl_with_search_pat!((cx: LateContext<'tcx>, self: Expr<'tcx>) => expr_search_pat(cx.tcx, self));
707impl_with_search_pat!((_cx: LateContext<'tcx>, self: Item<'_>) => item_search_pat(self));
708impl_with_search_pat!((_cx: LateContext<'tcx>, self: TraitItem<'_>) => trait_item_search_pat(self));
709impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_search_pat(self));
710impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self));
711impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self));
712impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self));
713impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self));
714impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node));
715impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
716impl_with_search_pat!((_cx: LateContext<'tcx>, self: PolyTraitRef<'_>) => poly_trait_ref_search_pat(self));
717impl_with_search_pat!((cx: LateContext<'tcx>, self: rustc_hir::Pat<'_>) => pat_search_pat(cx.tcx, self));
718
719impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
720impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self));
721
722impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
723    type Context = LateContext<'cx>;
724
725    fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
726        fn_kind_pat(cx.tcx, self.0, self.1, self.2)
727    }
728
729    fn span(&self) -> Span {
730        self.3
731    }
732}
733
734/// Checks if the item likely came from a proc-macro.
735///
736/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
737/// it is significantly slower than both of those.
738pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
739    let (start_pat, end_pat) = item.search_pat(cx);
740    !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
741}
742
743/// Checks if the span actually refers to a match expression
744pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
745    span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
746}
747
748/// Checks if the span actually refers to an if expression
749pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
750    span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
751}