11use clippy_config:: Conf ;
22use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
3- use clippy_utils:: source:: {
4- HasSession , IntoSpan as _, SpanRangeExt , snippet, snippet_block, snippet_block_with_applicability,
5- } ;
6- use clippy_utils:: span_contains_comment;
7- use rustc_ast:: ast;
3+ use clippy_utils:: source:: { IntoSpan as _, SpanRangeExt , snippet, snippet_block, snippet_block_with_applicability} ;
4+ use rustc_ast:: BinOpKind ;
85use rustc_errors:: Applicability ;
9- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
6+ use rustc_hir:: { Block , Expr , ExprKind , StmtKind } ;
7+ use rustc_lint:: { LateContext , LateLintPass } ;
108use rustc_session:: impl_lint_pass;
119use rustc_span:: Span ;
1210
@@ -89,17 +87,16 @@ impl CollapsibleIf {
8987 }
9088 }
9189
92- fn check_collapsible_else_if ( cx : & EarlyContext < ' _ > , then_span : Span , else_ : & ast:: Expr ) {
93- if let ast:: ExprKind :: Block ( ref block, _) = else_. kind
94- && !block_starts_with_comment ( cx, block)
95- && let Some ( else_) = expr_block ( block)
96- && else_. attrs . is_empty ( )
90+ fn check_collapsible_else_if ( cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
91+ if !block_starts_with_comment ( cx, else_block)
92+ && let Some ( else_) = expr_block ( else_block)
93+ && cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
9794 && !else_. span . from_expansion ( )
98- && let ast :: ExprKind :: If ( ..) = else_. kind
95+ && let ExprKind :: If ( ..) = else_. kind
9996 {
10097 // Prevent "elseif"
10198 // Check that the "else" is followed by whitespace
102- let up_to_else = then_span. between ( block . span ) ;
99+ let up_to_else = then_span. between ( else_block . span ) ;
103100 let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
104101 !c. is_whitespace ( )
105102 } else {
@@ -110,29 +107,28 @@ impl CollapsibleIf {
110107 span_lint_and_sugg (
111108 cx,
112109 COLLAPSIBLE_ELSE_IF ,
113- block . span ,
110+ else_block . span ,
114111 "this `else { if .. }` block can be collapsed" ,
115112 "collapse nested if block" ,
116113 format ! (
117114 "{}{}" ,
118115 if requires_space { " " } else { "" } ,
119- snippet_block_with_applicability( cx, else_. span, ".." , Some ( block . span) , & mut applicability)
116+ snippet_block_with_applicability( cx, else_. span, ".." , Some ( else_block . span) , & mut applicability)
120117 ) ,
121118 applicability,
122119 ) ;
123120 }
124121 }
125122
126- fn check_collapsible_if_if ( & self , cx : & EarlyContext < ' _ > , expr : & ast :: Expr , check : & ast :: Expr , then : & ast :: Block ) {
123+ fn check_collapsible_if_if ( & self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > , check : & Expr < ' _ > , then : & Block < ' _ > ) {
127124 if let Some ( inner) = expr_block ( then)
128- && inner. attrs . is_empty ( )
129- && let ast :: ExprKind :: If ( check_inner, _, None ) = & inner. kind
125+ && cx . tcx . hir_attrs ( inner. hir_id ) . is_empty ( )
126+ && let ExprKind :: If ( check_inner, _, None ) = & inner. kind
130127 // Prevent triggering on `if c { if let a = b { .. } }`.
131- && !matches ! ( check_inner. kind, ast :: ExprKind :: Let ( ..) )
128+ && !matches ! ( check_inner. kind, ExprKind :: Let ( ..) )
132129 && let ctxt = expr. span . ctxt ( )
133130 && inner. span . ctxt ( ) == ctxt
134- && let contains_comment = span_contains_comment ( cx. sess ( ) . source_map ( ) , check. span . to ( check_inner. span ) )
135- && ( !contains_comment || self . lint_commented_code )
131+ && ( self . lint_commented_code || !block_starts_with_comment ( cx, then) )
136132 {
137133 span_lint_and_then (
138134 cx,
@@ -168,44 +164,54 @@ impl CollapsibleIf {
168164
169165impl_lint_pass ! ( CollapsibleIf => [ COLLAPSIBLE_IF , COLLAPSIBLE_ELSE_IF ] ) ;
170166
171- impl EarlyLintPass for CollapsibleIf {
172- fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & ast :: Expr ) {
173- if let ast :: ExprKind :: If ( cond, then, else_) = & expr. kind
167+ impl LateLintPass < ' _ > for CollapsibleIf {
168+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
169+ if let ExprKind :: If ( cond, then, else_) = & expr. kind
174170 && !expr. span . from_expansion ( )
175171 {
176- if let Some ( else_) = else_ {
172+ if let Some ( else_) = else_
173+ && let ExprKind :: Block ( else_, None ) = else_. kind
174+ {
177175 Self :: check_collapsible_else_if ( cx, then. span , else_) ;
178- } else if !matches ! ( cond. kind, ast:: ExprKind :: Let ( ..) ) {
176+ } else if else_. is_none ( )
177+ && !matches ! ( cond. kind, ExprKind :: Let ( ..) )
178+ && let ExprKind :: Block ( then, None ) = then. kind
179+ {
179180 // Prevent triggering on `if c { if let a = b { .. } }`.
180- self . check_collapsible_if_if ( cx, expr, cond, then) ;
181+ self . check_collapsible_if_if ( cx, expr, cond, block ! ( then) ) ;
181182 }
182183 }
183184 }
184185}
185186
186- fn block_starts_with_comment ( cx : & EarlyContext < ' _ > , expr : & ast :: Block ) -> bool {
187+ fn block_starts_with_comment ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
187188 // We trim all opening braces and whitespaces and then check if the next string is a comment.
188- let trimmed_block_text = snippet_block ( cx, expr . span , ".." , None )
189+ let trimmed_block_text = snippet_block ( cx, block . span , ".." , None )
189190 . trim_start_matches ( |c : char | c. is_whitespace ( ) || c == '{' )
190191 . to_owned ( ) ;
191192 trimmed_block_text. starts_with ( "//" ) || trimmed_block_text. starts_with ( "/*" )
192193}
193194
194- /// If the block contains only one expression, return it.
195- fn expr_block ( block : & ast:: Block ) -> Option < & ast:: Expr > {
196- if let [ stmt] = & * block. stmts
197- && let ast:: StmtKind :: Expr ( expr) | ast:: StmtKind :: Semi ( expr) = & stmt. kind
198- {
199- Some ( expr)
200- } else {
201- None
195+ /// If `block` is a block with either one expression or a statement containing an expression,
196+ /// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
197+ fn expr_block < ' tcx > ( block : & Block < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
198+ match block. stmts {
199+ [ ] => block. expr ,
200+ [ stmt] => {
201+ if let StmtKind :: Semi ( expr) = stmt. kind {
202+ Some ( expr)
203+ } else {
204+ None
205+ }
206+ } ,
207+ _ => None ,
202208 }
203209}
204210
205211/// If the expression is a `||`, suggest parentheses around it.
206- fn parens_around ( expr : & ast :: Expr ) -> Vec < ( Span , String ) > {
207- if let ast :: ExprKind :: Binary ( op, _, _) = expr. kind
208- && op. node == ast :: BinOpKind :: Or
212+ fn parens_around ( expr : & Expr < ' _ > ) -> Vec < ( Span , String ) > {
213+ if let ExprKind :: Binary ( op, _, _) = expr. peel_drop_temps ( ) . kind
214+ && op. node == BinOpKind :: Or
209215 {
210216 vec ! [
211217 ( expr. span. shrink_to_lo( ) , String :: from( "(" ) ) ,
0 commit comments