@@ -26,14 +26,13 @@ mod wild_in_or_pats;
2626
2727use clippy_config:: Conf ;
2828use clippy_utils:: msrvs:: { self , Msrv } ;
29- use clippy_utils:: source:: walk_span_to_context;
30- use clippy_utils:: {
31- higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments, sym,
32- } ;
29+ use clippy_utils:: source:: SpanExt ;
30+ use clippy_utils:: { higher, is_direct_expn_of, is_in_const_context, is_span_match, sym, tokenize_with_text} ;
3331use rustc_hir:: { Arm , Expr , ExprKind , LetStmt , MatchSource , Pat , PatKind } ;
32+ use rustc_lexer:: { TokenKind , is_whitespace} ;
3433use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
3534use rustc_session:: impl_lint_pass;
36- use rustc_span:: { SpanData , SyntaxContext } ;
35+ use rustc_span:: Span ;
3736
3837declare_clippy_lint ! {
3938 /// ### What it does
@@ -1084,7 +1083,29 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
10841083 try_err:: check ( cx, expr, ex) ;
10851084 }
10861085
1087- if !from_expansion && !contains_cfg_arm ( cx, expr, ex, arms) {
1086+ if !from_expansion
1087+ && let mut has_cfg = false
1088+ && let mut has_comments = false
1089+ && walk_intra_arm_text ( cx, expr. span , ex. span , arms, |s| {
1090+ let mut iter = tokenize_with_text ( s) . filter ( |( t, ..) | match t {
1091+ TokenKind :: Whitespace => false ,
1092+ TokenKind :: LineComment { .. } | TokenKind :: BlockComment { .. } => {
1093+ has_comments = true ;
1094+ false
1095+ } ,
1096+ _ => true ,
1097+ } ) ;
1098+ while let Some ( ( t, ..) ) = iter. next ( ) {
1099+ if matches ! ( t, TokenKind :: Pound )
1100+ && matches ! ( iter. next( ) , Some ( ( TokenKind :: OpenBracket , ..) ) )
1101+ && matches ! ( iter. next( ) , Some ( ( TokenKind :: Ident , "cfg" , _) ) )
1102+ {
1103+ has_cfg = true ;
1104+ }
1105+ }
1106+ } )
1107+ && !has_cfg
1108+ {
10881109 if source == MatchSource :: Normal {
10891110 if !( self . msrv . meets ( cx, msrvs:: MATCHES_MACRO )
10901111 && match_like_matches:: check_match ( cx, expr, ex, arms) )
@@ -1093,25 +1114,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
10931114 }
10941115
10951116 redundant_pattern_match:: check_match ( cx, expr, ex, arms) ;
1096- let mut match_comments = span_extract_comments ( cx, expr. span ) ;
1097- // We remove comments from inside arms block.
1098- if !match_comments. is_empty ( ) {
1099- for arm in arms {
1100- for comment in span_extract_comments ( cx, arm. body . span ) {
1101- if let Some ( index) = match_comments
1102- . iter ( )
1103- . enumerate ( )
1104- . find ( |( _, cm) | * * cm == comment)
1105- . map ( |( index, _) | index)
1106- {
1107- match_comments. remove ( index) ;
1108- }
1109- }
1110- }
1111- }
1112- // If there are still comments, it means they are outside of the arms. Tell the lint
1113- // code about it.
1114- single_match:: check ( cx, ex, arms, expr, !match_comments. is_empty ( ) ) ;
1117+ single_match:: check ( cx, ex, arms, expr, has_comments) ;
11151118 match_bool:: check ( cx, ex, arms, expr) ;
11161119 overlapping_arms:: check ( cx, ex, arms) ;
11171120 match_wild_enum:: check ( cx, ex, arms) ;
@@ -1216,64 +1219,51 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
12161219 }
12171220}
12181221
1219- /// Checks if there are any arms with a `#[cfg(..)]` attribute.
1220- fn contains_cfg_arm ( cx : & LateContext < ' _ > , e : & Expr < ' _ > , scrutinee : & Expr < ' _ > , arms : & [ Arm < ' _ > ] ) -> bool {
1221- let Some ( scrutinee_span) = walk_span_to_context ( scrutinee. span , SyntaxContext :: root ( ) ) else {
1222- // Shouldn't happen, but treat this as though a `cfg` attribute were found
1223- return true ;
1224- } ;
1225-
1226- let start = scrutinee_span. hi ( ) ;
1227- let mut arm_spans = arms. iter ( ) . map ( |arm| {
1228- let data = arm. span . data ( ) ;
1229- ( data. ctxt == SyntaxContext :: root ( ) ) . then_some ( ( data. lo , data. hi ) )
1230- } ) ;
1231- let end = e. span . hi ( ) ;
1232-
1233- // Walk through all the non-code space before each match arm. The space trailing the final arm is
1234- // handled after the `try_fold` e.g.
1235- //
1236- // match foo {
1237- // _________^- everything between the scrutinee and arm1
1238- //| arm1 => (),
1239- //|---^___________^ everything before arm2
1240- //| #[cfg(feature = "enabled")]
1241- //| arm2 => some_code(),
1242- //|---^____________________^ everything before arm3
1243- //| // some comment about arm3
1244- //| arm3 => some_code(),
1245- //|---^____________________^ everything after arm3
1246- //| #[cfg(feature = "disabled")]
1247- //| arm4 = some_code(),
1248- //|};
1249- //|^
1250- let found = arm_spans. try_fold ( start, |start, range| {
1251- let Some ( ( end, next_start) ) = range else {
1252- // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute
1253- // were found.
1254- return Err ( ( ) ) ;
1255- } ;
1256- let span = SpanData {
1257- lo : start,
1258- hi : end,
1259- ctxt : SyntaxContext :: root ( ) ,
1260- parent : None ,
1261- }
1262- . span ( ) ;
1263- ( !span_contains_cfg ( cx, span) ) . then_some ( next_start) . ok_or ( ( ) )
1264- } ) ;
1265- match found {
1266- Ok ( start) => {
1267- let span = SpanData {
1268- lo : start,
1269- hi : end,
1270- ctxt : SyntaxContext :: root ( ) ,
1271- parent : None ,
1222+ /// Calls the given function for each segment of the source text within the
1223+ /// match block which is not part of any arm. For the purposes of this function
1224+ /// attributes on an arm are not considered part of the arm.
1225+ ///
1226+ /// This will return whether all the relevant source text could be retrieved. If
1227+ /// all the source text cannot be retrieved it should be assumed that the match
1228+ /// originates from a macro.
1229+ #[ must_use]
1230+ fn walk_intra_arm_text (
1231+ cx : & LateContext < ' _ > ,
1232+ match_sp : Span ,
1233+ scrutinee_sp : Span ,
1234+ arms : & [ Arm < ' _ > ] ,
1235+ mut f : impl FnMut ( & str ) ,
1236+ ) -> bool {
1237+ if let Some ( src) = match_sp. get_source_range ( cx)
1238+ && let scrutinee_sp = scrutinee_sp. source_callsite ( ) . data ( )
1239+ && let block_start = ( scrutinee_sp. hi . 0 - src. sf . start_pos . 0 ) as usize
1240+ && let Some ( src_text) = src. sf . src . as_ref ( ) . map ( |x| & * * * x)
1241+ && let Some ( block_text) = src_text. get ( block_start..src. range . end )
1242+ && let Some ( stripped_text) = block_text. trim_start_matches ( is_whitespace) . strip_prefix ( '{' )
1243+ && let arms_start = block_start + ( block_text. len ( ) - stripped_text. len ( ) )
1244+ && let Some ( arms_end) = stripped_text
1245+ . trim_end_matches ( |c| is_whitespace ( c) || c == ')' )
1246+ . strip_suffix ( '}' )
1247+ . map ( |s| src. range . end - ( stripped_text. len ( ) - s. len ( ) ) )
1248+ && let Some ( range) = arms. iter ( ) . try_fold ( arms_start..arms_end, |range, arm| {
1249+ let arm_sp: rustc_span:: SpanData = arm. span . source_callsite ( ) . data ( ) ;
1250+ let arm_range = ( arm_sp. lo . 0 - src. sf . start_pos . 0 ) as usize ..( arm_sp. hi . 0 - src. sf . start_pos . 0 ) as usize ;
1251+ if range. start <= arm_range. start
1252+ && arm_range. end <= range. end
1253+ && let Some ( src) = src_text. get ( range. start ..arm_range. start )
1254+ {
1255+ f ( src) ;
1256+ Some ( arm_range. end ..range. end )
1257+ } else {
1258+ None
12721259 }
1273- . span ( ) ;
1274- span_contains_cfg ( cx, span)
1275- } ,
1276- Err ( ( ) ) => true ,
1260+ } )
1261+ && let Some ( src) = src_text. get ( range)
1262+ {
1263+ f ( src) ;
1264+ true
1265+ } else {
1266+ false
12771267 }
12781268}
12791269
0 commit comments