@@ -3,13 +3,16 @@ use crate::utils::{match_type, paths, span_lint_and_sugg};
3
3
use if_chain:: if_chain;
4
4
5
5
use rustc_errors:: Applicability ;
6
+ use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
6
7
use rustc_hir:: * ;
7
8
use rustc_lint:: { LateContext , LateLintPass } ;
9
+ use rustc_middle:: hir:: map:: Map ;
10
+ use rustc_middle:: ty:: TyCtxt ;
8
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9
12
10
13
declare_clippy_lint ! {
11
14
/// **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
13
16
/// idiomatically done with `Option::map_or` (if the else bit is a simple
14
17
/// expression) or `Option::map_or_else` (if the else bit is a longer
15
18
/// block).
@@ -82,13 +85,60 @@ struct OptionIfLetElseOccurence {
82
85
none_expr : String ,
83
86
}
84
87
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
+
85
137
/// If this expression is the option if let/else construct we're detecting, then
86
138
/// this function returns an OptionIfLetElseOccurence struct with details if
87
139
/// 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)> {
92
142
if_chain ! {
93
143
if let ExprKind :: Match ( let_body, arms, MatchSource :: IfLetDesugar { contains_else_clause: true } ) = & expr. kind;
94
144
if arms. len( ) == 2 ;
@@ -103,14 +153,7 @@ fn detect_option_if_let_else<'a>(
103
153
expr
104
154
} else {
105
155
// 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) {
114
157
return None ;
115
158
}
116
159
& arms[ 0 ] . body
@@ -123,14 +166,7 @@ fn detect_option_if_let_else<'a>(
123
166
if let & [ ] = & statements {
124
167
( expr, "map_or" )
125
168
} 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) {
134
170
return None ;
135
171
}
136
172
( & arms[ 1 ] . body, "map_or_else" )
@@ -160,7 +196,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OptionIfLetElse {
160
196
expr. span ,
161
197
format ! ( "use Option::{} instead of an if let/else" , detection. method_sugg) . as_str ( ) ,
162
198
"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
+ ) ,
164
203
Applicability :: MachineApplicable ,
165
204
) ;
166
205
}
0 commit comments