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
;
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 _}
;
15 declare_clippy_lint
! {
17 /// Generates clippy code that detects the offending pattern
21 /// // ./tests/ui/my_lint.rs
23 /// // detect the following pattern
26 /// // but ignore everything from here on
27 /// #![clippy::author = "ignore"]
33 /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
34 /// a `./tests/ui/new_lint.stdout` file with the generated code:
37 /// // ./tests/ui/new_lint.stdout
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,
45 /// // report your lint here
51 "helper for writing lints"
54 declare_lint_pass
!(Author
=> [LINT_AUTHOR
]);
56 /// Writes a line of output with indentation added
59 println
!(" {}", format_args
!($
($t
)*))
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`.
68 ($
self:ident $
(, $name
:ident
)+) => {
69 $
(let $name
= & $
self.bind(stringify
!($name
), $name
);)+
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
)));)+
82 /// Creates a `Binding` that accesses the field of an existing `Binding`
84 ($binding
:ident
.$field
:ident
) => {
86 name
: $binding
.name
.to_string() + stringify
!(.$field
),
87 value
: $binding
.value
.$field
,
93 println
!("if_chain! {{");
98 println
!(" // report your lint here");
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());
108 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::ImplItem
<'_
>) {
109 check_item(cx
, item
.hir_id());
112 fn check_trait_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::TraitItem
<'_
>) {
113 check_item(cx
, item
.hir_id());
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
));
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
));
128 fn check_stmt(&mut self, cx
: &LateContext
<'tcx
>, stmt
: &'tcx hir
::Stmt
<'_
>) {
130 StmtKind
::Expr(e
) | StmtKind
::Semi(e
) if has_attr(cx
, e
.hir_id
) => return,
133 check_node(cx
, stmt
.hir_id
, |v
| {
134 v
.stmt(&v
.bind("stmt", stmt
));
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
));
148 fn check_node(cx
: &LateContext
<'_
>, hir_id
: HirId
, f
: impl Fn(&PrintVisitor
<'_
, '_
>)) {
149 if has_attr(cx
, hir_id
) {
151 f(&PrintVisitor
::new(cx
));
161 impl<T
> Display
for Binding
<T
> {
162 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
163 f
.write_str(&self.name
)
167 struct OptionPat
<T
> {
171 impl<T
> OptionPat
<T
> {
172 fn new(opt
: Option
<T
>) -> Self {
176 fn if_some(&self, f
: impl Fn(&T
)) {
177 if let Some(t
) = &self.opt
{
183 impl<T
: Display
> Display
for OptionPat
<T
> {
184 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
186 None
=> f
.write_str("None"),
187 Some(node
) => write
!(f
, "Some({node})"),
192 struct PrintVisitor
<'a
, 'tcx
> {
193 cx
: &'a LateContext
<'tcx
>,
194 /// Fields are the current index that needs to be appended to pattern
196 ids
: std
::cell
::Cell
<FxHashMap
<&'
static str, u32>>,
199 #[allow(clippy::unused_self)]
200 impl<'a
, 'tcx
> PrintVisitor
<'a
, 'tcx
> {
201 fn new(cx
: &'a LateContext
<'tcx
>) -> Self {
204 ids
: std
::cell
::Cell
::default(),
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
213 // append a number starting with 1
214 n
=> format
!("{s}{n}"),
220 fn bind
<T
>(&self, name
: &'
static str, value
: T
) -> Binding
<T
> {
221 let name
= self.next(name
);
222 Binding { name, value }
225 fn option
<T
: Copy
>(&self, option
: &Binding
<Option
<T
>>, name
: &'
static str, f
: impl Fn(&Binding
<T
>)) {
227 None
=> out
!("if {option}.is_none();"),
229 let value
= &self.bind(name
, value
);
230 out
!("if let Some({value}) = {option};");
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();");
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 }
);
248 fn destination(&self, destination
: &Binding
<hir
::Destination
>) {
249 self.option(field
!(destination
.label
), "label", |label
| {
250 self.ident(field
!(label
.ident
));
254 fn ident(&self, ident
: &Binding
<Ident
>) {
255 out
!("if {ident}.as_str() == {:?};", ident
.value
.as_str());
258 fn symbol(&self, symbol
: &Binding
<Symbol
>) {
259 out
!("if {symbol}.as_str() == {:?};", symbol
.value
.as_str());
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:?}, _));");
266 out
!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath
.value
));
270 fn lit(&self, lit
: &Binding
<&Lit
>) {
271 let kind
= |kind
| out
!("if let LitKind::{kind} = {lit}.node;");
273 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
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"),
287 kind
!("Int({i}, {int_ty})");
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"),
294 kind
!("Float(_, {float_ty})");
296 LitKind
::ByteStr(ref vec
) => {
298 kind
!("ByteStr(ref {vec})");
299 out
!("if let [{:?}] = **{vec};", vec
.value
);
301 LitKind
::Str(s
, _
) => {
303 kind
!("Str({s}, _)");
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
)) => {
315 out
!("if let Some(Guard::If({expr})) = {arm}.guard;");
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
));
325 self.expr(field
!(arm
.body
));
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
);
333 "if let Some(higher::While {{ condition: {condition}, body: {body} }}) \
334 = higher::While::hir({expr});"
336 self.expr(condition
);
341 if let Some(higher
::WhileLet
{
345 }) = higher
::WhileLet
::hir(expr
.value
)
347 bind
!(self, let_pat
, let_expr
, if_then
);
349 "if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
350 = higher::WhileLet::hir({expr});"
358 if let Some(higher
::ForLoop { pat, arg, body, .. }
) = higher
::ForLoop
::hir(expr
.value
) {
359 bind
!(self, pat
, arg
, body
);
361 "if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
362 = higher::ForLoop::hir({expr});"
370 let kind
= |kind
| out
!("if let ExprKind::{kind} = {expr}.kind;");
372 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
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
382 if let Some(TyKind
::Path(ref qpath
)) = let_expr
.value
.ty
.as_ref().map(|ty
| &ty
.kind
) {
384 out
!("if let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind;");
387 self.expr(field
!(let_expr
.init
));
389 ExprKind
::Box(inner
) => {
391 kind
!("Box({inner})");
394 ExprKind
::Array(elements
) => {
395 bind
!(self, elements
);
396 kind
!("Array({elements})");
397 self.slice(elements
, |e
| self.expr(e
));
399 ExprKind
::Call(func
, args
) => {
400 bind
!(self, func
, args
);
401 kind
!("Call({func}, {args})");
403 self.slice(args
, |e
| self.expr(e
));
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
));
411 ExprKind
::Tup(elements
) => {
412 bind
!(self, elements
);
413 kind
!("Tup({elements})");
414 self.slice(elements
, |e
| self.expr(e
));
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
);
423 ExprKind
::Unary(op
, inner
) => {
425 kind
!("Unary(UnOp::{op:?}, {inner})");
428 ExprKind
::Lit(ref lit
) => {
430 kind
!("Lit(ref {lit})");
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
{
438 out
!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;");
443 ExprKind
::Type(expr
, _ty
) => {
445 kind
!("Type({expr}, _)");
448 ExprKind
::Loop(body
, label
, des
, _
) => {
450 opt_bind
!(self, label
);
451 kind
!("Loop({body}, {label}, LoopSource::{des:?}, _)");
453 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
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})");
461 else_expr
.if_some(|e
| self.expr(e
));
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
));
476 let movability
= OptionPat
::new(movability
.map(|m
| format
!("Movability::{m:?}")));
478 let ret_ty
= match fn_decl
.output
{
479 FnRetTy
::DefaultReturn(_
) => "FnRetTy::DefaultReturn(_)",
480 FnRetTy
::Return(_
) => "FnRetTy::Return(_ty)",
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;");
488 ExprKind
::Yield(sub
, source
) => {
490 kind
!("Yield(sub, YieldSource::{source:?})");
493 ExprKind
::Block(block
, label
) => {
495 opt_bind
!(self, label
);
496 kind
!("Block({block}, {label})");
498 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
500 ExprKind
::Assign(target
, value
, _
) => {
501 bind
!(self, target
, value
);
502 kind
!("Assign({target}, {value}, _span)");
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
);
513 ExprKind
::Field(object
, field_name
) => {
514 bind
!(self, object
, field_name
);
515 kind
!("Field({object}, {field_name})");
516 self.ident(field_name
);
519 ExprKind
::Index(object
, index
) => {
520 bind
!(self, object
, index
);
521 kind
!("Index({object}, {index})");
525 ExprKind
::Path(ref qpath
) => {
527 kind
!("Path(ref {qpath})");
530 ExprKind
::AddrOf(kind
, mutability
, inner
) => {
532 kind
!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
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
));
542 ExprKind
::Continue(destination
) => {
543 bind
!(self, destination
);
544 kind
!("Continue({destination})");
545 self.destination(destination
);
547 ExprKind
::Ret(value
) => {
548 opt_bind
!(self, value
);
549 kind
!("Ret({value})");
550 value
.if_some(|e
| self.expr(e
));
552 ExprKind
::InlineAsm(_
) => {
553 kind
!("InlineAsm(_)");
554 out
!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
556 ExprKind
::Struct(qpath
, fields
, base
) => {
557 bind
!(self, qpath
, fields
);
558 opt_bind
!(self, base
);
559 kind
!("Struct({qpath}, {fields}, {base})");
561 self.slice(fields
, |field
| {
562 self.ident(field
!(field
.ident
));
563 self.expr(field
!(field
.expr
));
565 base
.if_some(|e
| self.expr(e
));
567 ExprKind
::ConstBlock(_
) => kind
!("ConstBlock(_)"),
568 ExprKind
::Repeat(value
, length
) => {
569 bind
!(self, value
, length
);
570 kind
!("Repeat({value}, {length})");
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
));
581 ExprKind
::Err
=> kind
!("Err"),
582 ExprKind
::DropTemps(expr
) => {
584 kind
!("DropTemps({expr})");
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
| {
597 fn body(&self, body_id
: &Binding
<hir
::BodyId
>) {
598 let expr
= &self.cx
.tcx
.hir().body(body_id
.value
).value
;
600 out
!("let {expr} = &cx.tcx.hir().body({body_id}).value;");
604 fn pat(&self, pat
: &Binding
<&hir
::Pat
<'_
>>) {
605 let kind
= |kind
| out
!("if let PatKind::{kind} = {pat}.kind;");
607 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
610 match pat
.value
.kind
{
611 PatKind
::Wild
=> kind
!("Wild"),
612 PatKind
::Binding(anno
, .., name
, sub
) => {
614 opt_bind
!(self, sub
);
615 kind
!("Binding(BindingAnnotation::{anno:?}, _, {name}, {sub})");
617 sub
.if_some(|p
| self.pat(p
));
619 PatKind
::Struct(ref qpath
, fields
, ignore
) => {
620 bind
!(self, qpath
, fields
);
621 kind
!("Struct(ref {qpath}, {fields}, {ignore})");
623 self.slice(fields
, |field
| {
624 self.ident(field
!(field
.ident
));
625 self.pat(field
!(field
.pat
));
628 PatKind
::Or(fields
) => {
630 kind
!("Or({fields})");
631 self.slice(fields
, |pat
| self.pat(pat
));
633 PatKind
::TupleStruct(ref qpath
, fields
, skip_pos
) => {
634 bind
!(self, qpath
, fields
);
635 kind
!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
637 self.slice(fields
, |pat
| self.pat(pat
));
639 PatKind
::Path(ref qpath
) => {
641 kind
!("Path(ref {qpath})");
644 PatKind
::Tuple(fields
, skip_pos
) => {
646 kind
!("Tuple({fields}, {skip_pos:?})");
647 self.slice(fields
, |field
| self.pat(field
));
649 PatKind
::Box(pat
) => {
654 PatKind
::Ref(pat
, muta
) => {
656 kind
!("Ref({pat}, Mutability::{muta:?})");
659 PatKind
::Lit(lit_expr
) => {
660 bind
!(self, lit_expr
);
661 kind
!("Lit({lit_expr})");
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
));
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
));
681 fn stmt(&self, stmt
: &Binding
<&hir
::Stmt
<'_
>>) {
682 let kind
= |kind
| out
!("if let StmtKind::{kind} = {stmt}.kind;");
684 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
687 match stmt
.value
.kind
{
688 StmtKind
::Local(local
) => {
690 kind
!("Local({local})");
691 self.option(field
!(local
.init
), "init", |init
| {
694 self.pat(field
!(local
.pat
));
696 StmtKind
::Item(_
) => kind
!("Item(item_id)"),
697 StmtKind
::Expr(e
) => {
702 StmtKind
::Semi(e
) => {
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
716 fn path_to_string(path
: &QPath
<'_
>) -> String
{
717 fn inner(s
: &mut String
, path
: &QPath
<'_
>) {
719 QPath
::Resolved(_
, path
) => {
720 for (i
, segment
) in path
.segments
.iter().enumerate() {
724 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
727 QPath
::TypeRelative(ty
, segment
) => match &ty
.kind
{
728 hir
::TyKind
::Path(inner_path
) => {
729 inner(s
, inner_path
);
731 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
733 other
=> write
!(s
, "/* unimplemented: {:?}*/", other
).unwrap(),
735 QPath
::LangItem(..) => panic
!("path_to_string: called for lang item qpath"),
738 let mut s
= String
::new();