11use 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 } ;
33use clippy_utils:: source:: snippet;
44use 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+ } ;
68use if_chain:: if_chain;
79use rustc_ast:: ast:: { Crate as AstCrate , ItemKind , LitKind , ModKind , NodeId } ;
810use rustc_ast:: visit:: FnKind ;
@@ -14,15 +16,17 @@ use rustc_hir::def_id::DefId;
1416use rustc_hir:: hir_id:: CRATE_HIR_ID ;
1517use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
1618use 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 ,
1821} ;
19- use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass } ;
22+ use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
2023use rustc_middle:: hir:: map:: Map ;
2124use rustc_middle:: mir:: interpret:: ConstValue ;
2225use rustc_middle:: ty;
2326use 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 ;
2528use rustc_span:: symbol:: { Symbol , SymbolStr } ;
29+ use rustc_span:: { BytePos , Span } ;
2630use rustc_typeck:: hir_ty_to_ty;
2731
2832use std:: borrow:: { Borrow , Cow } ;
@@ -297,6 +301,13 @@ declare_clippy_lint! {
297301 "unnecessary conversion between Symbol and string"
298302}
299303
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+
300311declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
301312
302313impl EarlyLintPass for ClippyLintsInternal {
@@ -1063,3 +1074,159 @@ impl<'tcx> SymbolStrExpr<'tcx> {
10631074 }
10641075 }
10651076}
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