Skip to content

Commit 65be426

Browse files
authored
Merge comment and cfg checking in matches lint pass. (#17239)
Part of #14724. Supersedes #17221. This will be notably easier to read with the new API. changelog: none
2 parents 91e537b + 5a17348 commit 65be426

1 file changed

Lines changed: 72 additions & 82 deletions

File tree

  • clippy_lints/src/matches

clippy_lints/src/matches/mod.rs

Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ mod wild_in_or_pats;
2626

2727
use clippy_config::Conf;
2828
use 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};
3331
use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind};
32+
use rustc_lexer::{TokenKind, is_whitespace};
3433
use rustc_lint::{LateContext, LateLintPass, LintContext};
3534
use rustc_session::impl_lint_pass;
36-
use rustc_span::{SpanData, SyntaxContext};
35+
use rustc_span::Span;
3736

3837
declare_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

Comments
 (0)