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}
;
15 use std
::fmt
::{Display, Formatter, Write as _}
;
17 declare_clippy_lint
! {
19 /// Generates clippy code that detects the offending pattern
23 /// // ./tests/ui/my_lint.rs
25 /// // detect the following pattern
28 /// // but ignore everything from here on
29 /// #![clippy::author = "ignore"]
35 /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
36 /// a `./tests/ui/new_lint.stdout` file with the generated code:
39 /// // ./tests/ui/new_lint.stdout
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,
47 /// // report your lint here
53 "helper for writing lints"
56 declare_lint_pass
!(Author
=> [LINT_AUTHOR
]);
58 /// Writes a line of output with indentation added
61 println
!(" {}", format_args
!($
($t
)*))
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`.
70 ($
self:ident $
(, $name
:ident
)+) => {
71 $
(let $name
= & $
self.bind(stringify
!($name
), $name
);)+
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
)));)+
84 /// Creates a `Binding` that accesses the field of an existing `Binding`
86 ($binding
:ident
.$field
:ident
) => {
88 name
: $binding
.name
.to_string() + stringify
!(.$field
),
89 value
: $binding
.value
.$field
,
95 println
!("if_chain! {{");
100 println
!(" // report your lint here");
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()) {
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
) {
153 f(&PrintVisitor
::new(cx
));
163 impl<T
> Display
for Binding
<T
> {
164 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
165 f
.write_str(&self.name
)
169 struct OptionPat
<T
> {
173 impl<T
> OptionPat
<T
> {
174 fn new(opt
: Option
<T
>) -> Self {
178 fn if_some(&self, f
: impl Fn(&T
)) {
179 if let Some(t
) = &self.opt
{
185 impl<T
: Display
> Display
for OptionPat
<T
> {
186 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
188 None
=> f
.write_str("None"),
189 Some(node
) => write
!(f
, "Some({node})"),
194 struct PrintVisitor
<'a
, 'tcx
> {
195 cx
: &'a LateContext
<'tcx
>,
196 /// Fields are the current index that needs to be appended to pattern
198 ids
: std
::cell
::Cell
<FxHashMap
<&'
static str, u32>>,
201 #[allow(clippy::unused_self)]
202 impl<'a
, 'tcx
> PrintVisitor
<'a
, 'tcx
> {
203 fn new(cx
: &'a LateContext
<'tcx
>) -> Self {
206 ids
: std
::cell
::Cell
::default(),
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
215 // append a number starting with 1
216 n
=> format
!("{s}{n}"),
222 fn bind
<T
>(&self, name
: &'
static str, value
: T
) -> Binding
<T
> {
223 let name
= self.next(name
);
224 Binding { name, value }
227 fn option
<T
: Copy
>(&self, option
: &Binding
<Option
<T
>>, name
: &'
static str, f
: impl Fn(&Binding
<T
>)) {
229 None
=> out
!("if {option}.is_none();"),
231 let value
= &self.bind(name
, value
);
232 out
!("if let Some({value}) = {option};");
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();");
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 }
);
250 fn destination(&self, destination
: &Binding
<hir
::Destination
>) {
251 self.option(field
!(destination
.label
), "label", |label
| {
252 self.ident(field
!(label
.ident
));
256 fn ident(&self, ident
: &Binding
<Ident
>) {
257 out
!("if {ident}.as_str() == {:?};", ident
.value
.as_str());
260 fn symbol(&self, symbol
: &Binding
<Symbol
>) {
261 out
!("if {symbol}.as_str() == {:?};", symbol
.value
.as_str());
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:?}, _));");
268 out
!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath
.value
));
272 fn lit(&self, lit
: &Binding
<&Lit
>) {
273 let kind
= |kind
| out
!("if let LitKind::{kind} = {lit}.node;");
275 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
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"),
289 kind
!("Int({i}, {int_ty})");
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"),
296 kind
!("Float(_, {float_ty})");
298 LitKind
::ByteStr(ref vec
) => {
300 kind
!("ByteStr(ref {vec})");
301 out
!("if let [{:?}] = **{vec};", vec
.value
);
303 LitKind
::Str(s
, _
) => {
305 kind
!("Str({s}, _)");
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
)) => {
317 out
!("if let Some(Guard::If({expr})) = {arm}.guard;");
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
));
327 self.expr(field
!(arm
.body
));
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
);
335 "if let Some(higher::While {{ condition: {condition}, body: {body} }}) \
336 = higher::While::hir({expr});"
338 self.expr(condition
);
343 if let Some(higher
::WhileLet
{
347 }) = higher
::WhileLet
::hir(expr
.value
)
349 bind
!(self, let_pat
, let_expr
, if_then
);
351 "if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
352 = higher::WhileLet::hir({expr});"
360 if let Some(higher
::ForLoop { pat, arg, body, .. }
) = higher
::ForLoop
::hir(expr
.value
) {
361 bind
!(self, pat
, arg
, body
);
363 "if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
364 = higher::ForLoop::hir({expr});"
372 let kind
= |kind
| out
!("if let ExprKind::{kind} = {expr}.kind;");
374 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
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
384 if let Some(TyKind
::Path(ref qpath
)) = let_expr
.value
.ty
.as_ref().map(|ty
| &ty
.kind
) {
386 out
!("if let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind;");
389 self.expr(field
!(let_expr
.init
));
391 ExprKind
::Box(inner
) => {
393 kind
!("Box({inner})");
396 ExprKind
::Array(elements
) => {
397 bind
!(self, elements
);
398 kind
!("Array({elements})");
399 self.slice(elements
, |e
| self.expr(e
));
401 ExprKind
::Call(func
, args
) => {
402 bind
!(self, func
, args
);
403 kind
!("Call({func}, {args})");
405 self.slice(args
, |e
| self.expr(e
));
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
));
412 self.slice(args
, |e
| self.expr(e
));
414 ExprKind
::Tup(elements
) => {
415 bind
!(self, elements
);
416 kind
!("Tup({elements})");
417 self.slice(elements
, |e
| self.expr(e
));
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
);
426 ExprKind
::Unary(op
, inner
) => {
428 kind
!("Unary(UnOp::{op:?}, {inner})");
431 ExprKind
::Lit(ref lit
) => {
433 kind
!("Lit(ref {lit})");
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
{
441 out
!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;");
446 ExprKind
::Type(expr
, _ty
) => {
448 kind
!("Type({expr}, _)");
451 ExprKind
::Loop(body
, label
, des
, _
) => {
453 opt_bind
!(self, label
);
454 kind
!("Loop({body}, {label}, LoopSource::{des:?}, _)");
456 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
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})");
464 else_expr
.if_some(|e
| self.expr(e
));
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
));
472 ExprKind
::Closure(&Closure
{
479 let movability
= OptionPat
::new(movability
.map(|m
| format
!("Movability::{m:?}")));
481 let ret_ty
= match fn_decl
.output
{
482 FnRetTy
::DefaultReturn(_
) => "FnRetTy::DefaultReturn(_)",
483 FnRetTy
::Return(_
) => "FnRetTy::Return(_ty)",
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;");
491 ExprKind
::Yield(sub
, source
) => {
493 kind
!("Yield(sub, YieldSource::{source:?})");
496 ExprKind
::Block(block
, label
) => {
498 opt_bind
!(self, label
);
499 kind
!("Block({block}, {label})");
501 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
503 ExprKind
::Assign(target
, value
, _
) => {
504 bind
!(self, target
, value
);
505 kind
!("Assign({target}, {value}, _span)");
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
);
516 ExprKind
::Field(object
, field_name
) => {
517 bind
!(self, object
, field_name
);
518 kind
!("Field({object}, {field_name})");
519 self.ident(field_name
);
522 ExprKind
::Index(object
, index
) => {
523 bind
!(self, object
, index
);
524 kind
!("Index({object}, {index})");
528 ExprKind
::Path(ref qpath
) => {
530 kind
!("Path(ref {qpath})");
533 ExprKind
::AddrOf(kind
, mutability
, inner
) => {
535 kind
!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
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
));
545 ExprKind
::Continue(destination
) => {
546 bind
!(self, destination
);
547 kind
!("Continue({destination})");
548 self.destination(destination
);
550 ExprKind
::Ret(value
) => {
551 opt_bind
!(self, value
);
552 kind
!("Ret({value})");
553 value
.if_some(|e
| self.expr(e
));
555 ExprKind
::InlineAsm(_
) => {
556 kind
!("InlineAsm(_)");
557 out
!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
559 ExprKind
::Struct(qpath
, fields
, base
) => {
560 bind
!(self, qpath
, fields
);
561 opt_bind
!(self, base
);
562 kind
!("Struct({qpath}, {fields}, {base})");
564 self.slice(fields
, |field
| {
565 self.ident(field
!(field
.ident
));
566 self.expr(field
!(field
.expr
));
568 base
.if_some(|e
| self.expr(e
));
570 ExprKind
::ConstBlock(_
) => kind
!("ConstBlock(_)"),
571 ExprKind
::Repeat(value
, length
) => {
572 bind
!(self, value
, length
);
573 kind
!("Repeat({value}, {length})");
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
));
584 ExprKind
::Err
=> kind
!("Err"),
585 ExprKind
::DropTemps(expr
) => {
587 kind
!("DropTemps({expr})");
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
| {
600 fn body(&self, body_id
: &Binding
<hir
::BodyId
>) {
601 let expr
= self.cx
.tcx
.hir().body(body_id
.value
).value
;
603 out
!("let {expr} = &cx.tcx.hir().body({body_id}).value;");
607 fn pat(&self, pat
: &Binding
<&hir
::Pat
<'_
>>) {
608 let kind
= |kind
| out
!("if let PatKind::{kind} = {pat}.kind;");
610 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
613 match pat
.value
.kind
{
614 PatKind
::Wild
=> kind
!("Wild"),
615 PatKind
::Binding(ann
, _
, name
, sub
) => {
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",
624 kind
!("Binding(BindingAnnotation::{ann}, _, {name}, {sub})");
626 sub
.if_some(|p
| self.pat(p
));
628 PatKind
::Struct(ref qpath
, fields
, ignore
) => {
629 bind
!(self, qpath
, fields
);
630 kind
!("Struct(ref {qpath}, {fields}, {ignore})");
632 self.slice(fields
, |field
| {
633 self.ident(field
!(field
.ident
));
634 self.pat(field
!(field
.pat
));
637 PatKind
::Or(fields
) => {
639 kind
!("Or({fields})");
640 self.slice(fields
, |pat
| self.pat(pat
));
642 PatKind
::TupleStruct(ref qpath
, fields
, skip_pos
) => {
643 bind
!(self, qpath
, fields
);
644 kind
!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
646 self.slice(fields
, |pat
| self.pat(pat
));
648 PatKind
::Path(ref qpath
) => {
650 kind
!("Path(ref {qpath})");
653 PatKind
::Tuple(fields
, skip_pos
) => {
655 kind
!("Tuple({fields}, {skip_pos:?})");
656 self.slice(fields
, |field
| self.pat(field
));
658 PatKind
::Box(pat
) => {
663 PatKind
::Ref(pat
, muta
) => {
665 kind
!("Ref({pat}, Mutability::{muta:?})");
668 PatKind
::Lit(lit_expr
) => {
669 bind
!(self, lit_expr
);
670 kind
!("Lit({lit_expr})");
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
));
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
));
690 fn stmt(&self, stmt
: &Binding
<&hir
::Stmt
<'_
>>) {
691 let kind
= |kind
| out
!("if let StmtKind::{kind} = {stmt}.kind;");
693 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
696 match stmt
.value
.kind
{
697 StmtKind
::Local(local
) => {
699 kind
!("Local({local})");
700 self.option(field
!(local
.init
), "init", |init
| {
703 self.pat(field
!(local
.pat
));
705 StmtKind
::Item(_
) => kind
!("Item(item_id)"),
706 StmtKind
::Expr(e
) => {
711 StmtKind
::Semi(e
) => {
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
725 fn path_to_string(path
: &QPath
<'_
>) -> String
{
726 fn inner(s
: &mut String
, path
: &QPath
<'_
>) {
728 QPath
::Resolved(_
, path
) => {
729 for (i
, segment
) in path
.segments
.iter().enumerate() {
733 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
736 QPath
::TypeRelative(ty
, segment
) => match &ty
.kind
{
737 hir
::TyKind
::Path(inner_path
) => {
738 inner(s
, inner_path
);
740 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
742 other
=> write
!(s
, "/* unimplemented: {:?}*/", other
).unwrap(),
744 QPath
::LangItem(..) => panic
!("path_to_string: called for lang item qpath"),
747 let mut s
= String
::new();