1use 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#[derive(Clone)]
38pub enum Pat {
39 Str(&'static str),
41 MultiStr(&'static [&'static str]),
43 OwnedMultiStr(Vec<String>),
45 Sym(Symbol),
47 Num,
49 Attr(Symbol),
51}
52
53fn 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 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
89fn 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
110fn 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 [.., 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
156fn 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 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 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 (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 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 _ => (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(¶m.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 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 Pat::Str("")
480 };
481 let end = if let Some(last) = path.segments.last() {
482 match last.args.as_deref() {
483 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 ast_ty_search_pat(last).1
490 } else {
491 Pat::Str("(")
493 }
494 },
495 FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
497 },
498 Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."),
500 None => ident_search_pat(last.ident).1,
502 }
503 } else {
504 Pat::Str(
506 if qself_path.is_some() {
507 ">" } 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 (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 TyKind::ImplicitSelf
537
538 | TyKind::Pat(..)
540 | TyKind::FieldOf(..)
541 | TyKind::View(..)
542 | TyKind::DirectConstArg(..)
543
544 | TyKind::CVarArgs
546
547 | TyKind::Dummy
549 | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")),
550 }
551}
552
553fn 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 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 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 (start, Pat::Str(""))
628 },
629 PatKind::Or(plist) => {
630 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
734pub 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
743pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
745 span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
746}
747
748pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
750 span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
751}