]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/author.rs
Auto merge of #99918 - WaffleLapkin:fnFnfun, r=estebank
[rust.git] / src / tools / clippy / clippy_lints / src / utils / author.rs
1 //! A group of attributes that can be attached to Rust code in order
2 //! to generate a clippy lint detecting said code automatically.
3
4 use clippy_utils::{get_attr, higher};
5 use rustc_ast::ast::{LitFloatType, LitKind};
6 use rustc_ast::LitIntType;
7 use rustc_data_structures::fx::FxHashMap;
8 use rustc_hir as hir;
9 use rustc_hir::{
10     ArrayLen, BindingAnnotation, Closure, ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind,
11 };
12 use rustc_lint::{LateContext, LateLintPass, LintContext};
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::symbol::{Ident, Symbol};
15 use std::cell::Cell;
16 use std::fmt::{Display, Formatter, Write as _};
17
18 declare_clippy_lint! {
19     /// ### What it does
20     /// Generates clippy code that detects the offending pattern
21     ///
22     /// ### Example
23     /// ```rust,ignore
24     /// // ./tests/ui/my_lint.rs
25     /// fn foo() {
26     ///     // detect the following pattern
27     ///     #[clippy::author]
28     ///     if x == 42 {
29     ///         // but ignore everything from here on
30     ///         #![clippy::author = "ignore"]
31     ///     }
32     ///     ()
33     /// }
34     /// ```
35     ///
36     /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
37     /// a `./tests/ui/new_lint.stdout` file with the generated code:
38     ///
39     /// ```rust,ignore
40     /// // ./tests/ui/new_lint.stdout
41     /// if ExprKind::If(ref cond, ref then, None) = item.kind
42     ///     && let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind
43     ///     && let ExprKind::Path(ref path) = left.kind
44     ///     && let ExprKind::Lit(ref lit) = right.kind
45     ///     && let LitKind::Int(42, _) = lit.node
46     /// {
47     ///     // report your lint here
48     /// }
49     /// ```
50     pub LINT_AUTHOR,
51     internal_warn,
52     "helper for writing lints"
53 }
54
55 declare_lint_pass!(Author => [LINT_AUTHOR]);
56
57 /// Writes a line of output with indentation added
58 macro_rules! out {
59     ($($t:tt)*) => {
60         println!("    {}", format_args!($($t)*))
61     };
62 }
63
64 /// The variables passed in are replaced with `&Binding`s where the `value` field is set
65 /// to the original value of the variable. The `name` field is set to the name of the variable
66 /// (using `stringify!`) and is adjusted to avoid duplicate names.
67 /// Note that the `Binding` may be printed directly to output the `name`.
68 macro_rules! bind {
69     ($self:ident $(, $name:ident)+) => {
70         $(let $name = & $self.bind(stringify!($name), $name);)+
71     };
72 }
73
74 /// Transforms the given `Option<T>` variables into `OptionPat<Binding<T>>`.
75 /// This displays as `Some($name)` or `None` when printed. The name of the inner binding
76 /// is set to the name of the variable passed to the macro.
77 macro_rules! opt_bind {
78     ($self:ident $(, $name:ident)+) => {
79         $(let $name = OptionPat::new($name.map(|o| $self.bind(stringify!($name), o)));)+
80     };
81 }
82
83 /// Creates a `Binding` that accesses the field of an existing `Binding`
84 macro_rules! field {
85     ($binding:ident.$field:ident) => {
86         &Binding {
87             name: $binding.name.to_string() + stringify!(.$field),
88             value: $binding.value.$field,
89         }
90     };
91 }
92
93 /// Print a condition of a let chain, `chain!(self, "let Some(x) = y")` will print
94 /// `if let Some(x) = y` on the first call and `    && let Some(x) = y` thereafter
95 macro_rules! chain {
96     ($self:ident, $($t:tt)*) => {
97         if $self.first.take() {
98             println!("if {}", format_args!($($t)*));
99         } else {
100             println!("    && {}", format_args!($($t)*));
101         }
102     }
103 }
104
105 impl<'tcx> LateLintPass<'tcx> for Author {
106     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
107         check_item(cx, item.hir_id());
108     }
109
110     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
111         check_item(cx, item.hir_id());
112     }
113
114     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
115         check_item(cx, item.hir_id());
116     }
117
118     fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
119         check_node(cx, arm.hir_id, |v| {
120             v.arm(&v.bind("arm", arm));
121         });
122     }
123
124     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
125         check_node(cx, expr.hir_id, |v| {
126             v.expr(&v.bind("expr", expr));
127         });
128     }
129
130     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
131         match stmt.kind {
132             StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return,
133             _ => {},
134         }
135         check_node(cx, stmt.hir_id, |v| {
136             v.stmt(&v.bind("stmt", stmt));
137         });
138     }
139 }
140
141 fn check_item(cx: &LateContext<'_>, hir_id: HirId) {
142     let hir = cx.tcx.hir();
143     if let Some(body_id) = hir.maybe_body_owned_by(hir_id.expect_owner().def_id) {
144         check_node(cx, hir_id, |v| {
145             v.expr(&v.bind("expr", hir.body(body_id).value));
146         });
147     }
148 }
149
150 fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) {
151     if has_attr(cx, hir_id) {
152         f(&PrintVisitor::new(cx));
153         println!("{{");
154         println!("    // report your lint here");
155         println!("}}");
156     }
157 }
158
159 struct Binding<T> {
160     name: String,
161     value: T,
162 }
163
164 impl<T> Display for Binding<T> {
165     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166         f.write_str(&self.name)
167     }
168 }
169
170 struct OptionPat<T> {
171     pub opt: Option<T>,
172 }
173
174 impl<T> OptionPat<T> {
175     fn new(opt: Option<T>) -> Self {
176         Self { opt }
177     }
178
179     fn if_some(&self, f: impl Fn(&T)) {
180         if let Some(t) = &self.opt {
181             f(t);
182         }
183     }
184 }
185
186 impl<T: Display> Display for OptionPat<T> {
187     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
188         match &self.opt {
189             None => f.write_str("None"),
190             Some(node) => write!(f, "Some({node})"),
191         }
192     }
193 }
194
195 struct PrintVisitor<'a, 'tcx> {
196     cx: &'a LateContext<'tcx>,
197     /// Fields are the current index that needs to be appended to pattern
198     /// binding names
199     ids: Cell<FxHashMap<&'static str, u32>>,
200     /// Currently at the first condition in the if chain
201     first: Cell<bool>,
202 }
203
204 #[allow(clippy::unused_self)]
205 impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
206     fn new(cx: &'a LateContext<'tcx>) -> Self {
207         Self {
208             cx,
209             ids: Cell::default(),
210             first: Cell::new(true),
211         }
212     }
213
214     fn next(&self, s: &'static str) -> String {
215         let mut ids = self.ids.take();
216         let out = match *ids.entry(s).and_modify(|n| *n += 1).or_default() {
217             // first usage of the name, use it as is
218             0 => s.to_string(),
219             // append a number starting with 1
220             n => format!("{s}{n}"),
221         };
222         self.ids.set(ids);
223         out
224     }
225
226     fn bind<T>(&self, name: &'static str, value: T) -> Binding<T> {
227         let name = self.next(name);
228         Binding { name, value }
229     }
230
231     fn option<T: Copy>(&self, option: &Binding<Option<T>>, name: &'static str, f: impl Fn(&Binding<T>)) {
232         match option.value {
233             None => chain!(self, "{option}.is_none()"),
234             Some(value) => {
235                 let value = &self.bind(name, value);
236                 chain!(self, "let Some({value}) = {option}");
237                 f(value);
238             },
239         }
240     }
241
242     fn slice<T>(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) {
243         if slice.value.is_empty() {
244             chain!(self, "{slice}.is_empty()");
245         } else {
246             chain!(self, "{slice}.len() == {}", slice.value.len());
247             for (i, value) in slice.value.iter().enumerate() {
248                 let name = format!("{slice}[{i}]");
249                 f(&Binding { name, value });
250             }
251         }
252     }
253
254     fn destination(&self, destination: &Binding<hir::Destination>) {
255         self.option(field!(destination.label), "label", |label| {
256             self.ident(field!(label.ident));
257         });
258     }
259
260     fn ident(&self, ident: &Binding<Ident>) {
261         chain!(self, "{ident}.as_str() == {:?}", ident.value.as_str());
262     }
263
264     fn symbol(&self, symbol: &Binding<Symbol>) {
265         chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
266     }
267
268     fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
269         if let QPath::LangItem(lang_item, ..) = *qpath.value {
270             chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
271         } else {
272             chain!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath.value));
273         }
274     }
275
276     fn lit(&self, lit: &Binding<&Lit>) {
277         let kind = |kind| chain!(self, "let LitKind::{kind} = {lit}.node");
278         macro_rules! kind {
279             ($($t:tt)*) => (kind(format_args!($($t)*)));
280         }
281
282         match lit.value.node {
283             LitKind::Bool(val) => kind!("Bool({val:?})"),
284             LitKind::Char(c) => kind!("Char({c:?})"),
285             LitKind::Err => kind!("Err"),
286             LitKind::Byte(b) => kind!("Byte({b})"),
287             LitKind::Int(i, suffix) => {
288                 let int_ty = match suffix {
289                     LitIntType::Signed(int_ty) => format!("LitIntType::Signed(IntTy::{int_ty:?})"),
290                     LitIntType::Unsigned(uint_ty) => format!("LitIntType::Unsigned(UintTy::{uint_ty:?})"),
291                     LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"),
292                 };
293                 kind!("Int({i}, {int_ty})");
294             },
295             LitKind::Float(_, suffix) => {
296                 let float_ty = match suffix {
297                     LitFloatType::Suffixed(suffix_ty) => format!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"),
298                     LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"),
299                 };
300                 kind!("Float(_, {float_ty})");
301             },
302             LitKind::ByteStr(ref vec) => {
303                 bind!(self, vec);
304                 kind!("ByteStr(ref {vec})");
305                 chain!(self, "let [{:?}] = **{vec}", vec.value);
306             },
307             LitKind::Str(s, _) => {
308                 bind!(self, s);
309                 kind!("Str({s}, _)");
310                 self.symbol(s);
311             },
312         }
313     }
314
315     fn arm(&self, arm: &Binding<&hir::Arm<'_>>) {
316         self.pat(field!(arm.pat));
317         match arm.value.guard {
318             None => chain!(self, "{arm}.guard.is_none()"),
319             Some(hir::Guard::If(expr)) => {
320                 bind!(self, expr);
321                 chain!(self, "let Some(Guard::If({expr})) = {arm}.guard");
322                 self.expr(expr);
323             },
324             Some(hir::Guard::IfLet(let_expr)) => {
325                 bind!(self, let_expr);
326                 chain!(self, "let Some(Guard::IfLet({let_expr}) = {arm}.guard");
327                 self.pat(field!(let_expr.pat));
328                 self.expr(field!(let_expr.init));
329             },
330         }
331         self.expr(field!(arm.body));
332     }
333
334     #[allow(clippy::too_many_lines)]
335     fn expr(&self, expr: &Binding<&hir::Expr<'_>>) {
336         if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) {
337             bind!(self, condition, body);
338             chain!(
339                 self,
340                 "let Some(higher::While {{ condition: {condition}, body: {body} }}) \
341                 = higher::While::hir({expr})"
342             );
343             self.expr(condition);
344             self.expr(body);
345             return;
346         }
347
348         if let Some(higher::WhileLet {
349             let_pat,
350             let_expr,
351             if_then,
352         }) = higher::WhileLet::hir(expr.value)
353         {
354             bind!(self, let_pat, let_expr, if_then);
355             chain!(
356                 self,
357                 "let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
358                 = higher::WhileLet::hir({expr})"
359             );
360             self.pat(let_pat);
361             self.expr(let_expr);
362             self.expr(if_then);
363             return;
364         }
365
366         if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) {
367             bind!(self, pat, arg, body);
368             chain!(
369                 self,
370                 "let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
371                 = higher::ForLoop::hir({expr})"
372             );
373             self.pat(pat);
374             self.expr(arg);
375             self.expr(body);
376             return;
377         }
378
379         let kind = |kind| chain!(self, "let ExprKind::{kind} = {expr}.kind");
380         macro_rules! kind {
381             ($($t:tt)*) => (kind(format_args!($($t)*)));
382         }
383
384         match expr.value.kind {
385             ExprKind::Let(let_expr) => {
386                 bind!(self, let_expr);
387                 kind!("Let({let_expr})");
388                 self.pat(field!(let_expr.pat));
389                 // Does what ExprKind::Cast does, only adds a clause for the type
390                 // if it's a path
391                 if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
392                     bind!(self, qpath);
393                     chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
394                     self.qpath(qpath);
395                 }
396                 self.expr(field!(let_expr.init));
397             },
398             ExprKind::Box(inner) => {
399                 bind!(self, inner);
400                 kind!("Box({inner})");
401                 self.expr(inner);
402             },
403             ExprKind::Array(elements) => {
404                 bind!(self, elements);
405                 kind!("Array({elements})");
406                 self.slice(elements, |e| self.expr(e));
407             },
408             ExprKind::Call(func, args) => {
409                 bind!(self, func, args);
410                 kind!("Call({func}, {args})");
411                 self.expr(func);
412                 self.slice(args, |e| self.expr(e));
413             },
414             ExprKind::MethodCall(method_name, receiver, args, _) => {
415                 bind!(self, method_name, receiver, args);
416                 kind!("MethodCall({method_name}, {receiver}, {args}, _)");
417                 self.ident(field!(method_name.ident));
418                 self.expr(receiver);
419                 self.slice(args, |e| self.expr(e));
420             },
421             ExprKind::Tup(elements) => {
422                 bind!(self, elements);
423                 kind!("Tup({elements})");
424                 self.slice(elements, |e| self.expr(e));
425             },
426             ExprKind::Binary(op, left, right) => {
427                 bind!(self, op, left, right);
428                 kind!("Binary({op}, {left}, {right})");
429                 chain!(self, "BinOpKind::{:?} == {op}.node", op.value.node);
430                 self.expr(left);
431                 self.expr(right);
432             },
433             ExprKind::Unary(op, inner) => {
434                 bind!(self, inner);
435                 kind!("Unary(UnOp::{op:?}, {inner})");
436                 self.expr(inner);
437             },
438             ExprKind::Lit(ref lit) => {
439                 bind!(self, lit);
440                 kind!("Lit(ref {lit})");
441                 self.lit(lit);
442             },
443             ExprKind::Cast(expr, cast_ty) => {
444                 bind!(self, expr, cast_ty);
445                 kind!("Cast({expr}, {cast_ty})");
446                 if let TyKind::Path(ref qpath) = cast_ty.value.kind {
447                     bind!(self, qpath);
448                     chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
449                     self.qpath(qpath);
450                 }
451                 self.expr(expr);
452             },
453             ExprKind::Type(expr, _ty) => {
454                 bind!(self, expr);
455                 kind!("Type({expr}, _)");
456                 self.expr(expr);
457             },
458             ExprKind::Loop(body, label, des, _) => {
459                 bind!(self, body);
460                 opt_bind!(self, label);
461                 kind!("Loop({body}, {label}, LoopSource::{des:?}, _)");
462                 self.block(body);
463                 label.if_some(|l| self.ident(field!(l.ident)));
464             },
465             ExprKind::If(cond, then, else_expr) => {
466                 bind!(self, cond, then);
467                 opt_bind!(self, else_expr);
468                 kind!("If({cond}, {then}, {else_expr})");
469                 self.expr(cond);
470                 self.expr(then);
471                 else_expr.if_some(|e| self.expr(e));
472             },
473             ExprKind::Match(scrutinee, arms, des) => {
474                 bind!(self, scrutinee, arms);
475                 kind!("Match({scrutinee}, {arms}, MatchSource::{des:?})");
476                 self.expr(scrutinee);
477                 self.slice(arms, |arm| self.arm(arm));
478             },
479             ExprKind::Closure(&Closure {
480                 capture_clause,
481                 fn_decl,
482                 body: body_id,
483                 movability,
484                 ..
485             }) => {
486                 let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}")));
487
488                 let ret_ty = match fn_decl.output {
489                     FnRetTy::DefaultReturn(_) => "FnRetTy::DefaultReturn(_)",
490                     FnRetTy::Return(_) => "FnRetTy::Return(_ty)",
491                 };
492
493                 bind!(self, fn_decl, body_id);
494                 kind!("Closure(CaptureBy::{capture_clause:?}, {fn_decl}, {body_id}, _, {movability})");
495                 chain!(self, "let {ret_ty} = {fn_decl}.output");
496                 self.body(body_id);
497             },
498             ExprKind::Yield(sub, source) => {
499                 bind!(self, sub);
500                 kind!("Yield(sub, YieldSource::{source:?})");
501                 self.expr(sub);
502             },
503             ExprKind::Block(block, label) => {
504                 bind!(self, block);
505                 opt_bind!(self, label);
506                 kind!("Block({block}, {label})");
507                 self.block(block);
508                 label.if_some(|l| self.ident(field!(l.ident)));
509             },
510             ExprKind::Assign(target, value, _) => {
511                 bind!(self, target, value);
512                 kind!("Assign({target}, {value}, _span)");
513                 self.expr(target);
514                 self.expr(value);
515             },
516             ExprKind::AssignOp(op, target, value) => {
517                 bind!(self, op, target, value);
518                 kind!("AssignOp({op}, {target}, {value})");
519                 chain!(self, "BinOpKind::{:?} == {op}.node", op.value.node);
520                 self.expr(target);
521                 self.expr(value);
522             },
523             ExprKind::Field(object, field_name) => {
524                 bind!(self, object, field_name);
525                 kind!("Field({object}, {field_name})");
526                 self.ident(field_name);
527                 self.expr(object);
528             },
529             ExprKind::Index(object, index) => {
530                 bind!(self, object, index);
531                 kind!("Index({object}, {index})");
532                 self.expr(object);
533                 self.expr(index);
534             },
535             ExprKind::Path(ref qpath) => {
536                 bind!(self, qpath);
537                 kind!("Path(ref {qpath})");
538                 self.qpath(qpath);
539             },
540             ExprKind::AddrOf(kind, mutability, inner) => {
541                 bind!(self, inner);
542                 kind!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
543                 self.expr(inner);
544             },
545             ExprKind::Break(destination, value) => {
546                 bind!(self, destination);
547                 opt_bind!(self, value);
548                 kind!("Break({destination}, {value})");
549                 self.destination(destination);
550                 value.if_some(|e| self.expr(e));
551             },
552             ExprKind::Continue(destination) => {
553                 bind!(self, destination);
554                 kind!("Continue({destination})");
555                 self.destination(destination);
556             },
557             ExprKind::Ret(value) => {
558                 opt_bind!(self, value);
559                 kind!("Ret({value})");
560                 value.if_some(|e| self.expr(e));
561             },
562             ExprKind::InlineAsm(_) => {
563                 kind!("InlineAsm(_)");
564                 out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
565             },
566             ExprKind::Struct(qpath, fields, base) => {
567                 bind!(self, qpath, fields);
568                 opt_bind!(self, base);
569                 kind!("Struct({qpath}, {fields}, {base})");
570                 self.qpath(qpath);
571                 self.slice(fields, |field| {
572                     self.ident(field!(field.ident));
573                     self.expr(field!(field.expr));
574                 });
575                 base.if_some(|e| self.expr(e));
576             },
577             ExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
578             ExprKind::Repeat(value, length) => {
579                 bind!(self, value, length);
580                 kind!("Repeat({value}, {length})");
581                 self.expr(value);
582                 match length.value {
583                     ArrayLen::Infer(..) => chain!(self, "let ArrayLen::Infer(..) = length"),
584                     ArrayLen::Body(anon_const) => {
585                         bind!(self, anon_const);
586                         chain!(self, "let ArrayLen::Body({anon_const}) = {length}");
587                         self.body(field!(anon_const.body));
588                     },
589                 }
590             },
591             ExprKind::Err => kind!("Err"),
592             ExprKind::DropTemps(expr) => {
593                 bind!(self, expr);
594                 kind!("DropTemps({expr})");
595                 self.expr(expr);
596             },
597         }
598     }
599
600     fn block(&self, block: &Binding<&hir::Block<'_>>) {
601         self.slice(field!(block.stmts), |stmt| self.stmt(stmt));
602         self.option(field!(block.expr), "trailing_expr", |expr| {
603             self.expr(expr);
604         });
605     }
606
607     fn body(&self, body_id: &Binding<hir::BodyId>) {
608         let expr = self.cx.tcx.hir().body(body_id.value).value;
609         bind!(self, expr);
610         chain!(self, "{expr} = &cx.tcx.hir().body({body_id}).value");
611         self.expr(expr);
612     }
613
614     fn pat(&self, pat: &Binding<&hir::Pat<'_>>) {
615         let kind = |kind| chain!(self, "let PatKind::{kind} = {pat}.kind");
616         macro_rules! kind {
617             ($($t:tt)*) => (kind(format_args!($($t)*)));
618         }
619
620         match pat.value.kind {
621             PatKind::Wild => kind!("Wild"),
622             PatKind::Binding(ann, _, name, sub) => {
623                 bind!(self, name);
624                 opt_bind!(self, sub);
625                 let ann = match ann {
626                     BindingAnnotation::NONE => "NONE",
627                     BindingAnnotation::REF => "REF",
628                     BindingAnnotation::MUT => "MUT",
629                     BindingAnnotation::REF_MUT => "REF_MUT",
630                 };
631                 kind!("Binding(BindingAnnotation::{ann}, _, {name}, {sub})");
632                 self.ident(name);
633                 sub.if_some(|p| self.pat(p));
634             },
635             PatKind::Struct(ref qpath, fields, ignore) => {
636                 bind!(self, qpath, fields);
637                 kind!("Struct(ref {qpath}, {fields}, {ignore})");
638                 self.qpath(qpath);
639                 self.slice(fields, |field| {
640                     self.ident(field!(field.ident));
641                     self.pat(field!(field.pat));
642                 });
643             },
644             PatKind::Or(fields) => {
645                 bind!(self, fields);
646                 kind!("Or({fields})");
647                 self.slice(fields, |pat| self.pat(pat));
648             },
649             PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
650                 bind!(self, qpath, fields);
651                 kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
652                 self.qpath(qpath);
653                 self.slice(fields, |pat| self.pat(pat));
654             },
655             PatKind::Path(ref qpath) => {
656                 bind!(self, qpath);
657                 kind!("Path(ref {qpath})");
658                 self.qpath(qpath);
659             },
660             PatKind::Tuple(fields, skip_pos) => {
661                 bind!(self, fields);
662                 kind!("Tuple({fields}, {skip_pos:?})");
663                 self.slice(fields, |field| self.pat(field));
664             },
665             PatKind::Box(pat) => {
666                 bind!(self, pat);
667                 kind!("Box({pat})");
668                 self.pat(pat);
669             },
670             PatKind::Ref(pat, muta) => {
671                 bind!(self, pat);
672                 kind!("Ref({pat}, Mutability::{muta:?})");
673                 self.pat(pat);
674             },
675             PatKind::Lit(lit_expr) => {
676                 bind!(self, lit_expr);
677                 kind!("Lit({lit_expr})");
678                 self.expr(lit_expr);
679             },
680             PatKind::Range(start, end, end_kind) => {
681                 opt_bind!(self, start, end);
682                 kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
683                 start.if_some(|e| self.expr(e));
684                 end.if_some(|e| self.expr(e));
685             },
686             PatKind::Slice(start, middle, end) => {
687                 bind!(self, start, end);
688                 opt_bind!(self, middle);
689                 kind!("Slice({start}, {middle}, {end})");
690                 middle.if_some(|p| self.pat(p));
691                 self.slice(start, |pat| self.pat(pat));
692                 self.slice(end, |pat| self.pat(pat));
693             },
694         }
695     }
696
697     fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) {
698         let kind = |kind| chain!(self, "let StmtKind::{kind} = {stmt}.kind");
699         macro_rules! kind {
700             ($($t:tt)*) => (kind(format_args!($($t)*)));
701         }
702
703         match stmt.value.kind {
704             StmtKind::Local(local) => {
705                 bind!(self, local);
706                 kind!("Local({local})");
707                 self.option(field!(local.init), "init", |init| {
708                     self.expr(init);
709                 });
710                 self.pat(field!(local.pat));
711             },
712             StmtKind::Item(_) => kind!("Item(item_id)"),
713             StmtKind::Expr(e) => {
714                 bind!(self, e);
715                 kind!("Expr({e})");
716                 self.expr(e);
717             },
718             StmtKind::Semi(e) => {
719                 bind!(self, e);
720                 kind!("Semi({e})");
721                 self.expr(e);
722             },
723         }
724     }
725 }
726
727 fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
728     let attrs = cx.tcx.hir().attrs(hir_id);
729     get_attr(cx.sess(), attrs, "author").count() > 0
730 }
731
732 fn path_to_string(path: &QPath<'_>) -> String {
733     fn inner(s: &mut String, path: &QPath<'_>) {
734         match *path {
735             QPath::Resolved(_, path) => {
736                 for (i, segment) in path.segments.iter().enumerate() {
737                     if i > 0 {
738                         *s += ", ";
739                     }
740                     write!(s, "{:?}", segment.ident.as_str()).unwrap();
741                 }
742             },
743             QPath::TypeRelative(ty, segment) => match &ty.kind {
744                 hir::TyKind::Path(inner_path) => {
745                     inner(s, inner_path);
746                     *s += ", ";
747                     write!(s, "{:?}", segment.ident.as_str()).unwrap();
748                 },
749                 other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
750             },
751             QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"),
752         }
753     }
754     let mut s = String::new();
755     inner(&mut s, path);
756     s
757 }