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