1use rustc_abi::ExternAbi;
16use rustc_ast::AttrStyle;
17use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy};
18use rustc_ast::token::CommentKind;
19use rustc_hir::intravisit::FnKind;
20use rustc_hir::{
21 Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl,
22 ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
23 TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
24};
25use rustc_lint::{EarlyContext, LateContext, LintContext};
26use rustc_middle::ty::TyCtxt;
27use rustc_session::Session;
28use rustc_span::symbol::{Ident, kw};
29use rustc_span::{Span, Symbol};
30
31#[derive(Clone)]
33pub enum Pat {
34 Str(&'static str),
36 MultiStr(&'static [&'static str]),
38 OwnedMultiStr(Vec<String>),
40 Sym(Symbol),
42 Num,
44}
45
46fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
49 let pos = sess.source_map().lookup_byte_offset(span.lo());
50 let Some(ref src) = pos.sf.src else {
51 return false;
52 };
53 let end = span.hi() - pos.sf.start_pos;
54 src.get(pos.pos.0 as usize..end.0 as usize).is_some_and(|s| {
55 let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
57 let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
58 (match start_pat {
59 Pat::Str(text) => start_str.starts_with(text),
60 Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
61 Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
62 Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
63 Pat::Num => start_str.as_bytes().first().is_some_and(u8::is_ascii_digit),
64 } && match end_pat {
65 Pat::Str(text) => end_str.ends_with(text),
66 Pat::MultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
67 Pat::OwnedMultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)),
68 Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
69 Pat::Num => end_str.as_bytes().last().is_some_and(u8::is_ascii_hexdigit),
70 })
71 })
72}
73
74fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
76 match lit {
77 LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
78 LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
79 LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
80 LitKind::ByteStr(_, StrStyle::Cooked) => (Pat::Str("b\""), Pat::Str("\"")),
81 LitKind::ByteStr(_, StrStyle::Raw(0)) => (Pat::Str("br\""), Pat::Str("\"")),
82 LitKind::ByteStr(_, StrStyle::Raw(_)) => (Pat::Str("br#\""), Pat::Str("#")),
83 LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
84 LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
85 LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
86 LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
87 LitKind::Int(..) => (Pat::Num, Pat::Num),
88 LitKind::Float(..) => (Pat::Num, Pat::Str("")),
89 LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
90 LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
91 _ => (Pat::Str(""), Pat::Str("")),
92 }
93}
94
95fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
97 match path {
98 QPath::Resolved(ty, path) => {
99 let start = if ty.is_some() {
100 Pat::Str("<")
101 } else {
102 path.segments.first().map_or(Pat::Str(""), |seg| {
103 if seg.ident.name == kw::PathRoot {
104 Pat::Str("::")
105 } else {
106 Pat::Sym(seg.ident.name)
107 }
108 })
109 };
110 let end = path.segments.last().map_or(Pat::Str(""), |seg| {
111 if seg.args.is_some() {
112 Pat::Str(">")
113 } else {
114 Pat::Sym(seg.ident.name)
115 }
116 });
117 (start, end)
118 },
119 QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
120 QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")),
121 }
122}
123
124fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) {
125 let (head, tail) = match path.segments {
126 [] => return (Pat::Str(""), Pat::Str("")),
127 [p] => (Pat::Sym(p.ident.name), p),
128 [.., tail] => (Pat::Str(""), tail),
131 };
132 (
133 head,
134 if tail.args.is_some() {
135 Pat::Str(">")
136 } else {
137 Pat::Sym(tail.ident.name)
138 },
139 )
140}
141
142fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
144 fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) {
145 if !e.span.eq_ctxt(outer_span) {
151 return (Pat::Str(""), Pat::Str(""));
152 }
153
154 match e.kind {
155 ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
156 ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
159 ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1),
160 ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1),
161 ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1),
162 ExprKind::Lit(lit) => lit_search_pat(&lit.node),
163 ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
164 ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => {
165 (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("("))
166 },
167 ExprKind::Call(first, [.., last])
168 | ExprKind::MethodCall(_, first, [.., last], _)
169 | ExprKind::Binary(_, first, last)
170 | ExprKind::Tup([first, .., last])
171 | ExprKind::Assign(first, last, _)
172 | ExprKind::AssignOp(_, first, last) => (
173 expr_search_pat_inner(tcx, first, outer_span).0,
174 expr_search_pat_inner(tcx, last, outer_span).1,
175 ),
176 ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span),
177 ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")),
178 ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1),
179 ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
180 ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
181 ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
182 ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
183 ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
184 (Pat::Str("for"), Pat::Str("}"))
185 },
186 ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
187 ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => {
188 (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?"))
189 },
190 ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
191 (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await"))
192 },
193 ExprKind::Closure(&Closure { body, .. }) => (
194 Pat::Str(""),
195 expr_search_pat_inner(tcx, tcx.hir_body(body).value, outer_span).1,
196 ),
197 ExprKind::Block(
198 Block {
199 rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
200 ..
201 },
202 None,
203 ) => (Pat::Str("unsafe"), Pat::Str("}")),
204 ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
205 ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)),
206 ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")),
207 ExprKind::Path(ref path) => qpath_search_pat(path),
208 ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1),
209 ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
210 ExprKind::Break(Destination { label: Some(name), .. }, None) => {
211 (Pat::Str("break"), Pat::Sym(name.ident.name))
212 },
213 ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1),
214 ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
215 ExprKind::Continue(Destination { label: Some(name), .. }) => {
216 (Pat::Str("continue"), Pat::Sym(name.ident.name))
217 },
218 ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
219 ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1),
220 ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
221 ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1),
222 _ => (Pat::Str(""), Pat::Str("")),
223 }
224 }
225
226 expr_search_pat_inner(tcx, e, e.span)
227}
228
229fn fn_header_search_pat(header: FnHeader) -> Pat {
230 if header.is_async() {
231 Pat::Str("async")
232 } else if header.is_const() {
233 Pat::Str("const")
234 } else if header.is_unsafe() {
235 Pat::Str("unsafe")
236 } else if header.abi != ExternAbi::Rust {
237 Pat::Str("extern")
238 } else {
239 Pat::MultiStr(&["fn", "extern"])
240 }
241}
242
243fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
244 let (start_pat, end_pat) = match &item.kind {
245 ItemKind::ExternCrate(..) => (Pat::Str("extern"), Pat::Str(";")),
246 ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
247 ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
248 ItemKind::Fn { sig, .. } => (fn_header_search_pat(sig.header), Pat::Str("")),
249 ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
250 ItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
251 ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
252 ItemKind::Struct(_, _, VariantData::Struct { .. }) => (Pat::Str("struct"), Pat::Str("}")),
253 ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
254 ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
255 ItemKind::Trait(_, _, Safety::Unsafe, ..)
256 | ItemKind::Impl(Impl {
257 of_trait: Some(TraitImplHeader {
258 safety: Safety::Unsafe, ..
259 }),
260 ..
261 }) => (Pat::Str("unsafe"), Pat::Str("}")),
262 ItemKind::Trait(_, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
263 ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
264 ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
265 _ => return (Pat::Str(""), Pat::Str("")),
266 };
267 if item.vis_span.is_empty() {
268 (start_pat, end_pat)
269 } else {
270 (Pat::Str("pub"), end_pat)
271 }
272}
273
274fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
275 match &item.kind {
276 TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
277 TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
278 TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
279 }
280}
281
282fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
283 let (mut start_pat, end_pat) = match &item.kind {
284 ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
285 ImplItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
286 ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
287 };
288 if let ImplItemImplKind::Inherent { vis_span, .. } = item.impl_kind
289 && !vis_span.is_empty()
290 {
291 start_pat = Pat::Str("pub");
292 };
293 (start_pat, end_pat)
294}
295
296fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
297 if def.vis_span.is_empty() {
298 if def.is_positional() {
299 (Pat::Str(""), Pat::Str(""))
300 } else {
301 (Pat::Sym(def.ident.name), Pat::Str(""))
302 }
303 } else {
304 (Pat::Str("pub"), Pat::Str(""))
305 }
306}
307
308fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
309 match v.data {
310 VariantData::Struct { .. } => (Pat::Sym(v.ident.name), Pat::Str("}")),
311 VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
312 VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
313 }
314}
315
316fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
317 let (mut start_pat, end_pat) = match kind {
318 FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
319 FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
320 FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
321 };
322 match tcx.hir_node(hir_id) {
323 Node::Item(Item { vis_span, .. })
324 | Node::ImplItem(ImplItem { impl_kind: ImplItemImplKind::Inherent { vis_span, .. }, .. }) => {
325 if !vis_span.is_empty() {
326 start_pat = Pat::Str("pub")
327 }
328 },
329 Node::ImplItem(_) | Node::TraitItem(_) => {},
330 _ => start_pat = Pat::Str(""),
331 };
332 (start_pat, end_pat)
333}
334
335fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) {
336 match attr.kind {
337 AttrKind::Normal(..) => {
338 if let Some(ident) = attr.ident() {
339 let ident_string = ident.to_string();
341 if attr.style == AttrStyle::Outer {
342 (
343 Pat::OwnedMultiStr(vec!["#[".to_owned() + &ident_string, ident_string]),
344 Pat::Str(""),
345 )
346 } else {
347 (
348 Pat::OwnedMultiStr(vec!["#![".to_owned() + &ident_string, ident_string]),
349 Pat::Str(""),
350 )
351 }
352 } else {
353 (Pat::Str("#"), Pat::Str("]"))
354 }
355 },
356 AttrKind::DocComment(_kind @ CommentKind::Line, ..) => {
357 if attr.style == AttrStyle::Outer {
358 (Pat::Str("///"), Pat::Str(""))
359 } else {
360 (Pat::Str("//!"), Pat::Str(""))
361 }
362 },
363 AttrKind::DocComment(_kind @ CommentKind::Block, ..) => {
364 if attr.style == AttrStyle::Outer {
365 (Pat::Str("/**"), Pat::Str("*/"))
366 } else {
367 (Pat::Str("/*!"), Pat::Str("*/"))
368 }
369 },
370 }
371}
372
373fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
374 match ty.kind {
375 TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
376 TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1),
377 TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1),
378 TyKind::FnPtr(fn_ptr) => (
379 if fn_ptr.safety.is_unsafe() {
380 Pat::Str("unsafe")
381 } else if fn_ptr.abi != ExternAbi::Rust {
382 Pat::Str("extern")
383 } else {
384 Pat::MultiStr(&["fn", "extern"])
385 },
386 match fn_ptr.decl.output {
387 FnRetTy::DefaultReturn(_) => {
388 if let [.., ty] = fn_ptr.decl.inputs {
389 ty_search_pat(ty).1
390 } else {
391 Pat::Str("(")
392 }
393 },
394 FnRetTy::Return(ty) => ty_search_pat(ty).1,
395 },
396 ),
397 TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
398 TyKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
401 TyKind::Tup([ty]) => ty_search_pat(ty),
402 TyKind::Tup([head, .., tail]) => (ty_search_pat(head).0, ty_search_pat(tail).1),
403 TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
404 TyKind::Path(qpath) => qpath_search_pat(&qpath),
405 TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
406 TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
407 (Pat::Str("dyn"), Pat::Str(""))
408 },
409 _ => (Pat::Str(""), Pat::Str("")),
411 }
412}
413
414fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
415 (Pat::Sym(ident.name), Pat::Sym(ident.name))
416}
417
418pub trait WithSearchPat<'cx> {
419 type Context: LintContext;
420 fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
421 fn span(&self) -> Span;
422}
423macro_rules! impl_with_search_pat {
424 (($cx_ident:ident: $cx_ty:ident<$cx_lt:lifetime>, $self:tt: $ty:ty) => $fn:ident($($args:tt)*)) => {
425 impl<$cx_lt> WithSearchPat<$cx_lt> for $ty {
426 type Context = $cx_ty<$cx_lt>;
427 fn search_pat(&$self, $cx_ident: &Self::Context) -> (Pat, Pat) {
428 $fn($($args)*)
429 }
430 fn span(&self) -> Span {
431 self.span
432 }
433 }
434 };
435}
436impl_with_search_pat!((cx: LateContext<'tcx>, self: Expr<'tcx>) => expr_search_pat(cx.tcx, self));
437impl_with_search_pat!((_cx: LateContext<'tcx>, self: Item<'_>) => item_search_pat(self));
438impl_with_search_pat!((_cx: LateContext<'tcx>, self: TraitItem<'_>) => trait_item_search_pat(self));
439impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_search_pat(self));
440impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self));
441impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self));
442impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self));
443impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self));
444impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node));
445impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
446
447impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
448
449impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
450 type Context = LateContext<'cx>;
451
452 fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
453 fn_kind_pat(cx.tcx, self.0, self.1, self.2)
454 }
455
456 fn span(&self) -> Span {
457 self.3
458 }
459}
460
461pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
466 let (start_pat, end_pat) = item.search_pat(cx);
467 !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
468}
469
470pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
472 span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
473}
474
475pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
477 span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
478}