1 //! A group of attributes that can be attached to Rust code in order
2 //! to generate a clippy lint detecting said code automatically.
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;
10 ArrayLen, BindingAnnotation, Closure, ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind,
12 use rustc_lint::{LateContext, LateLintPass, LintContext};
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::symbol::{Ident, Symbol};
16 use std::fmt::{Display, Formatter, Write as _};
18 declare_clippy_lint! {
20 /// Generates clippy code that detects the offending pattern
24 /// // ./tests/ui/my_lint.rs
26 /// // detect the following pattern
29 /// // but ignore everything from here on
30 /// #![clippy::author = "ignore"]
36 /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
37 /// a `./tests/ui/new_lint.stdout` file with the generated code:
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
47 /// // report your lint here
52 "helper for writing lints"
55 declare_lint_pass!(Author => [LINT_AUTHOR]);
57 /// Writes a line of output with indentation added
60 println!(" {}", format_args!($($t)*))
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`.
69 ($self:ident $(, $name:ident)+) => {
70 $(let $name = & $self.bind(stringify!($name), $name);)+
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)));)+
83 /// Creates a `Binding` that accesses the field of an existing `Binding`
85 ($binding:ident.$field:ident) => {
87 name: $binding.name.to_string() + stringify!(.$field),
88 value: $binding.value.$field,
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
96 ($self:ident, $($t:tt)*) => {
97 if $self.first.take() {
98 println!("if {}", format_args!($($t)*));
100 println!(" && {}", format_args!($($t)*));
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());
110 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
111 check_item(cx, item.hir_id());
114 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
115 check_item(cx, item.hir_id());
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));
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));
130 fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
132 StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return,
135 check_node(cx, stmt.hir_id, |v| {
136 v.stmt(&v.bind("stmt", stmt));
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));
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));
154 println!(" // report your lint here");
164 impl<T> Display for Binding<T> {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 f.write_str(&self.name)
170 struct OptionPat<T> {
174 impl<T> OptionPat<T> {
175 fn new(opt: Option<T>) -> Self {
179 fn if_some(&self, f: impl Fn(&T)) {
180 if let Some(t) = &self.opt {
186 impl<T: Display> Display for OptionPat<T> {
187 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189 None => f.write_str("None"),
190 Some(node) => write!(f, "Some({node})"),
195 struct PrintVisitor<'a, 'tcx> {
196 cx: &'a LateContext<'tcx>,
197 /// Fields are the current index that needs to be appended to pattern
199 ids: Cell<FxHashMap<&'static str, u32>>,
200 /// Currently at the first condition in the if chain
204 #[allow(clippy::unused_self)]
205 impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
206 fn new(cx: &'a LateContext<'tcx>) -> Self {
209 ids: Cell::default(),
210 first: Cell::new(true),
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
219 // append a number starting with 1
220 n => format!("{s}{n}"),
226 fn bind<T>(&self, name: &'static str, value: T) -> Binding<T> {
227 let name = self.next(name);
228 Binding { name, value }
231 fn option<T: Copy>(&self, option: &Binding<Option<T>>, name: &'static str, f: impl Fn(&Binding<T>)) {
233 None => chain!(self, "{option}.is_none()"),
235 let value = &self.bind(name, value);
236 chain!(self, "let Some({value}) = {option}");
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()");
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 });
254 fn destination(&self, destination: &Binding<hir::Destination>) {
255 self.option(field!(destination.label), "label", |label| {
256 self.ident(field!(label.ident));
260 fn ident(&self, ident: &Binding<Ident>) {
261 chain!(self, "{ident}.as_str() == {:?}", ident.value.as_str());
264 fn symbol(&self, symbol: &Binding<Symbol>) {
265 chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
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:?}, _))");
272 chain!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath.value));
276 fn lit(&self, lit: &Binding<&Lit>) {
277 let kind = |kind| chain!(self, "let LitKind::{kind} = {lit}.node");
279 ($($t:tt)*) => (kind(format_args!($($t)*)));
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"),
293 kind!("Int({i}, {int_ty})");
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"),
300 kind!("Float(_, {float_ty})");
302 LitKind::ByteStr(ref vec) => {
304 kind!("ByteStr(ref {vec})");
305 chain!(self, "let [{:?}] = **{vec}", vec.value);
307 LitKind::Str(s, _) => {
309 kind!("Str({s}, _)");
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)) => {
321 chain!(self, "let Some(Guard::If({expr})) = {arm}.guard");
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));
331 self.expr(field!(arm.body));
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);
340 "let Some(higher::While {{ condition: {condition}, body: {body} }}) \
341 = higher::While::hir({expr})"
343 self.expr(condition);
348 if let Some(higher::WhileLet {
352 }) = higher::WhileLet::hir(expr.value)
354 bind!(self, let_pat, let_expr, if_then);
357 "let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
358 = higher::WhileLet::hir({expr})"
366 if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) {
367 bind!(self, pat, arg, body);
370 "let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
371 = higher::ForLoop::hir({expr})"
379 let kind = |kind| chain!(self, "let ExprKind::{kind} = {expr}.kind");
381 ($($t:tt)*) => (kind(format_args!($($t)*)));
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
391 if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
393 chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
396 self.expr(field!(let_expr.init));
398 ExprKind::Box(inner) => {
400 kind!("Box({inner})");
403 ExprKind::Array(elements) => {
404 bind!(self, elements);
405 kind!("Array({elements})");
406 self.slice(elements, |e| self.expr(e));
408 ExprKind::Call(func, args) => {
409 bind!(self, func, args);
410 kind!("Call({func}, {args})");
412 self.slice(args, |e| self.expr(e));
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));
419 self.slice(args, |e| self.expr(e));
421 ExprKind::Tup(elements) => {
422 bind!(self, elements);
423 kind!("Tup({elements})");
424 self.slice(elements, |e| self.expr(e));
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);
433 ExprKind::Unary(op, inner) => {
435 kind!("Unary(UnOp::{op:?}, {inner})");
438 ExprKind::Lit(ref lit) => {
440 kind!("Lit(ref {lit})");
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 {
448 chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
453 ExprKind::Type(expr, _ty) => {
455 kind!("Type({expr}, _)");
458 ExprKind::Loop(body, label, des, _) => {
460 opt_bind!(self, label);
461 kind!("Loop({body}, {label}, LoopSource::{des:?}, _)");
463 label.if_some(|l| self.ident(field!(l.ident)));
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})");
471 else_expr.if_some(|e| self.expr(e));
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));
479 ExprKind::Closure(&Closure {
486 let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}")));
488 let ret_ty = match fn_decl.output {
489 FnRetTy::DefaultReturn(_) => "FnRetTy::DefaultReturn(_)",
490 FnRetTy::Return(_) => "FnRetTy::Return(_ty)",
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");
498 ExprKind::Yield(sub, source) => {
500 kind!("Yield(sub, YieldSource::{source:?})");
503 ExprKind::Block(block, label) => {
505 opt_bind!(self, label);
506 kind!("Block({block}, {label})");
508 label.if_some(|l| self.ident(field!(l.ident)));
510 ExprKind::Assign(target, value, _) => {
511 bind!(self, target, value);
512 kind!("Assign({target}, {value}, _span)");
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);
523 ExprKind::Field(object, field_name) => {
524 bind!(self, object, field_name);
525 kind!("Field({object}, {field_name})");
526 self.ident(field_name);
529 ExprKind::Index(object, index) => {
530 bind!(self, object, index);
531 kind!("Index({object}, {index})");
535 ExprKind::Path(ref qpath) => {
537 kind!("Path(ref {qpath})");
540 ExprKind::AddrOf(kind, mutability, inner) => {
542 kind!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
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));
552 ExprKind::Continue(destination) => {
553 bind!(self, destination);
554 kind!("Continue({destination})");
555 self.destination(destination);
557 ExprKind::Ret(value) => {
558 opt_bind!(self, value);
559 kind!("Ret({value})");
560 value.if_some(|e| self.expr(e));
562 ExprKind::InlineAsm(_) => {
563 kind!("InlineAsm(_)");
564 out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
566 ExprKind::Struct(qpath, fields, base) => {
567 bind!(self, qpath, fields);
568 opt_bind!(self, base);
569 kind!("Struct({qpath}, {fields}, {base})");
571 self.slice(fields, |field| {
572 self.ident(field!(field.ident));
573 self.expr(field!(field.expr));
575 base.if_some(|e| self.expr(e));
577 ExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
578 ExprKind::Repeat(value, length) => {
579 bind!(self, value, length);
580 kind!("Repeat({value}, {length})");
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));
591 ExprKind::Err => kind!("Err"),
592 ExprKind::DropTemps(expr) => {
594 kind!("DropTemps({expr})");
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| {
607 fn body(&self, body_id: &Binding<hir::BodyId>) {
608 let expr = self.cx.tcx.hir().body(body_id.value).value;
610 chain!(self, "{expr} = &cx.tcx.hir().body({body_id}).value");
614 fn pat(&self, pat: &Binding<&hir::Pat<'_>>) {
615 let kind = |kind| chain!(self, "let PatKind::{kind} = {pat}.kind");
617 ($($t:tt)*) => (kind(format_args!($($t)*)));
620 match pat.value.kind {
621 PatKind::Wild => kind!("Wild"),
622 PatKind::Binding(ann, _, name, sub) => {
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",
631 kind!("Binding(BindingAnnotation::{ann}, _, {name}, {sub})");
633 sub.if_some(|p| self.pat(p));
635 PatKind::Struct(ref qpath, fields, ignore) => {
636 bind!(self, qpath, fields);
637 kind!("Struct(ref {qpath}, {fields}, {ignore})");
639 self.slice(fields, |field| {
640 self.ident(field!(field.ident));
641 self.pat(field!(field.pat));
644 PatKind::Or(fields) => {
646 kind!("Or({fields})");
647 self.slice(fields, |pat| self.pat(pat));
649 PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
650 bind!(self, qpath, fields);
651 kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
653 self.slice(fields, |pat| self.pat(pat));
655 PatKind::Path(ref qpath) => {
657 kind!("Path(ref {qpath})");
660 PatKind::Tuple(fields, skip_pos) => {
662 kind!("Tuple({fields}, {skip_pos:?})");
663 self.slice(fields, |field| self.pat(field));
665 PatKind::Box(pat) => {
670 PatKind::Ref(pat, muta) => {
672 kind!("Ref({pat}, Mutability::{muta:?})");
675 PatKind::Lit(lit_expr) => {
676 bind!(self, lit_expr);
677 kind!("Lit({lit_expr})");
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));
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));
697 fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) {
698 let kind = |kind| chain!(self, "let StmtKind::{kind} = {stmt}.kind");
700 ($($t:tt)*) => (kind(format_args!($($t)*)));
703 match stmt.value.kind {
704 StmtKind::Local(local) => {
706 kind!("Local({local})");
707 self.option(field!(local.init), "init", |init| {
710 self.pat(field!(local.pat));
712 StmtKind::Item(_) => kind!("Item(item_id)"),
713 StmtKind::Expr(e) => {
718 StmtKind::Semi(e) => {
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
732 fn path_to_string(path: &QPath<'_>) -> String {
733 fn inner(s: &mut String, path: &QPath<'_>) {
735 QPath::Resolved(_, path) => {
736 for (i, segment) in path.segments.iter().enumerate() {
740 write!(s, "{:?}", segment.ident.as_str()).unwrap();
743 QPath::TypeRelative(ty, segment) => match &ty.kind {
744 hir::TyKind::Path(inner_path) => {
745 inner(s, inner_path);
747 write!(s, "{:?}", segment.ident.as_str()).unwrap();
749 other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
751 QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"),
754 let mut s = String::new();