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