Enhance collapsible_match to cover if-elses (#16560)

Closes rust-lang/rust-clippy#16558

changelog: [`collapsible_match`] extend to cover if-elses
This commit is contained in:
llogiq
2026-02-22 13:07:10 +00:00
committed by GitHub
32 changed files with 375 additions and 276 deletions
+7 -11
View File
@@ -526,18 +526,14 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
let mut captures = [Capture::EMPTY];
while let Some(name) = cursor.find_any_ident() {
match cursor.get_text(name) {
"declare_clippy_lint" => {
if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) {
decl_end = Some(cursor.pos());
}
"declare_clippy_lint" if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) => {
decl_end = Some(cursor.pos());
},
"impl" => {
if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) {
match cursor.get_text(captures[0]) {
"LateLintPass" => context = Some("LateContext"),
"EarlyLintPass" => context = Some("EarlyContext"),
_ => {},
}
"impl" if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) => {
match cursor.get_text(captures[0]) {
"LateLintPass" => context = Some("LateContext"),
"EarlyLintPass" => context = Some("EarlyContext"),
_ => {},
}
},
_ => {},
+2 -4
View File
@@ -39,10 +39,8 @@ pub(super) fn check<'tcx>(
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
TyKind::Path(qpath) if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) => {
return false;
},
// Ignore `p as *const _`
TyKind::Infer(()) => return false,
+2 -4
View File
@@ -81,10 +81,8 @@ fn check<'tcx>(
}
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) => {
if !matches!(prev_expr, Some(ExprKind::Ret(_))) {
returns += 1;
}
ExprKind::Ret(_) if !matches!(prev_expr, Some(ExprKind::Ret(_))) => {
returns += 1;
},
_ => {},
}
+2 -2
View File
@@ -307,7 +307,7 @@ fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
}
/// If the expression is a `||`, suggest parentheses around it.
fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
pub(super) fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
if let ExprKind::Binary(op, _, _) = expr.peel_drop_temps().kind
&& op.node == BinOpKind::Or
{
@@ -334,7 +334,7 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option<Spa
}
/// Peel the parentheses from an `if` expression, e.g. `((if true {} else {}))`.
fn peel_parens(sm: &SourceMap, mut span: Span) -> (Span, Span, Span) {
pub(super) fn peel_parens(sm: &SourceMap, mut span: Span) -> (Span, Span, Span) {
use crate::rustc_span::Pos;
let start = span.shrink_to_lo();
+4 -12
View File
@@ -763,19 +763,11 @@ fn check_attributes(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute])
self.check_private_items,
);
match item.kind {
ItemKind::Fn { sig, body, .. } => {
ItemKind::Fn { sig, body, .. }
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id())
|| item.span.in_external_macro(cx.tcx.sess.source_map()))
{
missing_headers::check(
cx,
item.owner_id,
sig,
headers,
Some(body),
self.check_private_items,
);
}
|| item.span.in_external_macro(cx.tcx.sess.source_map())) =>
{
missing_headers::check(cx, item.owner_id, sig, headers, Some(body), self.check_private_items);
},
ItemKind::Trait(_, _, unsafety, ..) => match (headers.safety, unsafety) {
(false, Safety::Unsafe) => span_lint(
+36 -38
View File
@@ -46,28 +46,28 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
// ((..))
// ^^^^^^ expr
// ^^^^ inner
ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => {
if expr.span.eq_ctxt(inner.span)
ExprKind::Paren(inner)
if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_))
&& expr.span.eq_ctxt(inner.span)
&& !expr.span.in_external_macro(cx.sess().source_map())
&& check_source(cx, inner)
{
// suggest removing the outer parens
&& check_source(cx, inner) =>
{
// suggest removing the outer parens
let mut applicability = Applicability::MachineApplicable;
// We don't need to use `snippet_with_context` here, because:
// - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above)
// - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine
let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
DOUBLE_PARENS,
expr.span,
"unnecessary parentheses",
"remove them",
sugg.to_string(),
applicability,
);
}
let mut applicability = Applicability::MachineApplicable;
// We don't need to use `snippet_with_context` here, because:
// - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above)
// - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine
let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
DOUBLE_PARENS,
expr.span,
"unnecessary parentheses",
"remove them",
sugg.to_string(),
applicability,
);
},
// func((n))
@@ -76,26 +76,24 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
// ^ inner
ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. })
if let [arg] = &**args
&& let ExprKind::Paren(inner) = &arg.kind =>
{
if expr.span.eq_ctxt(arg.span)
&& let ExprKind::Paren(inner) = &arg.kind
&& expr.span.eq_ctxt(arg.span)
&& !arg.span.in_external_macro(cx.sess().source_map())
&& check_source(cx, arg)
{
// suggest removing the inner parens
&& check_source(cx, arg) =>
{
// suggest removing the inner parens
let mut applicability = Applicability::MachineApplicable;
let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0;
span_lint_and_sugg(
cx,
DOUBLE_PARENS,
arg.span,
"unnecessary parentheses",
"remove them",
sugg.to_string(),
applicability,
);
}
let mut applicability = Applicability::MachineApplicable;
let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0;
span_lint_and_sugg(
cx,
DOUBLE_PARENS,
arg.span,
"unnecessary parentheses",
"remove them",
sugg.to_string(),
applicability,
);
},
_ => {},
}
+2 -4
View File
@@ -252,10 +252,8 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
}
}
},
ExprKind::Binary(op, l, r) => {
if op.node.is_comparison() {
return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
}
ExprKind::Binary(op, l, r) if op.node.is_comparison() => {
return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
}, // TODO: ExprKind::Loop + Match
_ => (),
}
+11 -13
View File
@@ -57,19 +57,17 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
}
match parent.kind {
ExprKind::AssignOp(op, lhs, rhs) => {
if lhs.hir_id == expr.hir_id {
*state = if op.node == AssignOpKind::AddAssign
&& is_integer_const(self.cx, rhs, 1)
&& *state == IncrementVisitorVarState::Initial
&& self.depth == 0
{
IncrementVisitorVarState::IncrOnce
} else {
// Assigned some other value or assigned multiple times
IncrementVisitorVarState::DontWarn
};
}
ExprKind::AssignOp(op, lhs, rhs) if lhs.hir_id == expr.hir_id => {
*state = if op.node == AssignOpKind::AddAssign
&& is_integer_const(self.cx, rhs, 1)
&& *state == IncrementVisitorVarState::Initial
&& self.depth == 0
{
IncrementVisitorVarState::IncrOnce
} else {
// Assigned some other value or assigned multiple times
IncrementVisitorVarState::DontWarn
};
},
ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
*state = IncrementVisitorVarState::DontWarn;
+14 -25
View File
@@ -59,12 +59,11 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
return;
}
match expr.kind {
ExprKind::Match(scrutinee, [arm1, arm2], _) => {
ExprKind::Match(scrutinee, [arm1, arm2], _)
if is_none_pattern(cx, arm2.pat) && check_arms(cx, arm2, arm1)
|| is_none_pattern(cx, arm1.pat) && check_arms(cx, arm1, arm2)
{
check_as_ref(cx, scrutinee, span, self.msrv);
}
|| is_none_pattern(cx, arm1.pat) && check_arms(cx, arm1, arm2) =>
{
check_as_ref(cx, scrutinee, span, self.msrv);
},
ExprKind::If(cond, then, Some(other)) => {
if let ExprKind::Let(let_expr) = cond.kind
@@ -75,34 +74,24 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
check_as_ref(cx, let_expr.init, span, self.msrv);
}
},
ExprKind::MethodCall(seg, callee, [], _) => {
if seg.ident.name == sym::unwrap_or_default {
check_map(cx, callee, span, self.msrv);
}
ExprKind::MethodCall(seg, callee, [], _) if seg.ident.name == sym::unwrap_or_default => {
check_map(cx, callee, span, self.msrv);
},
ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name {
sym::unwrap_or => {
if is_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
sym::unwrap_or if is_empty_slice(cx, or) => {
check_map(cx, callee, span, self.msrv);
},
sym::unwrap_or_else => {
if returns_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
sym::unwrap_or_else if returns_empty_slice(cx, or) => {
check_map(cx, callee, span, self.msrv);
},
_ => {},
},
ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name {
sym::map_or => {
if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
sym::map_or if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) => {
check_as_ref(cx, callee, span, self.msrv);
},
sym::map_or_else => {
if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
sym::map_or_else if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) => {
check_as_ref(cx, callee, span, self.msrv);
},
_ => {},
},
+10 -11
View File
@@ -242,13 +242,13 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
&& self.cx.qpath_res(path, ex.hir_id) == self.target
{
match (self.strip_kind, start, end) {
(StripKind::Prefix, Some(start), None) => {
if eq_pattern_length(self.cx, self.pattern, start, self.ctxt) {
self.results.push(ex);
return;
}
(StripKind::Prefix, Some(start), None)
if eq_pattern_length(self.cx, self.pattern, start, self.ctxt) =>
{
self.results.push(ex);
return;
},
(StripKind::Suffix, None, Some(end)) => {
(StripKind::Suffix, None, Some(end))
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
@@ -259,11 +259,10 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
&& let Some(left_arg) = len_arg(self.cx, left)
&& let ExprKind::Path(left_path) = &left_arg.kind
&& self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
&& eq_pattern_length(self.cx, self.pattern, right, self.ctxt)
{
self.results.push(ex);
return;
}
&& eq_pattern_length(self.cx, self.pattern, right, self.ctxt) =>
{
self.results.push(ex);
return;
},
_ => {},
}
+71 -5
View File
@@ -1,17 +1,19 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::higher::{If, IfLetOrMatch};
use clippy_utils::msrvs::Msrv;
use clippy_utils::res::{MaybeDef, MaybeResPath};
use clippy_utils::source::snippet;
use clippy_utils::source::{IntoSpan, SpanRangeExt, snippet};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{SpanlessEq, get_ref_operators, is_unit_expr, peel_blocks_with_stmt, peel_ref_operators};
use rustc_ast::BorrowKind;
use rustc_errors::MultiSpan;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::Ident;
use rustc_span::{BytePos, Span};
use crate::collapsible_if::{parens_around, peel_parens};
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
@@ -34,7 +36,7 @@ pub(super) fn check_if_let<'tcx>(
check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv);
}
#[expect(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
fn check_arm<'tcx>(
cx: &LateContext<'tcx>,
outer_is_match: bool,
@@ -119,6 +121,70 @@ fn check_arm<'tcx>(
"the outer pattern can be modified to include the inner pattern",
);
});
} else if outer_is_match // Leave if-let to the `collapsible_if` lint
&& let Some(inner) = If::hir(inner_expr)
&& outer_pat.span.eq_ctxt(inner.cond.span)
&& match (outer_else_body, inner.r#else) {
(None, None) => true,
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
}
{
span_lint_hir_and_then(
cx,
COLLAPSIBLE_MATCH,
inner_expr.hir_id,
inner_expr.span,
"this `if` can be collapsed into the outer `match`",
|diag| {
let outer_then_open_bracket = outer_then_body
.span
.split_at(1)
.0
.with_leading_whitespace(cx)
.into_span();
let outer_then_closing_bracket = {
let end = outer_then_body.span.shrink_to_hi();
end.with_lo(end.lo() - BytePos(1))
.with_leading_whitespace(cx)
.into_span()
};
let outer_arrow_end = if let Some(outer_guard) = outer_guard {
outer_guard.span.shrink_to_hi()
} else {
outer_pat.span.shrink_to_hi()
};
let (paren_start, inner_if_span, paren_end) = peel_parens(cx.tcx.sess.source_map(), inner_expr.span);
let inner_if = inner_if_span.split_at(2).0;
let mut sugg = vec![
(inner.then.span.shrink_to_lo(), "=> ".to_string()),
(outer_arrow_end.to(outer_then_open_bracket), String::new()),
(outer_then_closing_bracket, String::new()),
];
if let Some(outer_guard) = outer_guard {
sugg.extend(parens_around(outer_guard));
sugg.push((inner_if, "&&".to_string()));
}
if !paren_start.is_empty() {
sugg.push((paren_start, String::new()));
}
if !paren_end.is_empty() {
sugg.push((paren_end, String::new()));
}
sugg.extend(parens_around(inner.cond));
if let Some(else_inner) = inner.r#else {
let else_inner_span = inner.then.span.shrink_to_hi().to(else_inner.span);
sugg.push((else_inner_span, String::new()));
}
diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable);
},
);
}
}
@@ -386,10 +386,8 @@ fn sugg_with_curlies<'a>(
| Node::Expr(Expr {
kind: ExprKind::Block(..) | ExprKind::ConstBlock(..),
..
}) => {
if needs_var_binding && is_var_binding_used_later {
add_curlies();
}
}) if needs_var_binding && is_var_binding_used_later => {
add_curlies();
},
Node::Expr(..)
| Node::AnonConst(..)
+7 -2
View File
@@ -5168,6 +5168,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
format_collect::check(cx, expr, m_arg, m_ident_span);
},
Some((sym::take, take_self_arg, [take_arg], _, _)) => {
#[expect(clippy::collapsible_match)]
if self.msrv.meets(cx, msrvs::STR_REPEAT) {
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
}
@@ -5493,7 +5494,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
(sym::open, [_]) => {
open_options::check(cx, expr, recv);
},
(sym::or_else, [arg]) => {
(sym::or_else, [arg]) =>
{
#[expect(clippy::collapsible_match)]
if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
}
@@ -5598,7 +5601,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
(sym::try_into, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::TryInto) => {
unnecessary_fallible_conversions::check_method(cx, expr);
},
(sym::to_owned, []) => {
(sym::to_owned, []) =>
{
#[expect(clippy::collapsible_match)]
if !suspicious_to_owned::check(cx, expr, span) {
implicit_clone::check(cx, name, expr, recv);
}
+4 -4
View File
@@ -84,10 +84,10 @@ pub(super) fn check<'tcx>(
return ControlFlow::Break(());
}
},
hir::ExprKind::MethodCall(..) => {
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, None) {
return ControlFlow::Break(());
}
hir::ExprKind::MethodCall(..)
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, None) =>
{
return ControlFlow::Break(());
},
_ => {},
}
@@ -131,17 +131,13 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
}
(true, true)
},
hir::ExprKind::MethodCall(segment, recv, [arg], _) => {
hir::ExprKind::MethodCall(segment, recv, [arg], _)
if segment.ident.name == sym::then_some
&& cx.typeck_results().expr_ty(recv).is_bool()
&& arg.res_local_id() == Some(arg_id)
{
// bool.then_some(arg_id)
(false, true)
} else {
// bool.then_some(not arg_id)
(true, true)
}
&& arg.res_local_id() == Some(arg_id) =>
{
// bool.then_some(arg_id)
(false, true)
},
hir::ExprKind::Block(block, _) => block
.expr
@@ -482,15 +482,11 @@ fn get_input_traits_and_projections<'tcx>(
let mut projection_predicates = Vec::new();
for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
match predicate.kind().skip_binder() {
ClauseKind::Trait(trait_predicate) => {
if trait_predicate.trait_ref.self_ty() == input {
trait_predicates.push(trait_predicate);
}
ClauseKind::Trait(trait_predicate) if trait_predicate.trait_ref.self_ty() == input => {
trait_predicates.push(trait_predicate);
},
ClauseKind::Projection(projection_predicate) => {
if projection_predicate.projection_term.self_ty() == input {
projection_predicates.push(projection_predicate);
}
ClauseKind::Projection(projection_predicate) if projection_predicate.projection_term.self_ty() == input => {
projection_predicates.push(projection_predicate);
},
_ => {},
}
@@ -154,18 +154,14 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind {
// fix #10776
ExprKind::Block(block, ..) => match (block.stmts, block.expr) {
(stmts, Some(e)) => {
if stmts.iter().all(|stmt| !stmt_might_diverge(stmt)) {
self.visit_expr(e);
}
(stmts, Some(e)) if stmts.iter().all(|stmt| !stmt_might_diverge(stmt)) => {
self.visit_expr(e);
},
([first @ .., stmt], None) => {
if first.iter().all(|stmt| !stmt_might_diverge(stmt)) {
match stmt.kind {
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
_ => {},
}
}
([first @ .., stmt], None)
if first.iter().all(|stmt| !stmt_might_diverge(stmt))
&& let StmtKind::Expr(e) | StmtKind::Semi(e) = stmt.kind =>
{
self.visit_expr(e);
},
_ => {},
},
@@ -165,10 +165,8 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
return self.visit_expr(inner);
},
ExprKind::Field(e, _) => {
if self.typeck_results.expr_ty(e).is_union() {
self.insert_span(expr.span, "union field access occurs here");
}
ExprKind::Field(e, _) if self.typeck_results.expr_ty(e).is_union() => {
self.insert_span(expr.span, "union field access occurs here");
},
ExprKind::Path(QPath::Resolved(
+9 -15
View File
@@ -205,17 +205,13 @@ fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'t
.iter()
.all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)),
ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, *ty, send_trait),
ty::Adt(_, args) => {
if contains_pointer_like(cx, ty) {
// descends only if ADT contains any raw pointers
args.iter().all(|generic_arg| match generic_arg.kind() {
GenericArgKind::Type(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
// Lifetimes and const generics are not solid part of ADT and ignored
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => true,
})
} else {
false
}
ty::Adt(_, args) if contains_pointer_like(cx, ty) => {
// descends only if ADT contains any raw pointers
args.iter().all(|generic_arg| match generic_arg.kind() {
GenericArgKind::Type(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
// Lifetimes and const generics are not solid part of ADT and ignored
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => true,
})
},
// Raw pointers are `!Send` but allowed by the heuristic
ty::RawPtr(_, _) => true,
@@ -231,10 +227,8 @@ fn contains_pointer_like<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> b
ty::RawPtr(_, _) => {
return true;
},
ty::Adt(adt_def, _) => {
if cx.tcx.is_diagnostic_item(sym::NonNull, adt_def.did()) {
return true;
}
ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::NonNull, adt_def.did()) => {
return true;
},
_ => (),
}
+7 -9
View File
@@ -71,15 +71,13 @@ fn check_bit_mask(
span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
}
},
BinOpKind::BitOr => {
if mask_value | cmp_value != cmp_value {
span_lint(
cx,
BAD_BIT_MASK,
span,
format!("incompatible bit mask: `_ | {mask_value}` can never be equal to `{cmp_value}`"),
);
}
BinOpKind::BitOr if mask_value | cmp_value != cmp_value => {
span_lint(
cx,
BAD_BIT_MASK,
span,
format!("incompatible bit mask: `_ | {mask_value}` can never be equal to `{cmp_value}`"),
);
},
_ => (),
},
+6 -10
View File
@@ -52,11 +52,9 @@ pub(crate) fn check<'tcx>(
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
if is_redundant_op(cx, right, 0, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub if is_redundant_op(cx, right, 0, ctxt) => {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
},
BinOpKind::Mul => {
if is_redundant_op(cx, left, 1, ctxt) {
@@ -67,11 +65,9 @@ pub(crate) fn check<'tcx>(
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::Div => {
if is_redundant_op(cx, right, 1, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
BinOpKind::Div if is_redundant_op(cx, right, 1, ctxt) => {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
},
BinOpKind::BitAnd => {
if is_redundant_op(cx, left, -1, ctxt) {
@@ -73,14 +73,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
}
},
ExprKind::MethodCall(method, receiver, [next_multiple_of_arg], _) => {
// x.next_multiple_of(Y) / Y
ExprKind::MethodCall(method, receiver, [next_multiple_of_arg], _)
if method.ident.name == sym::next_multiple_of
&& check_int_ty(cx.typeck_results().expr_ty(receiver))
&& check_eq_expr(cx, next_multiple_of_arg, rhs)
{
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
}
&& check_eq_expr(cx, next_multiple_of_arg, rhs) =>
{
// x.next_multiple_of(Y) / Y
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
},
ExprKind::Call(callee, [receiver, next_multiple_of_arg]) => {
// int_type::next_multiple_of(x, Y) / Y
+3 -1
View File
@@ -1064,7 +1064,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
assign_op_pattern::check(cx, e, lhs, rhs, self.msrv);
self_assignment::check(cx, e, lhs, rhs);
},
ExprKind::Unary(op, arg) => {
ExprKind::Unary(op, arg) =>
{
#[expect(clippy::collapsible_match)]
if op == UnOp::Neg {
self.arithmetic_context.check_negate(cx, e, arg);
}
+12 -18
View File
@@ -66,25 +66,19 @@ fn visit_type(ty: &Ty, cx: &EarlyContext<'_>, reason: &'static str) {
// Match the 'static lifetime
if let Some(lifetime) = *optional_lifetime {
match borrow_type.ty.kind {
TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => {
if lifetime.ident.name == kw::StaticLifetime {
let snip = snippet(cx, borrow_type.ty.span, "<type>");
let sugg = format!("&{}{snip}", borrow_type.mutbl.prefix_str());
span_lint_and_then(
cx,
REDUNDANT_STATIC_LIFETIMES,
lifetime.ident.span,
reason,
|diag| {
diag.span_suggestion(
ty.span,
"consider removing `'static`",
sugg,
Applicability::MachineApplicable, //snippet
);
},
TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..)
if lifetime.ident.name == kw::StaticLifetime =>
{
let snip = snippet(cx, borrow_type.ty.span, "<type>");
let sugg = format!("&{}{snip}", borrow_type.mutbl.prefix_str());
span_lint_and_then(cx, REDUNDANT_STATIC_LIFETIMES, lifetime.ident.span, reason, |diag| {
diag.span_suggestion(
ty.span,
"consider removing `'static`",
sugg,
Applicability::MachineApplicable, //snippet
);
}
});
},
_ => {},
}
+1 -8
View File
@@ -138,14 +138,7 @@ fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
return;
},
},
sym::alloc => {
if cx.tcx.crate_name(def_id.krate) == sym::core {
(ALLOC_INSTEAD_OF_CORE, "alloc", "core")
} else {
self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict);
return;
}
},
sym::alloc if cx.tcx.crate_name(def_id.krate) == sym::core => (ALLOC_INSTEAD_OF_CORE, "alloc", "core"),
_ => {
self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict);
return;
+19 -23
View File
@@ -155,36 +155,32 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
},
left,
_,
) => {
if is_string(cx, left) {
if !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
let parent = get_parent_expr(cx, e);
if let Some(p) = parent
) if is_string(cx, left) => {
if !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
let parent = get_parent_expr(cx, e);
if let Some(p) = parent
&& let ExprKind::Assign(target, _, _) = p.kind
// avoid duplicate matches
&& SpanlessEq::new(cx).eq_expr(target, left)
{
return;
}
{
return;
}
span_lint(
cx,
STRING_ADD,
e.span,
"you added something to a string. Consider using `String::push_str()` instead",
);
}
span_lint(
cx,
STRING_ADD,
e.span,
"you added something to a string. Consider using `String::push_str()` instead",
);
},
ExprKind::Assign(target, src, _) => {
if is_string(cx, target) && is_add(cx, src, target) {
span_lint(
cx,
STRING_ADD_ASSIGN,
e.span,
"you assigned the result of adding something to this string. Consider using \
ExprKind::Assign(target, src, _) if is_string(cx, target) && is_add(cx, src, target) => {
span_lint(
cx,
STRING_ADD_ASSIGN,
e.span,
"you assigned the result of adding something to this string. Consider using \
`String::push_str()` instead",
);
}
);
},
ExprKind::Index(target, _idx, _) => {
let e_ty = cx.typeck_results().expr_ty_adjusted(target).peel_refs();
+4 -6
View File
@@ -142,12 +142,10 @@ pub fn read_cargo(&mut self, sess: &Session) {
match (self.0, cargo_msrv) {
(None, Some(cargo_msrv)) => self.0 = Some(cargo_msrv),
(Some(clippy_msrv), Some(cargo_msrv)) => {
if clippy_msrv != cargo_msrv {
sess.dcx().warn(format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
));
}
(Some(clippy_msrv), Some(cargo_msrv)) if clippy_msrv != cargo_msrv => {
sess.dcx().warn(format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
));
},
_ => {},
}
+4 -5
View File
@@ -114,16 +114,15 @@ fn contains_ty_adt_constructor_opaque_inner<'tcx>(
match predicate.kind().skip_binder() {
// For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through
// and check substitutions to find `U`.
ty::ClauseKind::Trait(trait_predicate) => {
ty::ClauseKind::Trait(trait_predicate)
if trait_predicate
.trait_ref
.args
.types()
.skip(1) // Skip the implicit `Self` generic parameter
.any(|ty| contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen))
{
return true;
}
.any(|ty| contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen)) =>
{
return true;
},
// For `impl Trait<Assoc=U>`, it will register a predicate of `<T as Trait>::Assoc = U`,
// so we check the term for `U`.
+2 -4
View File
@@ -53,10 +53,8 @@ fn explore_directory(dir: &Path) -> Vec<String> {
if let Some(ext) = path.extension() {
match ext.to_str().unwrap() {
"rs" | "toml" => current_file.clone_from(&file_prefix),
"stderr" | "stdout" => {
if file_prefix != current_file {
missing_files.push(path.to_str().unwrap().to_string());
}
"stderr" | "stdout" if file_prefix != current_file => {
missing_files.push(path.to_str().unwrap().to_string());
},
_ => {},
}
+30
View File
@@ -0,0 +1,30 @@
#![warn(clippy::collapsible_match)]
#![allow(clippy::single_match, clippy::redundant_guards)]
fn issue16558() {
let opt = Some(1);
let _ = match opt {
Some(s)
if s == 1 => { s }
//~^ collapsible_match
,
_ => 1,
};
match opt {
Some(s)
if s == 1 => {
//~^ collapsible_match
todo!()
},
_ => {},
};
let _ = match opt {
Some(s) if s > 2
&& s == 1 => { s }
//~^ collapsible_match
,
_ => 1,
};
}
+31
View File
@@ -0,0 +1,31 @@
#![warn(clippy::collapsible_match)]
#![allow(clippy::single_match, clippy::redundant_guards)]
fn issue16558() {
let opt = Some(1);
let _ = match opt {
Some(s) => {
if s == 1 { s } else { 1 }
//~^ collapsible_match
},
_ => 1,
};
match opt {
Some(s) => {
(if s == 1 {
//~^ collapsible_match
todo!()
})
},
_ => {},
};
let _ = match opt {
Some(s) if s > 2 => {
if s == 1 { s } else { 1 }
//~^ collapsible_match
},
_ => 1,
};
}
+50
View File
@@ -0,0 +1,50 @@
error: this `if` can be collapsed into the outer `match`
--> tests/ui/collapsible_match_fixable.rs:8:13
|
LL | if s == 1 { s } else { 1 }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::collapsible-match` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::collapsible_match)]`
help: collapse nested if block
|
LL ~ Some(s)
LL ~ if s == 1 => { s }
LL |
LL ~ ,
|
error: this `if` can be collapsed into the outer `match`
--> tests/ui/collapsible_match_fixable.rs:16:13
|
LL | / (if s == 1 {
LL | |
LL | | todo!()
LL | | })
| |______________^
|
help: collapse nested if block
|
LL ~ Some(s)
LL ~ if s == 1 => {
LL |
LL | todo!()
LL ~ },
|
error: this `if` can be collapsed into the outer `match`
--> tests/ui/collapsible_match_fixable.rs:26:13
|
LL | if s == 1 { s } else { 1 }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: collapse nested if block
|
LL ~ Some(s) if s > 2
LL ~ && s == 1 => { s }
LL |
LL ~ ,
|
error: aborting due to 3 previous errors