1
1
use clippy_config:: Conf ;
2
- use clippy_utils:: diagnostics:: span_lint_and_then ;
2
+ use clippy_utils:: diagnostics:: span_lint_hir_and_then ;
3
3
use clippy_utils:: msrvs:: { self , Msrv } ;
4
4
use clippy_utils:: source:: { IntoSpan as _, SpanRangeExt , snippet, snippet_block_with_applicability} ;
5
- use clippy_utils:: { span_contains_non_whitespace, tokenize_with_text} ;
6
- use rustc_ast:: BinOpKind ;
5
+ use clippy_utils:: { span_contains_non_whitespace, sym , tokenize_with_text} ;
6
+ use rustc_ast:: { BinOpKind , MetaItemInner } ;
7
7
use rustc_errors:: Applicability ;
8
8
use rustc_hir:: { Block , Expr , ExprKind , StmtKind } ;
9
9
use rustc_lexer:: TokenKind ;
10
- use rustc_lint:: { LateContext , LateLintPass } ;
10
+ use rustc_lint:: { LateContext , LateLintPass , Level } ;
11
11
use rustc_session:: impl_lint_pass;
12
12
use rustc_span:: source_map:: SourceMap ;
13
- use rustc_span:: { BytePos , Span } ;
13
+ use rustc_span:: { BytePos , Span , Symbol } ;
14
14
15
15
declare_clippy_lint ! {
16
16
/// ### What it does
@@ -95,14 +95,14 @@ impl CollapsibleIf {
95
95
96
96
fn check_collapsible_else_if ( & self , cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
97
97
if let Some ( else_) = expr_block ( else_block)
98
- && cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
99
98
&& !else_. span . from_expansion ( )
100
99
&& let ExprKind :: If ( else_if_cond, ..) = else_. kind
101
- && ! block_starts_with_significant_tokens ( cx, else_block, else_, self . lint_commented_code )
100
+ && self . check_significant_tokens_and_expect_attrs ( cx, else_block, else_, sym :: collapsible_else_if )
102
101
{
103
- span_lint_and_then (
102
+ span_lint_hir_and_then (
104
103
cx,
105
104
COLLAPSIBLE_ELSE_IF ,
105
+ else_. hir_id ,
106
106
else_block. span ,
107
107
"this `else { if .. }` block can be collapsed" ,
108
108
|diag| {
@@ -166,15 +166,15 @@ impl CollapsibleIf {
166
166
167
167
fn check_collapsible_if_if ( & self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > , check : & Expr < ' _ > , then : & Block < ' _ > ) {
168
168
if let Some ( inner) = expr_block ( then)
169
- && cx. tcx . hir_attrs ( inner. hir_id ) . is_empty ( )
170
169
&& let ExprKind :: If ( check_inner, _, None ) = & inner. kind
171
170
&& self . eligible_condition ( cx, check_inner)
172
171
&& expr. span . eq_ctxt ( inner. span )
173
- && ! block_starts_with_significant_tokens ( cx, then, inner, self . lint_commented_code )
172
+ && self . check_significant_tokens_and_expect_attrs ( cx, then, inner, sym :: collapsible_if )
174
173
{
175
- span_lint_and_then (
174
+ span_lint_hir_and_then (
176
175
cx,
177
176
COLLAPSIBLE_IF ,
177
+ inner. hir_id ,
178
178
expr. span ,
179
179
"this `if` statement can be collapsed" ,
180
180
|diag| {
@@ -219,6 +219,45 @@ impl CollapsibleIf {
219
219
!matches ! ( cond. kind, ExprKind :: Let ( ..) )
220
220
|| ( cx. tcx . sess . edition ( ) . at_least_rust_2024 ( ) && self . msrv . meets ( cx, msrvs:: LET_CHAINS ) )
221
221
}
222
+
223
+ // Check that nothing significant can be found between the initial `{` of `inner_if` and
224
+ // the beginning of `inner_if_expr`...
225
+ //
226
+ // Unless it's only an `#[expect(clippy::collapsible{,_else}_if)]` attribute, in which case we
227
+ // _do_ need to lint, in order to actually fulfill its expectation (#13365)
228
+ fn check_significant_tokens_and_expect_attrs (
229
+ & self ,
230
+ cx : & LateContext < ' _ > ,
231
+ inner_if : & Block < ' _ > ,
232
+ inner_if_expr : & Expr < ' _ > ,
233
+ expected_lint_name : Symbol ,
234
+ ) -> bool {
235
+ match cx. tcx . hir_attrs ( inner_if_expr. hir_id ) {
236
+ [ ] => {
237
+ // There aren't any attributes, so just check for significant tokens
238
+ let span = inner_if. span . split_at ( 1 ) . 1 . until ( inner_if_expr. span ) ;
239
+ !span_contains_non_whitespace ( cx, span, self . lint_commented_code )
240
+ } ,
241
+
242
+ [ attr]
243
+ if matches ! ( Level :: from_attr( attr) , Some ( ( Level :: Expect , _) ) )
244
+ && let Some ( metas) = attr. meta_item_list ( )
245
+ && let Some ( MetaItemInner :: MetaItem ( meta_item) ) = metas. first ( )
246
+ && let [ tool, lint_name] = meta_item. path . segments . as_slice ( )
247
+ && tool. ident . name == sym:: clippy
248
+ && [ expected_lint_name, sym:: style, sym:: all] . contains ( & lint_name. ident . name ) =>
249
+ {
250
+ // There is an `expect` attribute -- check that there is no _other_ significant text
251
+ let span_before_attr = inner_if. span . split_at ( 1 ) . 1 . until ( attr. span ( ) ) ;
252
+ let span_after_attr = attr. span ( ) . between ( inner_if_expr. span ) ;
253
+ !span_contains_non_whitespace ( cx, span_before_attr, self . lint_commented_code )
254
+ && !span_contains_non_whitespace ( cx, span_after_attr, self . lint_commented_code )
255
+ } ,
256
+
257
+ // There are other attributes, which are significant tokens -- check failed
258
+ _ => false ,
259
+ }
260
+ }
222
261
}
223
262
224
263
impl_lint_pass ! ( CollapsibleIf => [ COLLAPSIBLE_IF , COLLAPSIBLE_ELSE_IF ] ) ;
@@ -242,18 +281,6 @@ impl LateLintPass<'_> for CollapsibleIf {
242
281
}
243
282
}
244
283
245
- // Check that nothing significant can be found but whitespaces between the initial `{` of `block`
246
- // and the beginning of `stop_at`.
247
- fn block_starts_with_significant_tokens (
248
- cx : & LateContext < ' _ > ,
249
- block : & Block < ' _ > ,
250
- stop_at : & Expr < ' _ > ,
251
- lint_commented_code : bool ,
252
- ) -> bool {
253
- let span = block. span . split_at ( 1 ) . 1 . until ( stop_at. span ) ;
254
- span_contains_non_whitespace ( cx, span, lint_commented_code)
255
- }
256
-
257
284
/// If `block` is a block with either one expression or a statement containing an expression,
258
285
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
259
286
fn expr_block < ' tcx > ( block : & Block < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
0 commit comments