Skip to content

Commit 0b3c2ed

Browse files
committed
Search for inactive cfg attributes and empty macro expansion through
the entire block
1 parent 392e955 commit 0b3c2ed

File tree

5 files changed

+229
-74
lines changed

5 files changed

+229
-74
lines changed

clippy_lints/src/matches/mod.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ mod wild_in_or_pats;
2525

2626
use clippy_utils::msrvs::{self, Msrv};
2727
use clippy_utils::source::{snippet_opt, walk_span_to_context};
28-
use clippy_utils::{higher, in_constant, is_span_match};
28+
use clippy_utils::{higher, in_constant, is_span_match, tokenize_with_text};
2929
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
30-
use rustc_lexer::{tokenize, TokenKind};
30+
use rustc_lexer::TokenKind;
3131
use rustc_lint::{LateContext, LateLintPass, LintContext};
3232
use rustc_middle::lint::in_external_macro;
3333
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -1147,12 +1147,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
11471147
// Assume true. This would require either an invalid span, or one which crosses file boundaries.
11481148
return true;
11491149
};
1150-
let mut pos = 0usize;
1151-
let mut iter = tokenize(&snip).map(|t| {
1152-
let start = pos;
1153-
pos += t.len as usize;
1154-
(t.kind, start..pos)
1155-
});
1150+
let mut iter = tokenize_with_text(&snip);
11561151

11571152
// Search for the token sequence [`#`, `[`, `cfg`]
11581153
while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
@@ -1163,7 +1158,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
11631158
)
11641159
});
11651160
if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
1166-
&& matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
1161+
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg")))
11671162
{
11681163
return true;
11691164
}

clippy_utils/src/hir_utils.rs

Lines changed: 110 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::consts::constant_simple;
22
use crate::macros::macro_backtrace;
3-
use crate::source::snippet_opt;
3+
use crate::source::{get_source_text, snippet_opt, walk_span_to_context, SpanRange};
4+
use crate::tokenize_with_text;
45
use rustc_ast::ast::InlineAsmTemplatePiece;
56
use rustc_data_structures::fx::FxHasher;
67
use rustc_hir::def::Res;
@@ -13,8 +14,9 @@ use rustc_hir::{
1314
use rustc_lexer::{tokenize, TokenKind};
1415
use rustc_lint::LateContext;
1516
use rustc_middle::ty::TypeckResults;
16-
use rustc_span::{sym, Symbol};
17+
use rustc_span::{sym, BytePos, Symbol, SyntaxContext};
1718
use std::hash::{Hash, Hasher};
19+
use std::ops::Range;
1820

1921
/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
2022
/// other conditions would make them equal.
@@ -127,51 +129,83 @@ impl HirEqInterExpr<'_, '_, '_> {
127129

128130
/// Checks whether two blocks are the same.
129131
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
130-
match (left.stmts, left.expr, right.stmts, right.expr) {
131-
([], None, [], None) => {
132-
// For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
133-
// expanded to nothing, or the cfg attribute was used.
134-
let (Some(left), Some(right)) = (
135-
snippet_opt(self.inner.cx, left.span),
136-
snippet_opt(self.inner.cx, right.span),
137-
) else { return true };
138-
let mut left_pos = 0;
139-
let left = tokenize(&left)
140-
.map(|t| {
141-
let end = left_pos + t.len as usize;
142-
let s = &left[left_pos..end];
143-
left_pos = end;
144-
(t, s)
145-
})
146-
.filter(|(t, _)| {
147-
!matches!(
148-
t.kind,
149-
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
150-
)
151-
})
152-
.map(|(_, s)| s);
153-
let mut right_pos = 0;
154-
let right = tokenize(&right)
155-
.map(|t| {
156-
let end = right_pos + t.len as usize;
157-
let s = &right[right_pos..end];
158-
right_pos = end;
159-
(t, s)
160-
})
161-
.filter(|(t, _)| {
162-
!matches!(
163-
t.kind,
164-
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
165-
)
166-
})
167-
.map(|(_, s)| s);
168-
left.eq(right)
169-
},
170-
_ => {
171-
over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r))
172-
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
173-
},
132+
use TokenKind::{BlockComment, LineComment, Semi, Whitespace};
133+
if left.stmts.len() != right.stmts.len() {
134+
return false;
174135
}
136+
let lspan = left.span.data();
137+
let rspan = right.span.data();
138+
if lspan.ctxt != SyntaxContext::root() && rspan.ctxt != SyntaxContext::root() {
139+
// Don't try to check in between statements inside macros.
140+
return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right))
141+
&& both(&left.expr, &right.expr, |left, right| self.eq_expr(left, right));
142+
}
143+
if lspan.ctxt != rspan.ctxt {
144+
return false;
145+
}
146+
147+
let mut lstart = lspan.lo;
148+
let mut rstart = rspan.lo;
149+
150+
for (left, right) in left.stmts.iter().zip(right.stmts) {
151+
if !self.eq_stmt(left, right) {
152+
return false;
153+
}
154+
let Some(lstmt_span) = walk_span_to_context(left.span, lspan.ctxt) else {
155+
return false;
156+
};
157+
let Some(rstmt_span) = walk_span_to_context(right.span, rspan.ctxt) else {
158+
return false;
159+
};
160+
let lstmt_span = lstmt_span.data();
161+
let rstmt_span = rstmt_span.data();
162+
163+
if lstmt_span.lo < lstart && rstmt_span.lo < rstart {
164+
// Can happen when macros expand to multiple statements, or rearrange statements.
165+
// Nothing in between the statements to check in this case.
166+
continue;
167+
} else if lstmt_span.lo < lstart || rstmt_span.lo < rstart {
168+
// Only one of the blocks had a weird macro.
169+
return false;
170+
}
171+
if !eq_span_tokens(self.inner.cx, lstart..lstmt_span.lo, rstart..rstmt_span.lo, |t| {
172+
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
173+
}) {
174+
return false;
175+
}
176+
177+
lstart = lstmt_span.hi;
178+
rstart = rstmt_span.hi;
179+
}
180+
181+
let (lend, rend) = match (left.expr, right.expr) {
182+
(Some(left), Some(right)) => {
183+
if !self.eq_expr(left, right) {
184+
return false;
185+
}
186+
let Some(lexpr_span) = walk_span_to_context(left.span, lspan.ctxt) else {
187+
return false;
188+
};
189+
let Some(rexpr_span) = walk_span_to_context(right.span, rspan.ctxt) else {
190+
return false;
191+
};
192+
(lexpr_span.lo(), rexpr_span.lo())
193+
},
194+
(None, None) => (lspan.hi, rspan.hi),
195+
(Some(_), None) | (None, Some(_)) => return false,
196+
};
197+
198+
if lend < lstart && rend < rstart {
199+
// Can happen when macros rearrange the input.
200+
// Nothing in between the statements to check in this case.
201+
return true;
202+
} else if lend < lstart || rend < rstart {
203+
// Only one of the blocks had a weird macro
204+
return false;
205+
}
206+
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
207+
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
208+
})
175209
}
176210

