1
1
use crate :: consts:: { constant_simple, Constant } ;
2
- use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help, span_lint_and_sugg} ;
2
+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then } ;
3
3
use clippy_utils:: source:: snippet;
4
4
use clippy_utils:: ty:: match_type;
5
- use clippy_utils:: { is_expn_of, match_def_path, match_qpath, method_calls, path_to_res, paths, run_lints, SpanlessEq } ;
5
+ use clippy_utils:: {
6
+ is_else_clause, is_expn_of, match_def_path, match_qpath, method_calls, path_to_res, paths, run_lints, SpanlessEq ,
7
+ } ;
6
8
use if_chain:: if_chain;
7
9
use rustc_ast:: ast:: { Crate as AstCrate , ItemKind , LitKind , ModKind , NodeId } ;
8
10
use rustc_ast:: visit:: FnKind ;
@@ -14,15 +16,17 @@ use rustc_hir::def_id::DefId;
14
16
use rustc_hir:: hir_id:: CRATE_HIR_ID ;
15
17
use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
16
18
use rustc_hir:: {
17
- BinOpKind , Crate , Expr , ExprKind , HirId , Item , MutTy , Mutability , Node , Path , StmtKind , Ty , TyKind , UnOp ,
19
+ BinOpKind , Block , Crate , Expr , ExprKind , HirId , Item , Local , MatchSource , MutTy , Mutability , Node , Path , Stmt ,
20
+ StmtKind , Ty , TyKind , UnOp ,
18
21
} ;
19
- use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass } ;
22
+ use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
20
23
use rustc_middle:: hir:: map:: Map ;
21
24
use rustc_middle:: mir:: interpret:: ConstValue ;
22
25
use rustc_middle:: ty;
23
26
use rustc_session:: { declare_lint_pass, declare_tool_lint, impl_lint_pass} ;
24
- use rustc_span:: source_map:: { Span , Spanned } ;
27
+ use rustc_span:: source_map:: Spanned ;
25
28
use rustc_span:: symbol:: { Symbol , SymbolStr } ;
29
+ use rustc_span:: { BytePos , Span } ;
26
30
use rustc_typeck:: hir_ty_to_ty;
27
31
28
32
use std:: borrow:: { Borrow , Cow } ;
@@ -297,6 +301,13 @@ declare_clippy_lint! {
297
301
"unnecessary conversion between Symbol and string"
298
302
}
299
303
304
+ declare_clippy_lint ! {
305
+ /// Finds unidiomatic usage of `if_chain!`
306
+ pub IF_CHAIN_STYLE ,
307
+ internal,
308
+ "non-idiomatic `if_chain!` usage"
309
+ }
310
+
300
311
declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
301
312
302
313
impl EarlyLintPass for ClippyLintsInternal {
@@ -1063,3 +1074,159 @@ impl<'tcx> SymbolStrExpr<'tcx> {
1063
1074
}
1064
1075
}
1065
1076
}
1077
+
1078
+ declare_lint_pass ! ( IfChainStyle => [ IF_CHAIN_STYLE ] ) ;
1079
+
1080
+ impl < ' tcx > LateLintPass < ' tcx > for IfChainStyle {
1081
+ fn check_block ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx hir:: Block < ' _ > ) {
1082
+ let ( local, after, if_chain_span) = if_chain ! {
1083
+ if let [ Stmt { kind: StmtKind :: Local ( local) , .. } , after @ ..] = block. stmts;
1084
+ if let Some ( if_chain_span) = is_expn_of( block. span, "if_chain" ) ;
1085
+ then { ( local, after, if_chain_span) } else { return }
1086
+ } ;
1087
+ if is_first_if_chain_expr ( cx, block. hir_id , if_chain_span) {
1088
+ span_lint (
1089
+ cx,
1090
+ IF_CHAIN_STYLE ,
1091
+ if_chain_local_span ( cx, local, if_chain_span) ,
1092
+ "`let` expression should be above the `if_chain!`" ,
1093
+ ) ;
1094
+ } else if local. span . ctxt ( ) == block. span . ctxt ( ) && is_if_chain_then ( after, block. expr , if_chain_span) {
1095
+ span_lint (
1096
+ cx,
1097
+ IF_CHAIN_STYLE ,
1098
+ if_chain_local_span ( cx, local, if_chain_span) ,
1099
+ "`let` expression should be inside `then { .. }`" ,
1100
+ )
1101
+ }
1102
+ }
1103
+
1104
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
1105
+ let ( cond, then, els) = match expr. kind {
1106
+ ExprKind :: If ( cond, then, els) => ( Some ( cond) , then, els. is_some ( ) ) ,
1107
+ ExprKind :: Match (
1108
+ _,
1109
+ [ arm, ..] ,
1110
+ MatchSource :: IfLetDesugar {
1111
+ contains_else_clause : els,
1112
+ } ,
1113
+ ) => ( None , arm. body , els) ,
1114
+ _ => return ,
1115
+ } ;
1116
+ let then_block = match then. kind {
1117
+ ExprKind :: Block ( block, _) => block,
1118
+ _ => return ,
1119
+ } ;
1120
+ let if_chain_span = is_expn_of ( expr. span , "if_chain" ) ;
1121
+ if !els {
1122
+ check_nested_if_chains ( cx, expr, then_block, if_chain_span) ;
1123
+ }
1124
+ let if_chain_span = match if_chain_span {
1125
+ None => return ,
1126
+ Some ( span) => span,
1127
+ } ;
1128
+ // check for `if a && b;`
1129
+ if_chain ! {
1130
+ if let Some ( cond) = cond;
1131
+ if let ExprKind :: Binary ( op, _, _) = cond. kind;
1132
+ if op. node == BinOpKind :: And ;
1133
+ if cx. sess( ) . source_map( ) . is_multiline( cond. span) ;
1134
+ then {
1135
+ span_lint( cx, IF_CHAIN_STYLE , cond. span, "`if a && b;` should be `if a; if b;`" ) ;
1136
+ }
1137
+ }
1138
+ if is_first_if_chain_expr ( cx, expr. hir_id , if_chain_span)
1139
+ && is_if_chain_then ( then_block. stmts , then_block. expr , if_chain_span)
1140
+ {
1141
+ span_lint ( cx, IF_CHAIN_STYLE , expr. span , "`if_chain!` only has one `if`" )
1142
+ }
1143
+ }
1144
+ }
1145
+
1146
+ fn check_nested_if_chains (
1147
+ cx : & LateContext < ' _ > ,
1148
+ if_expr : & Expr < ' _ > ,
1149
+ then_block : & Block < ' _ > ,
1150
+ if_chain_span : Option < Span > ,
1151
+ ) {
1152
+ #[ rustfmt:: skip]
1153
+ let ( head, tail) = match * then_block {
1154
+ Block { stmts, expr : Some ( tail) , .. } => ( stmts, tail) ,
1155
+ Block {
1156
+ stmts : & [
1157
+ ref head @ ..,
1158
+ Stmt { kind : StmtKind :: Expr ( tail) | StmtKind :: Semi ( tail) , .. }
1159
+ ] ,
1160
+ ..
1161
+ } => ( head, tail) ,
1162
+ _ => return ,
1163
+ } ;
1164
+ if_chain ! {
1165
+ if matches!( tail. kind,
1166
+ ExprKind :: If ( _, _, None )
1167
+ | ExprKind :: Match ( .., MatchSource :: IfLetDesugar { contains_else_clause: false } ) ) ;
1168
+ let sm = cx. sess( ) . source_map( ) ;
1169
+ if head
1170
+ . iter( )
1171
+ . all( |stmt| matches!( stmt. kind, StmtKind :: Local ( ..) ) && !sm. is_multiline( stmt. span) ) ;
1172
+ if if_chain_span. is_some( ) || !is_else_clause( cx. tcx, if_expr) ;
1173
+ then { } else { return }
1174
+ }
1175
+ let ( span, msg) = match ( if_chain_span, is_expn_of ( tail. span , "if_chain" ) ) {
1176
+ ( None , Some ( _) ) => ( if_expr. span , "this `if` can be part of the inner `if_chain!`" ) ,
1177
+ ( Some ( _) , None ) => ( tail. span , "this `if` can be part of the outer `if_chain!`" ) ,
1178
+ ( Some ( a) , Some ( b) ) if a != b => ( b, "this `if_chain!` can be merged with the outer `if_chain!`" ) ,
1179
+ _ => return ,
1180
+ } ;
1181
+ span_lint_and_then ( cx, IF_CHAIN_STYLE , span, msg, |diag| {
1182
+ let ( span, msg) = match head {
1183
+ [ ] => return ,
1184
+ [ stmt] => ( stmt. span , "this `let` statement can also be in the `if_chain!`" ) ,
1185
+ [ a, .., b] => (
1186
+ a. span . to ( b. span ) ,
1187
+ "these `let` statements can also be in the `if_chain!`" ,
1188
+ ) ,
1189
+ } ;
1190
+ diag. span_help ( span, msg) ;
1191
+ } ) ;
1192
+ }
1193
+
1194
+ fn is_first_if_chain_expr ( cx : & LateContext < ' _ > , hir_id : HirId , if_chain_span : Span ) -> bool {
1195
+ cx. tcx
1196
+ . hir ( )
1197
+ . parent_iter ( hir_id)
1198
+ . find ( |( _, node) | {
1199
+ #[ rustfmt:: skip]
1200
+ !matches ! ( node, Node :: Expr ( Expr { kind: ExprKind :: Block ( ..) , .. } ) | Node :: Stmt ( _) )
1201
+ } )
1202
+ . map_or ( false , |( id, _) | {
1203
+ is_expn_of ( cx. tcx . hir ( ) . span ( id) , "if_chain" ) != Some ( if_chain_span)
1204
+ } )
1205
+ }
1206
+
1207
+ /// Checks a trailing slice of statements and expression of a `Block` to see if they are part
1208
+ /// of the `then {..}` portion of an `if_chain!`
1209
+ fn is_if_chain_then ( stmts : & [ Stmt < ' _ > ] , expr : Option < & Expr < ' _ > > , if_chain_span : Span ) -> bool {
1210
+ let span = if let [ stmt, ..] = stmts {
1211
+ stmt. span
1212
+ } else if let Some ( expr) = expr {
1213
+ expr. span
1214
+ } else {
1215
+ // empty `then {}`
1216
+ return true ;
1217
+ } ;
1218
+ is_expn_of ( span, "if_chain" ) . map_or ( true , |span| span != if_chain_span)
1219
+ }
1220
+
1221
+ /// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
1222
+ fn if_chain_local_span ( cx : & LateContext < ' _ > , local : & Local < ' _ > , if_chain_span : Span ) -> Span {
1223
+ let mut span = local. pat . span ;
1224
+ if let Some ( init) = local. init {
1225
+ span = span. to ( init. span ) ;
1226
+ }
1227
+ span. adjust ( if_chain_span. ctxt ( ) . outer_expn ( ) ) ;
1228
+ let sm = cx. sess ( ) . source_map ( ) ;
1229
+ let span = sm. span_extend_to_prev_str ( span, "let" , false ) ;
1230
+ let span = sm. span_extend_to_next_char ( span, ';' , false ) ;
1231
+ Span :: new ( span. lo ( ) - BytePos ( 3 ) , span. hi ( ) + BytePos ( 1 ) , span. ctxt ( ) )
1232
+ }
0 commit comments