mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-16 13:05:18 +03:00
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:
@@ -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"),
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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(..)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -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}`"),
|
||||
);
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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`"
|
||||
));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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());
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user