Rollup merge of #156552 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? Manishearth

`Cargo.lock` update due to patch version bump in `ui_test`.

One day early, as I won't have access to my laptop from tomorrow till Sunday
This commit is contained in:
Jonathan Brouwer
2026-05-14 00:35:35 +02:00
committed by GitHub
102 changed files with 2051 additions and 549 deletions
+2 -2
View File
@@ -5948,9 +5948,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "ui_test"
version = "0.30.4"
version = "0.30.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8"
checksum = "980133b75aa9a95dc94feaf629d92d22c1172186f1fa1266b91f5b91414cf9a5"
dependencies = [
"annotate-snippets 0.11.5",
"anyhow",
+3
View File
@@ -6805,6 +6805,7 @@ Released 2018-09-13
[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
[`inline_modules`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_modules
[`inline_trait_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_trait_bounds
[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
@@ -6885,6 +6886,7 @@ Released 2018-09-13
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_checked_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_checked_ops
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_clear`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clear
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
@@ -7238,6 +7240,7 @@ Released 2018-09-13
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`some_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#some_filter
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
+1 -1
View File
@@ -34,7 +34,7 @@ anstream = "0.6.18"
[dev-dependencies]
cargo_metadata = "0.23"
ui_test = "0.30.2"
ui_test = "0.30.5"
regex = "1.5.5"
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.122"
+2 -2
View File
@@ -14,9 +14,9 @@ The different lint groups were defined in the [Clippy 1.0 RFC].
## Correctness
The `clippy::correctness` group is the only lint group in Clippy which lints are
The `clippy::correctness` group is the only lint group in Clippy whose lints are
deny-by-default and abort the compilation when triggered. This is for good
reason: If you see a `correctness` lint, it means that your code is outright
reason: if you see a `correctness` lint, it means that your code is outright
wrong or useless, and you should try to fix it.
Lints in this category are carefully picked and should be free of false
@@ -297,8 +297,9 @@ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
return Err("contains never type".to_owned());
}
let ctxt = e.span.ctxt();
for (n, expr) in self.terminals.iter().enumerate() {
if eq_expr_value(self.cx, e, expr) {
if eq_expr_value(self.cx, ctxt, e, expr) {
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Term(n as u8));
}
@@ -307,8 +308,8 @@ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
&& implements_ord(self.cx, e_lhs)
&& let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
&& negate(e_binop.node) == Some(expr_binop.node)
&& eq_expr_value(self.cx, e_lhs, expr_lhs)
&& eq_expr_value(self.cx, e_rhs, expr_rhs)
&& eq_expr_value(self.cx, ctxt, e_lhs, expr_lhs)
&& eq_expr_value(self.cx, ctxt, e_rhs, expr_rhs)
{
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
@@ -7,7 +7,7 @@
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Symbol;
use rustc_span::{Symbol, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@@ -62,7 +62,8 @@ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
},
_ => return,
}
&& !item.span.in_external_macro(cx.sess().source_map())
&& let ctxt = item.span.ctxt()
&& !ctxt.in_external_macro(cx.sess().source_map())
&& !is_in_const_context(cx)
&& let Some(cv) = match op2 {
// todo: check for case signed -> larger unsigned == only x >= 0
@@ -71,7 +72,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
let upper_lower = |lt1, gt1, lt2, gt2| {
check_upper_bound(lt1, gt1)
.zip(check_lower_bound(lt2, gt2))
.and_then(|(l, r)| l.combine(r, cx))
.and_then(|(l, r)| l.combine(r, cx, ctxt))
};
upper_lower(lt1, gt1, lt2, gt2).or_else(|| upper_lower(lt2, gt2, lt1, gt1))
},
@@ -126,8 +127,8 @@ fn read_le_ge<'tcx>(
impl<'a> Conversion<'a> {
/// Combine multiple conversions if the are compatible
pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
if self.is_compatible(&other, cx) {
pub fn combine(self, other: Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> Option<Conversion<'a>> {
if self.is_compatible(&other, cx, ctxt) {
// Prefer a Conversion that contains a type-constraint
Some(if self.to_type.is_some() { self } else { other })
} else {
@@ -137,9 +138,9 @@ pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>
/// Checks if two conversions are compatible
/// same type of conversion, same 'castee' and same 'to type'
pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool {
pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> bool {
(self.cvt == other.cvt)
&& (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast))
&& (SpanlessEq::new(cx).eq_expr(ctxt, self.expr_to_cast, other.expr_to_cast))
&& (self.has_compatible_to_type(other))
}
@@ -6,7 +6,7 @@
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use rustc_span::{SyntaxContext, sym};
declare_clippy_lint! {
/// ### What it does
@@ -90,8 +90,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Check that both sets of operands are equal
let mut spanless_eq = SpanlessEq::new(cx);
let same_fixed_operands = spanless_eq.eq_expr(lhs1, lhs2) && spanless_eq.eq_expr(rhs1, rhs2);
let same_transposed_operands = spanless_eq.eq_expr(lhs1, rhs2) && spanless_eq.eq_expr(rhs1, lhs2);
let same_fixed_operands = spanless_eq.eq_expr(SyntaxContext::root(), lhs1, lhs2)
&& spanless_eq.eq_expr(SyntaxContext::root(), rhs1, rhs2);
let same_transposed_operands = spanless_eq.eq_expr(SyntaxContext::root(), lhs1, rhs2)
&& spanless_eq.eq_expr(SyntaxContext::root(), rhs1, lhs2);
if !same_fixed_operands && !same_transposed_operands {
return;
@@ -232,6 +232,7 @@
crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO,
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
crate::inline_trait_bounds::INLINE_TRAIT_BOUNDS_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
@@ -414,6 +415,7 @@
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
crate::methods::LINES_FILTER_MAP_OK_INFO,
crate::methods::MANUAL_C_STR_LITERALS_INFO,
crate::methods::MANUAL_CLEAR_INFO,
crate::methods::MANUAL_CONTAINS_INFO,
crate::methods::MANUAL_FILTER_MAP_INFO,
crate::methods::MANUAL_FIND_MAP_INFO,
@@ -474,6 +476,7 @@
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
crate::methods::SKIP_WHILE_NEXT_INFO,
crate::methods::SLICED_STRING_AS_BYTES_INFO,
crate::methods::SOME_FILTER_INFO,
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
crate::methods::STRING_EXTEND_CHARS_INFO,
+6 -5
View File
@@ -498,11 +498,11 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
}
match try_parse_insert(self.cx, expr) {
Some(insert_expr) if self.spanless_eq.eq_expr(self.map, insert_expr.map) => {
Some(insert_expr) if self.spanless_eq.eq_expr(self.ctxt, self.map, insert_expr.map) => {
self.visit_insert_expr_arguments(&insert_expr);
// Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
if self.is_map_used
|| !self.spanless_eq.eq_expr(self.key, insert_expr.key)
|| !self.spanless_eq.eq_expr(self.ctxt, self.key, insert_expr.key)
|| expr.span.ctxt() != self.ctxt
{
self.can_use_entry = false;
@@ -521,10 +521,10 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
self.visit_non_tail_expr(insert_expr.value);
self.is_single_insert = is_single_insert;
},
_ if is_any_expr_in_map_used(self.cx, &mut self.spanless_eq, self.map, expr) => {
_ if is_any_expr_in_map_used(self.cx, &mut self.spanless_eq, self.ctxt, self.map, expr) => {
self.is_map_used = true;
},
_ if self.spanless_eq.eq_expr(self.key, expr) => {
_ if self.spanless_eq.eq_expr(self.ctxt, self.key, expr) => {
self.is_key_used = true;
},
_ => match expr.kind {
@@ -600,11 +600,12 @@ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
fn is_any_expr_in_map_used<'tcx>(
cx: &LateContext<'tcx>,
spanless_eq: &mut SpanlessEq<'_, 'tcx>,
ctxt: SyntaxContext,
map: &'tcx Expr<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> bool {
for_each_expr(cx, map, |e| {
if spanless_eq.eq_expr(e, expr) {
if spanless_eq.eq_expr(ctxt, e, expr) {
return ControlFlow::Break(());
}
ControlFlow::Continue(())
@@ -14,11 +14,15 @@
/// test is positive or an expression which tests whether or not test
/// is nonnegative.
/// Used for check-custom-abs function below
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
fn is_testing_positive(cx: &LateContext<'_>, ctxt: SyntaxContext, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Gt | BinOpKind::Ge => {
is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, ctxt, left, test)
},
BinOpKind::Lt | BinOpKind::Le => {
is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, ctxt, right, test)
},
_ => false,
}
} else {
@@ -27,11 +31,15 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
}
/// See [`is_testing_positive`]
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
fn is_testing_negative(cx: &LateContext<'_>, ctxt: SyntaxContext, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Gt | BinOpKind::Ge => {
is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, ctxt, right, test)
},
BinOpKind::Lt | BinOpKind::Le => {
is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, ctxt, left, test)
},
_ => false,
}
} else {
@@ -55,14 +63,21 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
/// one of the two expressions
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
fn are_negated<'a>(
cx: &LateContext<'_>,
ctxt: SyntaxContext,
expr1: &'a Expr<'a>,
expr2: &'a Expr<'a>,
) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
&& expr1_negated.span.ctxt() == ctxt
&& eq_expr_value(cx, ctxt, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
&& expr2_negated.span.ctxt() == ctxt
&& eq_expr_value(cx, ctxt, expr1, expr2_negated)
{
return Some((true, expr1));
}
@@ -77,11 +92,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
&& let ctxt = expr.span.ctxt()
&& let Some((if_expr_positive, body)) = are_negated(cx, ctxt, if_body_expr, else_body_expr)
{
let sugg_positive_abs = if is_testing_positive(cx, cond, body) {
let sugg_positive_abs = if is_testing_positive(cx, ctxt, cond, body) {
if_expr_positive
} else if is_testing_negative(cx, cond, body) {
} else if is_testing_negative(cx, ctxt, cond, body) {
!if_expr_positive
} else {
return;
@@ -19,6 +19,7 @@ pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applic
add_rhs,
) = receiver.kind
{
let ctxt = receiver.span.ctxt();
// check if expression of the form x * x + y * y
if let ExprKind::Binary(
Spanned {
@@ -34,8 +35,8 @@ pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applic
rmul_lhs,
rmul_rhs,
) = add_rhs.kind
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
&& eq_expr_value(cx, ctxt, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, ctxt, rmul_lhs, rmul_rhs)
{
return Some(format!(
"{}.hypot({})",
@@ -4,18 +4,18 @@
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::Spanned;
use rustc_span::{Spanned, SyntaxContext};
use super::SUBOPTIMAL_FLOPS;
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
fn are_same_base_logs(cx: &LateContext<'_>, ctxt: SyntaxContext, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind
&& let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind
{
return method_a.name == method_b.name
&& args_a.len() == args_b.len()
&& (matches!(method_a.name, sym::ln | sym::log2 | sym::log10)
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, ctxt, &args_a[0], &args_b[0]));
}
false
@@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
lhs,
rhs,
) = expr.kind
&& are_same_base_logs(cx, lhs, rhs)
&& are_same_base_logs(cx, expr.span.ctxt(), lhs, rhs)
&& let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind
&& let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind
{
@@ -7,6 +7,7 @@
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use rustc_span::edition::Edition::Edition2024;
declare_clippy_lint! {
@@ -61,9 +62,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_else: Some(if_else),
..
}) = higher::IfLet::hir(cx, expr)
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None))
&& let ctxt = expr.span.ctxt()
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, ctxt, e, None))
&& let Some(arm_mutex) =
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex)))
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, ctxt, e, Some(op_mutex)))
{
let diag = |diag: &mut Diag<'_, ()>| {
diag.span_label(
@@ -89,6 +91,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
fn mutex_lock_call<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
expr: &'tcx Expr<'_>,
op_mutex: Option<&'tcx Expr<'_>>,
) -> ControlFlow<&'tcx Expr<'tcx>> {
@@ -96,7 +99,7 @@ fn mutex_lock_call<'tcx>(
&& path.ident.name == sym::lock
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& ty.is_diag_item(cx, sym::Mutex)
&& op_mutex.is_none_or(|op| eq_expr_value(cx, self_arg, op))
&& op_mutex.is_none_or(|op| eq_expr_value(cx, ctxt, self_arg, op))
{
ControlFlow::Break(self_arg)
} else {
@@ -13,7 +13,7 @@
use rustc_lint::LateContext;
use rustc_span::hygiene::walk_chain;
use rustc_span::source_map::SourceMap;
use rustc_span::{Span, Symbol};
use rustc_span::{Span, Symbol, SyntaxContext};
use super::BRANCHES_SHARING_CODE;
@@ -196,7 +196,12 @@ fn eq_stmts(
.all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(cx, s, new_bindings)))
} else {
true
}) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt)))
}) && blocks.iter().all(|b| {
get_stmt(b).is_some_and(|s| {
eq.set_eval_ctxt(SyntaxContext::root());
eq.eq_stmt(s, stmt)
})
})
}
#[expect(clippy::too_many_lines)]
@@ -207,7 +212,7 @@ fn scan_block_for_eq<'tcx>(
blocks: &[&'tcx Block<'_>],
) -> BlockEq {
let mut eq = SpanlessEq::new(cx);
let mut eq = eq.inter_expr();
let mut eq = eq.inter_expr(SyntaxContext::root());
let mut moved_locals = Vec::new();
let mut cond_locals = HirIdSet::default();
@@ -334,6 +339,7 @@ fn scan_block_for_eq<'tcx>(
});
if let Some(e) = block.expr {
for block in blocks {
eq.set_eval_ctxt(SyntaxContext::root());
if block.expr.is_some_and(|expr| !eq.eq_expr(expr, e)) {
moved_locals.truncate(moved_locals_at_start);
return BlockEq {
@@ -3,6 +3,7 @@
use clippy_utils::higher::has_let_expr;
use rustc_hir::{Block, Expr};
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use super::IF_SAME_THEN_ELSE;
@@ -12,7 +13,10 @@ pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<
.array_windows::<2>()
.enumerate()
.fold(true, |all_eq, (i, &[lhs, rhs])| {
if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) {
if eq.eq_block(SyntaxContext::root(), lhs, rhs)
&& !has_let_expr(conds[i])
&& conds.get(i + 1).is_none_or(|e| !has_let_expr(e))
{
span_lint_and_note(
cx,
IF_SAME_THEN_ELSE,
@@ -4,6 +4,7 @@
use clippy_utils::{SpanlessEq, eq_expr_value, find_binding_init, hash_expr, search_same};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use super::IFS_SAME_COND;
@@ -34,10 +35,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_
if method_caller_is_mutable(cx, caller, interior_mut) {
false
} else {
SpanlessEq::new(cx).eq_expr(lhs, rhs)
SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs, rhs)
}
} else {
eq_expr_value(cx, lhs, rhs)
eq_expr_value(cx, SyntaxContext::root(), lhs, rhs)
}
},
) {
@@ -2,6 +2,7 @@
use clippy_utils::{SpanlessEq, eq_expr_value, hash_expr, search_same};
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use super::SAME_FUNCTIONS_IN_IF_CONDITION;
@@ -13,10 +14,10 @@ pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
return false;
}
// Do not spawn warning if `IFS_SAME_COND` already produced it.
if eq_expr_value(cx, lhs, rhs) {
if eq_expr_value(cx, SyntaxContext::root(), lhs, rhs) {
return false;
}
SpanlessEq::new(cx).eq_expr(lhs, rhs)
SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs, rhs)
};
for group in search_same(conds, |e| hash_expr(cx, e), eq) {
@@ -67,7 +67,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
&& let ctxt = expr.span.ctxt()
&& ex.span.ctxt() == ctxt
&& cond.span.ctxt() == ctxt
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
&& clippy_utils::SpanlessEq::new(cx).eq_expr(ctxt, l, target)
&& AssignOpKind::AddAssign == op1.node
&& let ExprKind::Lit(lit) = value.kind
&& let LitKind::Int(Pu128(1), LitIntType::Unsuffixed) = lit.node
@@ -184,7 +184,7 @@ fn check_gt(
msrv: Msrv,
is_composited: bool,
) {
if is_side_effect_free(cx, big_expr) && is_side_effect_free(cx, little_expr) {
if !big_expr.can_have_side_effects() && !little_expr.can_have_side_effects() {
check_subtraction(
cx,
condition_span,
@@ -199,10 +199,6 @@ fn check_gt(
}
}
fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
eq_expr_value(cx, expr, expr)
}
#[expect(clippy::too_many_arguments)]
fn check_subtraction(
cx: &LateContext<'_>,
@@ -242,7 +238,8 @@ fn check_subtraction(
&& let ExprKind::Binary(op, left, right) = if_block.kind
&& let BinOpKind::Sub = op.node
{
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
let ctxt = expr_span.ctxt();
if eq_expr_value(cx, ctxt, left, big_expr) && eq_expr_value(cx, ctxt, right, little_expr) {
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) {
@@ -277,8 +274,8 @@ fn check_subtraction(
},
);
}
} else if eq_expr_value(cx, left, little_expr)
&& eq_expr_value(cx, right, big_expr)
} else if eq_expr_value(cx, ctxt, left, little_expr)
&& eq_expr_value(cx, ctxt, right, big_expr)
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
@@ -322,14 +319,15 @@ fn check_with_condition<'tcx>(
// Extracting out the variable name
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
let ctxt = expr.span.ctxt();
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(ctxt, cond_left, target) {
if BinOpKind::Gt == cond_op || BinOpKind::Ne == cond_op {
(cond_left, cond_right)
} else {
return;
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
} else if SpanlessEq::new(cx).eq_expr(ctxt, cond_right, target) {
if BinOpKind::Lt == cond_op || BinOpKind::Ne == cond_op {
(cond_right, cond_left)
} else {
@@ -399,7 +397,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
ExprKind::Assign(target, value, _) => {
if let ExprKind::Binary(ref op1, left1, right1) = value.kind
&& BinOpKind::Sub == op1.node
&& SpanlessEq::new(cx).eq_expr(left1, target)
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), left1, target)
&& is_integer_literal(right1, 1)
{
Some(target)
@@ -0,0 +1,137 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{HasSession, snippet};
use rustc_ast::NodeId;
use rustc_ast::ast::{Fn, FnRetTy, GenericParam, GenericParamKind};
use rustc_ast::visit::{FnCtxt, FnKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Enforce that `where` bounds are used for all trait bounds.
///
/// ### Why restrict this?
/// Enforce a single style throughout a codebase.
/// Avoid uncertainty about whether a bound should be inline
/// or out-of-line (i.e. a where bound).
/// Avoid complex inline bounds, which could make a function declaration more difficult to read.
///
/// ### Known limitations
/// Only lints functions and method declararions. Bounds on structs, enums,
/// and impl blocks are not yet covered.
///
/// ### Example
/// ```no_run
/// fn foo<T: Clone>() {}
/// ```
///
/// Use instead:
/// ```no_run
/// fn foo<T>() where T: Clone {}
/// ```
#[clippy::version = "1.97.0"]
pub INLINE_TRAIT_BOUNDS,
restriction,
"enforce that `where` bounds are used for all trait bounds"
}
declare_lint_pass!(InlineTraitBounds => [INLINE_TRAIT_BOUNDS]);
impl EarlyLintPass for InlineTraitBounds {
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
let FnKind::Fn(ctxt, _vis, f) = kind else {
return;
};
// Skip foreign functions (extern "C" etc.)
if !matches!(ctxt, FnCtxt::Free | FnCtxt::Assoc(..)) {
return;
}
if f.sig.span.in_external_macro(cx.sess().source_map()) {
return;
}
lint_fn(cx, f);
}
}
fn lint_fn(cx: &EarlyContext<'_>, f: &Fn) {
let generics = &f.generics;
let offenders: Vec<&GenericParam> = generics
.params
.iter()
.filter(|param| {
!param.bounds.is_empty() && matches!(param.kind, GenericParamKind::Lifetime | GenericParamKind::Type { .. })
})
.collect();
if offenders.is_empty() {
return;
}
let predicates = offenders
.iter()
.map(|param| build_predicate_text(cx, param))
.collect::<Vec<_>>();
let mut edits = Vec::new();
for param in offenders {
if let Some(colon) = param.colon_span {
let remove_span = colon.to(param.bounds.last().unwrap().span());
edits.push((remove_span, String::new()));
}
}
let predicate_text = predicates.join(", ");
let where_clause = &generics.where_clause;
if where_clause.has_where_token {
let (insert_at, suffix) = if let Some(last_pred) = where_clause.predicates.last() {
// existing `where` with predicates: append after last predicate
(last_pred.span.shrink_to_hi(), format!(", {predicate_text}"))
} else {
// `where` token present but empty predicate list
(where_clause.span.shrink_to_hi(), format!(" {predicate_text}"))
};
edits.push((insert_at, suffix));
} else {
let insert_at = match &f.sig.decl.output {
FnRetTy::Default(span) => span.shrink_to_lo(),
FnRetTy::Ty(ty) => ty.span.shrink_to_hi(),
};
edits.push((insert_at, format!(" where {predicate_text}")));
}
span_lint_and_then(
cx,
INLINE_TRAIT_BOUNDS,
generics.span,
"inline trait bounds used",
|diag| {
diag.multipart_suggestion(
"move bounds to a `where` clause",
edits,
Applicability::MachineApplicable,
);
},
);
}
fn build_predicate_text(cx: &EarlyContext<'_>, param: &GenericParam) -> String {
// bounds is guaranteed non-empty by the filter in `lint_fn`
let first = param.bounds.first().unwrap();
let last = param.bounds.last().unwrap();
let bounds_span = first.span().to(last.span());
let lhs = snippet(cx, param.ident.span, "..");
let rhs = snippet(cx, bounds_span, "..");
format!("{lhs}: {rhs}")
}
+2
View File
@@ -168,6 +168,7 @@
mod inherent_to_string;
mod init_numbered_fields;
mod inline_fn_without_body;
mod inline_trait_bounds;
mod int_plus_one;
mod item_name_repetitions;
mod items_after_statements;
@@ -518,6 +519,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)),
Box::new(|| Box::new(cfg_not_test::CfgNotTest)),
Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())),
Box::new(|| Box::new(inline_trait_bounds::InlineTraitBounds)),
// add early passes here, used by `cargo dev new_lint`
];
store.early_passes.extend(early_lints);
@@ -89,13 +89,13 @@ fn check_index_usage<'tcx>(
// `Index` directly and no deref to `str` would happen in that case).
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str()
&& BYTE_INDEX_METHODS.contains(&segment.ident.name)
&& eq_expr_value(cx, chars_recv, recv) =>
&& eq_expr_value(cx, expr.span.ctxt(), chars_recv, recv) =>
{
"passing a character position to a method that expects a byte index"
},
ExprKind::Index(target, ..)
if is_string_like(cx.typeck_results().expr_ty_adjusted(target).peel_refs())
&& eq_expr_value(cx, chars_recv, target) =>
&& eq_expr_value(cx, expr.span.ctxt(), chars_recv, target) =>
{
"indexing into a string with a character position where a byte index is expected"
},
@@ -65,7 +65,7 @@ fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>, is_empty_recv: &Expr
&& let ExprKind::MethodCall(_, pop_recv, ..) = unwrap_recv.kind
{
// make sure they're the same `Vec`
SpanlessEq::new(cx).eq_expr(pop_recv, is_empty_recv)
SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), pop_recv, is_empty_recv)
} else {
false
}
@@ -101,8 +101,9 @@ pub(super) fn check<'tcx>(
if let ExprKind::Binary(ref op, left, right) = end.kind
&& op.node == BinOpKind::Add
{
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
let ctxt = start.span.ctxt();
let start_equal_left = SpanlessEq::new(cx).eq_expr(ctxt, start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(ctxt, start, right);
if start_equal_left {
take_expr = right;
@@ -121,12 +121,12 @@ fn is_sub_expr(
expected_b: &Expr<'_>,
expected_ty: Ty<'_>,
) -> bool {
let expr = peel_blocks(expr).kind;
let expr = peel_blocks(expr);
if let ty::Int(ty) = expected_ty.kind() {
let unsigned = Ty::new_uint(cx.tcx, ty.to_unsigned());
return if let ExprKind::Cast(expr, cast_ty) = expr
return if let ExprKind::Cast(expr, cast_ty) = expr.kind
&& cx.typeck_results().node_type(cast_ty.hir_id) == unsigned
{
is_sub_expr(cx, expr, expected_a, expected_b, unsigned)
@@ -135,10 +135,11 @@ fn is_sub_expr(
};
}
if let ExprKind::Binary(op, a, b) = expr
let ctxt = expr.span.ctxt();
if let ExprKind::Binary(op, a, b) = expr.kind
&& let BinOpKind::Sub = op.node
&& eq_expr_value(cx, a, expected_a)
&& eq_expr_value(cx, b, expected_b)
&& eq_expr_value(cx, ctxt, a, expected_a)
&& eq_expr_value(cx, ctxt, b, expected_b)
{
true
} else {
@@ -5,6 +5,7 @@
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use std::ops::ControlFlow;
declare_clippy_lint! {
@@ -60,7 +61,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
&& let Some(block) = branch_block(then, r#else, branch)
{
let mut eq = SpanlessEq::new(cx).deny_side_effects().paths_by_resolution();
if !eq.eq_expr(divisor, divisor) {
if !eq.eq_expr(SyntaxContext::root(), divisor, divisor) {
return;
}
@@ -70,7 +71,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let found_early_use = for_each_expr_without_closures(block, |e| {
if let ExprKind::Binary(binop, lhs, rhs) = e.kind
&& binop.node == BinOpKind::Div
&& eq.eq_expr(rhs, divisor)
&& eq.eq_expr(SyntaxContext::root(), rhs, divisor)
&& is_unsigned_integer(cx, lhs)
{
match first_use {
@@ -84,7 +85,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
ControlFlow::<(), _>::Continue(Descend::No)
} else if let ExprKind::AssignOp(op, lhs, rhs) = e.kind
&& op.node == AssignOpKind::DivAssign
&& eq.eq_expr(rhs, divisor)
&& eq.eq_expr(SyntaxContext::root(), rhs, divisor)
&& is_unsigned_integer(cx, lhs)
{
match first_use {
@@ -96,7 +97,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
division_spans.push(e.span);
ControlFlow::<(), _>::Continue(Descend::No)
} else if eq.eq_expr(e, divisor) {
} else if eq.eq_expr(SyntaxContext::root(), e, divisor) {
if first_use.is_none() {
first_use = Some(UseKind::Other);
return ControlFlow::Break(());
@@ -15,7 +15,7 @@
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
use std::cmp::Ordering;
use std::ops::Deref;
@@ -261,6 +261,7 @@ fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx
{
let params = is_clamp_meta_pattern(
cx,
expr.span.ctxt(),
&BinaryOp::new(peel_blocks(cond))?,
&BinaryOp::new(peel_blocks(else_if_cond))?,
peel_blocks(then),
@@ -268,7 +269,7 @@ fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx
None,
)?;
// Contents of the else should be the resolved input.
if !eq_expr_value(cx, params.input, peel_blocks(else_body)) {
if !eq_expr_value(cx, expr.span.ctxt(), params.input, peel_blocks(else_body)) {
return None;
}
Some(ClampSuggestion {
@@ -445,6 +446,7 @@ fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Opt
}
if let Some(params) = is_clamp_meta_pattern(
cx,
expr.span.ctxt(),
&first,
&second,
first_expr,
@@ -496,11 +498,14 @@ fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) ->
peel_blocks_with_stmt(first_then).kind
&& let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) =
peel_blocks_with_stmt(second_then).kind
&& eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path)
&& let ctxt = first_expr.span.ctxt()
&& second_expr.span.ctxt() == ctxt
&& eq_expr_value(cx, ctxt, maybe_input_first_path, maybe_input_second_path)
&& let Some(first_bin) = BinaryOp::new(first_cond)
&& let Some(second_bin) = BinaryOp::new(second_cond)
&& let Some(input_min_max) = is_clamp_meta_pattern(
cx,
ctxt,
&first_bin,
&second_bin,
maybe_min_max_first,
@@ -552,15 +557,17 @@ fn is_if_elseif_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
&& let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) =
peel_blocks_with_stmt(else_if_then).kind
{
let ctxt = expr.span.ctxt();
let params = is_clamp_meta_pattern(
cx,
ctxt,
&BinaryOp::new(peel_blocks(cond))?,
&BinaryOp::new(peel_blocks(else_if_cond))?,
peel_blocks(maybe_min_max_first),
peel_blocks(maybe_min_max_second),
None,
)?;
if !eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) {
if !eq_expr_value(cx, ctxt, maybe_input_first_path, maybe_input_second_path) {
return None;
}
Some(ClampSuggestion {
@@ -631,6 +638,7 @@ fn flip(&self) -> Self {
/// result can not be the shared argument in either case.
fn is_clamp_meta_pattern<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
first_bin: &BinaryOp<'tcx>,
second_bin: &BinaryOp<'tcx>,
first_expr: &'tcx Expr<'tcx>,
@@ -642,8 +650,10 @@ fn is_clamp_meta_pattern<'tcx>(
// be the input variable, not the min or max.
input_hir_ids: Option<(HirId, HirId)>,
) -> Option<InputMinMax<'tcx>> {
#[expect(clippy::too_many_arguments)]
fn check<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
first_bin: &BinaryOp<'tcx>,
second_bin: &BinaryOp<'tcx>,
first_expr: &'tcx Expr<'tcx>,
@@ -659,11 +669,11 @@ fn check<'tcx>(
peel_blocks(first_bin.left).res_local_id() == Some(first_hir_id)
&& peel_blocks(second_bin.left).res_local_id() == Some(second_hir_id)
},
None => eq_expr_value(cx, first_bin.left, second_bin.left),
None => eq_expr_value(cx, ctxt, first_bin.left, second_bin.left),
};
(refers_to_input
&& eq_expr_value(cx, first_bin.right, first_expr)
&& eq_expr_value(cx, second_bin.right, second_expr))
&& eq_expr_value(cx, ctxt, first_bin.right, first_expr)
&& eq_expr_value(cx, ctxt, second_bin.right, second_expr))
.then_some(InputMinMax {
input: first_bin.left,
min,
@@ -699,9 +709,20 @@ fn check<'tcx>(
];
cases.into_iter().find_map(|(first, second)| {
check(cx, &first, &second, first_expr, second_expr, input_hir_ids, is_float).or_else(|| {
check(
cx,
ctxt,
&first,
&second,
first_expr,
second_expr,
input_hir_ids,
is_float,
)
.or_else(|| {
check(
cx,
ctxt,
&second,
&first,
second_expr,
@@ -9,6 +9,7 @@
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::impl_lint_pass;
use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@@ -109,11 +110,12 @@ fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Optio
/// Return `greater` if `smaller == greater - 1`
fn is_one_less<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
greater: &'tcx Expr<'tcx>,
smaller: &Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub)
&& SpanlessEq::new(cx).eq_expr(greater, lhs)
&& SpanlessEq::new(cx).eq_expr(ctxt, greater, lhs)
&& is_integer_literal(rhs, 1)
&& matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_))
{
@@ -126,7 +128,7 @@ fn is_one_less<'tcx>(
/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v`
fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?;
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
is_one_less(cx, expr.span.ctxt(), lhs, rhs).or_else(|| is_one_less(cx, expr.span.ctxt(), rhs, lhs))
}
/// Return the operands of the `expr` binary operation if the operator is `op` and none of the
@@ -212,7 +212,7 @@ fn check_is_some_and_pattern<'tcx>(
&& let ExprKind::Closure(closure) = closure_arg.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
&& eq_expr_value(cx, collection_expr, pop_collection)
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
&& let Some(param) = body.params.first()
&& let Some(ident) = param.pat.simple_ident()
{
@@ -274,7 +274,7 @@ fn check_if_let_pattern<'tcx>(
if let ExprKind::If(inner_cond, inner_then, None) = inner_if.kind
&& is_local_used(cx, inner_cond, binding_id)
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, inner_then, pop_method)
&& eq_expr_value(cx, collection_expr, pop_collection)
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
{
return Some(ManualPopIfPattern {
kind,
@@ -327,7 +327,7 @@ fn check_let_chain_pattern<'tcx>(
&& kind.is_diag_item(cx, collection_expr)
&& is_local_used(cx, right, binding_id)
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
&& eq_expr_value(cx, collection_expr, pop_collection)
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
{
return Some(ManualPopIfPattern {
kind,
@@ -372,7 +372,7 @@ fn check_map_unwrap_or_pattern<'tcx>(
&& let body = cx.tcx.hir_body(closure.body)
&& cx.typeck_results().expr_ty(body.value).is_bool()
&& let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method)
&& eq_expr_value(cx, collection_expr, pop_collection)
&& eq_expr_value(cx, if_expr_span.ctxt(), collection_expr, pop_collection)
&& let Some(param) = body.params.first()
&& let Some(ident) = param.pat.simple_ident()
{
@@ -85,7 +85,7 @@ fn check_into_iter(
&& let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id)
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
&& match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, struct_expr)
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
&& let filter_body = cx.tcx.hir_body(closure.body)
@@ -132,7 +132,7 @@ fn check_iter(
&& let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id)
&& match_acceptable_sym(cx, iter_expr_def_id)
&& match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, struct_expr)
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
&& let filter_body = cx.tcx.hir_body(closure.body)
@@ -190,7 +190,7 @@ fn check_to_owned(
&& cx.tcx.is_diagnostic_item(sym::str_chars, chars_expr_def_id)
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
&& ty.is_lang_item(cx, hir::LangItem::String)
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
&& SpanlessEq::new(cx).eq_expr(parent_expr_span.ctxt(), left_expr, str_expr)
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
&& let filter_body = cx.tcx.hir_body(closure.body)
@@ -75,7 +75,8 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
&& let Some((l_shift_dir, l_expr, l_amount)) = parse_shift(l)
&& let Some((r_shift_dir, r_expr, r_amount)) = parse_shift(r)
&& l_shift_dir != r_shift_dir
&& clippy_utils::eq_expr_value(cx, l_expr, r_expr)
&& let ctxt = expr.span.ctxt()
&& clippy_utils::eq_expr_value(cx, ctxt, l_expr, r_expr)
&& let Some(bit_width) = match cx.typeck_results().expr_ty(expr).kind() {
ty::Int(itype) => itype.bit_width(),
ty::Uint(itype) => itype.bit_width(),
@@ -84,7 +85,6 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
{
let const_eval = ConstEvalCtxt::new(cx);
let ctxt = expr.span.ctxt();
let (shift_function, amount) = if let Some(Constant::Int(l_amount_val)) =
const_eval.eval_local(l_amount, ctxt)
&& let Some(Constant::Int(r_amount_val)) = const_eval.eval_local(r_amount, ctxt)
@@ -103,7 +103,7 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
};
if let Some(Constant::Int(minuend)) = const_eval.eval_local(minuend, ctxt)
&& clippy_utils::eq_expr_value(cx, amount1, amount2)
&& clippy_utils::eq_expr_value(cx, ctxt, amount1, amount2)
// (x << s) | (x >> bit_width - s)
&& ((binop.node == BinOpKind::Sub && u128::from(bit_width) == minuend)
// (x << s) | (x >> (bit_width - 1) ^ s)
@@ -188,7 +188,7 @@ fn eq_pattern_length<'tcx>(
{
constant_length(cx, pattern, ctxt).is_some_and(|length| n == length)
} else {
len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg))
len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, SyntaxContext::root(), pattern, arg))
}
}
@@ -1,4 +1,5 @@
use clippy_config::Conf;
use clippy_utils::SpanlessEq;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{MEM_TAKE, Msrv};
use clippy_utils::source::snippet_with_context;
@@ -74,10 +75,11 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
&& let StmtKind::Semi(assignment) = stmt.kind
&& let ExprKind::Assign(mut_c, possible_false, _) = assignment.kind
&& let ExprKind::Path(_) = mut_c.kind
&& !expr.span.in_external_macro(cx.sess().source_map())
&& let ctxt = expr.span.ctxt()
&& !ctxt.in_external_macro(cx.sess().source_map())
&& let Some(std_or_core) = clippy_utils::std_or_core(cx)
&& self.msrv.meets(cx, MEM_TAKE)
&& clippy_utils::SpanlessEq::new(cx).eq_expr(cond, mut_c)
&& SpanlessEq::new(cx).eq_expr(ctxt, cond, mut_c)
&& Some(false) == as_const_bool(possible_false)
&& let Some(then_bool) = as_const_bool(then_expr)
&& let Some(else_bool) = as_const_bool(else_expr)
@@ -14,12 +14,10 @@
use rustc_lint::LateContext;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty;
use rustc_span::symbol::Ident;
use rustc_span::{BytePos, Span};
use crate::collapsible_if::{parens_around, peel_parens};
use rustc_span::{BytePos, Ident, Span, SyntaxContext};
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
use crate::collapsible_if::{parens_around, peel_parens};
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
@@ -28,6 +26,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ar
let only_wildcards_after = last_non_wildcard.is_none_or(|lnw| idx >= lnw);
check_arm(
cx,
arm.span.ctxt(),
true,
arm.pat,
expr,
@@ -43,18 +42,20 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ar
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
pat: &'tcx Pat<'_>,
body: &'tcx Expr<'_>,
else_expr: Option<&'tcx Expr<'_>>,
let_expr: &'tcx Expr<'_>,
msrv: Msrv,
) {
check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv, false);
check_arm(cx, ctxt, false, pat, let_expr, body, None, else_expr, msrv, false);
}
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
fn check_arm<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
outer_is_match: bool,
outer_pat: &'tcx Pat<'tcx>,
outer_cond: &'tcx Expr<'tcx>,
@@ -94,7 +95,7 @@ fn check_arm<'tcx>(
&& match (outer_else_body, inner_else_body) {
(None, None) => true,
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(ctxt, a, b),
}
// the binding must not be used in the if guard
&& outer_guard.is_none_or(|e| !is_local_used(cx, e, binding_id))
@@ -145,7 +146,7 @@ fn check_arm<'tcx>(
&& 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),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(ctxt, a, b),
}
&& !pat_bindings_moved_or_mutated(cx, outer_pat, inner.cond)
{
@@ -13,7 +13,7 @@
use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::{self, TypeckResults};
use rustc_span::{ByteSymbol, ErrorGuaranteed, Span, Symbol};
use rustc_span::{ByteSymbol, ErrorGuaranteed, Span, Symbol, SyntaxContext};
use super::MATCH_SAME_ARMS;
@@ -87,7 +87,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
SpanlessEq::new(cx)
.expr_fallback(eq_fallback)
.eq_expr(expr_a, expr_b)
.eq_expr(SyntaxContext::root(), expr_a, expr_b)
// these checks could be removed to allow unused bindings
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
@@ -1138,6 +1138,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
collapsible_match::check_if_let(
cx,
if_let.let_span.ctxt(),
if_let.let_pat,
if_let.if_then,
if_let.if_else,
@@ -12,10 +12,10 @@
Arm, BindingMode, ByRef, Expr, ExprKind, ItemKind, Node, Pat, PatExpr, PatExprKind, PatKind, Path, QPath,
};
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_span::{SyntaxContext, sym};
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, expr.span.ctxt(), ex, arms) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
@@ -49,7 +49,10 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>],
/// }
/// ```
pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
if !is_else_clause(cx.tcx, ex)
&& expr_ty_matches_p_ty(cx, if_let.let_expr, ex)
&& check_if_let_inner(cx, ex.span.ctxt(), if_let)
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
@@ -63,7 +66,7 @@ pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let:
}
}
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
fn check_all_arms(cx: &LateContext<'_>, ctxt: SyntaxContext, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
for arm in arms {
let arm_expr = peel_blocks_with_stmt(arm.body);
@@ -74,7 +77,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
}
if let PatKind::Wild = arm.pat.kind {
if !eq_expr_value(cx, match_expr, arm_expr) {
if !eq_expr_value(cx, ctxt, match_expr, arm_expr) {
return false;
}
} else if !pat_same_as_expr(arm.pat, arm_expr) {
@@ -85,7 +88,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
true
}
fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
fn check_if_let_inner(cx: &LateContext<'_>, ctxt: SyntaxContext, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
return false;
@@ -93,9 +96,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
// Recursively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else)
&& SpanlessEq::new(cx).eq_expr(nested_if_let.let_expr, if_let.let_expr)
&& SpanlessEq::new(cx).eq_expr(ctxt, nested_if_let.let_expr, if_let.let_expr)
{
return check_if_let_inner(cx, nested_if_let);
return check_if_let_inner(cx, ctxt, nested_if_let);
}
if matches!(if_else.kind, ExprKind::Block(..)) {
@@ -106,9 +109,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
if let_expr_ty.is_diag_item(cx, sym::Option) {
return else_expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone)
|| eq_expr_value(cx, if_let.let_expr, else_expr);
|| eq_expr_value(cx, ctxt, if_let.let_expr, else_expr);
}
return eq_expr_value(cx, if_let.let_expr, else_expr);
return eq_expr_value(cx, ctxt, if_let.let_expr, else_expr);
}
}
@@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
// of the last replace call in the current chain, don't lint as it was already linted
if let Some(parent) = get_parent_expr(cx, expr)
&& let Some((sym::replace, _, [current_from, current_to], _, _)) = method_call(parent)
&& eq_expr_value(cx, to, current_to)
&& eq_expr_value(cx, parent.span.ctxt(), to, current_to)
&& from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
{
return;
@@ -46,9 +46,10 @@ fn collect_replace_calls<'tcx>(
let mut methods = VecDeque::new();
let mut from_args = VecDeque::new();
let ctxt = expr.span.ctxt();
let _: Option<()> = for_each_expr_without_closures(expr, |e| {
if let Some((sym::replace, _, [from, to], _, _)) = method_call(e) {
if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
if eq_expr_value(cx, ctxt, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
methods.push_front(e);
from_args.push_front(from);
ControlFlow::Continue(())
@@ -11,8 +11,8 @@
use rustc_lint::LateContext;
use rustc_middle::ty::TypeckResults;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::Span;
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::{Span, SyntaxContext};
use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP};
@@ -109,6 +109,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
pub fn check_map_call(
&self,
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
map_body: &'tcx Body<'tcx>,
map_param_id: HirId,
filter_param_id: HirId,
@@ -150,7 +151,7 @@ pub fn check_map_call(
&& a_typeck_results.expr_ty_adjusted(a) == b_typeck_results.expr_ty_adjusted(b)
})
&& (simple_equal
|| SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(receiver, map_arg_peeled))
|| SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(ctxt, receiver, map_arg_peeled))
{
Some(CheckResult::Method {
map_arg,
@@ -323,7 +324,9 @@ pub(super) fn check(
return;
}
if let Some((map_param_ident, check_result)) = is_find_or_filter(cx, map_recv, filter_arg, map_arg) {
if let Some((map_param_ident, check_result)) =
is_find_or_filter(cx, expr.span.ctxt(), map_recv, filter_arg, map_arg)
{
let span = filter_span.with_hi(expr.span.hi());
let (filter_name, lint) = if is_find {
("find", MANUAL_FIND_MAP)
@@ -397,6 +400,7 @@ pub(super) fn check(
fn is_find_or_filter<'a>(
cx: &LateContext<'a>,
ctxt: SyntaxContext,
map_recv: &Expr<'_>,
filter_arg: &Expr<'_>,
map_arg: &Expr<'_>,
@@ -422,7 +426,7 @@ fn is_find_or_filter<'a>(
&& let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind
&& let Some(check_result) =
offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref)
offending_expr.check_map_call(cx, ctxt, map_body, map_param_id, filter_param_id, is_filter_param_ref)
{
return Some((map_param_ident, check_result));
}
@@ -27,7 +27,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg:
&& is_integer_literal(rhs, 1)
// check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
&& SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), recv, lhs_recv)
&& !recv.can_have_side_effects()
{
let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
@@ -0,0 +1,30 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::MaybeDef;
use clippy_utils::{is_integer_literal, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, LangItem};
use rustc_lint::LateContext;
use rustc_span::Span;
use super::MANUAL_CLEAR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>, method_span: Span) {
let ty = cx.typeck_results().expr_ty_adjusted(recv);
let ty = ty.peel_refs();
let diag_name = ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did()));
if (matches!(diag_name, Some(sym::Vec | sym::VecDeque | sym::OsString)) || ty.is_lang_item(cx, LangItem::String))
&& is_integer_literal(arg, 0)
{
span_lint_and_then(cx, MANUAL_CLEAR, expr.span, "truncating to zero length", |diag| {
// Keep the receiver as-is and only rewrite the method.
diag.span_suggestion_verbose(
method_span.with_hi(expr.span.hi()),
"use `clear()` instead",
"clear()",
Applicability::MachineApplicable,
);
});
}
}
@@ -256,7 +256,7 @@ pub(super) fn check_or<'tcx>(
.expr_ty_adjusted(some_recv)
.peel_refs()
.is_diag_item(cx, sym::Option)
&& SpanlessEq::new(cx).eq_expr(none_recv, some_recv)
&& SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), none_recv, some_recv)
{
(some_recv, some_arg)
} else {
@@ -3,12 +3,11 @@
use clippy_utils::peel_blocks;
use clippy_utils::res::{MaybeDef, MaybeResPath};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::usage::local_used_in;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind, HirId, PatKind};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use std::ops::ControlFlow;
use super::MANUAL_OPTION_ZIP;
@@ -31,13 +30,7 @@ pub(super) fn check<'tcx>(
&& method_path.ident.name == sym::map
&& cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option)
// `b` does not reference the outer closure parameter `a`.
&& for_each_expr_without_closures(map_recv, |e| {
if e.res_local_id() == Some(outer_param_id) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}).is_none()
&& !local_used_in(cx, outer_param_id, map_recv)
// `|b| (a, b)`
&& let ExprKind::Closure(&hir::Closure { body: inner_body_id, .. }) = map_arg.kind
&& let hir::Body { params: [inner_param], value: inner_value, .. } = cx.tcx.hir_body(inner_body_id)
@@ -58,6 +58,7 @@
mod lib;
mod lines_filter_map_ok;
mod manual_c_str_literals;
mod manual_clear;
mod manual_contains;
mod manual_inspect;
mod manual_is_variant_and;
@@ -111,6 +112,7 @@
mod single_char_add_str;
mod skip_while_next;
mod sliced_string_as_bytes;
mod some_filter;
mod stable_sort_primitive;
mod str_split;
mod str_splitn;
@@ -1769,6 +1771,31 @@
r#"creating a `CStr` through functions when `c""` literals can be used"#
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `.truncate(0)` calls on standard library types where it can be replaced with `.clear()`.
///
/// Currently this includes `Vec`, `VecDeque`, `String`, and `OsString`.
///
/// ### Why is this bad?
/// `clear()` expresses the intent better and is likely to be more efficient than `truncate(0)`.
///
/// ### Example
/// ```no_run
/// let mut v = vec![1, 2, 3];
/// v.truncate(0);
/// ```
/// Use instead:
/// ```no_run
/// let mut v = vec![1, 2, 3];
/// v.clear();
/// ```
#[clippy::version = "1.97.0"]
pub MANUAL_CLEAR,
perf,
"using `truncate(0)` instead of `clear()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so.
@@ -3577,6 +3604,29 @@
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `Some(x).filter(|_| predicate)`.
///
/// ### Why is this bad?
/// Readability, this can be written more concisely as `predicate.then_some(x)`.
///
/// ### Example
/// ```no_run
/// let x = false;
/// Some(0).filter(|_| x);
/// ```
/// Use instead:
/// ```no_run
/// let x = false;
/// x.then_some(0);
/// ```
#[clippy::version = "1.97.0"]
pub SOME_FILTER,
complexity,
"using `Some(x).filter(|_| predicate)`, which is more succinctly expressed as `predicate.then(x)`"
}
declare_clippy_lint! {
/// ### What it does
/// When sorting primitive values (integers, bools, chars, as well
@@ -4839,6 +4889,7 @@
ITER_WITH_DRAIN,
JOIN_ABSOLUTE_PATHS,
LINES_FILTER_MAP_OK,
MANUAL_CLEAR,
MANUAL_CONTAINS,
MANUAL_C_STR_LITERALS,
MANUAL_FILTER_MAP,
@@ -4900,6 +4951,7 @@
SINGLE_CHAR_ADD_STR,
SKIP_WHILE_NEXT,
SLICED_STRING_AS_BYTES,
SOME_FILTER,
STABLE_SORT_PRIMITIVE,
STRING_EXTEND_CHARS,
STRING_LIT_CHARS_ANY,
@@ -5307,6 +5359,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// use the sourcemap to get the span of the closure
iter_filter::check(cx, expr, arg, span);
}
some_filter::check(cx, expr, recv, arg, self.msrv);
},
(sym::find, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
@@ -5808,6 +5861,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
(sym::to_string, []) => {
inefficient_to_string::check(cx, expr, recv, self.msrv);
},
(sym::truncate, [arg]) => {
manual_clear::check(cx, expr, recv, arg, method_span);
},
(sym::unwrap, []) => {
unwrap_expect_used::check(
cx,
@@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
return;
}
if SpanlessEq::new(cx).eq_expr(arg1, arg2) {
if SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), arg1, arg2) {
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
}
}
@@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'
// `.iter()` and `.len()` called on same `Path`
&& let ExprKind::Path(QPath::Resolved(_, iter_path)) = recv.kind
&& let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind
&& SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments)
&& SpanlessEq::new(cx).eq_path_segments(expr.span.ctxt(), iter_path.segments, len_path.segments)
{
span_lint_and_then(
cx,
@@ -0,0 +1,65 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::{as_some_expr, pat_is_wild, peel_blocks, span_contains_comment};
use rustc_ast::util::parser::ExprPrecedence;
use rustc_errors::Applicability;
use rustc_hir::{Body, Expr, ExprKind};
use rustc_lint::LateContext;
use super::SOME_FILTER;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
msrv: Msrv,
) {
let (condition, value) = if let Some(value) = as_some_expr(cx, recv)
&& let ExprKind::Closure(c) = arg.kind
&& let Body {
params: [p],
value: condition,
} = cx.tcx.hir_body(c.body)
&& pat_is_wild(cx, &p.pat.kind, arg)
&& msrv.meets(cx, msrvs::BOOL_THEN_SOME)
{
(condition, value)
} else {
return;
};
span_lint_and_then(
cx,
SOME_FILTER,
expr.span,
"use of `Some(x).filter(|_| predicate)`",
|diag| {
let condition = if span_contains_comment(cx, condition.span) {
condition
} else {
peel_blocks(condition)
};
let mut applicability = Applicability::MaybeIncorrect;
let (condition_text, condition_is_macro) =
snippet_with_context(cx, condition.span, arg.span.ctxt(), "_", &mut applicability);
let parentheses = !condition_is_macro && cx.precedence(condition) < ExprPrecedence::Unambiguous;
let value_text = snippet_with_applicability(cx, value.span, "_", &mut applicability);
let sugg = format!(
"{}{condition_text}{}.then_some({value_text})",
if parentheses { "(" } else { "" },
if parentheses { ")" } else { "" },
);
diag.span_suggestion_verbose(
expr.span,
"consider using `bool::then_some` instead",
sugg,
applicability,
);
diag.note(
"this change will alter the order in which the condition and \
the value are evaluated",
);
},
);
}
@@ -66,7 +66,7 @@ fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Ex
for_each_expr_without_closures(block, |e| {
match e.kind {
ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => {
change |= !can_mut_borrow_both(cx, caller, assignee);
change |= !can_mut_borrow_both(cx, body.span.ctxt(), caller, assignee);
},
_ => {},
}
+3 -1
View File
@@ -238,7 +238,9 @@ fn used_underscore_binding<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
/// of what it means for an expression to be "used".
fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
get_parent_expr(cx, expr).is_none_or(|parent| match parent.kind {
ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => {
SpanlessEq::new(cx).eq_expr(parent.span.ctxt(), rhs, expr)
},
_ => is_used(cx, parent),
})
}
@@ -245,7 +245,10 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni
let hash = hash_expr(cx, slice);
let indexes = map.entry(hash).or_default();
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
let ctxt = expr.span.ctxt();
let entry = indexes
.iter_mut()
.find(|entry| eq_expr_value(cx, ctxt, entry.slice(), slice));
if let Some(entry) = entry {
match entry {
@@ -305,7 +308,10 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
let hash = hash_expr(cx, slice);
let indexes = map.entry(hash).or_default();
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
let ctxt = expr.span.ctxt();
let entry = indexes
.iter_mut()
.find(|entry| eq_expr_value(cx, ctxt, entry.slice(), slice));
if let Some(entry) = entry {
if let IndexEntry::IndexWithoutAssert {
@@ -10,6 +10,7 @@
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@@ -169,7 +170,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
}
if let Some((lhs_a, a)) = fetch_assign(then)
&& let Some((lhs_b, b)) = fetch_assign(else_expr)
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), lhs_a, lhs_b)
{
let mut applicability = Applicability::MachineApplicable;
let cond = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "..", &mut applicability);
@@ -1,10 +1,11 @@
use super::implicit_return::IMPLICIT_RETURN;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::res::{MaybeDef, MaybeQPath};
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_from_proc_macro, last_path_segment, std_or_core};
use clippy_utils::{is_from_proc_macro, is_lint_allowed, last_path_segment, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Stmt, StmtKind, UnOp};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{TyCtxt, TypeckResults};
use rustc_session::impl_lint_pass;
@@ -185,8 +186,8 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let Some(copy_trait) = self.copy_trait
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
{
for (assoc, _, block) in assoc_fns {
check_clone_on_copy(cx, assoc, block);
for (assoc, body, _) in assoc_fns {
check_clone_on_copy(cx, assoc, body.value);
}
}
},
@@ -208,29 +209,34 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
}
}
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) {
if impl_item.ident.name == sym::clone {
if block.stmts.is_empty()
&& let Some(expr) = block.expr
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
&& let ExprKind::Path(qpath) = deref.kind
&& last_path_segment(&qpath).ident.name == kw::SelfLower
{
// this is the canonical implementation, `fn clone(&self) -> Self { *self }`
return;
}
fn is_deref_self(expr: &Expr<'_>) -> bool {
if let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
&& let ExprKind::Path(qpath) = deref.kind
&& last_path_segment(&qpath).ident.name == kw::SelfLower
{
return true;
}
false
}
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, body_expr: &Expr<'_>) {
if impl_item.ident.name == sym::clone {
if is_from_proc_macro(cx, impl_item) {
return;
}
let add_return = match is_canonical_clone_body(body_expr) {
IsCanonical::WithReturn if is_lint_allowed(cx, IMPLICIT_RETURN, body_expr.hir_id) => false,
IsCanonical::WithReturn | IsCanonical::WithoutReturn => return,
IsCanonical::No => !is_lint_allowed(cx, IMPLICIT_RETURN, body_expr.hir_id),
};
span_lint_and_sugg(
cx,
NON_CANONICAL_CLONE_IMPL,
block.span,
body_expr.span,
"non-canonical implementation of `clone` on a `Copy` type",
"change this to",
"{ *self }".to_owned(),
if add_return { "{ return *self; }" } else { "{ *self }" }.to_owned(),
Applicability::MaybeIncorrect,
);
}
@@ -248,6 +254,40 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
}
}
enum IsCanonical {
WithoutReturn,
WithReturn,
No,
}
fn is_canonical_clone_body(body_expr: &Expr<'_>) -> IsCanonical {
let ExprKind::Block(block, ..) = body_expr.kind else {
return IsCanonical::No;
};
let single_expr = match (block.stmts, block.expr) {
([], Some(expr)) => Some(expr),
(
[
Stmt {
kind: StmtKind::Expr(expr) | StmtKind::Semi(expr),
..
},
],
None,
) => Some(*expr),
_ => None,
};
let Some(expr) = single_expr else {
return IsCanonical::No;
};
match expr.kind {
ExprKind::Ret(Some(ret)) if is_deref_self(ret) => IsCanonical::WithReturn,
_ if is_deref_self(expr) => IsCanonical::WithoutReturn,
_ => IsCanonical::No,
}
}
fn check_partial_ord_on_ord<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &ImplItem<'_>,
@@ -269,7 +309,7 @@ fn check_partial_ord_on_ord<'tcx>(
// Fix #12683, allow [`needless_return`] here
else if block.expr.is_none()
&& let Some(stmt) = block.stmts.first()
&& let rustc_hir::StmtKind::Semi(Expr {
&& let StmtKind::Semi(Expr {
kind: ExprKind::Ret(Some(ret)),
..
}) = stmt.kind
@@ -89,9 +89,10 @@ pub(super) fn check<'tcx>(
}
};
let ctxt = expr.span.ctxt();
let mut found = false;
let found_multiple = for_each_expr_without_closures(e, |e| {
if eq_expr_value(cx, assignee, e) {
if eq_expr_value(cx, ctxt, assignee, e) {
if found {
return ControlFlow::Break(());
}
@@ -103,12 +104,12 @@ pub(super) fn check<'tcx>(
if found && !found_multiple {
// a = a op b
if eq_expr_value(cx, assignee, l) {
if eq_expr_value(cx, ctxt, assignee, l) {
lint(assignee, r);
}
// a = b commutative_op a
// Limited to primitive type as these ops are know to be commutative
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
if eq_expr_value(cx, ctxt, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
match op.node {
hir::BinOpKind::Add
| hir::BinOpKind::Mul
@@ -63,7 +63,7 @@ pub(super) fn check<'tcx>(
&& left_type == right_type
// Check that the same expression is compared in both comparisons
&& SpanlessEq::new(cx).eq_expr(left_expr, right_expr)
&& SpanlessEq::new(cx).eq_expr(span.ctxt(), left_expr, right_expr)
&& !left_expr.can_have_side_effects()
@@ -11,8 +11,9 @@
pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) {
if let ExprKind::Binary(lop, llhs, lrhs) = lhs.kind
&& let ExprKind::Binary(rop, rlhs, rrhs) = rhs.kind
&& eq_expr_value(cx, llhs, rlhs)
&& eq_expr_value(cx, lrhs, rrhs)
&& let ctxt = span.ctxt()
&& eq_expr_value(cx, ctxt, llhs, rlhs)
&& eq_expr_value(cx, ctxt, lrhs, rrhs)
{
let op = match (op, lop.node, rop.node) {
// x == y || x < y => x <= y
@@ -14,7 +14,7 @@ pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
Some(sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
)
}) && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
&& eq_expr_value(cx, lhs, rhs)
&& eq_expr_value(cx, macro_call.span.ctxt(), lhs, rhs)
&& macro_call.is_local()
&& !is_in_test_function(cx.tcx, e.hir_id)
{
@@ -37,7 +37,10 @@ pub(crate) fn check<'tcx>(
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if is_useless_with_eq_exprs(op) && eq_expr_value(cx, left, right) && !is_in_test_function(cx.tcx, e.hir_id) {
if is_useless_with_eq_exprs(op)
&& eq_expr_value(cx, e.span.ctxt(), left, right)
&& !is_in_test_function(cx.tcx, e.hir_id)
{
span_lint_and_then(
cx,
EQ_OP,
@@ -21,6 +21,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
&& check_int_ty_and_feature(cx, cx.typeck_results().expr_ty(rhs))
&& msrv.meets(cx, msrvs::DIV_CEIL)
{
let ctxt = expr.span.ctxt();
match lhs.kind {
ExprKind::Binary(inner_op, inner_lhs, inner_rhs) => {
// (x + (y - 1)) / y
@@ -28,7 +29,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
&& inner_op.node == BinOpKind::Add
&& sub_op.node == BinOpKind::Sub
&& check_literal(sub_rhs)
&& check_eq_expr(cx, sub_lhs, rhs)
&& SpanlessEq::new(cx).eq_expr(ctxt, sub_lhs, rhs)
{
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
return;
@@ -39,7 +40,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
&& inner_op.node == BinOpKind::Add
&& sub_op.node == BinOpKind::Sub
&& check_literal(sub_rhs)
&& check_eq_expr(cx, sub_lhs, rhs)
&& SpanlessEq::new(cx).eq_expr(ctxt, sub_lhs, rhs)
{
build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability);
return;
@@ -50,7 +51,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
&& inner_op.node == BinOpKind::Sub
&& add_op.node == BinOpKind::Add
&& check_literal(inner_rhs)
&& check_eq_expr(cx, add_rhs, rhs)
&& SpanlessEq::new(cx).eq_expr(ctxt, add_rhs, rhs)
{
build_suggestion(cx, expr, add_lhs, rhs, &mut applicability);
}
@@ -76,7 +77,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
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) =>
&& SpanlessEq::new(cx).eq_expr(ctxt, next_multiple_of_arg, rhs) =>
{
// x.next_multiple_of(Y) / Y
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
@@ -88,7 +89,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &
.assoc_fn_parent(cx)
.opt_impl_ty(cx)
&& check_int_ty(impl_ty_binder.skip_binder())
&& check_eq_expr(cx, next_multiple_of_arg, rhs)
&& SpanlessEq::new(cx).eq_expr(ctxt, next_multiple_of_arg, rhs)
{
build_suggestion(cx, expr, receiver, rhs, &mut applicability);
}
@@ -137,10 +138,6 @@ fn check_literal(expr: &Expr<'_>) -> bool {
false
}
fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
SpanlessEq::new(cx).eq_expr(lhs, rhs)
}
fn build_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
@@ -19,9 +19,10 @@ pub(super) fn check<'tcx>(
return;
}
// lhs op= l op r
if eq_expr_value(cx, lhs, l) {
let ctxt = expr.span.ctxt();
if eq_expr_value(cx, ctxt, lhs, l) {
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, r);
} else if is_commutative(op) && eq_expr_value(cx, lhs, r) {
} else if is_commutative(op) && eq_expr_value(cx, ctxt, lhs, r) {
// lhs op= l commutative_op r
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, l);
}
@@ -7,7 +7,7 @@
use super::SELF_ASSIGNMENT;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>) {
if eq_expr_value(cx, lhs, rhs) {
if eq_expr_value(cx, e.span.ctxt(), lhs, rhs) {
let lhs = snippet(cx, lhs.span, "<lhs>");
let rhs = snippet(cx, rhs.span, "<rhs>");
span_lint(
@@ -72,7 +72,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& ty == typeck.expr_ty(op_rhs)
&& ty == typeck.expr_ty(other)
&& !expr.span.in_external_macro(cx.tcx.sess.source_map())
&& (eq_expr_value(cx, op_lhs, other) || (commutative && eq_expr_value(cx, op_rhs, other)))
&& (eq_expr_value(cx, ctxt, op_lhs, other) || (commutative && eq_expr_value(cx, ctxt, op_rhs, other)))
{
span_lint(
cx,
@@ -299,7 +299,7 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
let by_ref = !cx.type_is_copy_modulo_regions(caller_ty)
&& !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
let sugg = if let Some(else_inner) = r#else {
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
if eq_expr_value(cx, expr.span.ctxt(), caller, peel_blocks(else_inner)) {
format!("Some({receiver_str}?)")
} else {
return;
@@ -537,7 +537,7 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
&& let is_option_early_return = is_early_return(sym::Option, cx, &if_block)
&& (is_option_early_return || is_early_return(sym::Result, cx, &if_block))
&& if_else
.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e)))
.map(|e| eq_expr_value(cx, expr.span.ctxt(), let_expr, peel_blocks(e)))
.is_none_or(|e| !e)
{
if !is_copy(cx, caller_ty)
@@ -3,7 +3,7 @@
use clippy_utils::{is_from_proc_macro, is_inside_let_else};
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::{ExprKind, HirId, ItemKind, MatchSource, Node, OwnerNode, Stmt, StmtKind};
use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::adjustment::Adjust;
@@ -23,10 +23,8 @@ pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
&& maybe_constr.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr)
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id))
&& let ItemKind::Fn { body, .. } = item.kind
&& let block = cx.tcx.hir_body(body).value
&& let ExprKind::Block(block, _) = block.kind
&& let block = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)).value
&& let ExprKind::Block(block, _) = peel_async_body(block).kind
&& !is_inside_let_else(cx.tcx, expr)
&& let [.., final_stmt] = block.stmts
&& final_stmt.hir_id != stmt.hir_id
@@ -45,6 +43,21 @@ pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
}
}
/// In async functions, the body is wrapped in a
/// `Block { expr: DropTemps(inner) }`. We peel through to `inner` so
/// we can check the actual stmts.
/// Returns `body_value` unchanged for non-async functions.
fn peel_async_body<'a>(body_value: &'a Expr<'a>) -> &'a Expr<'a> {
if let ExprKind::Block(block, _) = body_value.kind
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(inner) = expr.kind
{
inner
} else {
body_value
}
}
/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed.
/// This is the case when the enclosing block expression is coerced to some other type,
/// which only works because of the never-ness of `return` expressions
@@ -79,7 +79,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind
&& fn_path.ident.name == sym::from_raw_parts
&& args.len() >= 3
&& eq_expr_value(cx, &args[1], &args[2])
&& eq_expr_value(cx, expr.span.ctxt(), &args[1], &args[2])
{
let middle_ty = cx.typeck_results().node_type(ty.hir_id);
if middle_ty.is_diag_item(cx, rustc_sym::Vec) {
@@ -7,8 +7,8 @@
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::Symbol;
use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@@ -119,7 +119,7 @@ fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, ex
cx.typeck_results().expr_ty(expr).peel_refs().opt_diag_name(cx),
Some(sym::HashSet | sym::BTreeSet)
)
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, expr.peel_borrows())
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.receiver, expr.peel_borrows())
}
fn find_insert_calls<'tcx>(
@@ -129,8 +129,8 @@ fn find_insert_calls<'tcx>(
) -> Option<OpExpr<'tcx>> {
for_each_expr(cx, expr, |e| {
if let Some((insert_expr, _)) = try_parse_op_call(cx, e, sym::insert)
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.receiver, insert_expr.receiver)
&& SpanlessEq::new(cx).eq_expr(SyntaxContext::root(), contains_expr.value, insert_expr.value)
{
return ControlFlow::Break(Some(insert_expr));
}
@@ -8,6 +8,7 @@
use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@@ -265,7 +266,7 @@ fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) {
{
let is_matching_resize = if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
// If we have a size expression, check that it is equal to what's passed to `resize`
SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
SpanlessEq::new(self.cx).eq_expr(SyntaxContext::root(), len_arg, size_expr)
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.name == sym::capacity)
} else {
self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg);
@@ -287,7 +288,7 @@ fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
{
if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
// Check that len expression is equals to `with_capacity` expression
return SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
return SpanlessEq::new(self.cx).eq_expr(SyntaxContext::root(), len_arg, size_expr)
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.name == sym::capacity);
}
+8 -7
View File
@@ -11,7 +11,7 @@
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::Spanned;
use rustc_span::{Spanned, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@@ -220,7 +220,8 @@
impl<'tcx> LateLintPass<'tcx> for StringAdd {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.in_external_macro(cx.sess().source_map()) {
let ctxt = e.span.ctxt();
if ctxt.in_external_macro(cx.sess().source_map()) {
return;
}
match e.kind {
@@ -235,8 +236,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
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)
// avoid duplicate matches
&& SpanlessEq::new(cx).eq_expr(ctxt, target, left)
{
return;
}
@@ -248,7 +249,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
"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) => {
ExprKind::Assign(target, src, _) if is_string(cx, target) && is_add(cx, ctxt, src, target) => {
span_lint(
cx,
STRING_ADD_ASSIGN,
@@ -280,7 +281,7 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
.is_lang_item(cx, LangItem::String)
}
fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
fn is_add(cx: &LateContext<'_>, ctxt: SyntaxContext, src: &Expr<'_>, target: &Expr<'_>) -> bool {
match peel_blocks(src).kind {
ExprKind::Binary(
Spanned {
@@ -288,7 +289,7 @@ fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
},
left,
_,
) => SpanlessEq::new(cx).eq_expr(target, left),
) => SpanlessEq::new(cx).eq_expr(ctxt, target, left),
_ => false,
}
}
+17 -15
View File
@@ -98,12 +98,12 @@ fn generate_swap_warning<'tcx>(
let ctxt = span.ctxt();
let mut applicability = Applicability::MachineApplicable;
if !can_mut_borrow_both(cx, e1, e2) {
if !can_mut_borrow_both(cx, ctxt, e1, e2) {
if let ExprKind::Index(lhs1, idx1, _) = e1.kind
&& let ExprKind::Index(lhs2, idx2, _) = e2.kind
&& eq_expr_value(cx, lhs1, lhs2)
&& e1.span.ctxt() == ctxt
&& e2.span.ctxt() == ctxt
&& eq_expr_value(cx, ctxt, lhs1, lhs2)
{
let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
@@ -189,14 +189,15 @@ fn check_manual_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
&& rhs2_path.segments.len() == 1
&& ident.name == rhs2_path.segments[0].ident.name
&& eq_expr_value(cx, tmp_init, lhs1)
&& eq_expr_value(cx, rhs1, lhs2)
&& let ctxt = s1.span.ctxt()
&& s2.span.ctxt() == ctxt
&& s3.span.ctxt() == ctxt
&& first.span.ctxt() == ctxt
&& second.span.ctxt() == ctxt
&& eq_expr_value(cx, ctxt, tmp_init, lhs1)
&& eq_expr_value(cx, ctxt, rhs1, lhs2)
{
let span = s1.span.to(s3.span);
generate_swap_warning(block, cx, lhs1, lhs2, rhs1, rhs2, span, false);
@@ -209,11 +210,12 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
for [first, second] in block.stmts.array_windows() {
if let Some((lhs0, rhs0)) = parse(first)
&& let Some((lhs1, rhs1)) = parse(second)
&& first.span.eq_ctxt(second.span)
&& !first.span.in_external_macro(cx.sess().source_map())
&& is_same(cx, lhs0, rhs1)
&& is_same(cx, lhs1, rhs0)
&& !is_same(cx, lhs1, rhs1) // Ignore a = b; a = a (#10421)
&& let ctxt = first.span.ctxt()
&& ctxt == second.span.ctxt()
&& !ctxt.in_external_macro(cx.sess().source_map())
&& is_same(cx, ctxt, lhs0, rhs1)
&& is_same(cx, ctxt, lhs1, rhs0)
&& !is_same(cx, ctxt, lhs1, rhs1) // Ignore a = b; a = a (#10421)
&& let Some(lhs_sugg) = match &lhs0 {
ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr),
ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())),
@@ -241,9 +243,9 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
}
}
fn is_same(cx: &LateContext<'_>, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool {
fn is_same(cx: &LateContext<'_>, ctxt: SyntaxContext, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool {
match lhs {
ExprOrIdent::Expr(expr) => eq_expr_value(cx, expr, rhs),
ExprOrIdent::Expr(expr) => eq_expr_value(cx, ctxt, expr, rhs),
ExprOrIdent::Ident(ident) => {
if let ExprKind::Path(QPath::Resolved(None, path)) = rhs.kind
&& let [segment] = &path.segments
@@ -284,10 +286,10 @@ fn check_xor_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt)
&& let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt)
&& let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt)
&& eq_expr_value(cx, lhs0, rhs1)
&& eq_expr_value(cx, lhs2, rhs1)
&& eq_expr_value(cx, lhs1, rhs0)
&& eq_expr_value(cx, lhs1, rhs2)
&& eq_expr_value(cx, ctxt, lhs0, rhs1)
&& eq_expr_value(cx, ctxt, lhs2, rhs1)
&& eq_expr_value(cx, ctxt, lhs1, rhs0)
&& eq_expr_value(cx, ctxt, lhs1, rhs2)
&& s2.span.ctxt() == ctxt
&& s3.span.ctxt() == ctxt
{
@@ -15,7 +15,7 @@
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@@ -157,9 +157,11 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tc
.filter_map(get_trait_info_from_bound)
.for_each(|(trait_item_res, trait_item_segments, span)| {
if let Some(self_segments) = self_bounds_map.get(&trait_item_res)
&& SpanlessEq::new(cx)
.paths_by_resolution()
.eq_path_segments(self_segments, trait_item_segments)
&& SpanlessEq::new(cx).paths_by_resolution().eq_path_segments(
SyntaxContext::root(),
self_segments,
trait_item_segments,
)
{
span_lint_and_help(
cx,
@@ -252,7 +254,7 @@ struct SpanlessTy<'cx, 'tcx> {
impl PartialEq for SpanlessTy<'_, '_> {
fn eq(&self, other: &Self) -> bool {
let mut eq = SpanlessEq::new(self.cx);
eq.inter_expr().eq_ty(self.ty, other.ty)
eq.inter_expr(SyntaxContext::root()).eq_ty(self.ty, other.ty)
}
}
impl Hash for SpanlessTy<'_, '_> {
@@ -382,9 +384,11 @@ struct ComparableTraitRef<'a, 'tcx> {
impl PartialEq for ComparableTraitRef<'_, '_> {
fn eq(&self, other: &Self) -> bool {
SpanlessEq::eq_modifiers(self.modifiers, other.modifiers)
&& SpanlessEq::new(self.cx)
.paths_by_resolution()
.eq_path(self.trait_ref.path, other.trait_ref.path)
&& SpanlessEq::new(self.cx).paths_by_resolution().eq_path(
SyntaxContext::root(),
self.trait_ref.path,
other.trait_ref.path,
)
}
}
impl Eq for ComparableTraitRef<'_, '_> {}
@@ -5,6 +5,7 @@
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::SyntaxContext;
use super::EAGER_TRANSMUTE;
@@ -54,9 +55,9 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
lang_items.range_to_struct()
].into_iter().any(|did| did == Some(receiver_adt.did())) =>
{
eq_expr_value(cx, local_expr, arg.peel_borrows())
eq_expr_value(cx, SyntaxContext::root(), local_expr, arg.peel_borrows())
},
_ => eq_expr_value(cx, local_expr, expr),
_ => eq_expr_value(cx, SyntaxContext::root(), local_expr, expr),
}
}
+19 -10
View File
@@ -7,7 +7,7 @@
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
declare_clippy_lint! {
@@ -64,15 +64,23 @@
// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
impl<'tcx> LateLintPass<'tcx> for UninitVec {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
if !block.span.in_external_macro(cx.tcx.sess.source_map()) {
for w in block.stmts.windows(2) {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
handle_uninit_vec_pair(cx, &w[0], expr);
let ctxt = block.span.ctxt();
if !ctxt.in_external_macro(cx.tcx.sess.source_map()) {
for [stmt1, stmt2] in block.stmts.array_windows::<2>() {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt2.kind
&& stmt1.span.ctxt() == ctxt
&& stmt2.span.ctxt() == ctxt
&& expr.span.ctxt() == ctxt
{
handle_uninit_vec_pair(cx, ctxt, stmt1, expr);
}
}
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
handle_uninit_vec_pair(cx, stmt, expr);
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr)
&& stmt.span.ctxt() == ctxt
&& expr.span.ctxt() == ctxt
{
handle_uninit_vec_pair(cx, ctxt, stmt, expr);
}
}
}
@@ -80,12 +88,13 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
fn handle_uninit_vec_pair<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
maybe_set_len: &'tcx Expr<'tcx>,
) {
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve)
&& let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len)
&& vec.location.eq_expr(cx, set_len_self)
&& vec.location.eq_expr(cx, ctxt, set_len_self)
&& let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind()
&& let ty::Adt(_, args) = vec_ty.kind()
// `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
@@ -138,10 +147,10 @@ enum VecLocation<'tcx> {
}
impl<'tcx> VecLocation<'tcx> {
pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
pub fn eq_expr(self, cx: &LateContext<'tcx>, ctxt: SyntaxContext, expr: &'tcx Expr<'tcx>) -> bool {
match self {
VecLocation::Local(hir_id) => expr.res_local_id() == Some(hir_id),
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(ctxt, self_expr, expr),
}
}
}
@@ -89,10 +89,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& let ExprKind::Path(..) = recv.kind
{
let mut app = Applicability::MachineApplicable;
let expr_ctxt = expr.span.ctxt();
let ctxt = expr.span.ctxt();
let and_then_snippets = get_and_then_snippets(
cx,
expr_ctxt,
ctxt,
call_cx.span,
call_lint.span,
call_sp.span,
@@ -101,28 +101,24 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match ps.ident.name {
sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => {
let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app);
sym::span_suggestion if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => {
let snippets = span_suggestion_snippets(cx, ctxt, span_call_args, &mut app);
suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app);
},
sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
sym::span_help if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => {
let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app);
},
sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
sym::span_note if sle.eq_expr(ctxt, call_sp, &span_call_args[0]) => {
let note_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app);
},
sym::help => {
let help_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
let help_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app);
},
sym::note => {
let note_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
let note_snippet = snippet_with_context(cx, span_call_args[0].span, ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app);
},
_ => (),
@@ -140,7 +136,7 @@ struct AndThenSnippets {
fn get_and_then_snippets(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
ctxt: SyntaxContext,
cx_span: Span,
lint_span: Span,
span_span: Span,
@@ -150,7 +146,7 @@ fn get_and_then_snippets(
let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app);
let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app);
let span_snippet = snippet_with_applicability(cx, span_span, "span", app);
let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0;
let msg_snippet = snippet_with_context(cx, msg_span, ctxt, r#""...""#, app).0;
AndThenSnippets {
cx: cx_snippet,
@@ -168,12 +164,12 @@ struct SpanSuggestionSnippets {
fn span_suggestion_snippets<'hir>(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
ctxt: SyntaxContext,
span_call_args: &'hir [Expr<'hir>],
app: &mut Applicability,
) -> SpanSuggestionSnippets {
let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0;
let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0;
let help_snippet = snippet_with_context(cx, span_call_args[1].span, ctxt, r#""...""#, app).0;
let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, ctxt, "..", app).0;
let applicability_snippet =
snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app);
@@ -152,6 +152,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
)
{
let lint_span = stmt1_span.to(stmt2_span);
let ctxt = lint_span.ctxt();
// if recv1.is_diag_item(cx, sym1) && .. {
// ..
@@ -161,8 +162,8 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
// }
if let Some(first @ (span1, (cx1, recv1, _))) = extract_nested_is_diag_item(cx, cond1)
&& let Some(second @ (span2, (cx2, recv2, _))) = extract_nested_is_diag_item(cx, cond2)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
&& eq_expr_value(cx, ctxt, cx1, cx2)
&& eq_expr_value(cx, ctxt, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
@@ -208,8 +209,8 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
// }
if let Some(first @ (span1, (tcx1, did1, _))) = extract_nested_is_diagnostic_item(cx, cond1)
&& let Some(second @ (span2, (tcx2, did2, _))) = extract_nested_is_diagnostic_item(cx, cond2)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
&& eq_expr_value(cx, ctxt, tcx1, tcx2)
&& eq_expr_value(cx, ctxt, did1, did2)
{
span_lint_and_then(
cx,
@@ -264,11 +265,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
}
fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) {
let ctxt = span.ctxt();
// recv1.is_diag_item(cx, sym1) || recv2.is_diag_item(cx, sym2)
if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left)
&& let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
&& eq_expr_value(cx, ctxt, cx1, cx2)
&& eq_expr_value(cx, ctxt, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
@@ -300,8 +303,8 @@ fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>
// cx.tcx.is_diagnostic_item(sym1, did) || cx.tcx.is_diagnostic_item(sym2, did)
if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left)
&& let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
&& eq_expr_value(cx, ctxt, tcx1, tcx2)
&& eq_expr_value(cx, ctxt, did1, did2)
{
span_lint_and_then(
cx,
@@ -328,11 +331,13 @@ fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>
}
fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) {
let ctxt = span.ctxt();
// !recv1.is_diag_item(cx, sym1) && !recv2.is_diag_item(cx, sym2)
if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left)
&& let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
&& eq_expr_value(cx, ctxt, cx1, cx2)
&& eq_expr_value(cx, ctxt, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
@@ -364,8 +369,8 @@ fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_
// !cx.tcx.is_diagnostic_item(sym1, did) && !cx.tcx.is_diagnostic_item(sym2, did)
if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left)
&& let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
&& eq_expr_value(cx, ctxt, tcx1, tcx2)
&& eq_expr_value(cx, ctxt, did1, did2)
{
span_lint_and_then(
cx,
@@ -400,9 +405,10 @@ fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'t
// ..
// }
let mut found = conds.iter().filter_map(|cond| extract_nested_is_diag_item(cx, cond));
let ctxt = expr.span.ctxt();
if let Some(first @ (_, (cx_1, recv1, _))) = found.next()
&& let other =
found.filter(|(_, (cx_, recv, _))| eq_expr_value(cx, cx_, cx_1) && eq_expr_value(cx, recv, recv1))
&& let other = found
.filter(|(_, (cx_, recv, _))| eq_expr_value(cx, ctxt, cx_, cx_1) && eq_expr_value(cx, ctxt, recv, recv1))
&& let results = iter::once(first).chain(other).collect::<Vec<_>>()
&& results.len() > 1
{
@@ -457,7 +463,8 @@ fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'t
.into_iter()
.filter_map(|cond| extract_nested_is_diagnostic_item(cx, cond));
if let Some(first @ (_, (tcx1, did1, _))) = found.next()
&& let other = found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, tcx, tcx1) && eq_expr_value(cx, did, did1))
&& let other =
found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, ctxt, tcx, tcx1) && eq_expr_value(cx, ctxt, did, did1))
&& let results = iter::once(first).chain(other).collect::<Vec<_>>()
&& results.len() > 1
{
+25 -83
View File
@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
@@ -34,9 +34,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "backtrace"
version = "0.3.75"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
@@ -44,7 +44,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
"windows-link",
]
[[package]]
@@ -177,9 +177,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.31.1"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "io-uring"
@@ -203,9 +203,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "lock_api"
@@ -234,9 +234,9 @@ dependencies = [
[[package]]
name = "mio"
version = "1.0.4"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
@@ -245,9 +245,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.7"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
@@ -264,15 +264,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.11"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-link",
]
[[package]]
@@ -425,75 +425,17 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-targets",
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+1 -1
View File
@@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2026-04-30
nightly-2026-05-13
```
<!-- end autogenerated nightly -->
+183 -115
View File
@@ -2,6 +2,7 @@
use crate::macros::macro_backtrace;
use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context};
use crate::{sym, tokenize_with_text};
use core::mem;
use rustc_ast::ast;
use rustc_ast::ast::InlineAsmTemplatePiece;
use rustc_data_structures::fx::{FxHasher, FxIndexMap};
@@ -107,46 +108,57 @@ pub fn expr_fallback(
/// Use this method to wrap comparisons that may involve inter-expression context.
/// See `self.locals`.
pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
pub fn inter_expr(&mut self, ctxt: SyntaxContext) -> HirEqInterExpr<'_, 'a, 'tcx> {
HirEqInterExpr {
inner: self,
left_ctxt: SyntaxContext::root(),
right_ctxt: SyntaxContext::root(),
eval_ctxt: ctxt,
prev_left_ctxt: ctxt,
prev_right_ctxt: ctxt,
locals: HirIdMap::default(),
local_items: FxIndexMap::default(),
}
}
pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
self.inter_expr().eq_block(left, right)
pub fn eq_block(&mut self, ctxt: SyntaxContext, left: &Block<'_>, right: &Block<'_>) -> bool {
self.inter_expr(ctxt).eq_block(left, right)
}
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
self.inter_expr().eq_expr(left, right)
pub fn eq_expr(&mut self, ctxt: SyntaxContext, left: &Expr<'_>, right: &Expr<'_>) -> bool {
self.inter_expr(ctxt).eq_expr(left, right)
}
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
self.inter_expr().eq_path(left, right)
pub fn eq_path(&mut self, ctxt: SyntaxContext, left: &Path<'_>, right: &Path<'_>) -> bool {
self.inter_expr(ctxt).eq_path(left, right)
}
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
self.inter_expr().eq_path_segment(left, right)
pub fn eq_path_segment(&mut self, ctxt: SyntaxContext, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
self.inter_expr(ctxt).eq_path_segment(left, right)
}
pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
self.inter_expr().eq_path_segments(left, right)
pub fn eq_path_segments(
&mut self,
ctxt: SyntaxContext,
left: &[PathSegment<'_>],
right: &[PathSegment<'_>],
) -> bool {
self.inter_expr(ctxt).eq_path_segments(left, right)
}
pub fn eq_modifiers(left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool {
std::mem::discriminant(&left.constness) == std::mem::discriminant(&right.constness)
&& std::mem::discriminant(&left.polarity) == std::mem::discriminant(&right.polarity)
mem::discriminant(&left.constness) == mem::discriminant(&right.constness)
&& mem::discriminant(&left.polarity) == mem::discriminant(&right.polarity)
}
}
pub struct HirEqInterExpr<'a, 'b, 'tcx> {
inner: &'a mut SpanlessEq<'b, 'tcx>,
left_ctxt: SyntaxContext,
right_ctxt: SyntaxContext,
/// The root context to view each side from.
eval_ctxt: SyntaxContext,
// Optimization to avoid rechecking the context of desugarings.
prev_left_ctxt: SyntaxContext,
prev_right_ctxt: SyntaxContext,
// When binding are declared, the binding ID in the left expression is mapped to the one on the
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
@@ -156,7 +168,17 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> {
}
impl HirEqInterExpr<'_, '_, '_> {
pub fn set_eval_ctxt(&mut self, ctxt: SyntaxContext) {
self.eval_ctxt = ctxt;
self.prev_left_ctxt = ctxt;
self.prev_right_ctxt = ctxt;
}
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
if self.check_ctxt(left.span.ctxt(), right.span.ctxt()) == Some(false) {
return false;
}
match (&left.kind, &right.kind) {
(StmtKind::Let(l), StmtKind::Let(r)) => {
// This additional check ensures that the type of the locals are equivalent even if the init
@@ -372,15 +394,16 @@ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
}
let lspan = left.span.data();
let rspan = right.span.data();
if lspan.ctxt != SyntaxContext::root() && rspan.ctxt != SyntaxContext::root() {
// Don't try to check in between statements inside macros.
return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right))
&& both(left.expr.as_ref(), right.expr.as_ref(), |left, right| {
self.eq_expr(left, right)
});
}
if lspan.ctxt != rspan.ctxt {
return false;
match self.check_ctxt(lspan.ctxt, rspan.ctxt) {
Some(false) => return false,
None if self.eval_ctxt.is_root() => {},
_ => {
// Don't try to check in between statements inside macros.
return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right))
&& both(left.expr.as_ref(), right.expr.as_ref(), |left, right| {
self.eq_expr(left, right)
});
},
}
let mut lstart = lspan.lo;
@@ -475,26 +498,28 @@ pub fn eq_body(&mut self, left: BodyId, right: BodyId) -> bool {
#[expect(clippy::too_many_lines)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
return false;
}
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results
&& typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right)
&& let (Some(l), Some(r)) = (
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs)
.eval_local(left, self.left_ctxt),
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs)
.eval_local(right, self.right_ctxt),
)
&& l == r
{
return true;
match self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
None => {
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results
&& typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right)
&& let (Some(l), Some(r)) = (
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs)
.eval_local(left, self.eval_ctxt),
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs)
.eval_local(right, self.eval_ctxt),
)
&& l == r
{
return true;
}
},
Some(false) => return false,
Some(true) => {},
}
let is_eq = match (
reduce_exprkind(self.inner.cx, &left.kind),
reduce_exprkind(self.inner.cx, &right.kind),
reduce_exprkind(self.inner.cx, self.eval_ctxt, &left.kind),
reduce_exprkind(self.inner.cx, self.eval_ctxt, &right.kind),
) {
(ExprKind::AddrOf(lb, l_mut, le), ExprKind::AddrOf(rb, r_mut, re)) => {
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
@@ -542,7 +567,12 @@ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
&& both(l.ty.as_ref(), r.ty.as_ref(), |l, r| self.eq_ty(l, r))
&& self.eq_expr(l.init, r.init)
},
(ExprKind::Lit(l), ExprKind::Lit(r)) => l.node == r.node,
(ExprKind::Lit(l), ExprKind::Lit(r)) => {
if self.check_ctxt(l.span.ctxt(), r.span.ctxt()) == Some(false) {
return false;
}
l.node == r.node
},
(ExprKind::Loop(lb, ll, lls, _), ExprKind::Loop(rb, rl, rls, _)) => {
lls == rls && self.eq_block(lb, rb)
&& both(ll.as_ref(), rl.as_ref(), |l, r| l.ident.name == r.ident.name)
@@ -673,7 +703,7 @@ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> b
}
fn eq_const_arg(&mut self, left: &ConstArg<'_>, right: &ConstArg<'_>) -> bool {
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
if self.check_ctxt(left.span.ctxt(), right.span.ctxt()) == Some(false) {
return false;
}
@@ -892,46 +922,72 @@ fn eq_assoc_eq_constraint(&mut self, left: &AssocItemConstraint<'_>, right: &Ass
|| both_some_and(left.ct(), right.ct(), |l, r| self.eq_const_arg(l, r)))
}
fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool {
if self.left_ctxt == left && self.right_ctxt == right {
return true;
} else if self.left_ctxt == left || self.right_ctxt == right {
// Only one context has changed. This can only happen if the two nodes are written differently.
return false;
} else if left != SyntaxContext::root() {
/// Checks whether either operand is within a macro context, and if so, whether the macro calls
/// are equal.
fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> Option<bool> {
let prev_left = mem::replace(&mut self.prev_left_ctxt, left);
let prev_right = mem::replace(&mut self.prev_right_ctxt, right);
if left == self.eval_ctxt && right == self.eval_ctxt {
None
} else if left == prev_left && right == prev_right {
// Same as the previous context, no need to recheck anything
Some(true)
} else if left == prev_left
|| right == prev_right
|| left == self.eval_ctxt
|| right == self.eval_ctxt
|| left.is_root()
|| right.is_root()
{
// Either only one context changed, or at least one context is a parent of the
// evaluation context.
// Unfortunately we can't get a span of a metavariable so we have to treat the
// second case as unequal.
Some(false)
} else {
// Walk each context in lockstep up to the evaluation context checking that each
// expansion has the same kind.
let mut left_data = left.outer_expn_data();
let mut right_data = right.outer_expn_data();
loop {
use TokenKind::{BlockComment, LineComment, Whitespace};
if left_data.macro_def_id != right_data.macro_def_id
|| (matches!(
left_data.kind,
ExpnKind::Macro(MacroKind::Bang, name)
if name == sym::cfg || name == sym::option_env
) && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| {
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. })
}))
if left_data.macro_def_id != right_data.macro_def_id || left_data.kind != right_data.kind {
return Some(false);
}
let left = left_data.call_site.ctxt();
let right = right_data.call_site.ctxt();
if left == self.eval_ctxt && right == self.eval_ctxt {
// Finally if the outermost expansion is a macro call, check if the
// tokens are the same.
if let ExpnKind::Macro(MacroKind::Bang, _) = left_data.kind {
return Some(eq_span_tokens(
self.inner.cx,
left_data.call_site,
right_data.call_site,
|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }),
));
}
return Some(true);
}
if left == prev_left && right == prev_right {
return Some(true);
}
if left == prev_left
|| right == prev_right
|| left == self.eval_ctxt
|| right == self.eval_ctxt
|| left.is_root()
|| right.is_root()
{
// Either a different chain of macro calls, or different arguments to the `cfg` macro.
return false;
// Either there's a different number of expansions, or at least one context is
// a parent of the evaluation context.
return Some(false);
}
let left_ctxt = left_data.call_site.ctxt();
let right_ctxt = right_data.call_site.ctxt();
if left_ctxt == SyntaxContext::root() && right_ctxt == SyntaxContext::root() {
break;
}
if left_ctxt == SyntaxContext::root() || right_ctxt == SyntaxContext::root() {
// Different lengths for the expansion stack. This can only happen if nodes are written differently,
// or shouldn't be compared to start with.
return false;
}
left_data = left_ctxt.outer_expn_data();
right_data = right_ctxt.outer_expn_data();
left_data = left.outer_expn_data();
right_data = right.outer_expn_data();
}
}
self.left_ctxt = left;
self.right_ctxt = right;
true
}
fn swap_binop<'a>(
@@ -970,7 +1026,11 @@ fn swap_binop<'a>(
}
/// Some simple reductions like `{ return }` => `return`
fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> {
fn reduce_exprkind<'hir>(
cx: &LateContext<'_>,
eval_ctxt: SyntaxContext,
kind: &'hir ExprKind<'hir>,
) -> &'hir ExprKind<'hir> {
if let ExprKind::Block(block, _) = kind {
match (block.stmts, block.expr) {
// From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty
@@ -978,17 +1038,20 @@ fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'
([], None) if block.span.is_empty() => &ExprKind::Tup(&[]),
// `{}` => `()`
([], None)
if block.span.check_source_text(cx, |src| {
tokenize(src, FrontmatterAllowed::No)
.map(|t| t.kind)
.filter(|t| {
!matches!(
t,
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
)
})
.eq([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied())
}) =>
if block.span.ctxt() != eval_ctxt
|| block.span.check_source_text(cx, |src| {
tokenize(src, FrontmatterAllowed::No)
.map(|t| t.kind)
.filter(|t| {
!matches!(
t,
TokenKind::LineComment { .. }
| TokenKind::BlockComment { .. }
| TokenKind::Whitespace
)
})
.eq([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied())
}) =>
{
&ExprKind::Tup(&[])
},
@@ -1039,8 +1102,13 @@ pub fn count_eq<X: Sized>(
}
/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
///
/// The context argument is the context used to view the two expressions. e.g. when comparing the
/// two arguments in `f(m!(1), m!(2))` the context of the call expression should be used. This is
/// needed to handle the case where two macros expand to the same thing, but the arguments are
/// different.
pub fn eq_expr_value(cx: &LateContext<'_>, ctxt: SyntaxContext, left: &Expr<'_>, right: &Expr<'_>) -> bool {
SpanlessEq::new(cx).deny_side_effects().eq_expr(ctxt, left, right)
}
/// Returns the segments of a path that might have generic parameters.
@@ -1104,7 +1172,7 @@ pub fn hash_block(&mut self, b: &Block<'_>) {
self.hash_expr(e);
}
std::mem::discriminant(&b.rules).hash(&mut self.s);
mem::discriminant(&b.rules).hash(&mut self.s);
}
#[expect(clippy::too_many_lines)]
@@ -1120,11 +1188,11 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
return;
}
std::mem::discriminant(&e.kind).hash(&mut self.s);
mem::discriminant(&e.kind).hash(&mut self.s);
match &e.kind {
ExprKind::AddrOf(kind, m, e) => {
std::mem::discriminant(kind).hash(&mut self.s);
mem::discriminant(kind).hash(&mut self.s);
m.hash(&mut self.s);
self.hash_expr(e);
},
@@ -1141,7 +1209,7 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
self.hash_expr(r);
},
ExprKind::AssignOp(o, l, r) => {
std::mem::discriminant(&o.node).hash(&mut self.s);
mem::discriminant(&o.node).hash(&mut self.s);
self.hash_expr(l);
self.hash_expr(r);
},
@@ -1152,7 +1220,7 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
self.hash_block(b);
},
ExprKind::Binary(op, l, r) => {
std::mem::discriminant(&op.node).hash(&mut self.s);
mem::discriminant(&op.node).hash(&mut self.s);
self.hash_expr(l);
self.hash_expr(r);
},
@@ -1175,7 +1243,7 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
ExprKind::Closure(Closure {
capture_clause, body, ..
}) => {
std::mem::discriminant(capture_clause).hash(&mut self.s);
mem::discriminant(capture_clause).hash(&mut self.s);
// closures inherit TypeckResults
self.hash_expr(self.cx.tcx.hir_body(*body).value);
},
@@ -1326,11 +1394,11 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
self.hash_expr(expr);
},
ExprKind::Unary(l_op, le) => {
std::mem::discriminant(l_op).hash(&mut self.s);
mem::discriminant(l_op).hash(&mut self.s);
self.hash_expr(le);
},
ExprKind::UnsafeBinderCast(kind, expr, ty) => {
std::mem::discriminant(kind).hash(&mut self.s);
mem::discriminant(kind).hash(&mut self.s);
self.hash_expr(expr);
if let Some(ty) = ty {
self.hash_ty(ty);
@@ -1363,7 +1431,7 @@ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
}
pub fn hash_pat_expr(&mut self, lit: &PatExpr<'_>) {
std::mem::discriminant(&lit.kind).hash(&mut self.s);
mem::discriminant(&lit.kind).hash(&mut self.s);
match &lit.kind {
PatExprKind::Lit { lit, negated } => {
lit.node.hash(&mut self.s);
@@ -1374,7 +1442,7 @@ pub fn hash_pat_expr(&mut self, lit: &PatExpr<'_>) {
}
pub fn hash_ty_pat(&mut self, pat: &TyPat<'_>) {
std::mem::discriminant(&pat.kind).hash(&mut self.s);
mem::discriminant(&pat.kind).hash(&mut self.s);
match pat.kind {
TyPatKind::Range(s, e) => {
self.hash_const_arg(s);
@@ -1390,16 +1458,16 @@ pub fn hash_ty_pat(&mut self, pat: &TyPat<'_>) {
}
pub fn hash_pat(&mut self, pat: &Pat<'_>) {
std::mem::discriminant(&pat.kind).hash(&mut self.s);
mem::discriminant(&pat.kind).hash(&mut self.s);
match &pat.kind {
PatKind::Missing => unreachable!(),
PatKind::Binding(BindingMode(by_ref, mutability), _, _, pat) => {
std::mem::discriminant(by_ref).hash(&mut self.s);
mem::discriminant(by_ref).hash(&mut self.s);
if let ByRef::Yes(pi, mu) = by_ref {
std::mem::discriminant(pi).hash(&mut self.s);
std::mem::discriminant(mu).hash(&mut self.s);
mem::discriminant(pi).hash(&mut self.s);
mem::discriminant(mu).hash(&mut self.s);
}
std::mem::discriminant(mutability).hash(&mut self.s);
mem::discriminant(mutability).hash(&mut self.s);
if let Some(pat) = pat {
self.hash_pat(pat);
}
@@ -1418,12 +1486,12 @@ pub fn hash_pat(&mut self, pat: &Pat<'_>) {
if let Some(e) = e {
self.hash_pat_expr(e);
}
std::mem::discriminant(i).hash(&mut self.s);
mem::discriminant(i).hash(&mut self.s);
},
PatKind::Ref(pat, pi, mu) => {
self.hash_pat(pat);
std::mem::discriminant(pi).hash(&mut self.s);
std::mem::discriminant(mu).hash(&mut self.s);
mem::discriminant(pi).hash(&mut self.s);
mem::discriminant(mu).hash(&mut self.s);
},
PatKind::Guard(pat, guard) => {
self.hash_pat(pat);
@@ -1492,12 +1560,12 @@ pub fn hash_path(&mut self, path: &Path<'_>) {
pub fn hash_modifiers(&mut self, modifiers: TraitBoundModifiers) {
let TraitBoundModifiers { constness, polarity } = modifiers;
std::mem::discriminant(&polarity).hash(&mut self.s);
std::mem::discriminant(&constness).hash(&mut self.s);
mem::discriminant(&polarity).hash(&mut self.s);
mem::discriminant(&constness).hash(&mut self.s);
}
pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
std::mem::discriminant(&b.kind).hash(&mut self.s);
mem::discriminant(&b.kind).hash(&mut self.s);
match &b.kind {
StmtKind::Let(local) => {
@@ -1518,14 +1586,14 @@ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
lifetime.ident.name.hash(&mut self.s);
std::mem::discriminant(&lifetime.kind).hash(&mut self.s);
mem::discriminant(&lifetime.kind).hash(&mut self.s);
if let LifetimeKind::Param(param_id) = lifetime.kind {
param_id.hash(&mut self.s);
}
}
pub fn hash_ty(&mut self, ty: &Ty<'_>) {
std::mem::discriminant(&ty.kind).hash(&mut self.s);
mem::discriminant(&ty.kind).hash(&mut self.s);
self.hash_tykind(&ty.kind);
}
@@ -1564,7 +1632,7 @@ pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
for arg in fn_ptr.decl.inputs {
self.hash_ty(arg);
}
std::mem::discriminant(&fn_ptr.decl.output).hash(&mut self.s);
mem::discriminant(&fn_ptr.decl.output).hash(&mut self.s);
match fn_ptr.decl.output {
FnRetTy::DefaultReturn(_) => {},
FnRetTy::Return(ty) => {
+16 -19
View File
@@ -469,19 +469,23 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, owner: OwnerId) -> Opti
/// this method will return a tuple, composed of a `Vec`
/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
/// and an `Expr` for root of them, `v`
fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
fn projection_stack<'a, 'hir>(
mut e: &'a Expr<'hir>,
ctxt: SyntaxContext,
) -> Option<(Vec<&'a Expr<'hir>>, &'a Expr<'hir>)> {
let mut result = vec![];
let root = loop {
match e.kind {
ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => {
ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) if e.span.ctxt() == ctxt => {
result.push(e);
e = ep;
},
ExprKind::Index(..) | ExprKind::Field(..) => return None,
_ => break e,
}
};
result.reverse();
(result, root)
Some((result, root))
}
/// Gets the mutability of the custom deref adjustment, if any.
@@ -499,10 +503,14 @@ pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Optio
/// Checks if two expressions can be mutably borrowed simultaneously
/// and they aren't dependent on borrowing same thing twice
pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
let (s1, r1) = projection_stack(e1);
let (s2, r2) = projection_stack(e2);
if !eq_expr_value(cx, r1, r2) {
pub fn can_mut_borrow_both(cx: &LateContext<'_>, ctxt: SyntaxContext, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
let Some((s1, r1)) = projection_stack(e1, ctxt) else {
return false;
};
let Some((s2, r2)) = projection_stack(e2, ctxt) else {
return false;
};
if !eq_expr_value(cx, ctxt, r1, r2) {
return true;
}
if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() {
@@ -520,11 +528,6 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
return true;
}
},
(ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => {
if !eq_expr_value(cx, i1, i2) {
return false;
}
},
_ => return false,
}
}
@@ -1744,13 +1747,7 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool
pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir_parent_owner_iter(id)
.filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
.any(|(id, _)| {
find_attr!(
tcx,
id.def_id,
AutomaticallyDerived
)
})
.any(|(id, _)| find_attr!(tcx, id.def_id, AutomaticallyDerived))
}
/// Checks if the given `DefId` matches the `libc` item.
@@ -134,10 +134,11 @@ fn check_rvalue<'tcx>(
) -> McfResult {
match rvalue {
Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::Reborrow(_, _, place) | Rvalue::RawPtr(_, place) => {
check_place(cx, *place, span, body, msrv)
},
Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv),
Rvalue::Discriminant(place)
| Rvalue::Ref(_, _, place)
| Rvalue::Reborrow(_, _, place)
| Rvalue::RawPtr(_, place)
| Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv),
Rvalue::Repeat(operand, _)
| Rvalue::Use(operand, _)
| Rvalue::WrapUnsafeBinder(operand, _)
+1 -1
View File
@@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2026-04-30"
channel = "nightly-2026-05-13"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"
+57 -5
View File
@@ -89,11 +89,33 @@ fn internal_extern_flags() -> Vec<String> {
help: Try adding to dev-dependencies in Cargo.toml\n\
help: Be sure to also add `extern crate ...;` to tests/compile-test.rs",
);
crates
let mut args: Vec<String> = crates
.into_iter()
.map(|(name, path)| format!("--extern={name}={path}"))
.chain([format!("-Ldependency={}", deps_path.display())])
.collect()
.collect();
if deps_path.ends_with("deps") {
args.push(format!("-Ldependency={}", deps_path.display()));
} else {
// If the dep_path does not point to `/deps` it very likely means Cargo is using the v2 build-dir
// layout
assert!(deps_path.ends_with("out"));
// Get a path to `target/<platform-profile>/build`
let build_dir = {
let mut d = deps_path.to_path_buf();
d.pop(); // remove `out`
d.pop(); // remove `<hash>`
d.pop(); // remove `<pkgname>`
d
};
let out_dirs = discover_out_dirs(&build_dir);
args.extend(out_dirs.iter().map(|path| format!("-Ldependency={}", path.display())));
}
args
}
// whether to run internal tests or not
@@ -214,8 +236,21 @@ fn base_config(&self, test_dir: &str, mandatory_annotations: bool) -> Config {
config.program.envs.push(("RUSTC_ICE".into(), Some("0".into())));
if let Some(host_libs) = option_env!("HOST_LIBS") {
let dep = format!("-Ldependency={}", Path::new(host_libs).join("deps").display());
config.program.args.push(dep.into());
let deps_dir = Path::new(host_libs).join("deps");
if deps_dir.exists() {
let dep = format!("-Ldependency={}", deps_dir.display());
config.program.args.push(dep.into());
} else {
// If `/deps` does not exist, assume Cargo v2 build-dir layout
let build_dir = Path::new(host_libs).join("build");
let dependencies = discover_out_dirs(&build_dir);
for dep in dependencies {
let dep = format!("-Ldependency={}", dep.display());
config.program.args.push(dep.into());
}
}
}
if let Some(sysroot) = option_env!("TEST_SYSROOT") {
config.program.args.push(format!("--sysroot={sysroot}").into());
@@ -648,3 +683,20 @@ fn applicability_str(&self) -> &str {
}
}
}
/// Gets all of the `out` dirs in a given Cargo `build-dir/<profile>/build` dir.
fn discover_out_dirs(dir: &Path) -> Vec<PathBuf> {
if !dir.exists() {
return Vec::new();
}
let read_dir = |path: &Path| path.read_dir().ok().into_iter().flatten().filter_map(Result::ok);
dir.read_dir()
.unwrap_or_else(|e| panic!("Couldn't read {}: {}", dir.display(), e))
.map(|e| e.unwrap())
.flat_map(|e| read_dir(&e.path()))
.flat_map(|e| read_dir(&e.path()))
.map(|e| e.path())
.filter(|path| path.ends_with("out"))
.collect::<Vec<_>>()
}
@@ -0,0 +1,94 @@
#![warn(clippy::inline_trait_bounds)]
// Free functions
fn inline_simple<T>() where T: Clone {}
//~^ inline_trait_bounds
fn inline_multiple<T, U>() where T: Clone + Copy, U: core::fmt::Debug {}
//~^ inline_trait_bounds
fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b {
//~^ inline_trait_bounds
y
}
#[allow(clippy::multiple_bound_locations)]
fn inline_with_where<T>()
//~^ inline_trait_bounds
where
T: core::fmt::Debug, T: Clone,
{
}
fn inline_with_const<T, const N: usize>() where T: Clone {}
//~^ inline_trait_bounds
fn inline_with_return<T>(val: T) -> T where T: Clone {
//~^ inline_trait_bounds
val
}
// Trait methods
trait MyTrait {
fn trait_method_inline<T>(&self) where T: Clone;
//~^ inline_trait_bounds
fn trait_method_default<T>(&self) where T: Clone + Copy {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone;
}
// Impl methods
struct MyStruct;
impl MyStruct {
fn impl_method_inline<T>(&self) where T: Clone {}
//~^ inline_trait_bounds
fn impl_method_multiple<T, U>(&self) where T: Clone, U: core::fmt::Debug {}
//~^ inline_trait_bounds
}
impl MyTrait for MyStruct {
fn trait_method_inline<T>(&self) where T: Clone {}
//~^ inline_trait_bounds
fn trait_method_default<T>(&self) where T: Clone + Copy {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone,
{
}
}
// Should NOT lint
fn where_only<T>()
where
T: Clone,
{
}
fn no_bounds<T, U>() {}
fn no_generics() {}
struct InlineStruct<T: Clone>(T);
enum InlineEnum<T: Clone> {
A(T),
}
#[allow(invalid_type_param_default)]
//~v inline_trait_bounds
fn with_default_value<T = u32>(x: T) -> T where T: Clone {
x
}
@@ -0,0 +1,94 @@
#![warn(clippy::inline_trait_bounds)]
// Free functions
fn inline_simple<T: Clone>() {}
//~^ inline_trait_bounds
fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
//~^ inline_trait_bounds
fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
//~^ inline_trait_bounds
y
}
#[allow(clippy::multiple_bound_locations)]
fn inline_with_where<T: Clone>()
//~^ inline_trait_bounds
where
T: core::fmt::Debug,
{
}
fn inline_with_const<T: Clone, const N: usize>() {}
//~^ inline_trait_bounds
fn inline_with_return<T: Clone>(val: T) -> T {
//~^ inline_trait_bounds
val
}
// Trait methods
trait MyTrait {
fn trait_method_inline<T: Clone>(&self);
//~^ inline_trait_bounds
fn trait_method_default<T: Clone + Copy>(&self) {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone;
}
// Impl methods
struct MyStruct;
impl MyStruct {
fn impl_method_inline<T: Clone>(&self) {}
//~^ inline_trait_bounds
fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
//~^ inline_trait_bounds
}
impl MyTrait for MyStruct {
fn trait_method_inline<T: Clone>(&self) {}
//~^ inline_trait_bounds
fn trait_method_default<T: Clone + Copy>(&self) {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone,
{
}
}
// Should NOT lint
fn where_only<T>()
where
T: Clone,
{
}
fn no_bounds<T, U>() {}
fn no_generics() {}
struct InlineStruct<T: Clone>(T);
enum InlineEnum<T: Clone> {
A(T),
}
#[allow(invalid_type_param_default)]
//~v inline_trait_bounds
fn with_default_value<T: Clone = u32>(x: T) -> T {
x
}
@@ -0,0 +1,162 @@
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:5:17
|
LL | fn inline_simple<T: Clone>() {}
| ^^^^^^^^^^
|
= note: `-D clippy::inline-trait-bounds` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::inline_trait_bounds)]`
help: move bounds to a `where` clause
|
LL - fn inline_simple<T: Clone>() {}
LL + fn inline_simple<T>() where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:8:19
|
LL | fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
LL + fn inline_multiple<T, U>() where T: Clone + Copy, U: core::fmt::Debug {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:11:19
|
LL | fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
| ^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
LL + fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b {
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:17:21
|
LL | fn inline_with_where<T: Clone>()
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL ~ fn inline_with_where<T>()
LL |
LL | where
LL ~ T: core::fmt::Debug, T: Clone,
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:24:21
|
LL | fn inline_with_const<T: Clone, const N: usize>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_with_const<T: Clone, const N: usize>() {}
LL + fn inline_with_const<T, const N: usize>() where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:27:22
|
LL | fn inline_with_return<T: Clone>(val: T) -> T {
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_with_return<T: Clone>(val: T) -> T {
LL + fn inline_with_return<T>(val: T) -> T where T: Clone {
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:35:27
|
LL | fn trait_method_inline<T: Clone>(&self);
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_inline<T: Clone>(&self);
LL + fn trait_method_inline<T>(&self) where T: Clone;
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:38:28
|
LL | fn trait_method_default<T: Clone + Copy>(&self) {}
| ^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_default<T: Clone + Copy>(&self) {}
LL + fn trait_method_default<T>(&self) where T: Clone + Copy {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:51:26
|
LL | fn impl_method_inline<T: Clone>(&self) {}
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn impl_method_inline<T: Clone>(&self) {}
LL + fn impl_method_inline<T>(&self) where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:54:28
|
LL | fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
LL + fn impl_method_multiple<T, U>(&self) where T: Clone, U: core::fmt::Debug {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:59:27
|
LL | fn trait_method_inline<T: Clone>(&self) {}
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_inline<T: Clone>(&self) {}
LL + fn trait_method_inline<T>(&self) where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:62:28
|
LL | fn trait_method_default<T: Clone + Copy>(&self) {}
| ^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_default<T: Clone + Copy>(&self) {}
LL + fn trait_method_default<T>(&self) where T: Clone + Copy {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:92:22
|
LL | fn with_default_value<T: Clone = u32>(x: T) -> T {
| ^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn with_default_value<T: Clone = u32>(x: T) -> T {
LL + fn with_default_value<T = u32>(x: T) -> T where T: Clone {
|
error: aborting due to 13 previous errors
@@ -0,0 +1,54 @@
#![feature(os_string_truncate)]
#![warn(clippy::manual_clear)]
use std::collections::VecDeque;
use std::ffi::OsString;
struct CustomTruncate(String);
impl CustomTruncate {
fn truncate(&mut self, len: usize) {
self.0.truncate(len);
}
fn clear(&mut self) {
self.0.clear();
}
}
fn main() {
let mut v = vec![1, 2, 3];
v.clear(); //~ manual_clear
let mut d: VecDeque<i32> = VecDeque::from([1, 2, 3]);
d.clear(); //~ manual_clear
// lint: macro receiver
macro_rules! get_vec {
($e:expr) => {
$e
};
}
get_vec!(v).clear(); //~ manual_clear
// no lint: other args
v.truncate(1);
// no lint: `0` from a different context
{
// `0` inside a block expression should not be changed into `clear()`
v.truncate({ 0 });
}
// lint: String
let mut s = String::from("abc");
s.clear(); //~ manual_clear
// lint: OsString
let mut os = OsString::from("abc");
os.clear(); //~ manual_clear
// no lint: custom type
let mut c = CustomTruncate(String::from("abc"));
c.truncate(0);
}
+54
View File
@@ -0,0 +1,54 @@
#![feature(os_string_truncate)]
#![warn(clippy::manual_clear)]
use std::collections::VecDeque;
use std::ffi::OsString;
struct CustomTruncate(String);
impl CustomTruncate {
fn truncate(&mut self, len: usize) {
self.0.truncate(len);
}
fn clear(&mut self) {
self.0.clear();
}
}
fn main() {
let mut v = vec![1, 2, 3];
v.truncate(0); //~ manual_clear
let mut d: VecDeque<i32> = VecDeque::from([1, 2, 3]);
d.truncate(0); //~ manual_clear
// lint: macro receiver
macro_rules! get_vec {
($e:expr) => {
$e
};
}
get_vec!(v).truncate(0); //~ manual_clear
// no lint: other args
v.truncate(1);
// no lint: `0` from a different context
{
// `0` inside a block expression should not be changed into `clear()`
v.truncate({ 0 });
}
// lint: String
let mut s = String::from("abc");
s.truncate(0); //~ manual_clear
// lint: OsString
let mut os = OsString::from("abc");
os.truncate(0); //~ manual_clear
// no lint: custom type
let mut c = CustomTruncate(String::from("abc"));
c.truncate(0);
}
@@ -0,0 +1,64 @@
error: truncating to zero length
--> tests/ui/manual_clear.rs:21:5
|
LL | v.truncate(0);
| ^^^^^^^^^^^^^
|
= note: `-D clippy::manual-clear` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_clear)]`
help: use `clear()` instead
|
LL - v.truncate(0);
LL + v.clear();
|
error: truncating to zero length
--> tests/ui/manual_clear.rs:24:5
|
LL | d.truncate(0);
| ^^^^^^^^^^^^^
|
help: use `clear()` instead
|
LL - d.truncate(0);
LL + d.clear();
|
error: truncating to zero length
--> tests/ui/manual_clear.rs:32:5
|
LL | get_vec!(v).truncate(0);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `clear()` instead
|
LL - get_vec!(v).truncate(0);
LL + get_vec!(v).clear();
|
error: truncating to zero length
--> tests/ui/manual_clear.rs:45:5
|
LL | s.truncate(0);
| ^^^^^^^^^^^^^
|
help: use `clear()` instead
|
LL - s.truncate(0);
LL + s.clear();
|
error: truncating to zero length
--> tests/ui/manual_clear.rs:49:5
|
LL | os.truncate(0);
| ^^^^^^^^^^^^^^
|
help: use `clear()` instead
|
LL - os.truncate(0);
LL + os.clear();
|
error: aborting due to 5 previous errors
@@ -2,6 +2,7 @@
#![allow(
unused_variables,
clippy::question_mark,
clippy::some_filter,
clippy::useless_vec,
clippy::nonminimal_bool
)]
@@ -2,6 +2,7 @@
#![allow(
unused_variables,
clippy::question_mark,
clippy::some_filter,
clippy::useless_vec,
clippy::nonminimal_bool
)]
+20 -20
View File
@@ -1,5 +1,5 @@
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:10:5
--> tests/ui/manual_filter.rs:11:5
|
LL | / match Some(0) {
LL | |
@@ -14,7 +14,7 @@ LL | | };
= help: to override `-D warnings` add `#[allow(clippy::manual_filter)]`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:22:5
--> tests/ui/manual_filter.rs:23:5
|
LL | / match Some(1) {
LL | |
@@ -26,7 +26,7 @@ LL | | };
| |_____^ help: try: `Some(1).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:34:5
--> tests/ui/manual_filter.rs:35:5
|
LL | / match Some(2) {
LL | |
@@ -38,7 +38,7 @@ LL | | };
| |_____^ help: try: `Some(2).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:46:5
--> tests/ui/manual_filter.rs:47:5
|
LL | / match Some(3) {
LL | |
@@ -50,7 +50,7 @@ LL | | };
| |_____^ help: try: `Some(3).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:59:5
--> tests/ui/manual_filter.rs:60:5
|
LL | / match y {
LL | |
@@ -62,7 +62,7 @@ LL | | };
| |_____^ help: try: `y.filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:72:5
--> tests/ui/manual_filter.rs:73:5
|
LL | / match Some(5) {
LL | |
@@ -74,7 +74,7 @@ LL | | };
| |_____^ help: try: `Some(5).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:84:5
--> tests/ui/manual_filter.rs:85:5
|
LL | / match Some(6) {
LL | |
@@ -86,7 +86,7 @@ LL | | };
| |_____^ help: try: `Some(6).as_ref().filter(|&x| x > &0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:97:5
--> tests/ui/manual_filter.rs:98:5
|
LL | / match Some(String::new()) {
LL | |
@@ -98,7 +98,7 @@ LL | | };
| |_____^ help: try: `Some(String::new()).filter(|x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:109:5
--> tests/ui/manual_filter.rs:110:5
|
LL | / if let Some(x) = Some(7) {
LL | |
@@ -109,7 +109,7 @@ LL | | };
| |_____^ help: try: `Some(7).filter(|&x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:116:5
--> tests/ui/manual_filter.rs:117:5
|
LL | / match &Some(8) {
LL | |
@@ -121,7 +121,7 @@ LL | | };
| |_____^ help: try: `Some(8).filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:128:5
--> tests/ui/manual_filter.rs:129:5
|
LL | / match Some(9) {
LL | |
@@ -133,7 +133,7 @@ LL | | };
| |_____^ help: try: `Some(9).filter(|&x| x > 10 && x < 100)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:155:5
--> tests/ui/manual_filter.rs:156:5
|
LL | / match Some(11) {
LL | |
@@ -153,7 +153,7 @@ LL ~ });
|
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:200:13
--> tests/ui/manual_filter.rs:201:13
|
LL | let _ = match Some(14) {
| _____________^
@@ -166,7 +166,7 @@ LL | | };
| |_____^ help: try: `Some(14).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:211:13
--> tests/ui/manual_filter.rs:212:13
|
LL | let _ = match Some(15) {
| _____________^
@@ -177,7 +177,7 @@ LL | | };
| |_____^ help: try: `Some(15).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:220:12
--> tests/ui/manual_filter.rs:221:12
|
LL | } else if let Some(x) = Some(16) {
| ____________^
@@ -190,31 +190,31 @@ LL | | };
| |_____^ help: try: `{ Some(16).filter(|&x| x % 2 == 0) }`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:303:9
--> tests/ui/manual_filter.rs:304:9
|
LL | opt.and_then(|x| if x == 0 { None } else { Some(x) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:307:9
--> tests/ui/manual_filter.rs:308:9
|
LL | opt.and_then(move |x| if x == y { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(move |&x| x == y)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:311:10
--> tests/ui/manual_filter.rs:312:10
|
LL | opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|s| s.len() > 2)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:317:9
--> tests/ui/manual_filter.rs:318:9
|
LL | opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:319:9
--> tests/ui/manual_filter.rs:320:9
|
LL | opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
@@ -116,3 +116,10 @@ impl NotOption {
self.0.and_then(f)
}
}
fn issue16968() {
let a = Some(1);
let opts = [1, 2];
let _ = a.and_then(|a| opts.into_iter().find(|b| *b == a).map(|b| (a, b)));
}
@@ -116,3 +116,10 @@ fn and_then<U>(self, f: impl FnOnce(i32) -> Option<U>) -> Option<U> {
self.0.and_then(f)
}
}
fn issue16968() {
let a = Some(1);
let opts = [1, 2];
let _ = a.and_then(|a| opts.into_iter().find(|b| *b == a).map(|b| (a, b)));
}
@@ -128,3 +128,29 @@ fn general_return() {
Ok(())
}
}
async fn async_fn() -> Result<(), ()> {
if true {
Err(())?;
//~^ needless_return_with_question_mark
}
Ok(())
}
async fn async_block() -> Result<(), ()> {
async {
if true {
Err(())?;
//~^ needless_return_with_question_mark
}
Ok(())
}
.await
}
async fn async_block_final_stmt() -> Result<(), ()> {
async {
return Err(())?;
}
.await
}
@@ -128,3 +128,29 @@ fn foo(ok: bool) -> Result<(), ()> {
Ok(())
}
}
async fn async_fn() -> Result<(), ()> {
if true {
return Err(())?;
//~^ needless_return_with_question_mark
}
Ok(())
}
async fn async_block() -> Result<(), ()> {
async {
if true {
return Err(())?;
//~^ needless_return_with_question_mark
}
Ok(())
}
.await
}
async fn async_block_final_stmt() -> Result<(), ()> {
async {
return Err(())?;
}
.await
}
@@ -13,5 +13,17 @@ error: unneeded `return` statement with `?` operator
LL | return Err(())?;
| ^^^^^^^ help: remove it
error: aborting due to 2 previous errors
error: unneeded `return` statement with `?` operator
--> tests/ui/needless_return_with_question_mark.rs:134:9
|
LL | return Err(())?;
| ^^^^^^^ help: remove it
error: unneeded `return` statement with `?` operator
--> tests/ui/needless_return_with_question_mark.rs:143:13
|
LL | return Err(())?;
| ^^^^^^^ help: remove it
error: aborting due to 4 previous errors
@@ -6,8 +6,6 @@
extern crate proc_macros;
use proc_macros::inline_macros;
// lint
struct A(u32);
impl Clone for A {
@@ -31,12 +29,10 @@ impl Clone for B {
impl Copy for B {}
// do not lint derived (clone's implementation is `*self` here anyway)
#[derive(Clone, Copy)]
struct C(u32);
// do not lint derived (fr this time)
struct D(u32);
#[automatically_derived]
@@ -54,7 +50,6 @@ impl Clone for D {
impl Copy for D {}
// do not lint if clone is not manually implemented
struct E(u32);
#[automatically_derived]
@@ -83,7 +78,6 @@ impl Clone for F {
}
// do not lint since copy has more restrictive bounds
#[derive(Eq, PartialEq)]
struct Uwu<A: Copy>(A);
@@ -104,7 +98,7 @@ impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}
mod issue12788 {
use proc_macros::{external, with_span};
// lint -- not an external macro
// lint non-external macro
inline!(
#[derive(Copy)]
pub struct A;
@@ -114,7 +108,7 @@ mod issue12788 {
}
);
// do not lint -- should skip external macros
// do not lint external macros
external!(
#[derive(Copy)]
pub struct B;
@@ -126,7 +120,7 @@ mod issue12788 {
}
);
// do not lint -- should skip proc macros
// do not lint proc macros
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct C;
@@ -142,3 +136,36 @@ mod issue12788 {
}
);
}
struct N(u32);
impl Clone for N {
fn clone(&self) -> Self { *self }
}
impl Copy for N {}
/// Test for corner cases with `implicit_return` enabled
mod with_implicit_return {
#![warn(clippy::implicit_return)]
#![allow(clippy::needless_return)]
// Don't lint `return *self` under `implicit_return`
struct G(u32);
impl Clone for G {
fn clone(&self) -> Self {
return *self;
}
}
impl Copy for G {}
struct H(u32);
impl Clone for H {
fn clone(&self) -> Self { return *self; }
}
impl Copy for H {}
}
@@ -6,8 +6,6 @@
extern crate proc_macros;
use proc_macros::inline_macros;
// lint
struct A(u32);
impl Clone for A {
@@ -38,12 +36,10 @@ fn clone(&self) -> Self {
impl Copy for B {}
// do not lint derived (clone's implementation is `*self` here anyway)
#[derive(Clone, Copy)]
struct C(u32);
// do not lint derived (fr this time)
struct D(u32);
#[automatically_derived]
@@ -61,7 +57,6 @@ fn clone_from(&mut self, source: &Self) {
impl Copy for D {}
// do not lint if clone is not manually implemented
struct E(u32);
#[automatically_derived]
@@ -97,7 +92,6 @@ fn clone_from(&mut self, source: &Self) {
}
// do not lint since copy has more restrictive bounds
#[derive(Eq, PartialEq)]
struct Uwu<A: Copy>(A);
@@ -118,7 +112,7 @@ impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}
mod issue12788 {
use proc_macros::{external, with_span};
// lint -- not an external macro
// lint non-external macro
inline!(
#[derive(Copy)]
pub struct A;
@@ -131,7 +125,7 @@ fn clone(&self) -> Self {
}
);
// do not lint -- should skip external macros
// do not lint external macros
external!(
#[derive(Copy)]
pub struct B;
@@ -143,7 +137,7 @@ fn clone(&self) -> Self {
}
);
// do not lint -- should skip proc macros
// do not lint proc macros
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct C;
@@ -159,3 +153,44 @@ fn clone(&self) -> Self {
}
);
}
struct N(u32);
impl Clone for N {
fn clone(&self) -> Self {
//~^ non_canonical_clone_impl
{ *self }
}
}
impl Copy for N {}
/// Test for corner cases with `implicit_return` enabled
mod with_implicit_return {
#![warn(clippy::implicit_return)]
#![allow(clippy::needless_return)]
// Don't lint `return *self` under `implicit_return`
struct G(u32);
impl Clone for G {
fn clone(&self) -> Self {
return *self;
}
}
impl Copy for G {}
struct H(u32);
impl Clone for H {
fn clone(&self) -> Self {
//~^ non_canonical_clone_impl
{
return *self;
}
}
}
impl Copy for H {}
}
@@ -1,5 +1,5 @@
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:14:29
--> tests/ui/non_canonical_clone_impl.rs:12:29
|
LL | fn clone(&self) -> Self {
| _____________________________^
@@ -12,7 +12,7 @@ LL | | }
= help: to override `-D warnings` add `#[allow(clippy::non_canonical_clone_impl)]`
error: unnecessary implementation of `clone_from` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:19:5
--> tests/ui/non_canonical_clone_impl.rs:17:5
|
LL | / fn clone_from(&mut self, source: &Self) {
LL | |
@@ -22,7 +22,7 @@ LL | | }
| |_____^ help: remove it
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:87:29
--> tests/ui/non_canonical_clone_impl.rs:82:29
|
LL | fn clone(&self) -> Self {
| _____________________________^
@@ -32,7 +32,7 @@ LL | | }
| |_____^ help: change this to: `{ *self }`
error: unnecessary implementation of `clone_from` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:92:5
--> tests/ui/non_canonical_clone_impl.rs:87:5
|
LL | / fn clone_from(&mut self, source: &Self) {
LL | |
@@ -42,7 +42,7 @@ LL | | }
| |_____^ help: remove it
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:127:37
--> tests/ui/non_canonical_clone_impl.rs:121:37
|
LL | fn clone(&self) -> Self {
| _____________________________________^
@@ -53,5 +53,27 @@ LL | | }
|
= note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 5 previous errors
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:160:29
|
LL | fn clone(&self) -> Self {
| _____________________________^
LL | |
LL | | { *self }
LL | | }
| |_____^ help: change this to: `{ *self }`
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:187:33
|
LL | fn clone(&self) -> Self {
| _________________________________^
LL | |
LL | | {
LL | | return *self;
LL | | }
LL | | }
| |_________^ help: change this to: `{ return *self; }`
error: aborting due to 7 previous errors
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)]
fn main() {
let _ = Some(Some(1)).flatten();
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)]
fn main() {
let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap);
@@ -0,0 +1,70 @@
#![warn(clippy::some_filter)]
#![allow(clippy::const_is_empty)]
macro_rules! unchanged {
($result:expr) => {
$result
};
}
macro_rules! condition {
($condition:expr) => {
$condition || false
};
}
#[clippy::msrv = "1.61"]
fn older() {
let _ = Some(0).filter(|_| false);
}
#[clippy::msrv = "1.62"]
fn newer() {
let _ = false.then_some(0);
//~^ some_filter
}
fn main() {
let _ = false.then_some(0);
//~^ some_filter
// The condition contains an operator. The program should add parentheses.
let _ = (1 == 0).then_some(0);
//~^ some_filter
let _ = match 0 {
//~^ some_filter
0 => false,
1 => true,
_ => true,
}.then_some(0);
// The argument to filter requires the value in the option. The program
// can't figure out how to change it. It should leave it alone for now.
let _ = Some(0).filter(|x| *x == 0);
// The expression is a macro argument. The program should change the macro
// argument. It should not expand the macro.
let _ = unchanged!(false.then_some(0));
//~^ some_filter
let _ = vec![false].is_empty().then_some(0);
//~^ some_filter
// The condition is a macro that expands to an expression containing an
// operator. The program should not add parentheses.
let _ = condition!(false).then_some(0);
//~^ some_filter
(1 == 0).then_some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
));
{
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
}.then_some(5);
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
));
}
+74
View File
@@ -0,0 +1,74 @@
#![warn(clippy::some_filter)]
#![allow(clippy::const_is_empty)]
macro_rules! unchanged {
($result:expr) => {
$result
};
}
macro_rules! condition {
($condition:expr) => {
$condition || false
};
}
#[clippy::msrv = "1.61"]
fn older() {
let _ = Some(0).filter(|_| false);
}
#[clippy::msrv = "1.62"]
fn newer() {
let _ = Some(0).filter(|_| false);
//~^ some_filter
}
fn main() {
let _ = Some(0).filter(|_| false);
//~^ some_filter
// The condition contains an operator. The program should add parentheses.
let _ = Some(0).filter(|_| 1 == 0);
//~^ some_filter
let _ = Some(0).filter(|_| match 0 {
//~^ some_filter
0 => false,
1 => true,
_ => true,
});
// The argument to filter requires the value in the option. The program
// can't figure out how to change it. It should leave it alone for now.
let _ = Some(0).filter(|x| *x == 0);
// The expression is a macro argument. The program should change the macro
// argument. It should not expand the macro.
let _ = unchanged!(Some(0).filter(|_| false));
//~^ some_filter
let _ = Some(0).filter(|_| vec![false].is_empty());
//~^ some_filter
// The condition is a macro that expands to an expression containing an
// operator. The program should not add parentheses.
let _ = Some(0).filter(|_| condition!(false));
//~^ some_filter
Some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
))
.filter(|_| 1 == 0);
Some(5).filter(|_| {
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
});
Some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
))
.filter(|_| {
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
});
}

Some files were not shown because too many files have changed in this diff Show More