177211
fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
@@ -1038,3 +1072,33 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
10381072
h.hash_expr(e);
10391073
h.finish()
10401074
}
1075+
1076+
fn eq_span_tokens(
1077+
cx: &LateContext<'_>,
1078+
left: impl SpanRange,
1079+
right: impl SpanRange,
1080+
pred: impl Fn(TokenKind) -> bool,
1081+
) -> bool {
1082+
fn f(cx: &LateContext<'_>, left: Range<BytePos>, right: Range<BytePos>, pred: impl Fn(TokenKind) -> bool) -> bool {
1083+
if let Some(lsrc) = get_source_text(cx, left)
1084+
&& let Some(lsrc) = lsrc.as_str()
1085+
&& let Some(rsrc) = get_source_text(cx, right)
1086+
&& let Some(rsrc) = rsrc.as_str()
1087+
{
1088+
let pred = |t: &(_, _)| pred(t.0);
1089+
let map = |(_, x)| x;
1090+
1091+
let ltok = tokenize_with_text(lsrc)
1092+
.filter(pred)
1093+
.map(map);
1094+
let rtok = tokenize_with_text(rsrc)
1095+
.filter(pred)
1096+
.map(map);
1097+
ltok.eq(rtok)
1098+
} else {
1099+
// Unable to access the source. Conservatively assume the blocks aren't equal.
1100+
false
1101+
}
1102+
}
1103+
f(cx, left.into_range(), right.into_range(), pred)
1104+
}

clippy_utils/src/lib.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ use std::sync::OnceLock;
7777
use std::sync::{Mutex, MutexGuard};
7878

7979
use if_chain::if_chain;
80+
use itertools::Itertools;
8081
use rustc_ast::ast::{self, LitKind, RangeLimits};
8182
use rustc_ast::Attribute;
8283
use rustc_data_structures::fx::FxHashMap;
@@ -2490,6 +2491,17 @@ pub fn walk_to_expr_usage<'tcx, T>(
24902491
None
24912492
}
24922493

