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