1+ use clippy_utils:: source:: snippet;
2+ use rustc_middle:: lint:: in_external_macro;
13use rustc_hir:: * ;
2- use rustc_lint:: { LateContext , LateLintPass } ;
4+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
35use rustc_session:: declare_lint_pass;
6+ use rustc_errors:: Applicability ;
7+ use clippy_utils:: visitors:: { for_each_expr, Descend } ;
8+ use clippy_utils:: diagnostics:: span_lint_and_sugg;
9+ use std:: ops:: ControlFlow ;
410
511declare_clippy_lint ! {
612 /// ### What it does
7- /// Checks for stacked `if` and `match`, e.g., `if if `.
13+ /// Checks for `if if ` and `match match `.
814 ///
915 /// ### Why is this bad?
10- /// Stacked `if`'s and `match`'s are hard to read.
16+ /// `if if` and `match match` are hard to read.
1117 ///
1218 /// ### Example
1319 /// ```no_run
@@ -17,23 +23,19 @@ declare_clippy_lint! {
1723 /// e == f
1824 /// } {
1925 /// println!("true");
20- /// } else {
21- /// println!("false");
2226 /// }
2327 /// ```
2428 ///
2529 /// Use instead:
2630 /// ```no_run
27- /// let cond = if a == b {
31+ /// let result = if a == b {
2832 /// c == d
2933 /// } else {
3034 /// e == f
3135 /// };
3236 ///
33- /// if cond {
37+ /// if result {
3438 /// println!("true");
35- /// } else {
36- /// println!("false");
3739 /// }
3840 /// ```
3941 #[ clippy:: version = "1.82.0" ]
@@ -46,6 +48,48 @@ declare_lint_pass!(StackedIfMatch => [STACKED_IF_MATCH]);
4648
4749impl < ' tcx > LateLintPass < ' tcx > for StackedIfMatch {
4850 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
51+ if expr. span . from_expansion ( ) || in_external_macro ( cx. sess ( ) , expr. span ) {
52+ return ;
53+ }
54+
55+ let Some ( ( cond, keyword) ) = ( match expr. kind {
56+ ExprKind :: If ( if_expr, _, _) => Some ( ( if_expr, "if" ) ) ,
57+ ExprKind :: Match ( match_expr, _, MatchSource :: Normal ) => Some ( ( match_expr, "match" ) ) ,
58+ _ => None ,
59+ } ) else {
60+ return ;
61+ } ;
62+
63+ let cond_snippet = snippet ( cx, cond. span , "" ) ;
64+ if !cond_snippet. starts_with ( "if" ) && !cond_snippet. starts_with ( "match" ) {
65+ return ;
66+ }
67+
68+ for_each_expr ( cx, cond, |sub_expr| {
69+ if matches ! ( sub_expr. kind, ExprKind :: DropTemps ( ..) ) {
70+ return ControlFlow :: Continue ( Descend :: Yes ) ;
71+ }
72+
73+ if !sub_expr. span . eq_ctxt ( expr. span ) || sub_expr. span . lo ( ) != cond. span . lo ( ) {
74+ return ControlFlow :: Continue ( Descend :: No ) ;
75+ }
4976
77+ if ( keyword == "if" && matches ! ( sub_expr. kind, ExprKind :: If ( ..) ) )
78+ || ( keyword == "match" && matches ! ( sub_expr. kind, ExprKind :: Match ( .., MatchSource :: Normal ) ) ) {
79+ let inner_snippet = snippet ( cx, sub_expr. span , ".." ) ;
80+ span_lint_and_sugg (
81+ cx,
82+ STACKED_IF_MATCH ,
83+ expr. span . with_hi ( sub_expr. span . hi ( ) ) ,
84+ format ! ( "avoid using `{keyword} {keyword}`" ) ,
85+ format ! ( "try binding inner `{keyword}` with `let`" ) ,
86+ format ! ( "let result = {inner_snippet}; {keyword} result" ) ,
87+ Applicability :: MachineApplicable ,
88+ ) ;
89+ ControlFlow :: Break ( ( ) )
90+ } else {
91+ ControlFlow :: Continue ( Descend :: Yes )
92+ }
93+ } ) ;
5094 }
5195}
0 commit comments