@@ -3,13 +3,16 @@ use crate::utils::{match_type, paths, span_lint_and_sugg};
33use if_chain:: if_chain;
44
55use rustc_errors:: Applicability ;
6+ use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
67use rustc_hir:: * ;
78use rustc_lint:: { LateContext , LateLintPass } ;
9+ use rustc_middle:: hir:: map:: Map ;
10+ use rustc_middle:: ty:: TyCtxt ;
811use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
912
1013declare_clippy_lint ! {
1114 /// **What it does:**
12- /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
15+ /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
1316 /// idiomatically done with `Option::map_or` (if the else bit is a simple
1417 /// expression) or `Option::map_or_else` (if the else bit is a longer
1518 /// block).
@@ -82,13 +85,60 @@ struct OptionIfLetElseOccurence {
8285 none_expr : String ,
8386}
8487
88+ struct ReturnBreakContinueVisitor < ' tcx > {
89+ tcx : TyCtxt < ' tcx > ,
90+ seen_return_break_continue : bool ,
91+ }
92+ impl < ' tcx > ReturnBreakContinueVisitor < ' tcx > {
93+ fn new ( tcx : TyCtxt < ' tcx > ) -> ReturnBreakContinueVisitor < ' tcx > {
94+ ReturnBreakContinueVisitor {
95+ seen_return_break_continue : false ,
96+ tcx,
97+ }
98+ }
99+
100+ fn seen_return_break_continue ( & self ) -> bool {
101+ self . seen_return_break_continue
102+ }
103+ }
104+ impl < ' tcx > Visitor < ' tcx > for ReturnBreakContinueVisitor < ' tcx > {
105+ type Map = Map < ' tcx > ;
106+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
107+ NestedVisitorMap :: OnlyBodies ( self . tcx . hir ( ) )
108+ }
109+
110+ fn visit_expr ( & mut self , ex : & ' tcx Expr < ' tcx > ) {
111+ if self . seen_return_break_continue {
112+ // No need to look farther if we've already seen one of them
113+ return ;
114+ }
115+ match & ex. kind {
116+ ExprKind :: Ret ( ..) | ExprKind :: Break ( ..) | ExprKind :: Continue ( ..) => {
117+ self . seen_return_break_continue = true ;
118+ } ,
119+ // Do nothing if encountering a loop, because internal breaks and
120+ // continues shouldn't be flagged
121+ ExprKind :: Loop ( ..) => { } ,
122+ _ => {
123+ rustc_hir:: intravisit:: walk_expr ( self , ex) ;
124+ } ,
125+ }
126+ }
127+ }
128+
129+ fn contains_return_break_continue < ' tcx > ( cx : & LateContext < ' _ , ' tcx > , statements : & ' tcx [ Stmt < ' tcx > ] ) -> bool {
130+ let mut recursive_visitor: ReturnBreakContinueVisitor < ' tcx > = ReturnBreakContinueVisitor :: new ( cx. tcx ) ;
131+ for statement in statements {
132+ recursive_visitor. visit_stmt ( statement) ;
133+ }
134+ recursive_visitor. seen_return_break_continue ( )
135+ }
136+
85137/// If this expression is the option if let/else construct we're detecting, then
86138/// this function returns an OptionIfLetElseOccurence struct with details if
87139/// this construct is found, or None if this construct is not found.
88- fn detect_option_if_let_else < ' a > (
89- cx : & LateContext < ' _ , ' _ > ,
90- expr : & ' a Expr < ' _ > ,
91- ) -> Option < OptionIfLetElseOccurence > { //(String, String, String, String)> {
140+ fn detect_option_if_let_else < ' a > ( cx : & LateContext < ' _ , ' a > , expr : & ' a Expr < ' a > ) -> Option < OptionIfLetElseOccurence > {
141+ //(String, String, String, String)> {
92142 if_chain ! {
93143 if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
94144 if arms. len( ) == 2 ;
@@ -103,14 +153,7 @@ fn detect_option_if_let_else<'a>(
103153 expr
104154 } else {
105155 // Don't trigger if there is a return, break, or continue inside
106- if statements. iter( ) . any( |statement| {
107- match & statement {
108- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Ret ( ..) , ..} ) , .. } => true ,
109- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Break ( ..) , ..} ) , .. } => true ,
110- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Continue ( ..) , ..} ) , .. } => true ,
111- _ => false ,
112- }
113- } ) {
156+ if contains_return_break_continue( cx, statements) {
114157 return None ;
115158 }
116159 & arms[ 0 ] . body
@@ -123,14 +166,7 @@ fn detect_option_if_let_else<'a>(
123166 if let & [ ] = & statements {
124167 ( expr, "map_or" )
125168 } else {
126- if statements. iter( ) . any( |statement| {
127- match & statement {
128- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Ret ( ..) , ..} ) , .. } => true ,
129- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Break ( ..) , ..} ) , .. } => true ,
130- Stmt { kind: StmtKind :: Semi ( Expr { kind: ExprKind :: Continue ( ..) , ..} ) , .. } => true ,
131- _ => false ,
132- }
133- } ) {
169+ if contains_return_break_continue( cx, statements) {
134170 return None ;
135171 }
136172 ( & arms[ 1 ] . body, "map_or_else" )
@@ -160,7 +196,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OptionIfLetElse {
160196 expr. span ,
161197 format ! ( "use Option::{} instead of an if let/else" , detection. method_sugg) . as_str ( ) ,
162198 "try" ,
163- format ! ( "{}.{}({}, {})" , detection. option, detection. method_sugg, detection. none_expr, detection. some_expr) ,
199+ format ! (
200+ "{}.{}({}, {})" ,
201+ detection. option, detection. method_sugg, detection. none_expr, detection. some_expr
202+ ) ,
164203 Applicability :: MachineApplicable ,
165204 ) ;
166205 }
0 commit comments