2494+
/// Tokenizes the input while keeping the text associated with each token.
2495+
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
2496+
let mut pos = 0;
2497+
tokenize(s).map(move |t| {
2498+
let end = pos + t.len;
2499+
let range = pos as usize..end as usize;
2500+
pos = end;
2501+
(t.kind, s.get(range).unwrap_or_default())
2502+
})
2503+
}
2504+
24932505
/// Checks whether a given span has any comment token
24942506
/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
24952507
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
@@ -2506,23 +2518,11 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
25062518
/// Comments are returned wrapped with their relevant delimiters
25072519
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
25082520
let snippet = sm.span_to_snippet(span).unwrap_or_default();
2509-
let mut comments_buf: Vec<String> = Vec::new();
2510-
let mut index: usize = 0;
2511-
2512-
for token in tokenize(&snippet) {
2513-
let token_range = index..(index + token.len as usize);
2514-
index += token.len as usize;
2515-
match token.kind {
2516-
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => {
2517-
if let Some(comment) = snippet.get(token_range) {
2518-
comments_buf.push(comment.to_string());
2519-
}
2520-
},
2521-
_ => (),
2522-
}
2523-
}
2524-
2525-
comments_buf.join("\n")
2521+
let res = tokenize_with_text(&snippet)
2522+
.filter(|(t, _)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
2523+
.map(|(_, s)| s)
2524+
.join("\n");
2525+
res
25262526
}
25272527

25282528
pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {

clippy_utils/src/source.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,64 @@
22
33
#![allow(clippy::module_name_repetitions)]
44

5+
use rustc_data_structures::sync::Lrc;
56
use rustc_errors::Applicability;
67
use rustc_hir::{Expr, ExprKind};
78
use rustc_lint::{LateContext, LintContext};
89
use rustc_session::Session;
9-
use rustc_span::hygiene;
1010
use rustc_span::source_map::{original_sp, SourceMap};
11+
use rustc_span::{hygiene, SourceFile};
1112
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
1213
use std::borrow::Cow;
14+
use std::ops::Range;
15+
16+
/// A type which can be converted to the range portion of a `Span`.
17+
pub trait SpanRange {
18+
fn into_range(self) -> Range<BytePos>;
19+
}
20+
impl SpanRange for Span {
21+
fn into_range(self) -> Range<BytePos> {
22+
let data = self.data();
23+
data.lo..data.hi
24+
}
25+
}
26+
impl SpanRange for SpanData {
27+
fn into_range(self) -> Range<BytePos> {
28+
self.lo..self.hi
29+
}
30+
}
31+
impl SpanRange for Range<BytePos> {
32+
fn into_range(self) -> Range<BytePos> {
33+
self
34+
}
35+
}
36+
37+
pub struct SourceFileRange {
38+
pub sf: Lrc<SourceFile>,
39+
pub range: Range<usize>,
40+
}
41+
impl SourceFileRange {
42+
/// Attempts to get the text from the source file. This can fail if the source text isn't
43+
/// loaded.
44+
pub fn as_str(&self) -> Option<&str> {
45+
self.sf.src.as_ref().and_then(|x| x.get(self.range.clone()))
46+
}
47+
}
48+
49+
/// Gets the source file, and range in the file, of the given span. Returns `None` if the span
50+
/// extends through multiple files, or is malformed.
51+
pub fn get_source_text(cx: &impl LintContext, sp: impl SpanRange) -> Option<SourceFileRange> {
52+
fn f(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
53+
let start = sm.lookup_byte_offset(sp.start);
54+
let end = sm.lookup_byte_offset(sp.end);
55+
if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
56+
return None;
57+
}
58+
let range = start.pos.to_usize()..end.pos.to_usize();
59+
Some(SourceFileRange { sf: start.sf, range })
60+
}
61+
f(cx.sess().source_map(), sp.into_range())
62+
}
1363

1464
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
1565
pub fn expr_block<T: LintContext>(

tests/ui/match_same_arms.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,50 @@ mod issue4244 {
5353
}
5454
}
5555

56-
fn main() {}
56+
macro_rules! m {
57+
(foo) => {};
58+
(bar) => {};
59+
}
60+
61+
fn main() {
62+
let x = 0;
63+
let _ = match 0 {
64+
0 => {
65+
m!(foo);
66+
x
67+
},
68+
1 => {
69+
m!(bar);
70+
x
71+
},
72+
_ => 1,
73+
};
74+
75+
let _ = match 0 {
76+
0 => {
77+
let mut x = 0;
78+
#[cfg(not_enabled)]
79+
{
80+
x = 5;
81+
}
82+
#[cfg(not(not_enabled))]
83+
{
84+
x = 6;
85+
}
86+
x
87+
},
88+
1 => {
89+
let mut x = 0;
90+
#[cfg(also_not_enabled)]
91+
{
92+
x = 5;
93+
}
94+
#[cfg(not(also_not_enabled))]
95+
{
96+
x = 6;
97+
}
98+
x
99+
},
100+
_ => 0,
101+
};
102+
}

0 commit comments

Comments
 (0)