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