From 203c2162e6df8a918f0309a588a57f9d0fa3af5e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 5 Apr 2026 13:20:03 +0200 Subject: [PATCH 01/36] Cc PR author + assignee when opening the FCP thread on Zulip See https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/Automatic.20creation.20of.20FCP.20Zulip.20topics/near/583586375 --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 8e7c36dac841..4ea2c1374ea1 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -73,6 +73,7 @@ This lint has been nominated for inclusion. message_on_add = [ """\ PR rust-clippy#{number} "{title}" has been nominated for lint inclusion. +Cc: {recipients} """, """\ /poll Approve lint inclusion of rust-clippy#{number}? From 14e481ddf65149f0ebc705da6fd82a315e51f7ef Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 29 Sep 2025 06:14:54 -0400 Subject: [PATCH 02/36] Handle different root comparison contexts in `SpanlessEq` --- clippy_lints/src/booleans.rs | 7 +- clippy_lints/src/checked_conversions.rs | 15 +- clippy_lints/src/comparison_chain.rs | 8 +- clippy_lints/src/entry.rs | 11 +- .../floating_point_arithmetic/custom_abs.rs | 40 ++- .../src/floating_point_arithmetic/hypot.rs | 5 +- .../floating_point_arithmetic/log_division.rs | 8 +- clippy_lints/src/if_let_mutex.rs | 9 +- clippy_lints/src/ifs/branches_sharing_code.rs | 12 +- clippy_lints/src/ifs/if_same_then_else.rs | 6 +- clippy_lints/src/ifs/ifs_same_cond.rs | 5 +- .../src/ifs/same_functions_in_if_cond.rs | 5 +- clippy_lints/src/implicit_saturating_add.rs | 2 +- clippy_lints/src/implicit_saturating_sub.rs | 20 +- .../src/loops/char_indices_as_byte_indices.rs | 4 +- .../src/loops/manual_while_let_some.rs | 2 +- clippy_lints/src/loops/needless_range_loop.rs | 5 +- clippy_lints/src/manual_abs_diff.rs | 11 +- clippy_lints/src/manual_checked_ops.rs | 9 +- clippy_lints/src/manual_clamp.rs | 37 ++- clippy_lints/src/manual_is_power_of_two.rs | 6 +- clippy_lints/src/manual_pop_if.rs | 8 +- clippy_lints/src/manual_retain.rs | 6 +- clippy_lints/src/manual_rotate.rs | 6 +- clippy_lints/src/manual_strip.rs | 2 +- clippy_lints/src/manual_take.rs | 6 +- clippy_lints/src/matches/collapsible_match.rs | 15 +- clippy_lints/src/matches/match_same_arms.rs | 4 +- clippy_lints/src/matches/mod.rs | 1 + clippy_lints/src/matches/needless_match.rs | 23 +- .../src/methods/collapsible_str_replace.rs | 5 +- clippy_lints/src/methods/filter_map.rs | 12 +- clippy_lints/src/methods/get_last_with_len.rs | 2 +- .../src/methods/manual_is_variant_and.rs | 2 +- clippy_lints/src/methods/no_effect_replace.rs | 2 +- .../src/methods/range_zip_with_len.rs | 2 +- .../src/methods/unnecessary_iter_cloned.rs | 2 +- clippy_lints/src/misc.rs | 4 +- .../src/missing_asserts_for_indexing.rs | 10 +- clippy_lints/src/needless_bool.rs | 3 +- .../src/operators/assign_op_pattern.rs | 7 +- .../src/operators/const_comparisons.rs | 2 +- .../src/operators/double_comparison.rs | 5 +- clippy_lints/src/operators/eq_op.rs | 7 +- clippy_lints/src/operators/manual_div_ceil.rs | 15 +- .../src/operators/misrefactored_assign_op.rs | 5 +- clippy_lints/src/operators/self_assignment.rs | 2 +- clippy_lints/src/panicking_overflow_checks.rs | 2 +- clippy_lints/src/question_mark.rs | 4 +- clippy_lints/src/same_length_and_capacity.rs | 2 +- clippy_lints/src/set_contains_or_insert.rs | 8 +- .../src/slow_vector_initialization.rs | 5 +- clippy_lints/src/strings.rs | 15 +- clippy_lints/src/swap.rs | 32 +- clippy_lints/src/trait_bounds.rs | 20 +- clippy_lints/src/transmute/eager_transmute.rs | 5 +- clippy_lints/src/uninit_vec.rs | 29 +- .../src/collapsible_span_lint_calls.rs | 34 +- .../src/repeated_is_diagnostic_item.rs | 37 ++- clippy_utils/src/hir_utils.rs | 298 +++++++++++------- clippy_utils/src/lib.rs | 27 +- 61 files changed, 540 insertions(+), 363 deletions(-) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index ed963dc3e90e..5446148b0f9d 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -297,8 +297,9 @@ fn negate(bin_op_kind: BinOpKind) -> Option { 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 { && 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)))); diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 5e9009c67197..c80930f0b443 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -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> { - if self.is_compatible(&other, cx) { + pub fn combine(self, other: Self, cx: &LateContext<'_>, ctxt: SyntaxContext) -> Option> { + 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 /// 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)) } diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index a2ddf3dad7a2..554f1ab18685 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -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; diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index f700cb677343..a24cc957df07 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -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(()) diff --git a/clippy_lints/src/floating_point_arithmetic/custom_abs.rs b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs index f476abae708d..feff92fbf876 100644 --- a/clippy_lints/src/floating_point_arithmetic/custom_abs.rs +++ b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs @@ -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; diff --git a/clippy_lints/src/floating_point_arithmetic/hypot.rs b/clippy_lints/src/floating_point_arithmetic/hypot.rs index 8d3bd8084db8..f9b42761f3a7 100644 --- a/clippy_lints/src/floating_point_arithmetic/hypot.rs +++ b/clippy_lints/src/floating_point_arithmetic/hypot.rs @@ -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({})", diff --git a/clippy_lints/src/floating_point_arithmetic/log_division.rs b/clippy_lints/src/floating_point_arithmetic/log_division.rs index 947935369de1..ad0038a4c242 100644 --- a/clippy_lints/src/floating_point_arithmetic/log_division.rs +++ b/clippy_lints/src/floating_point_arithmetic/log_division.rs @@ -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 { diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index eed2d5d88535..38fda71fccf2 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -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 { diff --git a/clippy_lints/src/ifs/branches_sharing_code.rs b/clippy_lints/src/ifs/branches_sharing_code.rs index b6e8d047c5cd..06ebd3ac7f5f 100644 --- a/clippy_lints/src/ifs/branches_sharing_code.rs +++ b/clippy_lints/src/ifs/branches_sharing_code.rs @@ -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 { diff --git a/clippy_lints/src/ifs/if_same_then_else.rs b/clippy_lints/src/ifs/if_same_then_else.rs index 69402ec89076..b923559e4b73 100644 --- a/clippy_lints/src/ifs/if_same_then_else.rs +++ b/clippy_lints/src/ifs/if_same_then_else.rs @@ -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, diff --git a/clippy_lints/src/ifs/ifs_same_cond.rs b/clippy_lints/src/ifs/ifs_same_cond.rs index 3ea941320a08..ac010a9314b3 100644 --- a/clippy_lints/src/ifs/ifs_same_cond.rs +++ b/clippy_lints/src/ifs/ifs_same_cond.rs @@ -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) } }, ) { diff --git a/clippy_lints/src/ifs/same_functions_in_if_cond.rs b/clippy_lints/src/ifs/same_functions_in_if_cond.rs index 1f6bf04e22e7..018c2141d4fc 100644 --- a/clippy_lints/src/ifs/same_functions_in_if_cond.rs +++ b/clippy_lints/src/ifs/same_functions_in_if_cond.rs @@ -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) { diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index f0a2ad59cd28..06342113b104 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -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 diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 666c4cb737f4..718f682c3fc6 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -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 { @@ -389,7 +387,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) diff --git a/clippy_lints/src/loops/char_indices_as_byte_indices.rs b/clippy_lints/src/loops/char_indices_as_byte_indices.rs index 4aae602ea051..41022e6a5321 100644 --- a/clippy_lints/src/loops/char_indices_as_byte_indices.rs +++ b/clippy_lints/src/loops/char_indices_as_byte_indices.rs @@ -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" }, diff --git a/clippy_lints/src/loops/manual_while_let_some.rs b/clippy_lints/src/loops/manual_while_let_some.rs index df5e9ffa9863..22c97bcdbd97 100644 --- a/clippy_lints/src/loops/manual_while_let_some.rs +++ b/clippy_lints/src/loops/manual_while_let_some.rs @@ -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 } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index f1b7d4bdfa93..d2513765f631 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -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; diff --git a/clippy_lints/src/manual_abs_diff.rs b/clippy_lints/src/manual_abs_diff.rs index 400cfcd18f2c..b3c2e93a077f 100644 --- a/clippy_lints/src/manual_abs_diff.rs +++ b/clippy_lints/src/manual_abs_diff.rs @@ -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 { diff --git a/clippy_lints/src/manual_checked_ops.rs b/clippy_lints/src/manual_checked_ops.rs index 5fd1e27d5b99..0bc1c192b571 100644 --- a/clippy_lints/src/manual_checked_ops.rs +++ b/clippy_lints/src/manual_checked_ops.rs @@ -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(()); diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index c8fba3e5a7bc..6848af7748f3 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -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> { + #[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, diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs index 4501612540fb..26e0a6e2a51f 100644 --- a/clippy_lints/src/manual_is_power_of_two.rs +++ b/clippy_lints/src/manual_is_power_of_two.rs @@ -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 diff --git a/clippy_lints/src/manual_pop_if.rs b/clippy_lints/src/manual_pop_if.rs index bf53a8c27a6b..f279f330a3c0 100644 --- a/clippy_lints/src/manual_pop_if.rs +++ b/clippy_lints/src/manual_pop_if.rs @@ -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() { diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 564d5b53fa70..bc2e67de99d8 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -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) diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs index c371e5b47df8..a3a937ad713d 100644 --- a/clippy_lints/src/manual_rotate.rs +++ b/clippy_lints/src/manual_rotate.rs @@ -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) diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index e0a60e3747a0..eaa6539653af 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -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)) } } diff --git a/clippy_lints/src/manual_take.rs b/clippy_lints/src/manual_take.rs index dd8b0554a9ce..dec2a641231a 100644 --- a/clippy_lints/src/manual_take.rs +++ b/clippy_lints/src/manual_take.rs @@ -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) diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index d86b05e5c882..2ee2cabbea07 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -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) { diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index a8312a04f36f..caef6a0b3637 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -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()) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 0e43bba60682..ae0f3ffa0edc 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -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, diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index 9c6cf66019f0..a0f196489615 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -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); } } diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints/src/methods/collapsible_str_replace.rs index 6d0b944df55d..ce296d45d605 100644 --- a/clippy_lints/src/methods/collapsible_str_replace.rs +++ b/clippy_lints/src/methods/collapsible_str_replace.rs @@ -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(()) diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index f4b4eed26090..9ddc3d1bf1d8 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -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)); } diff --git a/clippy_lints/src/methods/get_last_with_len.rs b/clippy_lints/src/methods/get_last_with_len.rs index 14e40328a416..bdf8eb3045e0 100644 --- a/clippy_lints/src/methods/get_last_with_len.rs +++ b/clippy_lints/src/methods/get_last_with_len.rs @@ -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() { diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs index 472d21977c7a..824e5aff872e 100644 --- a/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/clippy_lints/src/methods/manual_is_variant_and.rs @@ -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 { diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints/src/methods/no_effect_replace.rs index 9fa51f78c99d..2afda2956beb 100644 --- a/clippy_lints/src/methods/no_effect_replace.rs +++ b/clippy_lints/src/methods/no_effect_replace.rs @@ -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"); } } diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints/src/methods/range_zip_with_len.rs index 7ece83ba7ca3..cf6347d466f2 100644 --- a/clippy_lints/src/methods/range_zip_with_len.rs +++ b/clippy_lints/src/methods/range_zip_with_len.rs @@ -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, diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 4142f9f75773..444d0a1d7242 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -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); }, _ => {}, } diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index c77b87268910..0bfb28d6b8bf 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -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), }) } diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index b5becbdeb30d..885e0c4ed527 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -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 { diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 064c1b8909fb..7da507c6d395 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -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); diff --git a/clippy_lints/src/operators/assign_op_pattern.rs b/clippy_lints/src/operators/assign_op_pattern.rs index 2d303e40bd1c..5695779425f4 100644 --- a/clippy_lints/src/operators/assign_op_pattern.rs +++ b/clippy_lints/src/operators/assign_op_pattern.rs @@ -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 diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index e5a5b46b7b2e..85128f9114e6 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -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() diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs index a40a724d2da5..88cd2d11999b 100644 --- a/clippy_lints/src/operators/double_comparison.rs +++ b/clippy_lints/src/operators/double_comparison.rs @@ -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 diff --git a/clippy_lints/src/operators/eq_op.rs b/clippy_lints/src/operators/eq_op.rs index d79101a687df..8e086cea7d91 100644 --- a/clippy_lints/src/operators/eq_op.rs +++ b/clippy_lints/src/operators/eq_op.rs @@ -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, diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs index 304c51ba2627..57f5a046cb06 100644 --- a/clippy_lints/src/operators/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -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<'_>, diff --git a/clippy_lints/src/operators/misrefactored_assign_op.rs b/clippy_lints/src/operators/misrefactored_assign_op.rs index 8daedd1c9014..f0b6407a141b 100644 --- a/clippy_lints/src/operators/misrefactored_assign_op.rs +++ b/clippy_lints/src/operators/misrefactored_assign_op.rs @@ -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); } diff --git a/clippy_lints/src/operators/self_assignment.rs b/clippy_lints/src/operators/self_assignment.rs index a932378fbb52..2054cf6ac588 100644 --- a/clippy_lints/src/operators/self_assignment.rs +++ b/clippy_lints/src/operators/self_assignment.rs @@ -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, ""); let rhs = snippet(cx, rhs.span, ""); span_lint( diff --git a/clippy_lints/src/panicking_overflow_checks.rs b/clippy_lints/src/panicking_overflow_checks.rs index bc1821a48a34..7e75e0affc72 100644 --- a/clippy_lints/src/panicking_overflow_checks.rs +++ b/clippy_lints/src/panicking_overflow_checks.rs @@ -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, diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 4bd6b1696b35..902552fd6bd1 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -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) diff --git a/clippy_lints/src/same_length_and_capacity.rs b/clippy_lints/src/same_length_and_capacity.rs index ebf649c24307..1d5d960665ef 100644 --- a/clippy_lints/src/same_length_and_capacity.rs +++ b/clippy_lints/src/same_length_and_capacity.rs @@ -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) { diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index 7482bac4c7b4..4aba49071ee5 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -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> { 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)); } diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index c99f2e2fb942..ce74a9bea57f 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -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); } diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 61b5842ea542..df3ca09dd76d 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -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, } } diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 262612a2a2d3..871c59d52659 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -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 { diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 4cd3707854c4..dd5da9a581c7 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -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 @@ -155,9 +155,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, @@ -250,7 +252,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<'_, '_> { @@ -380,9 +382,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<'_, '_> {} diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 97e68b3df94e..05c0af922854 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -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), } } diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index df06982904b3..9449d44c009e 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -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), } } } diff --git a/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs index bbcef0856ba4..bac05acebf34 100644 --- a/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -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); diff --git a/clippy_lints_internal/src/repeated_is_diagnostic_item.rs b/clippy_lints_internal/src/repeated_is_diagnostic_item.rs index 4492e3539ffb..b300dfa27b0e 100644 --- a/clippy_lints_internal/src/repeated_is_diagnostic_item.rs +++ b/clippy_lints_internal/src/repeated_is_diagnostic_item.rs @@ -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::>() && 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::>() && results.len() > 1 { diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index a4d8fd20e4d3..54ff0bdcdd8a 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -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 { + 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( } /// 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) => { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a1860adb4407..4b7b1daed286 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -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, } } From 436aad4e0fa745a686c33852520e9799850e68e7 Mon Sep 17 00:00:00 2001 From: ceptontech <> Date: Tue, 7 Apr 2026 09:52:40 -0700 Subject: [PATCH 03/36] Add a check for some followed by filter The program will suggest using the `then_some` method. changelog: add [`some_filter`] --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/mod.rs | 26 ++++ clippy_lints/src/methods/some_filter.rs | 65 ++++++++++ tests/ui/manual_filter.fixed | 1 + tests/ui/manual_filter.rs | 1 + tests/ui/manual_filter.stderr | 40 +++--- tests/ui/option_filter_map.fixed | 2 +- tests/ui/option_filter_map.rs | 2 +- tests/ui/some_filter.fixed | 70 ++++++++++ tests/ui/some_filter.rs | 74 +++++++++++ tests/ui/some_filter.stderr | 163 ++++++++++++++++++++++++ 12 files changed, 424 insertions(+), 22 deletions(-) create mode 100644 clippy_lints/src/methods/some_filter.rs create mode 100644 tests/ui/some_filter.fixed create mode 100644 tests/ui/some_filter.rs create mode 100644 tests/ui/some_filter.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b91932567a..3459dae23094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7238,6 +7238,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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 79ed199147f1..2afcbd60e9ea 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -474,6 +474,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, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3dc5767438ae..b0381d959fc0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -111,6 +111,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; @@ -3577,6 +3578,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 @@ -4900,6 +4924,7 @@ SINGLE_CHAR_ADD_STR, SKIP_WHILE_NEXT, SLICED_STRING_AS_BYTES, + SOME_FILTER, STABLE_SORT_PRIMITIVE, STRING_EXTEND_CHARS, STRING_LIT_CHARS_ANY, @@ -5306,6 +5331,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) { diff --git a/clippy_lints/src/methods/some_filter.rs b/clippy_lints/src/methods/some_filter.rs new file mode 100644 index 000000000000..f3db0fa165af --- /dev/null +++ b/clippy_lints/src/methods/some_filter.rs @@ -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", + ); + }, + ); +} diff --git a/tests/ui/manual_filter.fixed b/tests/ui/manual_filter.fixed index 3e2cebee40fe..7d7f987ca362 100644 --- a/tests/ui/manual_filter.fixed +++ b/tests/ui/manual_filter.fixed @@ -2,6 +2,7 @@ #![allow( unused_variables, clippy::question_mark, + clippy::some_filter, clippy::useless_vec, clippy::nonminimal_bool )] diff --git a/tests/ui/manual_filter.rs b/tests/ui/manual_filter.rs index 2b80cb450e05..b15008173e18 100644 --- a/tests/ui/manual_filter.rs +++ b/tests/ui/manual_filter.rs @@ -2,6 +2,7 @@ #![allow( unused_variables, clippy::question_mark, + clippy::some_filter, clippy::useless_vec, clippy::nonminimal_bool )] diff --git a/tests/ui/manual_filter.stderr b/tests/ui/manual_filter.stderr index c5fdf14a9b43..772401a5f6a5 100644 --- a/tests/ui/manual_filter.stderr +++ b/tests/ui/manual_filter.stderr @@ -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) })` diff --git a/tests/ui/option_filter_map.fixed b/tests/ui/option_filter_map.fixed index d390c41af53c..b3191c6c1d46 100644 --- a/tests/ui/option_filter_map.fixed +++ b/tests/ui/option_filter_map.fixed @@ -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(); diff --git a/tests/ui/option_filter_map.rs b/tests/ui/option_filter_map.rs index 2d3b983a7670..7d373f353688 100644 --- a/tests/ui/option_filter_map.rs +++ b/tests/ui/option_filter_map.rs @@ -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); diff --git a/tests/ui/some_filter.fixed b/tests/ui/some_filter.fixed new file mode 100644 index 000000000000..a214dec920d4 --- /dev/null +++ b/tests/ui/some_filter.fixed @@ -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", + )); +} diff --git a/tests/ui/some_filter.rs b/tests/ui/some_filter.rs new file mode 100644 index 000000000000..eec797536a8f --- /dev/null +++ b/tests/ui/some_filter.rs @@ -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() + }); +} diff --git a/tests/ui/some_filter.stderr b/tests/ui/some_filter.stderr new file mode 100644 index 000000000000..a4f38f49df69 --- /dev/null +++ b/tests/ui/some_filter.stderr @@ -0,0 +1,163 @@ +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:23:13 + | +LL | let _ = Some(0).filter(|_| false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated + = note: `-D clippy::some-filter` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::some_filter)]` +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| false); +LL + let _ = false.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:28:13 + | +LL | let _ = Some(0).filter(|_| false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| false); +LL + let _ = false.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:32:13 + | +LL | let _ = Some(0).filter(|_| 1 == 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| 1 == 0); +LL + let _ = (1 == 0).then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:35:13 + | +LL | let _ = Some(0).filter(|_| match 0 { + | _____________^ +LL | | +LL | | 0 => false, +LL | | 1 => true, +LL | | _ => true, +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ let _ = match 0 { +LL + +LL + 0 => false, +LL + 1 => true, +LL + _ => true, +LL ~ }.then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:48:24 + | +LL | let _ = unchanged!(Some(0).filter(|_| false)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = unchanged!(Some(0).filter(|_| false)); +LL + let _ = unchanged!(false.then_some(0)); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:50:13 + | +LL | let _ = Some(0).filter(|_| vec![false].is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| vec![false].is_empty()); +LL + let _ = vec![false].is_empty().then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:55:13 + | +LL | let _ = Some(0).filter(|_| condition!(false)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL - let _ = Some(0).filter(|_| condition!(false)); +LL + let _ = condition!(false).then_some(0); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:58:5 + | +LL | / Some(String::from( +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL | | )) +LL | | .filter(|_| 1 == 0); + | |_______________________^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ (1 == 0).then_some(String::from( +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL ~ )); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:63:5 + | +LL | / Some(5).filter(|_| { +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ { +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL ~ }.then_some(5); + | + +error: use of `Some(x).filter(|_| predicate)` + --> tests/ui/some_filter.rs:67:5 + | +LL | / Some(String::from( +LL | | +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL | | )) +LL | | .filter(|_| { +LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty() +LL | | }); + | |______^ + | + = note: this change will alter the order in which the condition and the value are evaluated +help: consider using `bool::then_some` instead + | +LL ~ "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from( +LL + +LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", +LL ~ )); + | + +error: aborting due to 10 previous errors + From 0a73f89e0a5f781900814598695c78ac10f475a5 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 Apr 2026 13:03:27 +0200 Subject: [PATCH 04/36] Merge commit '377417cf68ab671a67af222a551623ce228ce540' into clippy-subtree-update --- .github/workflows/clippy_mq.yml | 4 +- .github/workflows/lintcheck.yml | 18 +- .github/workflows/lintcheck_summary.yml | 4 +- CHANGELOG.md | 3 + book/src/configuration.md | 2 +- clippy_dev/src/new_lint.rs | 2 +- .../src/arbitrary_source_item_ordering.rs | 12 +- clippy_lints/src/bool_assert_comparison.rs | 7 +- clippy_lints/src/booleans.rs | 2 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 7 +- clippy_lints/src/cloned_ref_to_slice_refs.rs | 120 +++++++- clippy_lints/src/declared_lints.rs | 3 + clippy_lints/src/default_numeric_fallback.rs | 8 +- .../src/default_union_representation.rs | 7 +- clippy_lints/src/dereference.rs | 32 ++- clippy_lints/src/derivable_impls.rs | 7 +- clippy_lints/src/enum_clike.rs | 6 +- clippy_lints/src/eta_reduction.rs | 2 +- clippy_lints/src/format_args.rs | 143 +++++++++- clippy_lints/src/from_over_into.rs | 20 +- clippy_lints/src/functions/ref_option.rs | 14 +- .../src/functions/renamed_function_params.rs | 2 +- clippy_lints/src/implicit_saturating_sub.rs | 14 +- clippy_lints/src/item_name_repetitions.rs | 2 +- .../src/iter_not_returning_iterator.rs | 11 +- clippy_lints/src/iter_without_into_iter.rs | 7 +- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/len_without_is_empty.rs | 21 +- clippy_lints/src/lib.rs | 2 + clippy_lints/src/loops/explicit_iter_loop.rs | 4 +- clippy_lints/src/loops/for_kv_map.rs | 19 +- clippy_lints/src/loops/needless_range_loop.rs | 8 +- clippy_lints/src/manual_assert_eq.rs | 122 ++++++++ clippy_lints/src/matches/collapsible_match.rs | 20 +- .../src/methods/bind_instead_of_map.rs | 1 + clippy_lints/src/methods/manual_ok_or.rs | 4 +- clippy_lints/src/methods/map_err_ignore.rs | 4 +- clippy_lints/src/methods/mod.rs | 13 +- clippy_lints/src/methods/needless_collect.rs | 20 +- clippy_lints/src/methods/or_fun_call.rs | 8 +- .../src/methods/stable_sort_primitive.rs | 7 +- .../src/methods/unnecessary_sort_by.rs | 7 +- .../src/methods/unnecessary_to_owned.rs | 5 +- clippy_lints/src/methods/utils.rs | 7 +- .../src/methods/wrong_self_convention.rs | 2 +- clippy_lints/src/methods/zst_offset.rs | 11 +- clippy_lints/src/missing_const_for_fn.rs | 8 +- clippy_lints/src/module_style.rs | 165 +++++++++-- .../src/needless_borrows_for_generic_args.rs | 22 +- clippy_lints/src/needless_ifs.rs | 2 +- clippy_lints/src/needless_maybe_sized.rs | 4 +- clippy_lints/src/non_copy_const.rs | 44 ++- clippy_lints/src/operators/bit_mask.rs | 3 +- clippy_lints/src/operators/identity_op.rs | 8 +- clippy_lints/src/operators/manual_div_ceil.rs | 6 +- clippy_lints/src/ptr/ptr_arg.rs | 13 +- clippy_lints/src/question_mark.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 7 +- clippy_lints/src/ref_option_ref.rs | 2 +- .../src/significant_drop_tightening.rs | 6 +- clippy_lints/src/trait_bounds.rs | 4 +- .../src/transmute/transmute_undefined_repr.rs | 12 +- clippy_lints/src/transmute/utils.rs | 11 +- clippy_lints/src/unit_types/unit_arg.rs | 3 +- clippy_lints/src/upper_case_acronyms.rs | 2 +- clippy_lints/src/useless_conversion.rs | 36 ++- .../src/collapsible_span_lint_calls.rs | 4 +- clippy_lints_internal/src/msrv_attr_impl.rs | 10 +- clippy_lints_internal/src/unusual_names.rs | 13 +- clippy_utils/README.md | 2 +- clippy_utils/src/ast_utils/mod.rs | 17 +- clippy_utils/src/check_proc_macro.rs | 8 +- clippy_utils/src/eager_or_lazy.rs | 11 +- clippy_utils/src/lib.rs | 14 +- clippy_utils/src/qualify_min_const_fn.rs | 7 +- clippy_utils/src/sugg.rs | 9 +- clippy_utils/src/sym.rs | 2 - clippy_utils/src/ty/mod.rs | 46 ++- lintcheck/src/main.rs | 4 +- lintcheck/src/output.rs | 4 +- rust-toolchain.toml | 2 +- .../module_style/inline_mod/Cargo.stderr | 71 +++++ .../module_style/inline_mod/Cargo.toml | 9 + .../module_style/inline_mod/src/foo.rs | 2 + .../module_style/inline_mod/src/lib.rs | 34 +++ .../module_style/inline_mod/src/other.rs | 1 + .../module_style/inline_mod/src/qux/foo.rs | 1 + .../module_style/inline_mod/src/qux/mod.rs | 1 + tests/ui/assertions_on_constants.rs | 1 + tests/ui/assertions_on_constants.stderr | 34 +-- tests/ui/bind_instead_of_map.fixed | 6 + tests/ui/bind_instead_of_map.rs | 6 + tests/ui/bind_instead_of_map.stderr | 14 +- tests/ui/bit_masks.rs | 22 ++ tests/ui/cloned_ref_to_slice_refs.fixed | 80 +++++- tests/ui/cloned_ref_to_slice_refs.rs | 80 +++++- tests/ui/cloned_ref_to_slice_refs.stderr | 62 +++- tests/ui/cmp_null.fixed | 1 + tests/ui/cmp_null.rs | 1 + tests/ui/cmp_null.stderr | 14 +- tests/ui/collapsible_match.rs | 20 ++ tests/ui/collapsible_match_fixable.fixed | 15 + tests/ui/collapsible_match_fixable.rs | 16 ++ tests/ui/collapsible_match_fixable.stderr | 20 +- tests/ui/explicit_deref_methods.fixed | 7 +- tests/ui/explicit_deref_methods.rs | 7 +- tests/ui/explicit_deref_methods.stderr | 26 +- tests/ui/for_kv_map.fixed | 22 ++ tests/ui/for_kv_map.rs | 22 ++ tests/ui/for_kv_map.stderr | 50 +++- tests/ui/from_over_into.fixed | 20 ++ tests/ui/from_over_into.rs | 20 ++ tests/ui/infinite_loops.rs | 4 +- tests/ui/manual_assert_eq.fixed | 114 ++++++++ tests/ui/manual_assert_eq.rs | 114 ++++++++ tests/ui/manual_assert_eq.stderr | 88 ++++++ tests/ui/missing_asserts_for_indexing.fixed | 2 +- tests/ui/missing_asserts_for_indexing.rs | 2 +- tests/ui/needless_ifs.fixed | 10 +- tests/ui/needless_ifs.rs | 10 +- tests/ui/needless_ifs.stderr | 26 +- tests/ui/panic_in_result_fn_assertions.rs | 4 +- tests/ui/panic_in_result_fn_assertions.stderr | 6 +- .../ui/panic_in_result_fn_debug_assertions.rs | 4 +- tests/ui/question_mark.fixed | 11 + tests/ui/question_mark.rs | 12 + tests/ui/question_mark.stderr | 20 +- tests/ui/recursive_format_impl.rs | 1 + tests/ui/recursive_format_impl.stderr | 20 +- tests/ui/uninit_vec.rs | 2 +- tests/ui/unnecessary_map_or.fixed | 11 +- tests/ui/unnecessary_map_or.rs | 11 +- tests/ui/unnecessary_map_or.stderr | 60 ++-- tests/ui/unused_format_specs_width.rs | 41 +++ tests/ui/unused_format_specs_width.stderr | 148 ++++++++++ tests/ui/useless_borrows_in_formatting.fixed | 118 ++++++++ tests/ui/useless_borrows_in_formatting.rs | 118 ++++++++ tests/ui/useless_borrows_in_formatting.stderr | 266 ++++++++++++++++++ tests/ui/useless_conversion.fixed | 37 ++- tests/ui/useless_conversion.rs | 37 ++- tests/ui/useless_conversion.stderr | 46 ++- tests/ui/zero_offset.rs | 13 + tests/ui/zero_offset.stderr | 20 +- triagebot.toml | 1 + 144 files changed, 2911 insertions(+), 410 deletions(-) create mode 100644 clippy_lints/src/manual_assert_eq.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/Cargo.stderr create mode 100644 tests/ui-cargo/module_style/inline_mod/Cargo.toml create mode 100644 tests/ui-cargo/module_style/inline_mod/src/foo.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/lib.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/other.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs create mode 100644 tests/ui/manual_assert_eq.fixed create mode 100644 tests/ui/manual_assert_eq.rs create mode 100644 tests/ui/manual_assert_eq.stderr create mode 100644 tests/ui/unused_format_specs_width.rs create mode 100644 tests/ui/unused_format_specs_width.stderr create mode 100644 tests/ui/useless_borrows_in_formatting.fixed create mode 100644 tests/ui/useless_borrows_in_formatting.rs create mode 100644 tests/ui/useless_borrows_in_formatting.stderr diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index c49241bdff1b..b612ea4611a9 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -135,7 +135,7 @@ jobs: find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf - name: Upload Binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: binaries path: target/debug @@ -179,7 +179,7 @@ jobs: # Download - name: Download target dir - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: binaries path: target/debug diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 9ce0b7f5fc46..981fecd17c98 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -44,7 +44,7 @@ jobs: - name: Cache lintcheck bin id: cache-lintcheck-bin - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} @@ -59,7 +59,7 @@ jobs: - name: Cache results JSON id: cache-json - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: lintcheck-logs/ci_crates_logs.json key: ${{ steps.key.outputs.key }} @@ -69,7 +69,7 @@ jobs: run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload base JSON - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: base path: lintcheck-logs/ci_crates_logs.json @@ -87,7 +87,7 @@ jobs: - name: Cache lintcheck bin id: cache-lintcheck-bin - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} @@ -100,7 +100,7 @@ jobs: run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload head JSON - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: head path: lintcheck-logs/ci_crates_logs.json @@ -119,14 +119,14 @@ jobs: persist-credentials: false - name: Restore lintcheck bin - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} fail-on-cache-miss: true - name: Download JSON - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 - name: Store PR number run: echo ${{ github.event.pull_request.number }} > pr.txt @@ -140,13 +140,13 @@ jobs: ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --write-summary summary.json > full_diff.md - name: Upload full diff - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: full_diff path: full_diff.md - name: Upload summary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: summary path: | diff --git a/.github/workflows/lintcheck_summary.yml b/.github/workflows/lintcheck_summary.yml index 6768cd65701a..da6a7474cdb9 100644 --- a/.github/workflows/lintcheck_summary.yml +++ b/.github/workflows/lintcheck_summary.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: summary path: untrusted @@ -35,7 +35,7 @@ jobs: github-token: ${{ github.token }} - name: Format comment - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: script: | const fs = require("fs"); diff --git a/CHANGELOG.md b/CHANGELOG.md index 1276ab3d4bd3..24b91932567a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6804,6 +6804,7 @@ Released 2018-09-13 [`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax [`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 [`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 @@ -6878,6 +6879,7 @@ Released 2018-09-13 [`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion [`manual_abs_diff`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff [`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert +[`manual_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert_eq [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals @@ -7393,6 +7395,7 @@ Released 2018-09-13 [`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute +[`useless_borrows_in_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_borrows_in_formatting [`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat [`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion [`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format diff --git a/book/src/configuration.md b/book/src/configuration.md index b270c11ab397..45faf039dd67 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -79,7 +79,7 @@ This also works with lint groups. For example, you can run Clippy with warnings cargo clippy -- -W clippy::pedantic ``` -If you care only about a certain lints, you can allow all others and then explicitly warn on the lints you are +If you care only about certain lints, you can allow all others and then explicitly warn on the lints you are interested in: ```terminal diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index dc2c6d8aa520..a5e2050a3865 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -502,7 +502,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> file_contents.replace_range(arr_start + 1..arr_end, &new_arr_content); // Just add the mod declaration at the top, it'll be fixed by rustfmt - file_contents.insert_str(0, &format!("mod {};\n", &lint.name)); + file_contents.insert_str(0, &format!("mod {};\n", lint.name)); let mut file = OpenOptions::new() .write(true) diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 21cb3c5d0443..6afc1f9ef092 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -307,15 +307,15 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { } }, ItemKind::Trait { - impl_restriction:_, - constness:_, + impl_restriction: _, + constness: _, is_auto, - safety:_, - ident:_, + safety: _, + ident: _, generics: _, bounds: _, - items: item_ref} - if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { + items: item_ref, + } if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { let mut cur_t: Option<(TraitItemId, Ident)> = None; for &item in *item_ref { diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index 8e85ce617184..cdcb66566d95 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -4,12 +4,11 @@ use clippy_utils::sugg::Sugg; use clippy_utils::sym; use clippy_utils::ty::{implements_trait, is_copy}; -use rustc_middle::ty::Unnormalized; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Lit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, Unnormalized}; use rustc_session::declare_lint_pass; use rustc_span::symbol::Ident; @@ -65,7 +64,9 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) - }) .is_some_and(|assoc_item| { let proj = Ty::new_projection(cx.tcx, assoc_item.def_id, cx.tcx.mk_args_trait(ty, [])); - let nty = cx.tcx.normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj)); + let nty = cx + .tcx + .normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj)); nty.is_bool() }) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 986e75577412..ed963dc3e90e 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -360,7 +360,7 @@ fn recurse(&mut self, suggestion: &Bool) -> Option<()> { if app != Applicability::MachineApplicable { return None; } - let _cannot_fail = write!(&mut self.output, "{}", &(!snip)); + let _cannot_fail = write!(&mut self.output, "{}", !snip); } }, True | False | Not(_) => { diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index c54dab7e4aec..69490fb23646 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -59,7 +59,12 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { if matches!(name.ident.name, sym::read_unaligned | sym::write_unaligned) && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && let Some(def_id) = cx.tcx.impl_of_assoc(def_id) - && cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().is_raw_ptr() + && cx + .tcx + .type_of(def_id) + .instantiate_identity() + .skip_norm_wip() + .is_raw_ptr() { true } else { diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index c5eabe4c2b88..4f663f4aa909 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -1,15 +1,22 @@ +use std::ops::ControlFlow; + use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::sugg::Sugg; use clippy_utils::visitors::is_const_evaluatable; -use clippy_utils::{is_in_const_context, is_mutable}; +use clippy_utils::{is_in_const_context, is_mutable, sym}; +use rustc_ast::Mutability; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId, LangItem}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind, OverloadedDeref}; use rustc_session::impl_lint_pass; -use rustc_span::sym; +use rustc_span::Symbol; + +use crate::methods::is_clone_like; declare_clippy_lint! { /// ### What it does @@ -73,29 +80,116 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { && let ExprKind::Array([item]) = &arr.kind // check for clones - && let ExprKind::MethodCall(_, val, _, _) = item.kind - && cx.ty_based_def(item).opt_parent(cx).is_diag_item(cx, sym::Clone) + && let ExprKind::MethodCall(path, recv, _, _) = item.kind + && let Some(adjustment) = is_needless_clone_or_equivalent(cx, recv, path.ident.name, item.hir_id) // check for immutability or purity - && (!is_mutable(cx, val) || is_const_evaluatable(cx, val)) + && (!is_mutable(cx, recv) || is_const_evaluatable(cx, recv)) // get appropriate crate for `slice::from_ref` && let Some(builtin_crate) = clippy_utils::std_or_core(cx) { - let mut sugg = Sugg::hir(cx, val, "_"); - if !cx.typeck_results().expr_ty(val).is_ref() { - sugg = sugg.addr(); - } + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, recv, expr.span.ctxt(), "_", &mut applicability); span_lint_and_sugg( cx, CLONED_REF_TO_SLICE_REFS, expr.span, - format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"), + format!( + "unnecessary use of `{}` to create a slice from a reference", + path.ident.name + ), "try", - format!("{builtin_crate}::slice::from_ref({sugg})"), - Applicability::MaybeIncorrect, + format!("{builtin_crate}::slice::from_ref({adjustment}{sugg})"), + applicability, ); } } } + +/// Checks if a method call is a needless clone or equivalent. If so, returns the necessary +/// adjustments to use the method receiver directly without cloning. +/// For example, in the code below: +/// ```rust,no_run +/// use std::path::PathBuf; +/// +/// let w = &PathBuf::new(); +/// let b = &[w.to_path_buf()]; +/// ``` +/// We would replace `&[w.to_path_buf()]` with `std::slice::from_ref(&*w)`, +/// hence we return `Some("&*")` as the adjustment. +fn is_needless_clone_or_equivalent<'tcx>( + cx: &LateContext<'tcx>, + method_recv: &'tcx Expr<'tcx>, + method_name: Symbol, + hir_id: HirId, +) -> Option { + let method_def = cx.ty_based_def(hir_id).opt_parent(cx)?; + if !method_def.is_lang_item(cx, LangItem::Clone) && !is_clone_like(cx, method_name, method_def) { + return None; + } + + let method_ret_ty = cx.typeck_results().node_type(hir_id); + let method_recv_ty = cx.typeck_results().expr_ty_adjusted(method_recv); + let ty::Ref(_, method_recv_ty_inner, Mutability::Not) = method_recv_ty.kind() else { + return None; + }; + + let method_recv_adjustments = cx.typeck_results().expr_adjustments(method_recv); + + // The return type of the clone-like method should be the same as the inner type of the reference + // being cloned, except for the following special cases: + // 1. `OsString`, which is first dereferenced to `OsStr` and the borrowed as `&OsStr`. + // 2. `PathBuf`, which is first dereferenced to `Path` and then borrowed as `&Path`. + let adjust_target_ty = if method_ret_ty == *method_recv_ty_inner { + method_ret_ty + } else if let Some(after_special_case_ty_name @ (sym::OsStr | sym::Path)) = method_recv_ty_inner.opt_diag_name(cx) + // Looking for the `OSString -> OSStr` or `PathBuf -> Path` adjustment in the abovementioned special cases + && let [preceeding_derefs @ .., special_case, last_borrow] = method_recv_adjustments + && matches!( + special_case.kind, + Adjust::Deref(DerefAdjustKind::Overloaded(OverloadedDeref { + mutbl: Mutability::Not, + .. + })) + ) + && matches!(last_borrow.kind, Adjust::Borrow(_)) + && special_case.target.is_diag_item(cx, after_special_case_ty_name) + && let before_special_case_ty = preceeding_derefs + .last().map_or_else(|| cx.typeck_results().expr_ty(method_recv), |a| a.target) + && matches!( + (before_special_case_ty.opt_diag_name(cx)?, after_special_case_ty_name), + (sym::OsString, sym::OsStr) | (sym::PathBuf, sym::Path)) + { + before_special_case_ty + } else { + return None; + }; + + // Find the number of adjustments required until `method_recv_ty_source` becomes `adjust_target_ty` + let method_recv_ty_source = cx.typeck_results().expr_ty(method_recv); + let adjust_count = method_recv_adjustments + .iter() + .enumerate() + .try_fold(method_recv_ty_source, |ty, (i, a)| { + if ty == adjust_target_ty { + ControlFlow::Break(i) + } else { + ControlFlow::Continue(a.target) + } + }) + .break_value()?; + + let (needs_borrow, deref_count) = if adjust_count == 0 || !method_recv_ty_source.is_ref() { + (true, adjust_count) + } else { + (false, adjust_count - 1) + }; + + Some(if needs_borrow { + format!("&{}", "*".repeat(deref_count)) + } else { + "*".repeat(deref_count) + }) +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c164241673a3..79ed199147f1 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -176,6 +176,7 @@ crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO, crate::format_args::UNNECESSARY_TRAILING_COMMA_INFO, crate::format_args::UNUSED_FORMAT_SPECS_INFO, + crate::format_args::USELESS_BORROWS_IN_FORMATTING_INFO, crate::format_impl::PRINT_IN_FORMAT_IMPL_INFO, crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO, crate::format_push_string::FORMAT_PUSH_STRING_INFO, @@ -297,6 +298,7 @@ crate::main_recursion::MAIN_RECURSION_INFO, crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, + crate::manual_assert_eq::MANUAL_ASSERT_EQ_INFO, crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, crate::manual_checked_ops::MANUAL_CHECKED_OPS_INFO, @@ -536,6 +538,7 @@ crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO, crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO, crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_INFO, + crate::module_style::INLINE_MODULES_INFO, crate::module_style::MOD_MODULE_FILES_INFO, crate::module_style::SELF_NAMED_MODULE_FILES_INFO, crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 4324a8465be6..f93f682bc929 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -166,7 +166,13 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { ExprKind::MethodCall(_, receiver, args, _) => { if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let fn_sig = self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); + let fn_sig = self + .cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); for (expr, bound) in iter::zip(iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) { self.ty_bounds.push((*bound).into()); self.visit_expr(expr); diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 49d550f1cbd4..76242d775688 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -80,7 +80,12 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { /// of that field does not matter either.) fn is_union_with_two_non_zst_fields<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool { if let ItemKind::Union(..) = &item.kind - && let ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() + && let ty::Adt(adt_def, args) = cx + .tcx + .type_of(item.owner_id) + .instantiate_identity() + .skip_norm_wip() + .kind() { adt_def.all_fields().filter(|f| !is_zst(cx, f, args)).count() >= 2 } else { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c1ebb675c58e..7a5150da6593 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -6,7 +6,6 @@ use clippy_utils::{ DefinedTy, ExprUseNode, get_expr_use_site, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, }; -use rustc_middle::ty::Unnormalized; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; @@ -18,7 +17,7 @@ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults, Unnormalized}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol, SyntaxContext}; use std::borrow::Cow; @@ -381,16 +380,21 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let args = typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() - && let impl_ty = - if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_norm_wip().skip_binder().inputs()[0] - .is_ref() - { - // Trait methods taking `&self` - sub_ty - } else { - // Trait methods taking `self` - arg_ty - } + && let impl_ty = if cx + .tcx + .fn_sig(fn_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder() + .inputs()[0] + .is_ref() + { + // Trait methods taking `&self` + sub_ty + } else { + // Trait methods taking `self` + arg_ty + } && impl_ty.is_ref() && implements_trait( cx, @@ -877,7 +881,9 @@ fn for_mir_ty<'tcx>(tcx: TyCtxt<'tcx>, def_site_def_id: Option, ty: Ty<'t if let Some(def_id) = def_site_def_id { let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id); - ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); } loop { break match *ty.kind() { diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 5e1144254fdb..811c360e533d 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -243,7 +243,12 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { && let Node::ImplItem(impl_item) = cx.tcx.hir_node(impl_item_hir) && let ImplItemKind::Fn(_, b) = &impl_item.kind && let Body { value: func_expr, .. } = cx.tcx.hir_body(*b) - && let &ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() + && let &ty::Adt(adt_def, args) = cx + .tcx + .type_of(item.owner_id) + .instantiate_identity() + .skip_norm_wip() + .kind() && let attrs = cx.tcx.hir_attrs(item.hir_id()) && !attrs.iter().any(|attr| attr.doc_str().is_some()) && cx.tcx.hir_attrs(impl_item_hir).is_empty() diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 2a8b6778fd0a..bd92e7e85087 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -42,7 +42,11 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { for var in def.variants { if let Some(anon_const) = &var.disr_expr { let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body); - let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity().skip_norm_wip(); + let mut ty = cx + .tcx + .type_of(def_id.to_def_id()) + .instantiate_identity() + .skip_norm_wip(); let constant = cx.tcx.const_eval_poly(def_id.to_def_id()).ok(); if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c, ty)) { if let ty::Adt(adt, _) = ty.kind() diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 229af104799d..6248ba6e44da 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -372,7 +372,7 @@ fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool { } } - assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len()); + assert_eq!(from_sig.inputs_and_output.len(), to_sig.inputs_and_output.len()); from_sig .inputs_and_output .iter() diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index fd2b9826bb20..ae92284f81f8 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -2,7 +2,7 @@ use arrayvec::ArrayVec; use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{ FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call, @@ -10,14 +10,14 @@ }; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::source::{SpanRangeExt, snippet, snippet_opt}; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_from_proc_macro, is_in_test, sym, trait_ref_of_method}; +use clippy_utils::{is_from_proc_macro, is_in_test, peel_hir_expr_while, sym, trait_ref_of_method}; use itertools::Itertools; -use rustc_middle::ty::Unnormalized; +use rustc_ast::FormatTrait::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}; use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, + BorrowKind, FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, + FormatOptions, FormatPlaceholder, }; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; @@ -25,7 +25,7 @@ use rustc_hir::{Expr, ExprKind, LangItem, RustcVersion, find_attr}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, DerefAdjustKind}; -use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast}; +use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Unnormalized, Upcast}; use rustc_session::impl_lint_pass; use rustc_span::edition::Edition::Edition2021; use rustc_span::{BytePos, Pos, Span, Symbol}; @@ -172,6 +172,11 @@ /// ### What it does /// Checks for `Debug` formatting (`{:?}`) applied to an `OsStr` or `Path`. /// + /// This includes: + /// - Format specifiers on `format_args!()` (width, precision have no effect) + /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1" + /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary) + /// /// ### Why is this bad? /// Rust doesn't guarantee what `Debug` formatting looks like, and it could /// change in the future. `OsStr`s and `Path`s can be `Display` formatted @@ -232,6 +237,11 @@ /// Detects [formatting parameters] that have no effect on the output of /// `format!()`, `println!()` or similar macros. /// + /// This includes: + /// - Format specifiers on `format_args!()` (width, precision have no effect) + /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1" + /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary) + /// /// ### Why is this bad? /// Shorter format specifiers are easier to read, it may also indicate that /// an expected formatting operation such as adding padding isn't happening. @@ -241,6 +251,9 @@ /// println!("{:.}", 1.0); /// /// println!("not padded: {:5}", format_args!("...")); + /// + /// // width 2 has no effect for alternate hex (outputs "0x1") + /// format!("{:#02x}", 1_u8); /// ``` /// Use instead: /// ```no_run @@ -249,6 +262,8 @@ /// println!("not padded: {}", format_args!("...")); /// // OR /// println!("padded: {:5}", format!("...")); + /// + /// format!("{:#04x}", 1_u8); // width 4 for two-digit zero-padded hex /// ``` /// /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters @@ -258,6 +273,34 @@ "use of a format specifier that has no effect" } +declare_clippy_lint! { + /// ### What it does + /// Detects format!-style macros (e.g. `format!`, `println!`, `write!`) where an argument + /// is passed with an explicit `&` but the value is already a reference, resulting in a + /// double reference (e.g. `&&T`). + /// + /// ### Why is this bad? + /// The extra `&` is redundant and can make the code less clear. Format macros take + /// references to the arguments internally, so passing `&x` when `x` is already a + /// reference produces a double reference. The compiler is currently unable to + /// optimize double references, which results in about 6% degradation per call. + /// + /// ### Example + /// ```no_run + /// let s: &str = "hello"; + /// println!("{}", &s); + /// ``` + /// Use instead: + /// ```no_run + /// let s: &str = "hello"; + /// println!("{}", s); + /// ``` + #[clippy::version = "1.97.0"] + pub USELESS_BORROWS_IN_FORMATTING, + perf, + "redundant reference in format args causes double reference" +} + impl_lint_pass!(FormatArgs<'_> => [ FORMAT_IN_FORMAT_ARGS, POINTER_FORMAT, @@ -266,6 +309,7 @@ UNNECESSARY_DEBUG_FORMATTING, UNNECESSARY_TRAILING_COMMA, UNUSED_FORMAT_SPECS, + USELESS_BORROWS_IN_FORMATTING, ]); #[expect(clippy::struct_field_names)] @@ -364,8 +408,21 @@ fn check_templates(&mut self) { && let Some(arg_expr) = find_format_arg_expr(self.expr, arg) { self.check_unused_format_specifier(placeholder, arg_expr); + self.check_useless_format_width(placeholder); + self.check_useless_borrows_in_formatting(placeholder, arg_expr); - if placeholder.format_trait == FormatTrait::Display + // Check width and precision arguments the same way as the value + for opt in [&placeholder.format_options.width, &placeholder.format_options.precision] { + if let Some(FormatCount::Argument(position)) = opt.as_ref() + && let Ok(pos_index) = position.index + && let Some(pos_arg) = self.format_args.arguments.all_args().get(pos_index) + && let Some(pos_arg_expr) = find_format_arg_expr(self.expr, pos_arg) + { + self.check_useless_borrows_in_formatting(placeholder, pos_arg_expr); + } + } + + if placeholder.format_trait == Display && placeholder.format_options == FormatOptions::default() && !self.is_aliased(index) { @@ -374,7 +431,7 @@ fn check_templates(&mut self) { self.check_to_string_in_format_args(name, arg_expr); } - if placeholder.format_trait == FormatTrait::Debug { + if placeholder.format_trait == Debug { let name = self.cx.tcx.item_name(self.macro_call.def_id); self.check_unnecessary_debug_formatting(name, arg_expr); if let Some(span) = placeholder.span @@ -384,7 +441,7 @@ fn check_templates(&mut self) { } } - if placeholder.format_trait == FormatTrait::Pointer + if placeholder.format_trait == Pointer && let Some(span) = placeholder.span { span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); @@ -393,6 +450,43 @@ fn check_templates(&mut self) { } } + fn check_useless_borrows_in_formatting(&self, placeholder: &FormatPlaceholder, arg_expr: &Expr<'tcx>) { + if !arg_expr.span.from_expansion() + && !is_from_proc_macro(self.cx, arg_expr) + && let Some(fmt_trait) = match placeholder.format_trait { + Display => self.cx.tcx.get_diagnostic_item(sym::Display), + Debug => self.cx.tcx.get_diagnostic_item(sym::Debug), + _ => None, + } + && let Some(sized_trait) = self.cx.tcx.lang_items().sized_trait() + && let peeled_expr = peel_hir_expr_while(arg_expr, |e| { + // Need to handle `&&&T` to `&T` when a single ref is still required + if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = e.kind + && let ty = self.cx.typeck_results().expr_ty(e) + && implements_trait(self.cx, ty, sized_trait, &[]) + && implements_trait(self.cx, ty, fmt_trait, &[]) + { + Some(e) + } else { + None + } + }) + && !std::ptr::eq(arg_expr, peeled_expr) + && let Some(peeled_snippet) = snippet_opt(self.cx, peeled_expr.span) + { + let name = self.cx.tcx.item_name(self.macro_call.def_id); + span_lint_and_sugg( + self.cx, + USELESS_BORROWS_IN_FORMATTING, + arg_expr.span, + format!("redundant reference in `{name}!` argument"), + "remove the redundant `&`", + peeled_snippet, + Applicability::MachineApplicable, + ); + } + } + fn check_unused_format_specifier(&self, placeholder: &FormatPlaceholder, arg: &Expr<'_>) { let options = &placeholder.format_options; @@ -443,6 +537,30 @@ fn check_unused_format_specifier(&self, placeholder: &FormatPlaceholder, arg: &E } } + /// Lint when format width has no effect on the output because the format trait's + /// minimum output is larger (e.g. `{:#02X}` outputs "0x1" so width 2 has no effect). + fn check_useless_format_width(&self, placeholder: &FormatPlaceholder) { + let min_width = match placeholder.format_trait { + // 0x prefix, e.g. 0x1, 0o1, 0b1 + LowerHex | UpperHex | Octal | Binary if placeholder.format_options.alternate => 4, + LowerExp | UpperExp | Pointer => 4, // e.g. 1e0 with exponent, 0x1 for pointer + _ => return, + }; + if let Some(FormatCount::Literal(width_value)) = placeholder.format_options.width + && width_value < min_width + && let Some(placeholder_span) = placeholder.span + { + span_lint_and_help( + self.cx, + UNUSED_FORMAT_SPECS, + placeholder_span, + "format width has no effect on the output for this format trait", + None, + format!("consider removing the width or increasing it to at least {min_width}"), + ); + } + } + fn check_uninlined_args(&self) { if self.format_args.span.from_expansion() { return; @@ -726,10 +844,7 @@ fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { let pointer_debug = derived_debug && adt.all_fields().any(|f| { self.has_pointer_debug( - tcx.normalize_erasing_regions( - typing_env, - Unnormalized::new_wip(f.ty(tcx, args)) - ), + tcx.normalize_erasing_regions(typing_env, Unnormalized::new_wip(f.ty(tcx, args))), depth, ) }); diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 944c4eee9025..433d59141879 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -10,11 +10,11 @@ use rustc_hir::intravisit::{Visitor, walk_path}; use rustc_hir::{ FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemId, ImplItemKind, Item, ItemKind, PatKind, Path, - PathSegment, Ty, TyKind, + PathSegment, Ty as HirTy, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter::OnlyBodies; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::symbol::{kw, sym}; use rustc_span::{Span, Symbol}; @@ -80,6 +80,8 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { && cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id) && !matches!(middle_trait_ref.args.type_at(1).kind(), ty::Alias(ty::AliasTy { kind: ty::Opaque{..} , .. })) && self.msrv.meets(cx, msrvs::RE_REBALANCING_COHERENCE) + // skip if there's a blanket From impl, the suggested impl would conflict + && !has_blanket_from_impl(cx, middle_trait_ref.self_ty()) { span_lint_and_then( cx, @@ -163,11 +165,21 @@ fn visit_name(&mut self, name: Symbol) -> Self::Result { } } +fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: Ty<'tcx>) -> bool { + let Some(from_def_id) = cx.tcx.get_diagnostic_item(sym::From) else { + return false; + }; + cx.tcx.non_blanket_impls_for_ty(from_def_id, self_ty).any(|impl_id| { + let impl_trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity().skip_norm_wip(); + matches!(impl_trait_ref.args.type_at(1).kind(), ty::Param(_)) + }) +} + fn convert_to_from( cx: &LateContext<'_>, into_trait_seg: &PathSegment<'_>, - target_ty: &Ty<'_>, - self_ty: &Ty<'_>, + target_ty: &HirTy<'_>, + self_ty: &HirTy<'_>, impl_item_ref: ImplItemId, ) -> Option> { if !target_ty.find_self_aliases().is_empty() { diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index c5c0cc0b5ab6..1727f50b521a 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -107,7 +107,12 @@ pub(crate) fn check_fn<'a>( check_fn_sig(cx, decl, inputs_output_span, sig); } else if !is_trait_impl_item(cx, hir_id) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); + let sig = cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { return; @@ -128,7 +133,12 @@ pub(super) fn check_trait_item<'a>( && !is_from_proc_macro(cx, trait_item) { let def_id = trait_item.owner_id.def_id; - let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); + let ty_sig = cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); check_fn_sig(cx, sig.decl, sig.span, ty_sig); } } diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs index e25611d48817..2d330835a037 100644 --- a/clippy_lints/src/functions/renamed_function_params.rs +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -57,7 +57,7 @@ fn new(default_idents: &mut I1, current_idents: &mut I2) -> Self { let mut renamed: Vec<(Span, String)> = vec![]; - debug_assert!(default_idents.size_hint() == current_idents.size_hint()); + debug_assert_eq!(default_idents.size_hint(), current_idents.size_hint()); for (default_ident, current_ident) in iter::zip(default_idents, current_idents) { let has_name_to_check = |ident: Option| { ident diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index f2e85a717dca..45adcfbb030f 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -360,7 +360,12 @@ fn check_with_condition<'tcx>( if name.ident.name == sym::MIN && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(const_id) - && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_integral() + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() + .is_integral() { print_lint_and_sugg(cx, var_name, expr); } @@ -370,7 +375,12 @@ fn check_with_condition<'tcx>( && name.ident.name == sym::min_value && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(func_id) - && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_integral() + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() + .is_integral() { print_lint_and_sugg(cx, var_name, expr); } diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 5ded0efacb81..9569b50a3207 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -528,7 +528,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { | ItemKind::Fn { ident, .. } | ItemKind::Macro(ident, ..) | ItemKind::Static(_, ident, ..) - | ItemKind::Trait { ident, ..} + | ItemKind::Trait { ident, .. } | ItemKind::TraitAlias(_, ident, ..) | ItemKind::TyAlias(ident, ..) | ItemKind::Union(ident, ..) diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index 11de287c0ea5..06ad45316811 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::implements_trait; -use rustc_middle::ty::Unnormalized; use rustc_hir::def_id::LocalDefId; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Unnormalized; use rustc_session::declare_lint_pass; use rustc_span::{Symbol, sym}; @@ -66,13 +66,12 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx> fn check_sig(cx: &LateContext<'_>, name: Symbol, sig: &FnSig<'_>, fn_id: LocalDefId) { if sig.decl.implicit_self().has_implicit_self() { + let ret_ty = cx.tcx.instantiate_bound_regions_with_erased( + cx.tcx.fn_sig(fn_id).instantiate_identity().skip_norm_wip().output(), + ); let ret_ty = cx .tcx - .instantiate_bound_regions_with_erased(cx.tcx - .fn_sig(fn_id).instantiate_identity().skip_norm_wip().output() - ); - let ret_ty = cx - .tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ret_ty)) + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ret_ty)) .unwrap_or(ret_ty); if cx .tcx diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 3746c9384957..fc0a725e0633 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -134,7 +134,12 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { .trait_def_id() .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::IntoIterator, did)) && !item.span.in_external_macro(cx.sess().source_map()) - && let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() + && let &ty::Ref(_, ty, mtbl) = cx + .tcx + .type_of(item.owner_id) + .instantiate_identity() + .skip_norm_wip() + .kind() && let expected_method_name = match mtbl { Mutability::Mut => sym::iter_mut, Mutability::Not => sym::iter, diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 11b5a339c18b..80b49ecc7a40 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -1,10 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use rustc_middle::ty::Unnormalized; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; +use rustc_middle::ty::Unnormalized; use rustc_middle::ty::layout::LayoutOf; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Pos, Span}; diff --git a/clippy_lints/src/len_without_is_empty.rs b/clippy_lints/src/len_without_is_empty.rs index 9bf06dbf452d..8ae91bbcd2a9 100644 --- a/clippy_lints/src/len_without_is_empty.rs +++ b/clippy_lints/src/len_without_is_empty.rs @@ -44,7 +44,11 @@ impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Trait { ident, items: trait_items, .. } = item.kind + if let ItemKind::Trait { + ident, + items: trait_items, + .. + } = item.kind && !item.span.from_expansion() { check_trait_items(cx, item, ident, trait_items); @@ -64,7 +68,14 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) && let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id() && let Some(local_id) = ty_id.as_local() && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id) - && let Some(output) = LenOutput::new(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_norm_wip().skip_binder()) + && let Some(output) = LenOutput::new( + cx, + cx.tcx + .fn_sig(item.owner_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(), + ) { let (name, kind) = match cx.tcx.hir_node(ty_hir_id) { Node::ForeignItem(x) => (x.ident.name, "extern type"), @@ -313,7 +324,11 @@ fn check_for_is_empty( if !(is_empty.is_method() && check_is_empty_sig( cx, - cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_norm_wip().skip_binder(), + cx.tcx + .fn_sig(is_empty.def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(), len_self_kind, len_output, )) => diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 72ee5cca0397..0875982f3bbf 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -196,6 +196,7 @@ mod main_recursion; mod manual_abs_diff; mod manual_assert; +mod manual_assert_eq; mod manual_async_fn; mod manual_bits; mod manual_checked_ops; @@ -867,6 +868,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))), Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))), Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)), + Box::new(|_| Box::new(manual_assert_eq::ManualAssertEq)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 6ee93fa759a9..2cb98fdfe448 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -139,7 +139,9 @@ fn is_ref_iterable<'tcx>( } let res_ty = cx.tcx.erase_and_anonymize_regions( - EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)).skip_norm_wip(), + EarlyBinder::bind(req_res_ty) + .instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)) + .skip_norm_wip(), ); let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() { Some(mutbl) diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 7fb8e51377a2..d28029790732 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -23,7 +23,20 @@ pub(super) fn check<'tcx>( && pat.len() == 2 { let arg_span = arg.span; - let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + let (arg, arg_ty) = match arg.kind { + // `for x in &expr` or `for x in &mut expr` + ExprKind::AddrOf(BorrowKind::Ref, _, expr) => (expr, cx.typeck_results().expr_ty(arg)), + // `for x in receiver.iter()` or `for x in receiver.iter_mut()` + ExprKind::MethodCall(path, receiver, [], ..) + if path.ident.name == sym::iter || path.ident.name == sym::iter_mut => + { + // Use `expr_ty_adjusted` because `.iter()` / `.iter_mut()` may introduce auto deferences + (receiver, cx.typeck_results().expr_ty_adjusted(receiver)) + }, + _ => (arg, cx.typeck_results().expr_ty(arg)), + }; + + let (new_pat_span, kind, ty, mutbl) = match *arg_ty.kind() { ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), @@ -35,10 +48,6 @@ pub(super) fn check<'tcx>( Mutability::Not => "", Mutability::Mut => "_mut", }; - let arg = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr, - _ => arg, - }; if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) && let Some(arg_span) = walk_span_to_context(arg_span, span.ctxt()) diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 307b8fcec150..3979e668c2cc 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -397,7 +397,13 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { ExprKind::MethodCall(_, receiver, args, _) => { let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); for (ty, expr) in iter::zip( - self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().inputs().skip_binder(), + self.cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .inputs() + .skip_binder(), iter::once(receiver).chain(args.iter()), ) { self.prefer_mutable = false; diff --git a/clippy_lints/src/manual_assert_eq.rs b/clippy_lints/src/manual_assert_eq.rs new file mode 100644 index 000000000000..d1770d2e95a6 --- /dev/null +++ b/clippy_lints/src/manual_assert_eq.rs @@ -0,0 +1,122 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{PanicCall, find_assert_args, root_macro_call_first_node}; +use clippy_utils::source::walk_span_to_context; +use clippy_utils::ty::implements_trait; +use clippy_utils::{is_in_const_context, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `assert!` and `debug_assert!` that consist of only an (in)equality check + /// + /// ### Why is this bad? + /// `assert_{eq,ne}!` and `debug_assert_{eq,ne}!` achieves the same goal, and provides some + /// additional debug information + /// + /// ### Example + /// ```no_run + /// assert!(2 * 2 == 4); + /// assert!(2 * 2 != 5); + /// debug_assert!(2 * 2 == 4); + /// debug_assert!(2 * 2 != 5); + /// ``` + /// Use instead: + /// ```no_run + /// assert_eq!(2 * 2, 4); + /// assert_ne!(2 * 2, 5); + /// debug_assert_eq!(2 * 2, 4); + /// debug_assert_ne!(2 * 2, 5); + /// ``` + #[clippy::version = "1.97.0"] + pub MANUAL_ASSERT_EQ, + pedantic, + "checks for assertions consisting of an (in)equality check" +} + +declare_lint_pass!(ManualAssertEq => [MANUAL_ASSERT_EQ]); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum EqKind { + Eq, + Ne, +} + +impl EqKind { + fn postfix(self) -> &'static str { + match self { + Self::Eq => "_eq", + Self::Ne => "_ne", + } + } +} + +impl LateLintPass<'_> for ManualAssertEq { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(macro_call) = root_macro_call_first_node(cx, expr) + && let macro_name = match cx.tcx.get_diagnostic_name(macro_call.def_id) { + Some(sym::assert_macro) => "assert", + Some(sym::debug_assert_macro) => "debug_assert", + _ => return, + } + && !is_in_const_context(cx) + && let Some((cond, panic_expn)) = find_assert_args(cx, expr, macro_call.expn) + // Don't lint if the user has a painstakingly written assertion message + && !matches!(panic_expn, PanicCall::Display(_) | PanicCall::Format(_)) + && let ExprKind::Binary(op, lhs, rhs) = cond.kind + && let eq_kind = match op.node { + BinOpKind::Eq => EqKind::Eq, + BinOpKind::Ne => EqKind::Ne, + _ => return, + } + && !cond.span.from_expansion() + && let Some(debug_trait) = cx.tcx.get_diagnostic_item(sym::Debug) + && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let rhs_ty = cx.typeck_results().expr_ty(rhs) + // Can't print the values unless the types implement `Debug` + && implements_trait(cx, lhs_ty, debug_trait, &[]) + && implements_trait(cx, rhs_ty, debug_trait, &[]) + // Printing raw pointers isn't very useful + && !lhs_ty.is_raw_ptr() + && !rhs_ty.is_raw_ptr() + // The output of `(debug_)assert_eq` isn't very useful when one of the sides is a constant value + && if eq_kind == EqKind::Ne { + let ecx = ConstEvalCtxt::new(cx); + ecx.eval(lhs).is_none() && ecx.eval(rhs).is_none() + } else { + true + } + { + span_lint_and_then( + cx, + MANUAL_ASSERT_EQ, + macro_call.span, + format!("used `{macro_name}!` with an equality comparison"), + |diag| { + let postfix = eq_kind.postfix(); + let new_name = format_args!("{macro_name}{postfix}"); + let msg = format!("replace it with `{new_name}!(..)`"); + + let ctxt = cond.span.ctxt(); + if let Some(lhs_span) = walk_span_to_context(lhs.span, ctxt) + && let Some(rhs_span) = walk_span_to_context(rhs.span, ctxt) + { + let macro_name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let eq_span = cond.span.with_lo(lhs_span.hi()).with_hi(rhs_span.lo()); + let suggestions = vec![ + (macro_name_span.shrink_to_hi(), postfix.to_string()), + (eq_span, ", ".to_string()), + ]; + + diag.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable); + } else { + diag.span_help(expr.span, msg); + } + }, + ); + } + } +} diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index cb784d1ff660..d86b05e5c882 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -23,8 +23,20 @@ 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)) { - for arm in arms { - check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv); + let last_non_wildcard = arms.iter().rposition(|arm| !arm_is_wild_like(cx, arm)); + for (idx, arm) in arms.iter().enumerate() { + let only_wildcards_after = last_non_wildcard.is_none_or(|lnw| idx >= lnw); + check_arm( + cx, + true, + arm.pat, + expr, + arm.body, + arm.guard, + Some(els_arm.body), + msrv, + only_wildcards_after, + ); } } } @@ -37,7 +49,7 @@ pub(super) fn check_if_let<'tcx>( let_expr: &'tcx Expr<'_>, msrv: Msrv, ) { - check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); + check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv, false); } #[expect(clippy::too_many_arguments, clippy::too_many_lines)] @@ -50,6 +62,7 @@ fn check_arm<'tcx>( outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, msrv: Msrv, + only_wildcards_after: bool, ) { let inner_expr = peel_blocks_with_stmt(outer_then_body); if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr) @@ -126,6 +139,7 @@ fn check_arm<'tcx>( ); }); } else if outer_is_match // Leave if-let to the `collapsible_if` lint + && only_wildcards_after // adding a guard allows fall-through; unsafe if other arms follow && let Some(inner) = If::hir(inner_expr) && outer_pat.span.eq_ctxt(inner.cond.span) && match (outer_else_body, inner.r#else) { diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index f8520c23ea50..6390680c067c 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -128,6 +128,7 @@ fn lint_closure(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: } }); let (span, msg) = if can_sugg + && !suggs.is_empty() && let hir::ExprKind::MethodCall(segment, ..) = expr.kind && let Some(msg) = self.lint_msg(cx) { diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs index 8f18de41e8a3..48f2c10f97cc 100644 --- a/clippy_lints/src/methods/manual_ok_or.rs +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -20,7 +20,9 @@ pub(super) fn check<'tcx>( && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx .tcx - .type_of(impl_id).instantiate_identity().skip_norm_wip() + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() .is_diag_item(cx, sym::Option) && let ExprKind::Call(err_path, [err_arg]) = or_expr.kind && err_path.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs index 3e59e0a642cd..96b8b9dc0322 100644 --- a/clippy_lints/src/methods/map_err_ignore.rs +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -11,7 +11,9 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx .tcx - .type_of(impl_id).instantiate_identity().skip_norm_wip() + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() .is_diag_item(cx, sym::Result) && let ExprKind::Closure(&Closure { capture_clause: CaptureBy::Ref, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index cbcc99d264ff..4fdde52c327e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -155,7 +155,6 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::{contains_return, iter_input_pats, peel_blocks, sym}; -pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -165,6 +164,9 @@ use crate::matches::manual_filter; +pub use implicit_clone::is_clone_like; +pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; + declare_clippy_lint! { /// ### What it does /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` @@ -5222,7 +5224,6 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { format_collect::check(cx, expr, m_arg, m_ident_span); }, Some((sym::take, take_self_arg, [take_arg], _, _)) => { - #[expect(clippy::collapsible_match)] if self.msrv.meets(cx, msrvs::STR_REPEAT) { manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); } @@ -5547,9 +5548,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { (sym::open, [_]) => { open_options::check(cx, expr, recv); }, - (sym::or_else, [arg]) => - { - #[expect(clippy::collapsible_match)] + (sym::or_else, [arg]) => { if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) { unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); } @@ -5654,9 +5653,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { (sym::try_into, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::TryInto) => { unnecessary_fallible_conversions::check_method(cx, expr); }, - (sym::to_owned, []) => - { - #[expect(clippy::collapsible_match)] + (sym::to_owned, []) => { if !suspicious_to_owned::check(cx, expr, span) { implicit_clone::check(cx, name, expr, recv); } diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index b5809d304024..4f281d745a94 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -8,7 +8,6 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::{has_non_owning_mutable_access, make_normalized_projection, make_projection}; use clippy_utils::{CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, sym}; -use rustc_middle::ty::Unnormalized; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; @@ -17,7 +16,7 @@ }; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty}; +use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty, Unnormalized}; use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol}; @@ -248,9 +247,11 @@ fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: && let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, sym::Item, [collect_ty]) && let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions( cx.typing_env(), - Unnormalized::new_wip( - Ty::new_projection_from_args(cx.tcx, into_iter_item_proj.kind.def_id(), into_iter_item_proj.args) - ), + Unnormalized::new_wip(Ty::new_projection_from_args( + cx.tcx, + into_iter_item_proj.kind.def_id(), + into_iter_item_proj.args, + )), ) { iter_item_ty == into_iter_item_ty @@ -280,9 +281,14 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) - ) && let args = cx.tcx.mk_args(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))]) && let proj_ty = Ty::new_projection_from_args(cx.tcx, iter_item.def_id, args) - && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj_ty)) + && let Ok(item_ty) = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj_ty)) { - item_ty == EarlyBinder::bind(search_ty).instantiate(cx.tcx, cx.typeck_results().node_args(call_id)).skip_norm_wip() + item_ty + == EarlyBinder::bind(search_ty) + .instantiate(cx.tcx, cx.typeck_results().node_args(call_id)) + .skip_norm_wip() } else { false } diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index a5d88a49ab34..a07cd5a8925a 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -135,7 +135,13 @@ fn check_unwrap_or_default( let output_type_implements_default = |fun| { let fun_ty = cx.typeck_results().expr_ty(fun); if let ty::FnDef(def_id, args) = *fun_ty.kind() { - let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_norm_wip().skip_binder().output(); + let output_ty = cx + .tcx + .fn_sig(def_id) + .instantiate(cx.tcx, args) + .skip_norm_wip() + .skip_binder() + .output(); cx.tcx .get_diagnostic_item(sym::Default) .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints/src/methods/stable_sort_primitive.rs index 23a5ae2a866b..75b67f1bdd7d 100644 --- a/clippy_lints/src/methods/stable_sort_primitive.rs +++ b/clippy_lints/src/methods/stable_sort_primitive.rs @@ -10,7 +10,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_slice() + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() + .is_slice() && let Some(slice_type) = is_slice_of_primitives(cx, recv) { span_lint_and_then( diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 281d9e5f712f..391209d8c365 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -243,7 +243,12 @@ fn mapping_of_mirrored_pats_inner( fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) -> Option { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_slice() + && cx + .tcx + .type_of(impl_id) + .instantiate_identity() + .skip_norm_wip() + .is_slice() && let ExprKind::Closure(&Closure { body, .. }) = arg.kind && let closure_body = cx.tcx.hir_body(body) && let &[Param { pat: l_pat, .. }, Param { pat: r_pat, .. }] = closure_body.params diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index d696bdfa6bd4..a56dcd894b6a 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -569,7 +569,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< })); if trait_predicates.any(|predicate| { - let predicate = bound_fn_sig.rebind(predicate).instantiate(cx.tcx, new_subst).skip_norm_wip(); + let predicate = bound_fn_sig + .rebind(predicate) + .instantiate(cx.tcx, new_subst) + .skip_norm_wip(); let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); !cx.tcx .infer_ctxt() diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 012a148bb2a2..33346d867ffe 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -111,7 +111,12 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { ExprKind::MethodCall(.., args, _) => { if args.iter().all(|arg| !self.is_binding(arg)) && let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id) - && let method_ty = self.cx.tcx.type_of(method_def_id).instantiate_identity().skip_norm_wip() + && let method_ty = self + .cx + .tcx + .type_of(method_def_id) + .instantiate_identity() + .skip_norm_wip() && let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder() && matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)) { diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index 12a6f345168f..439a1af93cad 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -121,7 +121,7 @@ pub(super) fn check<'tcx>( format!("methods with the following characteristics: ({s})") } else { - format!("methods called {}", &conventions[0]) + format!("methods called {}", conventions[0]) } }; diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs index 102fa7bc8953..941afb9e2a98 100644 --- a/clippy_lints/src/methods/zst_offset.rs +++ b/clippy_lints/src/methods/zst_offset.rs @@ -1,13 +1,20 @@ use clippy_utils::diagnostics::span_lint; +use clippy_utils::res::MaybeDef; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty; +use rustc_span::sym; use super::ZST_OFFSET; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if let ty::RawPtr(ty, _) = cx.typeck_results().expr_ty(recv).kind() - && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(*ty)) + let recv_ty = cx.typeck_results().expr_ty(recv); + let pointee_ty = match recv_ty.kind() { + ty::RawPtr(ty, _) => *ty, + ty::Adt(_, args) if recv_ty.is_diag_item(cx, sym::NonNull) => args.type_at(0), + _ => return, + }; + if let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(pointee_ty)) && layout.is_zst() { span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index b9378d2cdd8c..4d4dc5678d7b 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -203,7 +203,13 @@ fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: Msrv, abi: ExternAbi) -> /// Return `true` when the given `def_id` is a function that has `impl Trait` ty as one of /// its parameter types. fn fn_inputs_has_impl_trait_ty(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { - let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().inputs().skip_binder(); + let inputs = cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .inputs() + .skip_binder(); inputs.iter().any(|input| { matches!( input.kind(), diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index befd50a5c85f..c1c5a0142719 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -1,12 +1,46 @@ +use clippy_utils::ast_utils::is_cfg_test; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; use rustc_ast::ast::{self, Inline, ItemKind, ModKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; -use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; +use rustc_span::{FileName, Ident, SourceFile, Span, SyntaxContext, sym}; use std::path::{Component, Path, PathBuf}; use std::sync::Arc; +declare_clippy_lint! { + /// ### What it does + /// Checks that module layout does not use inline modules. + /// Inline test modules (anything annotated with `#[cfg(test)]`) are not linted. + /// + /// ### Why restrict this? + /// Having multiple module layout styles in a project can be confusing. + /// + /// ### Known problems + /// The lint currently doesn't lint inline modules whose parent module is annotated + /// with the `#[path]` attribute. + /// + /// ### Example + /// ```ignore + /// // in `src/lib.rs` + /// mod foo { + /// /* module contents */ + /// } + /// ``` + /// Use instead: + /// ```ignore + /// // in `src/lib.rs` + /// mod foo; + /// // in `src/foo.rs` (or `src/foo/mod.rs`) + /// /* module contents */ + /// ``` + #[clippy::version = "1.97.0"] + pub INLINE_MODULES, + restriction, + "checks that module layout does not use inline modules" +} + declare_clippy_lint! { /// ### What it does /// Checks that module layout uses only self named module files; bans `mod.rs` files. @@ -65,18 +99,36 @@ "checks that module layout is consistent" } -impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]); +impl_lint_pass!(ModStyle => [ + INLINE_MODULES, + MOD_MODULE_FILES, + SELF_NAMED_MODULE_FILES, +]); pub struct ModState { + mod_file: Arc, + mod_ident: Ident, + path_from_working_dir: Option, contains_external: bool, has_path_attr: bool, - mod_file: Arc, + is_cfg_test: bool, } #[derive(Default)] pub struct ModStyle { working_dir: Option, - module_stack: Vec, + regular_mod_stack: Vec, + inline_mod_stack: Vec, +} + +impl ModStyle { + fn inside_cfg_test_inline_mod(&self) -> bool { + self.inline_mod_stack.last().is_some_and(|last| last.is_cfg_test) + } + + fn get_relative_path_from_working_dir(&self, file: &SourceFile) -> Option { + try_trim_file_path_prefix(file, self.working_dir.as_ref()?).map(Path::to_path_buf) + } } impl EarlyLintPass for ModStyle { @@ -87,45 +139,83 @@ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(INLINE_MODULES).level == Level::Allow { return; } - if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, mod_spans, ..)) = &item.kind { + if let ItemKind::Mod(_, mod_ident, ModKind::Loaded(_, inline_or_not, mod_spans, ..)) = &item.kind { let has_path_attr = item.attrs.iter().any(|attr| attr.has_name(sym::path)); - if !has_path_attr && let Some(current) = self.module_stack.last_mut() { - current.contains_external = true; - } let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo()); - self.module_stack.push(ModState { + let path_from_working_dir = self.get_relative_path_from_working_dir(&mod_file); + let current = ModState { + mod_file, + mod_ident: *mod_ident, + path_from_working_dir, contains_external: false, has_path_attr, - mod_file, - }); + is_cfg_test: self.inside_cfg_test_inline_mod() || is_cfg_test(item), + }; + match inline_or_not { + Inline::Yes => { + if !current.is_cfg_test + && !item.span.from_expansion() + && self.regular_mod_stack.last().is_none_or(|last| !last.has_path_attr) + && let Some(path) = ¤t.path_from_working_dir + { + let opt_extra_mod_dir = self.regular_mod_stack.last().and_then(|last| { + if last.path_from_working_dir.as_ref()?.ends_with("mod.rs") { + None + } else { + Some(&last.mod_ident) + } + }); + check_inline_module( + cx, + path, + *mod_ident, + item.span, + opt_extra_mod_dir + .into_iter() + .chain(self.inline_mod_stack.iter().map(|state| &state.mod_ident)), + ); + } + self.inline_mod_stack.push(current); + }, + Inline::No { .. } => { + if !has_path_attr && let Some(last) = self.regular_mod_stack.last_mut() { + last.contains_external = true; + } + self.regular_mod_stack.push(current); + }, + } } } fn check_item_post(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(INLINE_MODULES).level == Level::Allow { return; } - if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, ..)) = &item.kind - && let Some(current) = self.module_stack.pop() - && !current.has_path_attr - { - let Some(path) = self - .working_dir - .as_ref() - .and_then(|src| try_trim_file_path_prefix(¤t.mod_file, src)) - else { - return; - }; - if current.contains_external { - check_self_named_module(cx, path, ¤t.mod_file); + if let ItemKind::Mod(.., ModKind::Loaded(_, inline_or_not, ..)) = &item.kind { + match inline_or_not { + Inline::Yes => { + self.inline_mod_stack.pop(); + }, + Inline::No { .. } => { + if let Some(current) = self.regular_mod_stack.pop() + && let Some(path) = ¤t.path_from_working_dir + && !current.has_path_attr + { + if current.contains_external { + check_self_named_module(cx, path, ¤t.mod_file); + } + check_mod_module(cx, path, ¤t.mod_file); + } + }, } - check_mod_module(cx, path, ¤t.mod_file); } } } @@ -173,6 +263,31 @@ fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { } } +fn check_inline_module<'a>( + cx: &EarlyContext<'_>, + path: &Path, + mod_ident: Ident, + mod_span: Span, + ancestor_mods: impl Iterator, +) { + let Some(parent) = path.parent() else { return }; + + span_lint_and_then(cx, INLINE_MODULES, mod_span, "inline module found", |diag| { + let mut mod_folder = parent.to_path_buf(); + mod_folder.extend(ancestor_mods.map(Ident::as_str)); + let mod_name = mod_ident.as_str(); + + let mod_file = mod_folder.join(mod_name).join("mod.rs"); + let self_named_mod_file = mod_folder.join(format!("{mod_name}.rs")); + let outlined_mod = snippet(cx, mod_span.with_hi(mod_ident.span.hi()), ""); + diag.help(format!( + "move the contents of the module to `{}` or `{}`, and replace this with `{outlined_mod};`", + self_named_mod_file.display(), + mod_file.display(), + )); + }); +} + fn try_trim_file_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> { if let FileName::Real(name) = &file.name && let Some(mut path) = name.local_path() diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 5c0ed6e4aec9..40db810c1284 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -5,7 +5,6 @@ use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_n_hir_expr_refs, sym}; -use rustc_middle::ty::Unnormalized; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -15,7 +14,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::{ - self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty, + self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty, Unnormalized, }; use rustc_session::impl_lint_pass; use rustc_span::SyntaxContext; @@ -180,7 +179,12 @@ fn needless_borrow_count<'tcx>( let meta_sized_trait_def_id = cx.tcx.lang_items().meta_sized_trait(); let drop_trait_def_id = cx.tcx.lang_items().drop_trait(); - let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_norm_wip().skip_binder(); + let fn_sig = cx + .tcx + .fn_sig(fn_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); let predicates = cx.tcx.param_env(fn_id).caller_bounds(); let projection_predicates = predicates .iter() @@ -281,7 +285,9 @@ fn needless_borrow_count<'tcx>( return false; } - let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty[..]).skip_norm_wip(); + let predicate = EarlyBinder::bind(predicate) + .instantiate(cx.tcx, &args_with_referent_ty[..]) + .skip_norm_wip(); let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); infcx.predicate_must_hold_modulo_regions(&obligation) @@ -430,10 +436,10 @@ fn replace_types<'tcx>( .expect_ty(cx.tcx) .to_ty(cx.tcx); - if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions( - cx.typing_env(), - Unnormalized::new_wip(projection), - ) && args[term_param_ty.index as usize] != GenericArg::from(projected_ty) + if let Ok(projected_ty) = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(projection)) + && args[term_param_ty.index as usize] != GenericArg::from(projected_ty) { deque.push_back((*term_param_ty, projected_ty)); } diff --git a/clippy_lints/src/needless_ifs.rs b/clippy_lints/src/needless_ifs.rs index ad1da9441dac..cdf9bd91339c 100644 --- a/clippy_lints/src/needless_ifs.rs +++ b/clippy_lints/src/needless_ifs.rs @@ -55,7 +55,7 @@ fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { // - comments // - #[cfg]'d out code src.bytes() - .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace()) + .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace() || ch == b'\x0b') }) && let Some(cond_span) = walk_span_to_context(cond.span, expr.span.ctxt()) && let Some(cond_snippet) = cond_span.get_source_text(cx) diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index ac3cf0250d0f..600e4beba7d2 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -92,7 +92,9 @@ fn search(cx: &LateContext<'_>, path: &mut Vec) -> bool { return true; } - for (predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id) + for (predicate, _) in cx + .tcx + .explicit_super_predicates_of(trait_def_id) .iter_identity_copied() .map(Unnormalized::skip_norm_wip) { diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index a06497e140ba..82095e361e0e 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -24,7 +24,6 @@ use clippy_utils::paths::{PathNS, lookup_path_str}; use clippy_utils::ty::{get_field_idx_by_name, implements_trait}; use clippy_utils::{is_in_const_context, sym}; -use rustc_middle::ty::Unnormalized; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdSet}; @@ -37,7 +36,7 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, DerefAdjustKind}; use rustc_middle::ty::{ self, EarlyBinder, GenericArgs, GenericArgsRef, Instance, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeckResults, - TypingEnv, + TypingEnv, Unnormalized, }; use rustc_session::impl_lint_pass; use rustc_span::DUMMY_SP; @@ -278,7 +277,9 @@ pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self { /// Checks if a value of the given type is `Freeze`, or may be depending on the value. fn is_ty_freeze(&mut self, tcx: TyCtxt<'tcx>, typing_env: TypingEnv<'tcx>, ty: Ty<'tcx>) -> IsFreeze { // FIXME: this should probably be using the trait solver - let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + let ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); match self.freeze_tys.entry(ty) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { @@ -346,7 +347,9 @@ fn is_value_freeze( ty: Ty<'tcx>, val: ConstValue, ) -> Result { - let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + let ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); match self.is_ty_freeze(tcx, typing_env, ty) { IsFreeze::Yes => Ok(true), IsFreeze::Maybe if matches!(ty.kind(), ty::Adt(..) | ty::Array(..) | ty::Tuple(..)) => { @@ -382,7 +385,9 @@ fn is_init_expr_freeze( ) -> bool { // Make sure to instantiate all types coming from `typeck` with `gen_args`. let ty = EarlyBinder::bind(typeck.expr_ty(e)).instantiate(tcx, gen_args); - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty.skip_norm_wip()); + let ty = tcx + .try_normalize_erasing_regions(typing_env, ty) + .unwrap_or(ty.skip_norm_wip()); match self.is_ty_freeze(tcx, typing_env, ty) { IsFreeze::Yes => true, IsFreeze::No => false, @@ -396,7 +401,9 @@ fn is_init_expr_freeze( }, ExprKind::Path(ref p) => { let res = typeck.qpath_res(p, e.hir_id); - let gen_args = EarlyBinder::bind(typeck.node_args(e.hir_id)).instantiate(tcx, gen_args).skip_norm_wip(); + let gen_args = EarlyBinder::bind(typeck.node_args(e.hir_id)) + .instantiate(tcx, gen_args) + .skip_norm_wip(); match res { Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, did) if let Ok(val) = @@ -448,7 +455,9 @@ fn is_non_freeze_expr_borrowed( loop { let ty = typeck.expr_ty(src_expr); // Normalized as we need to check if this is an array later. - let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + let ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); let is_freeze = self.is_ty_freeze(tcx, typing_env, ty); if is_freeze.is_freeze() { return None; @@ -489,7 +498,9 @@ fn is_non_freeze_val_borrowed( let mut ty = typeck.expr_ty(src_expr); loop { // Normalized as we need to check if this is an array later. - ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); if let [adjust, ..] = typeck.expr_adjustments(src_expr) { let res = if let Some(cause) = does_adjust_borrow(adjust) && !self.is_value_freeze(tcx, typing_env, ty, val)? @@ -588,10 +599,9 @@ fn is_non_freeze_init_borrowed( init_expr = next_init; }, ExprKind::Path(ref init_path) => { - let next_init_args = - EarlyBinder::bind(init_typeck.node_args(init_expr.hir_id)) - .instantiate(tcx, init_args) - .skip_norm_wip(); + let next_init_args = EarlyBinder::bind(init_typeck.node_args(init_expr.hir_id)) + .instantiate(tcx, init_args) + .skip_norm_wip(); match init_typeck.qpath_res(init_path, init_expr.hir_id) { Res::Def(DefKind::Ctor(..), _) => return None, Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, did) @@ -627,7 +637,9 @@ fn is_non_freeze_init_borrowed( // gets cached. let ty = typeck.expr_ty(src_expr); // Normalized as we need to check if this is an array later. - let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); + let ty = tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) + .unwrap_or(ty); if self.is_ty_freeze(tcx, typing_env, ty).is_freeze() { return None; } @@ -798,7 +810,11 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) let ty = (ReplaceAssocFolder { tcx: cx.tcx, trait_id, - self_ty: cx.tcx.type_of(parent_item.owner_id).instantiate_identity().skip_norm_wip(), + self_ty: cx + .tcx + .type_of(parent_item.owner_id) + .instantiate_identity() + .skip_norm_wip(), }) .fold_ty(cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip()); // `ty` may not be normalizable, but that should be fine. diff --git a/clippy_lints/src/operators/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs index 104f786ead16..1607d23e6a33 100644 --- a/clippy_lints/src/operators/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -43,7 +43,8 @@ fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, } if let Some(mask) = fetch_int_literal(cx, right).or_else(|| fetch_int_literal(cx, left)) { let ty = cx.typeck_results().expr_ty(bit_op); - if !ty.is_ptr_sized_integral() + if ty.is_primitive() + && !ty.is_ptr_sized_integral() && let bits = ty.primitive_size(cx.tcx) { // Strip high bits that don't fit into the result type as they won't be used in the comparison diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 832b12712f83..2e0beb2d4e82 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -283,7 +283,13 @@ fn is_assoc_fn_without_type_instance<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<' .. }, )) = func.kind - && let output_ty = cx.tcx.fn_sig(*def_id).instantiate_identity().skip_norm_wip().skip_binder().output() + && let output_ty = cx + .tcx + .fn_sig(*def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder() + .output() && let ty::Param(ty::ParamTy { name: kw::SelfUpper, .. }) = output_ty.kind() diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs index 5a4823ddfcf6..304c51ba2627 100644 --- a/clippy_lints/src/operators/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -177,11 +177,7 @@ fn build_suggestion( // suggestion message, we want to make a suggestion string before `div_ceil` like // `(-2048_{type_suffix})`. let suggestion_before_div_ceil = if has_enclosing_paren(÷nd_sugg_str) { - format!( - "{}{})", - ÷nd_sugg_str[..dividend_sugg_str.len() - 1].to_string(), - type_suffix - ) + format!("{}{type_suffix})", ÷nd_sugg_str[..dividend_sugg_str.len() - 1]) } else { format!("{dividend_sugg_str}{type_suffix}") }; diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs index ea1be8899b29..40bc42dcdcb3 100644 --- a/clippy_lints/src/ptr/ptr_arg.rs +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -36,7 +36,12 @@ pub(super) fn check_body<'tcx>( } let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_norm_wip().skip_binder(); + let sig = cx + .tcx + .fn_sig(item_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) .collect(); @@ -68,7 +73,11 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, s for arg in check_fn_args( cx, - cx.tcx.fn_sig(item_id).instantiate_identity().skip_norm_wip().skip_binder(), + cx.tcx + .fn_sig(item_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(), sig.decl.inputs, &[], ) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 4bd6b1696b35..f07cc10cbced 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -495,7 +495,7 @@ fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { let mut sugg = reindent_multiline(&arm_body_snippet, true, Some(indent)); let binding_snippet = snippet_with_applicability(cx, binding_span, "..", &mut applicability); let inner_indent = " ".repeat(indent + 4); - if matches!(arm_body.kind, ExprKind::Block(..)) { + if matches!(arm_body.kind, ExprKind::Block(..)) && sugg.starts_with('{') { sugg.insert_str( 1, &format!("\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;"), diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 3c0f04a8e87c..be5e4190e128 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -3,13 +3,12 @@ use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::peel_and_count_ty_refs; -use rustc_middle::ty::Unnormalized; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{GenericArg, Ty}; +use rustc_middle::ty::{GenericArg, Ty, Unnormalized}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -146,8 +145,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { Unnormalized::new_wip(Ty::new_projection_from_args( cx.tcx, target_id, - cx.tcx.mk_args(&[GenericArg::from(indexed_ty)]) - )) + cx.tcx.mk_args(&[GenericArg::from(indexed_ty)]), + )), ) && deref_ty == expr_ty { diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 074345e75321..f1cf505482e4 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -58,7 +58,7 @@ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx, AmbigArg>) { ty.span, "since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>`", "try", - format!("Option<{}>", &snippet(cx, inner_ty.span, "..")), + format!("Option<{}>", snippet(cx, inner_ty.span, "..")), Applicability::MaybeIncorrect, ); } diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 12ce31896968..53cac75b802f 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -2,7 +2,6 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; -use rustc_middle::ty::Unnormalized; use rustc_ast::BindingMode; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; @@ -10,7 +9,7 @@ use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{self as hir, HirId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{GenericArgKind, Ty}; +use rustc_middle::ty::{GenericArgKind, Ty, Unnormalized}; use rustc_session::impl_lint_pass; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span}; @@ -154,7 +153,8 @@ fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { } let ty = self .cx - .tcx.try_normalize_erasing_regions(self.cx.typing_env(), Unnormalized::new_wip(ty)) + .tcx + .try_normalize_erasing_regions(self.cx.typing_env(), Unnormalized::new_wip(ty)) .unwrap_or(ty); match self.type_cache.entry(ty) { Entry::Occupied(e) => return *e.get(), diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index e4faf8e82a8e..13ff3914f8e0 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -136,7 +136,9 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tc .. }) = segments.first() && let Some(Node::Item(Item { - kind: ItemKind::Trait {bounds: self_bounds,..}, + kind: ItemKind::Trait { + bounds: self_bounds, .. + }, .. })) = cx.tcx.hir_get_if_local(*def_id) { diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index d5a9006095da..05f407140647 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -1,10 +1,9 @@ use super::TRANSMUTE_UNDEFINED_REPR; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_c_void; -use rustc_middle::ty::Unnormalized; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgsRef, IntTy, Ty, UintTy}; +use rustc_middle::ty::{self, GenericArgsRef, IntTy, Ty, UintTy, Unnormalized}; #[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( @@ -241,7 +240,10 @@ enum ReducedTy<'tcx> { /// Reduce structs containing a single non-zero sized field to it's contained type. fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> { loop { - ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)).unwrap_or(ty); + ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) + .unwrap_or(ty); return match *ty.kind() { ty::Pat(base, _) => { ty = base; @@ -298,7 +300,9 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> } fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) + if let Ok(ty) = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(ty)) { layout.layout.size().bytes() == 0 diff --git a/clippy_lints/src/transmute/utils.rs b/clippy_lints/src/transmute/utils.rs index 74494aff0cb6..0306e6c2015b 100644 --- a/clippy_lints/src/transmute/utils.rs +++ b/clippy_lints/src/transmute/utils.rs @@ -1,13 +1,16 @@ -use rustc_middle::ty::Unnormalized; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, Unnormalized}; // check if the component types of the transmuted collection and the result have different ABI, // size or alignment pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { let typing_env = cx.typing_env(); - if let Ok(from) = cx.tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(from)) - && let Ok(to) = cx.tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(to)) + if let Ok(from) = cx + .tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(from)) + && let Ok(to) = cx + .tcx + .try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(to)) && let Ok(from_layout) = cx.tcx.layout_of(typing_env.as_query_input(from)) && let Ok(to_layout) = cx.tcx.layout_of(typing_env.as_query_input(to)) { diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index 973b2e95b474..c48ca652ffd1 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -231,9 +231,8 @@ fn fmt_stmts_and_call( let block_indent = call_expr_indent + 4; stmts_and_call_snippet = reindent_multiline(&stmts_and_call_snippet, true, Some(block_indent)); stmts_and_call_snippet = format!( - "{{\n{}{}\n{}}}", + "{{\n{}{stmts_and_call_snippet}\n{}}}", " ".repeat(block_indent), - &stmts_and_call_snippet, " ".repeat(call_expr_indent) ); } diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index 0b9546843612..15dc40af947e 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -131,7 +131,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) { return; } match it.kind { - ItemKind::TyAlias(ident, ..) | ItemKind::Struct(ident, ..) | ItemKind::Trait { ident, .. }=> { + ItemKind::TyAlias(ident, ..) | ItemKind::Struct(ident, ..) | ItemKind::Trait { ident, .. } => { check_ident(cx, &ident, it.hir_id(), self.upper_case_acronyms_aggressive); }, ItemKind::Enum(ident, _, ref enumdef) => { diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 0174b90064b5..d6db088ba76c 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -6,7 +6,7 @@ use clippy_utils::{get_parent_expr, is_ty_alias, sym}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, PatKind}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, LangItem, MatchSource, Mutability, Node, PatKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::Obligation; use rustc_lint::{LateContext, LateLintPass}; @@ -19,7 +19,7 @@ declare_clippy_lint! { /// ### What it does - /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls + /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` calls /// which uselessly convert to the same type. /// /// ### Why is this bad? @@ -38,7 +38,7 @@ #[clippy::version = "1.45.0"] pub USELESS_CONVERSION, complexity, - "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type" + "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` which perform useless conversions to the same type" } impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); @@ -322,13 +322,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { return; } - let a = cx.typeck_results().expr_ty(e); - let b = cx.typeck_results().expr_ty(recv); + let iter_ty = cx.typeck_results().expr_ty(e); + let into_iter_ty = cx.typeck_results().expr_ty(recv); // If the types are identical then .into_iter() can be removed, unless the type // implements Copy, in which case .into_iter() returns a copy of the receiver and // cannot be safely omitted. - if same_type_modulo_regions(a, b) && !is_copy(cx, b) { + if same_type_modulo_regions(iter_ty, into_iter_ty) && !is_copy(cx, into_iter_ty) { // Below we check if the parent method call meets the following conditions: // 1. First parameter is `&mut self` (requires mutable reference) // 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any) @@ -356,6 +356,28 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { return; } + // In a future edition of Rust (edition 2027, hopefully), or with the unstable + // `feature(new_range)`, the syntax `a..b` will change from producing type `core::ops::Range`, + // which implements `Iterator`, to producing type `core::range::Range`, which implements + // `IntoIterator` only. + // + // Therefore, an `(a..b).into_iter()` call that is technically useless today will be useful for + // edition migration or unstable feature testing; do not remove it. + // + // In the future, after most code has either migrated to the new range types or declined to + // do so, this special case will be much less useful and could be removed. + if let Some(parent) = get_parent_expr(cx, e) + // Is a method call, not, say, a for loop where the conversion *is* useless. + && let ExprKind::MethodCall(_, _, _, _) = parent.kind + // These lang items are the 3 core::ops range types that implement Iterator. + // All other range types do not implement Iterator, so this lint does not apply to them. + && (into_iter_ty.is_lang_item(cx, LangItem::Range) + || into_iter_ty.is_lang_item(cx, LangItem::RangeFrom) + || into_iter_ty.is_lang_item(cx, LangItem::RangeInclusiveStruct)) + { + return; + } + let mut applicability = Applicability::MachineApplicable; let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "", &mut applicability) .0 @@ -364,7 +386,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { cx, USELESS_CONVERSION, e.span, - format!("useless conversion to the same type: `{b}`"), + format!("useless conversion to the same type: `{into_iter_ty}`"), "consider removing `.into_iter()`", sugg, applicability, diff --git a/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs index b048a1004b0d..bbcef0856ba4 100644 --- a/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -232,8 +232,8 @@ fn suggest_help( "this call is collapsible", "collapse into", format!( - "span_lint_and_help({}, {}, {}, {}, {}, {help})", - and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span, + "span_lint_and_help({}, {}, {}, {}, {option_span}, {help})", + and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg ), app, ); diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs index a968629ac2dc..1225f42d5602 100644 --- a/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/clippy_lints_internal/src/msrv_attr_impl.rs @@ -26,13 +26,19 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { items, .. }) = &item.kind - && let trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity().skip_norm_wip() + && let trait_ref = cx + .tcx + .impl_trait_ref(item.owner_id) + .instantiate_identity() + .skip_norm_wip() && internal_paths::EARLY_LINT_PASS.matches(cx, trait_ref.def_id) && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind() && self_ty_def.is_struct() && self_ty_def.all_fields().any(|f| { cx.tcx - .type_of(f.did).instantiate_identity().skip_norm_wip() + .type_of(f.did) + .instantiate_identity() + .skip_norm_wip() .walk() .filter(|t| matches!(t.kind(), GenericArgKind::Type(_))) .any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty())) diff --git a/clippy_lints_internal/src/unusual_names.rs b/clippy_lints_internal/src/unusual_names.rs index b10f4be7497b..b68ebed713fc 100644 --- a/clippy_lints_internal/src/unusual_names.rs +++ b/clippy_lints_internal/src/unusual_names.rs @@ -65,11 +65,14 @@ fn check_fn( if matches!(kind, FnKind::Closure) { return; } - for (param, ty) in body - .params - .iter() - .zip(cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder().inputs()) - { + for (param, ty) in body.params.iter().zip( + cx.tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder() + .inputs(), + ) { check_pat_name_for_ty(cx, param.pat, *ty, "parameter"); } } diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 99489cb11e73..de7afb7cf13a 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2026-04-16 +nightly-2026-04-30 ``` diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 82142d55e21d..7b408471574f 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -5,7 +5,8 @@ #![allow(clippy::wildcard_imports, clippy::enum_glob_use)] use crate::{both, over}; -use rustc_ast::{self as ast, *}; +use rustc_ast::{self as ast, HasAttrs, *}; +use rustc_span::sym; use rustc_span::symbol::Ident; use std::mem; @@ -1044,3 +1045,17 @@ pub fn eq_delim_args(l: &DelimArgs, r: &DelimArgs) -> bool { && l.tokens.len() == r.tokens.len() && l.tokens.iter().zip(r.tokens.iter()).all(|(a, b)| a.eq_unspanned(b)) } + +/// Checks whether `#[cfg(test)]` is directly applied to `item`. +pub fn is_cfg_test(item: &impl HasAttrs) -> bool { + item.attrs().iter().any(|attr| { + if attr.has_name(sym::cfg) + && let Some(item_list) = attr.meta_item_list() + && item_list.iter().any(|item| item.has_name(sym::test)) + { + true + } else { + false + } + }) +} diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index e1382f5b706c..55c0372259c0 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -265,14 +265,18 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Struct(_, _, VariantData::Struct { .. }) => (Pat::Str("struct"), Pat::Str("}")), ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")), ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")), - ItemKind::Trait { safety: Safety::Unsafe, .. } + ItemKind::Trait { + safety: Safety::Unsafe, .. + } | ItemKind::Impl(Impl { of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), .. }) => (Pat::Str("unsafe"), Pat::Str("}")), - ItemKind::Trait { is_auto: IsAuto::Yes, .. } => (Pat::Str("auto"), Pat::Str("}")), + ItemKind::Trait { + is_auto: IsAuto::Yes, .. + } => (Pat::Str("auto"), Pat::Str("}")), ItemKind::Trait { .. } => (Pat::Str("trait"), Pat::Str("}")), ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")), ItemKind::Mod(..) => (Pat::Str("mod"), Pat::Str("")), diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index 0e28cdf3b9ca..cd609980b149 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -71,7 +71,12 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: // Due to the limited operations on these types functions should be fairly cheap. if def.variants().iter().flat_map(|v| v.fields.iter()).any(|x| { matches!( - cx.tcx.type_of(x.did).instantiate_identity().skip_norm_wip().peel_refs().kind(), + cx.tcx + .type_of(x.did) + .instantiate_identity() + .skip_norm_wip() + .peel_refs() + .kind(), ty::Param(_) ) }) && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() { @@ -82,7 +87,9 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: // Limit the function to either `(self) -> bool` or `(&self) -> bool` match &**cx .tcx - .fn_sig(fn_id).instantiate_identity().skip_norm_wip() + .fn_sig(fn_id) + .instantiate_identity() + .skip_norm_wip() .skip_binder() .inputs_and_output { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e5420f9f9d16..4e912bcaa9b7 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -548,7 +548,12 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< if let QPath::TypeRelative(_, method) = path && method.ident.name == sym::new && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) - && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().skip_norm_wip().ty_adt_def() + && let Some(adt) = cx + .tcx + .type_of(impl_did) + .instantiate_identity() + .skip_norm_wip() + .ty_adt_def() { return Some(adt.did()) == cx.tcx.lang_items().string() || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name)); @@ -1489,7 +1494,12 @@ pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> { /// Convenience function to get the nth argument type of a function. pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> { - let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip().input(nth); + let arg = cx + .tcx + .fn_sig(fn_def_id) + .instantiate_identity() + .skip_norm_wip() + .input(nth); cx.tcx.instantiate_bound_regions_with_erased(arg) } diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 8f7a140e91a8..41cac00e7004 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -50,7 +50,12 @@ pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Ms // impl trait is gone in MIR, so check the return type manually check_ty( cx, - cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().output().skip_binder(), + cx.tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_norm_wip() + .output() + .skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, msrv, )?; diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index a5d17b76aa5e..92f08b604ca5 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -896,7 +896,14 @@ fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir .cx .typeck_results() .type_dependent_def_id(parent_expr.hir_id) - .map(|did| self.cx.tcx.fn_sig(did).instantiate_identity().skip_norm_wip().skip_binder()) + .map(|did| { + self.cx + .tcx + .fn_sig(did) + .instantiate_identity() + .skip_norm_wip() + .skip_binder() + }) { std::iter::once(receiver) .chain(call_args.iter()) diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 87aac25f5bd1..d04257067be3 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -91,9 +91,7 @@ macro_rules! generate { MIN, MaybeDef, MpmcReceiver, - MpmcSender, MpscReceiver, - MpscSender, MsrvStack, Octal, OpenOptions, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 66d85b58eaae..4ab21f34de60 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -21,8 +21,7 @@ use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, BoundVarIndexKind, FnSig, GenericArg, GenericArgKind, GenericArgsRef, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, - Unnormalized, + TypeVisitableExt, TypeVisitor, UintTy, Unnormalized, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol}; @@ -111,7 +110,9 @@ fn contains_ty_adt_constructor_opaque_inner<'tcx>( return false; } - for (predicate, _span) in cx.tcx.explicit_item_self_bounds(def_id) + for (predicate, _span) in cx + .tcx + .explicit_item_self_bounds(def_id) .iter_identity_copied() .map(Unnormalized::skip_norm_wip) { @@ -610,7 +611,10 @@ pub fn predicates_id(&self) -> Option { /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = expr.res(cx) { - Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip(), Some(id))) + Some(ExprFnSig::Sig( + cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip(), + Some(id), + )) } else { ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) } @@ -628,7 +632,10 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate(cx.tcx, subs).skip_norm_wip(), Some(id))), + ty::FnDef(id, subs) => Some(ExprFnSig::Sig( + cx.tcx.fn_sig(id).instantiate(cx.tcx, subs).skip_norm_wip(), + Some(id), + )), ty::Alias(AliasTy { kind: ty::Opaque { def_id }, args, @@ -636,7 +643,10 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option sig_from_bounds( cx, ty, - cx.tcx.item_self_bounds(def_id).iter_instantiated(cx.tcx, args).map(Unnormalized::skip_norm_wip), + cx.tcx + .item_self_bounds(def_id) + .iter_instantiated(cx.tcx, args) + .map(Unnormalized::skip_norm_wip), cx.tcx.opt_parent(def_id), ), ty::FnPtr(sig_tys, hdr) => Some(ExprFnSig::Sig(sig_tys.with(hdr), None)), @@ -662,7 +672,10 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) { + ) => match cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) + { Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None)), }, @@ -892,7 +905,13 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option< Some((adt, adt.variant_with_id(var_id))) }, Res::SelfCtor(id) => { - let adt = cx.tcx.type_of(id).instantiate_identity().skip_norm_wip().ty_adt_def().unwrap(); + let adt = cx + .tcx + .type_of(id) + .instantiate_identity() + .skip_norm_wip() + .ty_adt_def() + .unwrap(); Some((adt, adt.non_enum_variant())) }, _ => None, @@ -1074,7 +1093,7 @@ fn helper<'tcx>(tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, ty: AliasTy< } match tcx.try_normalize_erasing_regions( typing_env, - Unnormalized::new_wip(Ty::new_projection_from_args(tcx, ty.kind.def_id(), ty.args)) + Unnormalized::new_wip(Ty::new_projection_from_args(tcx, ty.kind.def_id(), ty.args)), ) { Ok(ty) => Some(ty), Err(e) => { @@ -1180,7 +1199,10 @@ fn interior_mut_ty_chain_inner( ty::Alias(AliasTy { kind: ty::Projection { .. }, .. - }) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) { + }) => match cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) + { Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain_inner(cx, normalized_ty, depth), _ => None, }, @@ -1323,7 +1345,9 @@ pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>) -> bool { fn normalize_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)).unwrap_or(ty) + cx.tcx + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) + .unwrap_or(ty) } /// Check if `ty` contains mutable references or equivalent, which includes: diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index b30df7902378..6a9e6cab8957 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -85,12 +85,12 @@ fn run_clippy_lints( if config.max_jobs == 1 { println!( "{index}/{total_crates_to_lint} {perc}% Linting {} {}", - &self.name, &self.version + self.name, self.version ); } else { println!( "{index}/{total_crates_to_lint} {perc}% Linting {} {} in target dir {thread_index:?}", - &self.name, &self.version + self.name, self.version ); } diff --git a/lintcheck/src/output.rs b/lintcheck/src/output.rs index 1ecc3f7c2494..dfba1804642e 100644 --- a/lintcheck/src/output.rs +++ b/lintcheck/src/output.rs @@ -228,8 +228,8 @@ fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, us // remove duplicates from both hashmaps for (k, v) in &same_in_both_hashmaps { - assert!(old_stats_deduped.remove(k) == Some(*v)); - assert!(new_stats_deduped.remove(k) == Some(*v)); + assert_eq!(old_stats_deduped.remove(k), Some(*v)); + assert_eq!(new_stats_deduped.remove(k), Some(*v)); } println!("\nStats:"); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 97c8cf260cad..9992299153e2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2026-04-16" +channel = "nightly-2026-04-30" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui-cargo/module_style/inline_mod/Cargo.stderr b/tests/ui-cargo/module_style/inline_mod/Cargo.stderr new file mode 100644 index 000000000000..e3f18e37db9a --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/Cargo.stderr @@ -0,0 +1,71 @@ +error: inline module found + --> src/other.rs:1:1 + | +1 | mod foo {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/other/foo.rs` or `src/other/foo/mod.rs`, and replace this with `mod foo;` + = note: `-D clippy::inline-modules` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::inline_modules)]` + +error: inline module found + --> src/qux/foo.rs:1:1 + | +1 | mod bar {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/qux/foo/bar.rs` or `src/qux/foo/bar/mod.rs`, and replace this with `mod bar;` + +error: inline module found + --> src/lib.rs:9:1 + | + 9 | / pub mod test_nested_inline_mods { +10 | | mod bar { +11 | | mod baz {} +12 | | } +13 | | } + | |_^ + | + = help: move the contents of the module to `src/test_nested_inline_mods.rs` or `src/test_nested_inline_mods/mod.rs`, and replace this with `pub mod test_nested_inline_mods;` + +error: inline module found + --> src/lib.rs:10:5 + | +10 | / mod bar { +11 | | mod baz {} +12 | | } + | |_____^ + | + = help: move the contents of the module to `src/test_nested_inline_mods/bar.rs` or `src/test_nested_inline_mods/bar/mod.rs`, and replace this with `mod bar;` + +error: inline module found + --> src/lib.rs:11:9 + | +11 | mod baz {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/test_nested_inline_mods/bar/baz.rs` or `src/test_nested_inline_mods/bar/baz/mod.rs`, and replace this with `mod baz;` + +error: inline module found + --> src/lib.rs:20:1 + | +20 | / mod partially_escaped_test_mod { +21 | | #[cfg(test)] +22 | | mod tests { +23 | | mod bar {} +24 | | } +25 | | mod baz {} +26 | | } + | |_^ + | + = help: move the contents of the module to `src/partially_escaped_test_mod.rs` or `src/partially_escaped_test_mod/mod.rs`, and replace this with `mod partially_escaped_test_mod;` + +error: inline module found + --> src/lib.rs:25:5 + | +25 | mod baz {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/partially_escaped_test_mod/baz.rs` or `src/partially_escaped_test_mod/baz/mod.rs`, and replace this with `mod baz;` + +error: could not compile `inline-mod` (lib) due to 7 previous errors diff --git a/tests/ui-cargo/module_style/inline_mod/Cargo.toml b/tests/ui-cargo/module_style/inline_mod/Cargo.toml new file mode 100644 index 000000000000..4108d8aaa5c0 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "inline-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/inline_mod/src/foo.rs b/tests/ui-cargo/module_style/inline_mod/src/foo.rs new file mode 100644 index 000000000000..9e19e6e44fdd --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/foo.rs @@ -0,0 +1,2 @@ +/// Lint shouldn't fire because parent mod has a path attribute. +mod foo {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/lib.rs b/tests/ui-cargo/module_style/inline_mod/src/lib.rs new file mode 100644 index 000000000000..df5816e251dc --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/lib.rs @@ -0,0 +1,34 @@ +#![warn(clippy::inline_modules)] + +pub mod other; +#[path = "qux/mod.rs"] +pub mod something; +#[path = "foo.rs"] +pub mod stuff; + +pub mod test_nested_inline_mods { + mod bar { + mod baz {} + } +} + +#[cfg(test)] +mod escaped_test_mod { + mod bar {} +} + +mod partially_escaped_test_mod { + #[cfg(test)] + mod tests { + mod bar {} + } + mod baz {} +} + +macro_rules! inline_mod_from_expansion { + () => { + mod _foo {} + }; +} + +inline_mod_from_expansion!(); diff --git a/tests/ui-cargo/module_style/inline_mod/src/other.rs b/tests/ui-cargo/module_style/inline_mod/src/other.rs new file mode 100644 index 000000000000..e5736aedfdc7 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/other.rs @@ -0,0 +1 @@ +mod foo {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs b/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs new file mode 100644 index 000000000000..e4cdd207c663 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs @@ -0,0 +1 @@ +mod bar {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs b/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs new file mode 100644 index 000000000000..f4ad3bff5c97 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs @@ -0,0 +1 @@ +mod foo; diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index 1c49b6e6b7b1..44bcc5c724e9 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -1,4 +1,5 @@ #![allow(non_fmt_panics, clippy::needless_bool, clippy::eq_op)] +#![expect(clippy::manual_assert_eq)] macro_rules! assert_const { ($len:expr) => { diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index a996c41b6942..abace40735ed 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -1,5 +1,5 @@ error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:10:5 + --> tests/ui/assertions_on_constants.rs:11:5 | LL | assert!(true); | ^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | assert!(true); = help: to override `-D warnings` add `#[allow(clippy::assertions_on_constants)]` error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:13:5 + --> tests/ui/assertions_on_constants.rs:14:5 | LL | assert!(false); | ^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | assert!(false); = help: replace this with `panic!()` or `unreachable!()` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:16:5 + --> tests/ui/assertions_on_constants.rs:17:5 | LL | assert!(true, "true message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | assert!(true, "true message"); = help: remove the assertion error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:19:5 + --> tests/ui/assertions_on_constants.rs:20:5 | LL | assert!(false, "false message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | assert!(false, "false message"); = help: replace this with `panic!()` or `unreachable!()` error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:23:5 + --> tests/ui/assertions_on_constants.rs:24:5 | LL | assert!(false, "{}", msg.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | assert!(false, "{}", msg.to_uppercase()); = help: replace this with `panic!()` or `unreachable!()` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:27:5 + --> tests/ui/assertions_on_constants.rs:28:5 | LL | assert!(B); | ^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | assert!(B); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:31:5 + --> tests/ui/assertions_on_constants.rs:32:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -57,7 +57,7 @@ LL | assert!(C); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:34:5 + --> tests/ui/assertions_on_constants.rs:35:5 | LL | assert!(C, "C message"); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | assert!(C, "C message"); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:37:5 + --> tests/ui/assertions_on_constants.rs:38:5 | LL | debug_assert!(true); | ^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL | debug_assert!(true); = help: remove the assertion error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:45:5 + --> tests/ui/assertions_on_constants.rs:46:5 | LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,7 +81,7 @@ LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:54:19 + --> tests/ui/assertions_on_constants.rs:55:19 | LL | const _: () = assert!(true); | ^^^^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | const _: () = assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:57:5 + --> tests/ui/assertions_on_constants.rs:58:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:68:5 + --> tests/ui/assertions_on_constants.rs:69:5 | LL | assert!(true); | ^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:71:5 + --> tests/ui/assertions_on_constants.rs:72:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -113,7 +113,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:79:5 + --> tests/ui/assertions_on_constants.rs:80:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -121,7 +121,7 @@ LL | assert!(C); = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:90:5 + --> tests/ui/assertions_on_constants.rs:91:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -129,7 +129,7 @@ LL | assert!(C); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:96:5 + --> tests/ui/assertions_on_constants.rs:97:5 | LL | assert!(C); | ^^^^^^^^^^ diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed index a1c4cb5a4823..b1c83970ae9f 100644 --- a/tests/ui/bind_instead_of_map.fixed +++ b/tests/ui/bind_instead_of_map.fixed @@ -26,3 +26,9 @@ pub fn foo() -> Option { pub fn example2(x: bool) -> Option<&'static str> { Some("a").and_then(|s| Some(if x { s } else { return None })) } + +fn issue16861(b: bool, res: Result) { + let _: Result = res.or_else(|_| panic!("should not happen")); + let _: Result = res.map_err(|_| if b { panic!("should not happen") } else { 42 }); + //~^ bind_instead_of_map +} diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs index 1308fa9f416b..65e7e490a890 100644 --- a/tests/ui/bind_instead_of_map.rs +++ b/tests/ui/bind_instead_of_map.rs @@ -26,3 +26,9 @@ pub fn foo() -> Option { pub fn example2(x: bool) -> Option<&'static str> { Some("a").and_then(|s| Some(if x { s } else { return None })) } + +fn issue16861(b: bool, res: Result) { + let _: Result = res.or_else(|_| panic!("should not happen")); + let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); + //~^ bind_instead_of_map +} diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr index 08f85fb58549..1e2b93adffad 100644 --- a/tests/ui/bind_instead_of_map.stderr +++ b/tests/ui/bind_instead_of_map.stderr @@ -22,5 +22,17 @@ error: using `Result.and_then(Ok)`, which is a no-op LL | let _ = x.and_then(Ok); | ^^^^^^^^^^^^^^ help: use the expression directly: `x` -error: aborting due to 3 previous errors +error: using `Result.or_else(|x| Err(y))`, which is more succinctly expressed as `map_err(|x| y)` + --> tests/ui/bind_instead_of_map.rs:32:31 + | +LL | let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_err` instead + | +LL - let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); +LL + let _: Result = res.map_err(|_| if b { panic!("should not happen") } else { 42 }); + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/bit_masks.rs b/tests/ui/bit_masks.rs index bca5b2ec34e1..30dd8f912acf 100644 --- a/tests/ui/bit_masks.rs +++ b/tests/ui/bit_masks.rs @@ -99,3 +99,25 @@ fn signed(x: i8) -> bool { x & 0x70 == 0x11 << 4 } } + +mod issue16935 { + struct Wrapper(usize); + + impl std::ops::BitAnd for Wrapper { + type Output = Self; + + fn bitand(self, rhs: usize) -> Self::Output { + Self(self.0 & rhs) + } + } + + impl PartialEq for Wrapper { + fn eq(&self, other: &usize) -> bool { + self.0.eq(other) + } + } + + fn check(value: Wrapper) -> bool { + value & 0x1 != 0 + } +} diff --git a/tests/ui/cloned_ref_to_slice_refs.fixed b/tests/ui/cloned_ref_to_slice_refs.fixed index 818c6e23259e..5b39a3153809 100644 --- a/tests/ui/cloned_ref_to_slice_refs.fixed +++ b/tests/ui/cloned_ref_to_slice_refs.fixed @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_deref_ref)] #![warn(clippy::cloned_ref_to_slice_refs)] #[derive(Clone)] @@ -7,18 +8,18 @@ fn main() { { let data = Data; let data_ref = &data; - let _ = std::slice::from_ref(data_ref); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(data_ref); //~ cloned_ref_to_slice_refs } { - let _ = std::slice::from_ref(&Data); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(&Data); //~ cloned_ref_to_slice_refs } { #[derive(Clone)] struct Point(i32, i32); - let _ = std::slice::from_ref(&Point(0, 0)); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(&Point(0, 0)); //~ cloned_ref_to_slice_refs } // the string was cloned with the intention to not mutate @@ -62,3 +63,76 @@ fn main() { let _ = &[data_1.clone(), data_2.clone()]; } } + +fn issue16320(items: &[String]) { + use std::ffi::OsString; + use std::ops::Deref; + use std::path::PathBuf; + + let _a = String::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + let _c = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = OsString::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = PathBuf::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = &PathBuf::new(); + let _b = std::slice::from_ref(_a); + //~^ cloned_ref_to_slice_refs + + #[derive(Clone)] + struct A(i32); + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + let a = A(42); + _ = &[a.to_string()]; + + struct Wrapper(T); + impl Deref for Wrapper { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let w = Wrapper(String::from("hello")); + let w = Wrapper(w); + let _b = std::slice::from_ref(&**w); + //~^ cloned_ref_to_slice_refs + + let w = Wrapper(&PathBuf::new()); + let w = Wrapper(w); + let _b = std::slice::from_ref(&***w); + //~^ cloned_ref_to_slice_refs +} + +fn wrongly_unmangled_macros(items: &[String]) { + use std::path::PathBuf; + + struct Wrapper { + inner: PathBuf, + } + + let _a = Wrapper { inner: PathBuf::new() }; + + macro_rules! accessor { + ($e:expr) => { + $e.inner + }; + } + + let _d = std::slice::from_ref(&accessor!(_a)); + //~^ cloned_ref_to_slice_refs +} diff --git a/tests/ui/cloned_ref_to_slice_refs.rs b/tests/ui/cloned_ref_to_slice_refs.rs index 9517dbfd1569..ce9f9a115117 100644 --- a/tests/ui/cloned_ref_to_slice_refs.rs +++ b/tests/ui/cloned_ref_to_slice_refs.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_deref_ref)] #![warn(clippy::cloned_ref_to_slice_refs)] #[derive(Clone)] @@ -7,18 +8,18 @@ fn main() { { let data = Data; let data_ref = &data; - let _ = &[data_ref.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[data_ref.clone()]; //~ cloned_ref_to_slice_refs } { - let _ = &[Data.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[Data.clone()]; //~ cloned_ref_to_slice_refs } { #[derive(Clone)] struct Point(i32, i32); - let _ = &[Point(0, 0).clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[Point(0, 0).clone()]; //~ cloned_ref_to_slice_refs } // the string was cloned with the intention to not mutate @@ -62,3 +63,76 @@ fn main() { let _ = &[data_1.clone(), data_2.clone()]; } } + +fn issue16320(items: &[String]) { + use std::ffi::OsString; + use std::ops::Deref; + use std::path::PathBuf; + + let _a = String::new(); + let _b = &[_a.to_owned()]; + //~^ cloned_ref_to_slice_refs + let _c = &[_a.to_string()]; + //~^ cloned_ref_to_slice_refs + + let _a = OsString::new(); + let _b = &[_a.to_os_string()]; + //~^ cloned_ref_to_slice_refs + + let _a = PathBuf::new(); + let _b = &[_a.to_path_buf()]; + //~^ cloned_ref_to_slice_refs + + let _a = &PathBuf::new(); + let _b = &[_a.to_path_buf()]; + //~^ cloned_ref_to_slice_refs + + #[derive(Clone)] + struct A(i32); + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + let a = A(42); + _ = &[a.to_string()]; + + struct Wrapper(T); + impl Deref for Wrapper { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let w = Wrapper(String::from("hello")); + let w = Wrapper(w); + let _b = &[w.to_string()]; + //~^ cloned_ref_to_slice_refs + + let w = Wrapper(&PathBuf::new()); + let w = Wrapper(w); + let _b = &[w.to_path_buf()]; + //~^ cloned_ref_to_slice_refs +} + +fn wrongly_unmangled_macros(items: &[String]) { + use std::path::PathBuf; + + struct Wrapper { + inner: PathBuf, + } + + let _a = Wrapper { inner: PathBuf::new() }; + + macro_rules! accessor { + ($e:expr) => { + $e.inner + }; + } + + let _d = &[accessor!(_a).to_path_buf()]; + //~^ cloned_ref_to_slice_refs +} diff --git a/tests/ui/cloned_ref_to_slice_refs.stderr b/tests/ui/cloned_ref_to_slice_refs.stderr index 6a31d8782395..a37438147b52 100644 --- a/tests/ui/cloned_ref_to_slice_refs.stderr +++ b/tests/ui/cloned_ref_to_slice_refs.stderr @@ -1,5 +1,5 @@ -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:10:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:11:17 | LL | let _ = &[data_ref.clone()]; | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(data_ref)` @@ -7,17 +7,65 @@ LL | let _ = &[data_ref.clone()]; = note: `-D clippy::cloned-ref-to-slice-refs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cloned_ref_to_slice_refs)]` -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:14:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:15:17 | LL | let _ = &[Data.clone()]; | ^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Data)` -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:21:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:22:17 | LL | let _ = &[Point(0, 0).clone()]; | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Point(0, 0))` -error: aborting due to 3 previous errors +error: unnecessary use of `to_owned` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:73:14 + | +LL | let _b = &[_a.to_owned()]; + | ^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:75:14 + | +LL | let _c = &[_a.to_string()]; + | ^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_os_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:79:14 + | +LL | let _b = &[_a.to_os_string()]; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:83:14 + | +LL | let _b = &[_a.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:87:14 + | +LL | let _b = &[_a.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(_a)` + +error: unnecessary use of `to_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:112:14 + | +LL | let _b = &[w.to_string()]; + | ^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&**w)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:117:14 + | +LL | let _b = &[w.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&***w)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:136:14 + | +LL | let _d = &[accessor!(_a).to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&accessor!(_a))` + +error: aborting due to 11 previous errors diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 4a0ee439e94a..1b49549e9712 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -1,4 +1,5 @@ #![warn(clippy::cmp_null)] +#![allow(clippy::manual_assert_eq)] use std::ptr; diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 26ea8960e5fb..35de3a14afc6 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -1,4 +1,5 @@ #![warn(clippy::cmp_null)] +#![allow(clippy::manual_assert_eq)] use std::ptr; diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 51b98d2a2320..9831b4f6eb32 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:8:8 + --> tests/ui/cmp_null.rs:9:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,37 +8,37 @@ LL | if p == ptr::null() { = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:13:8 + --> tests/ui/cmp_null.rs:14:8 | LL | if ptr::null() == p { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:21:8 + --> tests/ui/cmp_null.rs:22:8 | LL | if m == ptr::null_mut() { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:26:8 + --> tests/ui/cmp_null.rs:27:8 | LL | if ptr::null_mut() == m { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:32:13 + --> tests/ui/cmp_null.rs:33:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:38:19 + --> tests/ui/cmp_null.rs:39:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:56:8 + --> tests/ui/cmp_null.rs:57:8 | LL | if dot_value!(x) == ptr::null() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dot_value!(x).is_null()` diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs index 98f2fcfdf479..51cb0168c735 100644 --- a/tests/ui/collapsible_match.rs +++ b/tests/ui/collapsible_match.rs @@ -390,6 +390,26 @@ fn take(t: T) {} fn main() {} +// https://github.com/rust-lang/rust-clippy/issues/16875 +// Adding a match guard allows fall-through to subsequent arms, which changes semantics +// when non-wildcard arms follow the arm being collapsed. +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + // should NOT lint: `_ if b == 1` is not wild-like (has a guard), so collapsing + // `Some(_)` into `Some(_) if b == 0` would let `_ if b == 1` match Some values + // that previously fell through to the no-op arm body. + match a { + Some(_) => { + if b == 0 { + res = 1; + } + }, + _ if b == 1 => res = 2, + _ => {}, + } + res +} + fn issue16705(x: Option) { fn takes_ownership(s: String) -> bool { true diff --git a/tests/ui/collapsible_match_fixable.fixed b/tests/ui/collapsible_match_fixable.fixed index db76530aee14..8e5934d3693a 100644 --- a/tests/ui/collapsible_match_fixable.fixed +++ b/tests/ui/collapsible_match_fixable.fixed @@ -28,3 +28,18 @@ fn issue16558() { _ => 1, }; } + +// https://github.com/rust-lang/rust-clippy/issues/16875 +// lint still fires when only wildcard-like arms follow (fall-through is harmless) +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + match a { + Some(_) + if b == 0 => { + //~^ collapsible_match + res = 1; + }, + _ => {}, + } + res +} diff --git a/tests/ui/collapsible_match_fixable.rs b/tests/ui/collapsible_match_fixable.rs index 94bf1d6bfdfa..e2ccae455961 100644 --- a/tests/ui/collapsible_match_fixable.rs +++ b/tests/ui/collapsible_match_fixable.rs @@ -29,3 +29,19 @@ fn issue16558() { _ => 1, }; } + +// https://github.com/rust-lang/rust-clippy/issues/16875 +// lint still fires when only wildcard-like arms follow (fall-through is harmless) +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + match a { + Some(_) => { + if b == 0 { + //~^ collapsible_match + res = 1; + } + }, + _ => {}, + } + res +} diff --git a/tests/ui/collapsible_match_fixable.stderr b/tests/ui/collapsible_match_fixable.stderr index 4d501cbd0993..0be47076fa47 100644 --- a/tests/ui/collapsible_match_fixable.stderr +++ b/tests/ui/collapsible_match_fixable.stderr @@ -46,5 +46,23 @@ LL | LL ~ , | -error: aborting due to 3 previous errors +error: this `if` can be collapsed into the outer `match` + --> tests/ui/collapsible_match_fixable.rs:39:13 + | +LL | / if b == 0 { +LL | | +LL | | res = 1; +LL | | } + | |_____________^ + | +help: collapse nested if block + | +LL ~ Some(_) +LL ~ if b == 0 => { +LL | +LL | res = 1; +LL ~ }, + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 6c29630dc3a5..35149755a34b 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -3,14 +3,15 @@ #![allow(unused_variables, unused_must_use)] #![allow( clippy::borrow_deref_ref, - suspicious_double_ref_op, - noop_method_call, + clippy::deref_addrof, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, + clippy::useless_borrows_in_formatting, clippy::uninlined_format_args, clippy::unnecessary_literal_unwrap, - clippy::deref_addrof + noop_method_call, + suspicious_double_ref_op )] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index f6309cd404b8..215c5450597f 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -3,14 +3,15 @@ #![allow(unused_variables, unused_must_use)] #![allow( clippy::borrow_deref_ref, - suspicious_double_ref_op, - noop_method_call, + clippy::deref_addrof, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, + clippy::useless_borrows_in_formatting, clippy::uninlined_format_args, clippy::unnecessary_literal_unwrap, - clippy::deref_addrof + noop_method_call, + suspicious_double_ref_op )] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index e2f2e68720b1..d7e41e4409fa 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:58:19 + --> tests/ui/explicit_deref_methods.rs:59:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try: `&*a` @@ -8,73 +8,73 @@ LL | let b: &str = a.deref(); = help: to override `-D warnings` add `#[allow(clippy::explicit_deref_methods)]` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:61:23 + --> tests/ui/explicit_deref_methods.rs:62:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try: `&mut **a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:65:39 + --> tests/ui/explicit_deref_methods.rs:66:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:65:50 + --> tests/ui/explicit_deref_methods.rs:66:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:69:20 + --> tests/ui/explicit_deref_methods.rs:70:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:73:11 + --> tests/ui/explicit_deref_methods.rs:74:11 | LL | match a.deref() { | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:78:28 + --> tests/ui/explicit_deref_methods.rs:79:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:81:13 + --> tests/ui/explicit_deref_methods.rs:82:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:84:28 + --> tests/ui/explicit_deref_methods.rs:85:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:124:31 + --> tests/ui/explicit_deref_methods.rs:125:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:154:14 + --> tests/ui/explicit_deref_methods.rs:155:14 | LL | let _ = &Deref::deref(&"foo"); | ^^^^^^^^^^^^^^^^^^^^ help: try: `*&"foo"` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:156:14 + --> tests/ui/explicit_deref_methods.rs:157:14 | LL | let _ = &DerefMut::deref_mut(&mut x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut **&mut x` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:157:14 + --> tests/ui/explicit_deref_methods.rs:158:14 | LL | let _ = &DerefMut::deref_mut((&mut &mut x).deref_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut ***(&mut &mut x)` diff --git a/tests/ui/for_kv_map.fixed b/tests/ui/for_kv_map.fixed index 6ec4cb01ffd1..2fd6d9ca499d 100644 --- a/tests/ui/for_kv_map.fixed +++ b/tests/ui/for_kv_map.fixed @@ -86,3 +86,25 @@ fn wrongly_unmangled_macros() { let _v = v; } } + +fn issue16822(mut x: HashMap) { + for v in x.values() { + //~^ for_kv_map + println!("{}", v); + } + + for v in x.values_mut() { + //~^ for_kv_map + *v += 1; + } + + for k in x.keys() { + //~^ for_kv_map + println!("{}", k); + } + + for k in x.keys() { + //~^ for_kv_map + println!("{}", k); + } +} diff --git a/tests/ui/for_kv_map.rs b/tests/ui/for_kv_map.rs index 19e907ff10a6..12e16d1dbfd6 100644 --- a/tests/ui/for_kv_map.rs +++ b/tests/ui/for_kv_map.rs @@ -86,3 +86,25 @@ macro_rules! test_map { let _v = v; } } + +fn issue16822(mut x: HashMap) { + for (_, v) in x.iter() { + //~^ for_kv_map + println!("{}", v); + } + + for (_, v) in x.iter_mut() { + //~^ for_kv_map + *v += 1; + } + + for (k, _) in x.iter() { + //~^ for_kv_map + println!("{}", k); + } + + for (k, _) in x.iter_mut() { + //~^ for_kv_map + println!("{}", k); + } +} diff --git a/tests/ui/for_kv_map.stderr b/tests/ui/for_kv_map.stderr index 5436592f2ab6..fac0407e1003 100644 --- a/tests/ui/for_kv_map.stderr +++ b/tests/ui/for_kv_map.stderr @@ -84,5 +84,53 @@ LL - for (_, v) in test_map!(wrapped) { LL + for v in test_map!(wrapped).values() { | -error: aborting due to 7 previous errors +error: you seem to want to iterate on a map's values + --> tests/ui/for_kv_map.rs:91:19 + | +LL | for (_, v) in x.iter() { + | ^^^^^^^^ + | +help: use the corresponding method + | +LL - for (_, v) in x.iter() { +LL + for v in x.values() { + | + +error: you seem to want to iterate on a map's values + --> tests/ui/for_kv_map.rs:96:19 + | +LL | for (_, v) in x.iter_mut() { + | ^^^^^^^^^^^^ + | +help: use the corresponding method + | +LL - for (_, v) in x.iter_mut() { +LL + for v in x.values_mut() { + | + +error: you seem to want to iterate on a map's keys + --> tests/ui/for_kv_map.rs:101:19 + | +LL | for (k, _) in x.iter() { + | ^^^^^^^^ + | +help: use the corresponding method + | +LL - for (k, _) in x.iter() { +LL + for k in x.keys() { + | + +error: you seem to want to iterate on a map's keys + --> tests/ui/for_kv_map.rs:106:19 + | +LL | for (k, _) in x.iter_mut() { + | ^^^^^^^^^^^^ + | +help: use the corresponding method + | +LL - for (k, _) in x.iter_mut() { +LL + for k in x.keys() { + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/from_over_into.fixed b/tests/ui/from_over_into.fixed index 7229e5a2d358..3eed0ad52e9a 100644 --- a/tests/ui/from_over_into.fixed +++ b/tests/ui/from_over_into.fixed @@ -116,4 +116,24 @@ fn issue_112502() { } } +fn issue_16823() { + pub struct Foo(pub String); + + impl From for Foo + where + String: From, + { + fn from(val: T) -> Self { + Self(String::from(val)) + } + } + + // no lint, From for String would conflict with the blanket impl above + impl Into for Foo { + fn into(self) -> String { + self.0 + } + } +} + fn main() {} diff --git a/tests/ui/from_over_into.rs b/tests/ui/from_over_into.rs index 9c75969c5c13..b4f58df0a437 100644 --- a/tests/ui/from_over_into.rs +++ b/tests/ui/from_over_into.rs @@ -116,4 +116,24 @@ fn into(self: MyInt) -> i64 { } } +fn issue_16823() { + pub struct Foo(pub String); + + impl From for Foo + where + String: From, + { + fn from(val: T) -> Self { + Self(String::from(val)) + } + } + + // no lint, From for String would conflict with the blanket impl above + impl Into for Foo { + fn into(self) -> String { + self.0 + } + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index 88e3328d662e..c0f0087b374c 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -295,7 +295,7 @@ fn panic_like_macros_1() { } fn panic_like_macros_2() { - let mut x = 0; + let mut x: i32 = 0; loop { do_something(); @@ -310,7 +310,7 @@ fn panic_like_macros_2() { } loop { do_something(); - assert!(x % 2 == 0); + assert!(x.is_positive()); } loop { do_something(); diff --git a/tests/ui/manual_assert_eq.fixed b/tests/ui/manual_assert_eq.fixed new file mode 100644 index 000000000000..175c33e27192 --- /dev/null +++ b/tests/ui/manual_assert_eq.fixed @@ -0,0 +1,114 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::manual_assert_eq)] +#![allow(clippy::manual_ignore_case_cmp)] // only raised before the fix +#![expect(clippy::eq_op, clippy::assertions_on_constants)] + +fn main() { + let a = "a"; + assert_eq!(a, "a".to_ascii_lowercase()); + //~^ manual_assert_eq + assert_ne!(a, "a".to_ascii_uppercase()); + //~^ manual_assert_eq + debug_assert_eq!(a, "a".to_ascii_lowercase()); + //~^ manual_assert_eq + debug_assert_ne!(a, "a".to_ascii_uppercase()); + //~^ manual_assert_eq + + // macros + let v = vec![]; + assert_eq!(v, vec![1, 2, 3]); + //~^ manual_assert_eq + assert_eq!(vec![1, 2, 3], v); + //~^ manual_assert_eq + assert_eq!(vec![1], vec![1, 2, 3]); + //~^ manual_assert_eq + + // Don't lint: has assert message + assert!(a == "a".to_ascii_lowercase(), "{a}"); + assert!(a == "a".to_ascii_lowercase(), "a==a"); + assert!(a == "a".to_ascii_lowercase(), "{a}==a"); + assert!(a != "a".to_ascii_uppercase(), "a!=A"); + debug_assert!(a == "a".to_ascii_lowercase(), "a==a"); + debug_assert!(a != "a".to_ascii_uppercase(), "a!=A"); + + // Don't lint: `!=`, and at least one of the sides is a constant value + assert!(a != "A"); + assert!("A" != a); + assert!("A" != "A"); + + // Don't lint: comparison of ptrs + fn cmp_ptrs(a: *const u8, b: *const u8) { + assert!(a == b); + } + + // Don't lint: one of the sides isn't `Debug` + { + #[derive(PartialEq)] + struct NotDebug; + + #[derive(PartialEq)] + struct NotDebug2; + + impl PartialEq for NotDebug { + fn eq(&self, other: &NotDebug2) -> bool { + unimplemented!() + } + } + impl PartialEq for NotDebug2 { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + #[derive(Debug)] + struct IsDebug; + + impl PartialEq for NotDebug { + fn eq(&self, other: &IsDebug) -> bool { + unimplemented!() + } + } + impl PartialEq for IsDebug { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + let nd = NotDebug; + assert!(nd == nd); + + let nd2 = NotDebug2; + assert!(nd == nd2); + assert!(nd2 == nd); + + let id = IsDebug; + assert!(id == nd); + assert!(nd == id); + } + + // Don't lint: in const context + const { + assert!(5 == 2 + 3); + } + + // Don't lint: in external macro + { + // NOTE: this only works because `root_macro_call_first_node` returns `external!`, + // which then gets rejected by the macro name check + proc_macros::external!(assert!('a' == 'b')); + proc_macros::external!({ + let some_padding_before = 'a'; + assert!('a' == 'b'); + let some_padding_after = 'b'; + }); + + // .. which also means that the following is _technically_ a FN -- but surely no one would write + // code like this (diverging/unit expression as a child expression of a macro call) + vec![(), assert!('a' == 'b'), ()]; + } +} + +// Don't lint: in const context +const _: () = { + assert!(8 == (7 + 1)); +}; diff --git a/tests/ui/manual_assert_eq.rs b/tests/ui/manual_assert_eq.rs new file mode 100644 index 000000000000..0df5518bd354 --- /dev/null +++ b/tests/ui/manual_assert_eq.rs @@ -0,0 +1,114 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::manual_assert_eq)] +#![allow(clippy::manual_ignore_case_cmp)] // only raised before the fix +#![expect(clippy::eq_op, clippy::assertions_on_constants)] + +fn main() { + let a = "a"; + assert!(a == "a".to_ascii_lowercase()); + //~^ manual_assert_eq + assert!(a != "a".to_ascii_uppercase()); + //~^ manual_assert_eq + debug_assert!(a == "a".to_ascii_lowercase()); + //~^ manual_assert_eq + debug_assert!(a != "a".to_ascii_uppercase()); + //~^ manual_assert_eq + + // macros + let v = vec![]; + assert!(v == vec![1, 2, 3]); + //~^ manual_assert_eq + assert!(vec![1, 2, 3] == v); + //~^ manual_assert_eq + assert!(vec![1] == vec![1, 2, 3]); + //~^ manual_assert_eq + + // Don't lint: has assert message + assert!(a == "a".to_ascii_lowercase(), "{a}"); + assert!(a == "a".to_ascii_lowercase(), "a==a"); + assert!(a == "a".to_ascii_lowercase(), "{a}==a"); + assert!(a != "a".to_ascii_uppercase(), "a!=A"); + debug_assert!(a == "a".to_ascii_lowercase(), "a==a"); + debug_assert!(a != "a".to_ascii_uppercase(), "a!=A"); + + // Don't lint: `!=`, and at least one of the sides is a constant value + assert!(a != "A"); + assert!("A" != a); + assert!("A" != "A"); + + // Don't lint: comparison of ptrs + fn cmp_ptrs(a: *const u8, b: *const u8) { + assert!(a == b); + } + + // Don't lint: one of the sides isn't `Debug` + { + #[derive(PartialEq)] + struct NotDebug; + + #[derive(PartialEq)] + struct NotDebug2; + + impl PartialEq for NotDebug { + fn eq(&self, other: &NotDebug2) -> bool { + unimplemented!() + } + } + impl PartialEq for NotDebug2 { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + #[derive(Debug)] + struct IsDebug; + + impl PartialEq for NotDebug { + fn eq(&self, other: &IsDebug) -> bool { + unimplemented!() + } + } + impl PartialEq for IsDebug { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + let nd = NotDebug; + assert!(nd == nd); + + let nd2 = NotDebug2; + assert!(nd == nd2); + assert!(nd2 == nd); + + let id = IsDebug; + assert!(id == nd); + assert!(nd == id); + } + + // Don't lint: in const context + const { + assert!(5 == 2 + 3); + } + + // Don't lint: in external macro + { + // NOTE: this only works because `root_macro_call_first_node` returns `external!`, + // which then gets rejected by the macro name check + proc_macros::external!(assert!('a' == 'b')); + proc_macros::external!({ + let some_padding_before = 'a'; + assert!('a' == 'b'); + let some_padding_after = 'b'; + }); + + // .. which also means that the following is _technically_ a FN -- but surely no one would write + // code like this (diverging/unit expression as a child expression of a macro call) + vec![(), assert!('a' == 'b'), ()]; + } +} + +// Don't lint: in const context +const _: () = { + assert!(8 == (7 + 1)); +}; diff --git a/tests/ui/manual_assert_eq.stderr b/tests/ui/manual_assert_eq.stderr new file mode 100644 index 000000000000..0694df01a8f8 --- /dev/null +++ b/tests/ui/manual_assert_eq.stderr @@ -0,0 +1,88 @@ +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:8:5 + | +LL | assert!(a == "a".to_ascii_lowercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-assert-eq` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_assert_eq)]` +help: replace it with `assert_eq!(..)` + | +LL - assert!(a == "a".to_ascii_lowercase()); +LL + assert_eq!(a, "a".to_ascii_lowercase()); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:10:5 + | +LL | assert!(a != "a".to_ascii_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_ne!(..)` + | +LL - assert!(a != "a".to_ascii_uppercase()); +LL + assert_ne!(a, "a".to_ascii_uppercase()); + | + +error: used `debug_assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:12:5 + | +LL | debug_assert!(a == "a".to_ascii_lowercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert_eq!(..)` + | +LL - debug_assert!(a == "a".to_ascii_lowercase()); +LL + debug_assert_eq!(a, "a".to_ascii_lowercase()); + | + +error: used `debug_assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:14:5 + | +LL | debug_assert!(a != "a".to_ascii_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert_ne!(..)` + | +LL - debug_assert!(a != "a".to_ascii_uppercase()); +LL + debug_assert_ne!(a, "a".to_ascii_uppercase()); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:19:5 + | +LL | assert!(v == vec![1, 2, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(v == vec![1, 2, 3]); +LL + assert_eq!(v, vec![1, 2, 3]); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:21:5 + | +LL | assert!(vec![1, 2, 3] == v); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(vec![1, 2, 3] == v); +LL + assert_eq!(vec![1, 2, 3], v); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:23:5 + | +LL | assert!(vec![1] == vec![1, 2, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(vec![1] == vec![1, 2, 3]); +LL + assert_eq!(vec![1], vec![1, 2, 3]); + | + +error: aborting due to 7 previous errors + diff --git a/tests/ui/missing_asserts_for_indexing.fixed b/tests/ui/missing_asserts_for_indexing.fixed index 50bc576dd1e2..f877cbc1f114 100644 --- a/tests/ui/missing_asserts_for_indexing.fixed +++ b/tests/ui/missing_asserts_for_indexing.fixed @@ -1,4 +1,4 @@ -#![allow(unused)] +#![expect(clippy::manual_assert_eq)] #![warn(clippy::missing_asserts_for_indexing)] // ok diff --git a/tests/ui/missing_asserts_for_indexing.rs b/tests/ui/missing_asserts_for_indexing.rs index 9e219a2af073..8084e0b71be9 100644 --- a/tests/ui/missing_asserts_for_indexing.rs +++ b/tests/ui/missing_asserts_for_indexing.rs @@ -1,4 +1,4 @@ -#![allow(unused)] +#![expect(clippy::manual_assert_eq)] #![warn(clippy::missing_asserts_for_indexing)] // ok diff --git a/tests/ui/needless_ifs.fixed b/tests/ui/needless_ifs.fixed index 0e0b0fa39c9b..89d5b1da7b3f 100644 --- a/tests/ui/needless_ifs.fixed +++ b/tests/ui/needless_ifs.fixed @@ -13,7 +13,6 @@ unused )] #![warn(clippy::needless_ifs)] - extern crate proc_macros; use proc_macros::{external, with_span}; @@ -113,3 +112,12 @@ fn issue15960() -> i32 { 1 // put something here so that `if` is a statement not an expression } + +#[rustfmt::skip] +fn issue_16845() { + + // Vertical tab (U+000B) should be treated as whitespace, + + //~^ needless_ifs + let () = if maybe_side_effect() {}; +} diff --git a/tests/ui/needless_ifs.rs b/tests/ui/needless_ifs.rs index fb0ee5c9cc83..837eaaf9647b 100644 --- a/tests/ui/needless_ifs.rs +++ b/tests/ui/needless_ifs.rs @@ -13,7 +13,6 @@ unused )] #![warn(clippy::needless_ifs)] - extern crate proc_macros; use proc_macros::{external, with_span}; @@ -114,3 +113,12 @@ fn issue15960() -> i32 { 1 // put something here so that `if` is a statement not an expression } + +#[rustfmt::skip] +fn issue_16845() { + + // Vertical tab (U+000B) should be treated as whitespace, + if true { } + //~^ needless_ifs + let () = if maybe_side_effect() {}; +} diff --git a/tests/ui/needless_ifs.stderr b/tests/ui/needless_ifs.stderr index 8684ec217a0a..7c7fcdc18366 100644 --- a/tests/ui/needless_ifs.stderr +++ b/tests/ui/needless_ifs.stderr @@ -1,5 +1,5 @@ error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:26:5 + --> tests/ui/needless_ifs.rs:25:5 | LL | if (true) {} | ^^^^^^^^^^^^ help: you can remove it @@ -8,13 +8,13 @@ LL | if (true) {} = help: to override `-D warnings` add `#[allow(clippy::needless_ifs)]` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:29:5 + --> tests/ui/needless_ifs.rs:28:5 | LL | if maybe_side_effect() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:35:5 + --> tests/ui/needless_ifs.rs:34:5 | LL | / if { LL | | @@ -31,7 +31,7 @@ LL + }); | error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:50:5 + --> tests/ui/needless_ifs.rs:49:5 | LL | / if { LL | | @@ -57,34 +57,40 @@ LL + } && true); | error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:95:5 + --> tests/ui/needless_ifs.rs:94:5 | LL | if { maybe_side_effect() } {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:98:5 + --> tests/ui/needless_ifs.rs:97:5 | LL | if { maybe_side_effect() } && true {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:103:5 + --> tests/ui/needless_ifs.rs:102:5 | LL | if true {} | ^^^^^^^^^^ help: you can remove it: `true;` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:110:5 + --> tests/ui/needless_ifs.rs:109:5 | LL | if matches!(2, 3) {} | ^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3);` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:112:5 + --> tests/ui/needless_ifs.rs:111:5 | LL | if matches!(2, 3) == (2 * 2 == 5) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3) == (2 * 2 == 5);` -error: aborting due to 9 previous errors +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:121:5 + | +LL | if true {␋} + | ^^^^^^^^^^^ help: you can remove it + +error: aborting due to 10 previous errors diff --git a/tests/ui/panic_in_result_fn_assertions.rs b/tests/ui/panic_in_result_fn_assertions.rs index 4e7028241579..17b221044b1f 100644 --- a/tests/ui/panic_in_result_fn_assertions.rs +++ b/tests/ui/panic_in_result_fn_assertions.rs @@ -7,7 +7,7 @@ impl A { fn result_with_assert_with_message(x: i32) -> Result // should emit lint //~^ panic_in_result_fn { - assert!(x == 5, "wrong argument"); + assert!(x.is_positive(), "wrong argument"); Ok(true) } @@ -27,7 +27,7 @@ fn result_with_assert_ne(x: i32) -> Result // should emit lint fn other_with_assert_with_message(x: i32) // should not emit lint { - assert!(x == 5, "wrong argument"); + assert!(x.is_positive(), "wrong argument"); } fn other_with_assert_eq(x: i32) // should not emit lint diff --git a/tests/ui/panic_in_result_fn_assertions.stderr b/tests/ui/panic_in_result_fn_assertions.stderr index cdb7762510d9..db881da06ba8 100644 --- a/tests/ui/panic_in_result_fn_assertions.stderr +++ b/tests/ui/panic_in_result_fn_assertions.stderr @@ -4,7 +4,7 @@ error: used `panic!()` or assertion in a function that returns `Result` LL | / fn result_with_assert_with_message(x: i32) -> Result // should emit lint LL | | LL | | { -LL | | assert!(x == 5, "wrong argument"); +LL | | assert!(x.is_positive(), "wrong argument"); LL | | Ok(true) LL | | } | |_____^ @@ -13,8 +13,8 @@ LL | | } note: return Err() instead of panicking --> tests/ui/panic_in_result_fn_assertions.rs:10:9 | -LL | assert!(x == 5, "wrong argument"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | assert!(x.is_positive(), "wrong argument"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: `-D clippy::panic-in-result-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::panic_in_result_fn)]` diff --git a/tests/ui/panic_in_result_fn_debug_assertions.rs b/tests/ui/panic_in_result_fn_debug_assertions.rs index c4549c6b8412..9cce339f4d60 100644 --- a/tests/ui/panic_in_result_fn_debug_assertions.rs +++ b/tests/ui/panic_in_result_fn_debug_assertions.rs @@ -9,7 +9,7 @@ impl A { fn result_with_debug_assert_with_message(x: i32) -> Result { - debug_assert!(x == 5, "wrong argument"); + debug_assert!(x.is_positive(), "wrong argument"); Ok(true) } @@ -24,7 +24,7 @@ fn result_with_debug_assert_ne(x: i32) -> Result { } fn other_with_debug_assert_with_message(x: i32) { - debug_assert!(x == 5, "wrong argument"); + debug_assert!(x.is_positive(), "wrong argument"); } fn other_with_debug_assert_eq(x: i32) { diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index bf4b4ff0a21e..786431bc1f53 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -564,3 +564,14 @@ fn issue16751(mut v: Option) -> Option { if n > 10 { Some(42) } else { None } } } + +fn issue_destructuring_assignment() -> Option<(i32, i32)> { + let mut a = 0i32; + let mut b = 0i32; + let opt: Option<(i32, i32)> = Some((1, 2)); + { + let x = opt?; + (a, b) = x + } + Some((a, b)) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 93f76f16576c..7cbcc604eb59 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -710,3 +710,15 @@ fn issue16751(mut v: Option) -> Option { None => return None, } } + +fn issue_destructuring_assignment() -> Option<(i32, i32)> { + let mut a = 0i32; + let mut b = 0i32; + let opt: Option<(i32, i32)> = Some((1, 2)); + match opt { + //~^ question_mark + Some(x) => (a, b) = x, + None => return None, + } + Some((a, b)) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 9d7cfc205764..74fb2c45a254 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -498,5 +498,23 @@ LL + if n > 10 { Some(42) } else { None } LL + } | -error: aborting due to 46 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:718:5 + | +LL | / match opt { +LL | | +LL | | Some(x) => (a, b) = x, +LL | | None => return None, +LL | | } + | |_____^ + | +help: try instead + | +LL ~ { +LL + let x = opt?; +LL + (a, b) = x +LL + } + | + +error: aborting due to 47 previous errors diff --git a/tests/ui/recursive_format_impl.rs b/tests/ui/recursive_format_impl.rs index 9f46fef62354..936d56877055 100644 --- a/tests/ui/recursive_format_impl.rs +++ b/tests/ui/recursive_format_impl.rs @@ -3,6 +3,7 @@ clippy::borrow_deref_ref, clippy::deref_addrof, clippy::inherent_to_string_shadow_display, + clippy::useless_borrows_in_formatting, clippy::to_string_in_format_args, clippy::uninlined_format_args )] diff --git a/tests/ui/recursive_format_impl.stderr b/tests/ui/recursive_format_impl.stderr index 4361d612bf2a..c813bb349b86 100644 --- a/tests/ui/recursive_format_impl.stderr +++ b/tests/ui/recursive_format_impl.stderr @@ -1,5 +1,5 @@ error: using `self.to_string` in `fmt::Display` implementation will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:31:25 + --> tests/ui/recursive_format_impl.rs:32:25 | LL | write!(f, "{}", self.to_string()) | ^^^^^^^^^^^^^^^^ @@ -8,55 +8,55 @@ LL | write!(f, "{}", self.to_string()) = help: to override `-D warnings` add `#[allow(clippy::recursive_format_impl)]` error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:76:9 + --> tests/ui/recursive_format_impl.rs:77:9 | LL | write!(f, "{}", self) | ^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:86:9 + --> tests/ui/recursive_format_impl.rs:87:9 | LL | write!(f, "{}", &self) | ^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Debug` in `impl Debug` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:93:9 + --> tests/ui/recursive_format_impl.rs:94:9 | LL | write!(f, "{:?}", &self) | ^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:103:9 + --> tests/ui/recursive_format_impl.rs:104:9 | LL | write!(f, "{}", &&&self) | ^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:178:9 + --> tests/ui/recursive_format_impl.rs:179:9 | LL | write!(f, "{}", &*self) | ^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Debug` in `impl Debug` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:185:9 + --> tests/ui/recursive_format_impl.rs:186:9 | LL | write!(f, "{:?}", &*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:202:9 + --> tests/ui/recursive_format_impl.rs:203:9 | LL | write!(f, "{}", *self) | ^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:219:9 + --> tests/ui/recursive_format_impl.rs:220:9 | LL | write!(f, "{}", **&&*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:236:9 + --> tests/ui/recursive_format_impl.rs:237:9 | LL | write!(f, "{}", &&**&&*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/uninit_vec.rs b/tests/ui/uninit_vec.rs index eeb281322da9..a0bac28e8729 100644 --- a/tests/ui/uninit_vec.rs +++ b/tests/ui/uninit_vec.rs @@ -87,7 +87,7 @@ fn main() { unsafe { // test the case where there are other statements in the following unsafe block vec.set_len(200); - assert!(vec.len() == 200); + assert_eq!(vec.len(), 200); } // handle vec stored in the field of a struct diff --git a/tests/ui/unnecessary_map_or.fixed b/tests/ui/unnecessary_map_or.fixed index 52c114339292..b1f991b9b26c 100644 --- a/tests/ui/unnecessary_map_or.fixed +++ b/tests/ui/unnecessary_map_or.fixed @@ -1,9 +1,12 @@ //@aux-build:proc_macros.rs #![warn(clippy::unnecessary_map_or)] -#![allow(clippy::no_effect)] -#![allow(clippy::eq_op)] -#![allow(clippy::unnecessary_lazy_evaluations)] -#![allow(clippy::nonminimal_bool)] +#![allow( + clippy::no_effect, + clippy::eq_op, + clippy::unnecessary_lazy_evaluations, + clippy::nonminimal_bool, + clippy::manual_assert_eq +)] #[clippy::msrv = "1.70.0"] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnecessary_map_or.rs b/tests/ui/unnecessary_map_or.rs index dd2e1a569469..edd5ea9d878f 100644 --- a/tests/ui/unnecessary_map_or.rs +++ b/tests/ui/unnecessary_map_or.rs @@ -1,9 +1,12 @@ //@aux-build:proc_macros.rs #![warn(clippy::unnecessary_map_or)] -#![allow(clippy::no_effect)] -#![allow(clippy::eq_op)] -#![allow(clippy::unnecessary_lazy_evaluations)] -#![allow(clippy::nonminimal_bool)] +#![allow( + clippy::no_effect, + clippy::eq_op, + clippy::unnecessary_lazy_evaluations, + clippy::nonminimal_bool, + clippy::manual_assert_eq +)] #[clippy::msrv = "1.70.0"] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnecessary_map_or.stderr b/tests/ui/unnecessary_map_or.stderr index d11e7179f921..12a973c84213 100644 --- a/tests/ui/unnecessary_map_or.stderr +++ b/tests/ui/unnecessary_map_or.stderr @@ -1,5 +1,5 @@ error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:13:13 + --> tests/ui/unnecessary_map_or.rs:16:13 | LL | let _ = Some(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let _ = Some(5) == Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:15:13 + --> tests/ui/unnecessary_map_or.rs:18:13 | LL | let _ = Some(5).map_or(true, |n| n != 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let _ = Some(5) != Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:17:13 + --> tests/ui/unnecessary_map_or.rs:20:13 | LL | let _ = Some(5).map_or(false, |n| { | _____________^ @@ -46,7 +46,7 @@ LL + let _ = Some(5) == Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:22:13 + --> tests/ui/unnecessary_map_or.rs:25:13 | LL | let _ = Some(5).map_or(false, |n| { | _____________^ @@ -63,7 +63,7 @@ LL + let _ = Some(5).is_some_and(|n| { | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:27:13 + --> tests/ui/unnecessary_map_or.rs:30:13 | LL | let _ = Some(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -75,7 +75,7 @@ LL + let _ = Some(vec![5]).is_some_and(|n| n == [5]); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:29:13 + --> tests/ui/unnecessary_map_or.rs:32:13 | LL | let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -87,7 +87,7 @@ LL + let _ = Some(vec![1]).is_some_and(|n| vec![2] == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:31:13 + --> tests/ui/unnecessary_map_or.rs:34:13 | LL | let _ = Some(5).map_or(false, |n| n == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -99,7 +99,7 @@ LL + let _ = Some(5).is_some_and(|n| n == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:33:13 + --> tests/ui/unnecessary_map_or.rs:36:13 | LL | let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,7 +111,7 @@ LL + let _ = Some(5).is_some_and(|n| n == if 2 > 1 { n } else { 0 }); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:35:13 + --> tests/ui/unnecessary_map_or.rs:38:13 | LL | let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -123,7 +123,7 @@ LL + let _ = Ok::, i32>(vec![5]).is_ok_and(|n| n == [5]); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:37:13 + --> tests/ui/unnecessary_map_or.rs:40:13 | LL | let _ = Ok::(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -135,7 +135,7 @@ LL + let _ = Ok::(5) == Ok(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:39:13 + --> tests/ui/unnecessary_map_or.rs:42:13 | LL | let _ = Some(5).map_or(false, |n| n == 5).then(|| 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -147,7 +147,7 @@ LL + let _ = (Some(5) == Some(5)).then(|| 1); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:41:13 + --> tests/ui/unnecessary_map_or.rs:44:13 | LL | let _ = Some(5).map_or(true, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -159,7 +159,7 @@ LL + let _ = Some(5).is_none_or(|n| n == 5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:43:13 + --> tests/ui/unnecessary_map_or.rs:46:13 | LL | let _ = Some(5).map_or(true, |n| 5 == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -171,7 +171,7 @@ LL + let _ = Some(5).is_none_or(|n| 5 == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:45:14 + --> tests/ui/unnecessary_map_or.rs:48:14 | LL | let _ = !Some(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -183,7 +183,7 @@ LL + let _ = !(Some(5) == Some(5)); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:47:13 + --> tests/ui/unnecessary_map_or.rs:50:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) || false; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL + let _ = (Some(5) == Some(5)) || false; | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:49:13 + --> tests/ui/unnecessary_map_or.rs:52:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -207,7 +207,7 @@ LL + let _ = (Some(5) == Some(5)) as usize; | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:74:13 + --> tests/ui/unnecessary_map_or.rs:77:13 | LL | let _ = r.map_or(false, |x| x == 7); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -219,7 +219,7 @@ LL + let _ = r.is_ok_and(|x| x == 7); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:80:13 + --> tests/ui/unnecessary_map_or.rs:83:13 | LL | let _ = r.map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^ @@ -231,7 +231,7 @@ LL + let _ = r.is_ok_and(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:82:13 + --> tests/ui/unnecessary_map_or.rs:85:13 | LL | let _ = Some(5).map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -243,7 +243,7 @@ LL + let _ = Some(5).is_some_and(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:84:13 + --> tests/ui/unnecessary_map_or.rs:87:13 | LL | let _ = Some(5).map_or(true, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -255,7 +255,7 @@ LL + let _ = Some(5).is_none_or(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:90:13 + --> tests/ui/unnecessary_map_or.rs:93:13 | LL | let _ = r.map_or(false, |x| x == 8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -267,7 +267,7 @@ LL + let _ = r == Ok(8); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:111:5 + --> tests/ui/unnecessary_map_or.rs:114:5 | LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -279,7 +279,7 @@ LL + o.is_none_or(|n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:111:34 + --> tests/ui/unnecessary_map_or.rs:114:34 | LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -291,7 +291,7 @@ LL + o.map_or(true, |n| n > 5) || (o as &Option).is_none_or(|n| n < 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:126:5 + --> tests/ui/unnecessary_map_or.rs:129:5 | LL | o.map_or(true, |n| n > 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -303,7 +303,7 @@ LL + o.is_none_or(|n| n > 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:131:13 + --> tests/ui/unnecessary_map_or.rs:134:13 | LL | let x = a.map_or(false, |a| a == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -315,7 +315,7 @@ LL + let x = a.is_some_and(|a| a == *s); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:133:13 + --> tests/ui/unnecessary_map_or.rs:136:13 | LL | let y = b.map_or(true, |b| b == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -327,7 +327,7 @@ LL + let y = b.is_none_or(|b| b == *s); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:139:13 + --> tests/ui/unnecessary_map_or.rs:142:13 | LL | assert!(Some("test").map_or(false, |x| x == "test")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -339,7 +339,7 @@ LL + assert!(Some("test") == Some("test")); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:143:13 + --> tests/ui/unnecessary_map_or.rs:146:13 | LL | assert!(Some("test").map_or(false, |x| x == "test").then(|| 1).is_some()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -351,7 +351,7 @@ LL + assert!((Some("test") == Some("test")).then(|| 1).is_some()); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:160:9 + --> tests/ui/unnecessary_map_or.rs:163:9 | LL | _ = s.lock().unwrap().map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -363,7 +363,7 @@ LL + _ = s.lock().unwrap().is_some_and(|s| s == "foo"); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:164:9 + --> tests/ui/unnecessary_map_or.rs:167:9 | LL | _ = s.map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/unused_format_specs_width.rs b/tests/ui/unused_format_specs_width.rs new file mode 100644 index 000000000000..e894ea76fc18 --- /dev/null +++ b/tests/ui/unused_format_specs_width.rs @@ -0,0 +1,41 @@ +//@no-rustfix +// Format width has no effect for certain traits (issue #15039) + +#![warn(clippy::unused_format_specs)] +#![allow(clippy::zero_ptr, clippy::manual_dangling_ptr)] + +fn main() { + // Integer formats with # (alternate): 0x/0o/0b prefix makes min width 4 + println!("{:#02X}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#2X}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02x}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02o}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02b}", 1u8); //~ ERROR: format width has no effect on the output + + // Exponent formats: min width 4 (e.g. 1e0) + println!("{:02e}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:02E}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:2e}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2E}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2e}", 0.1); //~ ERROR: format width has no effect on the output + println!("{:2E}", 0.1); //~ ERROR: format width has no effect on the output + + // Pointer: min width 4 (0x1) + println!("{:2p}", 0 as *const usize); //~ ERROR: format width has no effect on the output + println!("{:02p}", 1 as *const usize); //~ ERROR: format width has no effect on the output + + // Width 2 still too small for exponent; precision+width + println!("{:2.2e}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2.2E}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2.2e}", 0.1); //~ ERROR: format width has no effect on the output + println!("{:2.2E}", 0.1); //~ ERROR: format width has no effect on the output + + // Width 3 is exactly the minimum for alternate hex, still warn + println!("{:#03X}", 1u8); //~ ERROR: format width has no effect on the output + + // Not linted: width more than 3, or no # for x/o/b + println!("{:#04X}", 1u8); + println!("{:2X}", 1u8); // no #, so no prefix + println!("{:2o}", 1u8); + println!("{}", 1); +} diff --git a/tests/ui/unused_format_specs_width.stderr b/tests/ui/unused_format_specs_width.stderr new file mode 100644 index 000000000000..defb96ac08bf --- /dev/null +++ b/tests/ui/unused_format_specs_width.stderr @@ -0,0 +1,148 @@ +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:9:15 + | +LL | println!("{:#02X}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + = note: `-D clippy::unused-format-specs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unused_format_specs)]` + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:10:15 + | +LL | println!("{:#2X}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:11:15 + | +LL | println!("{:#02x}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:12:15 + | +LL | println!("{:#02o}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:13:15 + | +LL | println!("{:#02b}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:16:15 + | +LL | println!("{:02e}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:17:15 + | +LL | println!("{:02E}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:18:15 + | +LL | println!("{:2e}", 1.0); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:19:15 + | +LL | println!("{:2E}", 1.0); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:20:15 + | +LL | println!("{:2e}", 0.1); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:21:15 + | +LL | println!("{:2E}", 0.1); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:24:15 + | +LL | println!("{:2p}", 0 as *const usize); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:25:15 + | +LL | println!("{:02p}", 1 as *const usize); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:28:15 + | +LL | println!("{:2.2e}", 1.0); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:29:15 + | +LL | println!("{:2.2E}", 1.0); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:30:15 + | +LL | println!("{:2.2e}", 0.1); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:31:15 + | +LL | println!("{:2.2E}", 0.1); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:34:15 + | +LL | println!("{:#03X}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: aborting due to 18 previous errors + diff --git a/tests/ui/useless_borrows_in_formatting.fixed b/tests/ui/useless_borrows_in_formatting.fixed new file mode 100644 index 000000000000..82319252b468 --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.fixed @@ -0,0 +1,118 @@ +// When testing or blessing this lint, set TESTNAME so only this test runs: +// TESTNAME=useless_borrows_in_formatting cargo uitest +// TESTNAME=useless_borrows_in_formatting cargo uibless +#![warn(clippy::useless_borrows_in_formatting)] +#![allow(unused, clippy::useless_format)] + +fn main() { + let s: &str = "hello"; + println!("{}", s); //~ useless_borrows_in_formatting + println!("{:?}", s); //~ useless_borrows_in_formatting + println!("{}", s); //~ useless_borrows_in_formatting + + let string = String::from("world"); + println!("{}", string); //~ useless_borrows_in_formatting + println!("{:?}", string); //~ useless_borrows_in_formatting + println!("{}", string); //~ useless_borrows_in_formatting + println!("{}", &string[..2]); //~ useless_borrows_in_formatting + println!("{:?}", &string[..2]); //~ useless_borrows_in_formatting + // these are ok + println!("{}", &string[..2]); + println!("{:?}", &string[..2]); + + let n: i32 = 42; + println!("{}", n); //~ useless_borrows_in_formatting + println!("{:?}", n); //~ useless_borrows_in_formatting + println!("{}", n); //~ useless_borrows_in_formatting + + // Reference to slice element + let slice: [i32; 3] = [1, 2, 3]; + println!("{}", slice[0]); //~ useless_borrows_in_formatting + println!("{:?}", slice[0]); //~ useless_borrows_in_formatting + println!("{}", slice[0]); //~ useless_borrows_in_formatting + + // big array: should not suggest removing & because of the size of the output + println!( + "{:?}", + [ + //~^ useless_borrows_in_formatting + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ] + ); + + println!("{:?}", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + //~^ useless_borrows_in_formatting + + let a: [i32; 2] = [1, 2]; + println!("{:016x?}", [a[0], a[1], a[0], a[1]]); //~ useless_borrows_in_formatting + + // &slice[0..1] with {:?}: inner type [i32] is unsized, so we don't suggest removing & + println!("{:?}", &slice[0..1]); // don't change + println!("{:?}", &slice[0..1]); //~ useless_borrows_in_formatting + + // Pointer formatting ({:p}): never suggest any changes to it + let x: i32 = 0; + println!("{:p}", &x); // don't change + println!("{:p}", &&x); // should change, but out of scope + + struct Wrap(i32); + let w: Wrap = Wrap(42); + println!("{}", w.0); //~ useless_borrows_in_formatting + println!("{:?}", w.0); //~ useless_borrows_in_formatting + println!("{}", w.0); //~ useless_borrows_in_formatting + + struct WrapRef<'a>(&'a i32); + let n: i32 = 42; + let w: WrapRef<'_> = WrapRef(&n); + println!("{}", w.0); //~ useless_borrows_in_formatting + println!("{:?}", w.0); //~ useless_borrows_in_formatting + println!("{}", w.0); //~ useless_borrows_in_formatting + + let a: &mut String = &mut String::from("foo"); + println!("{}", *a); //~ useless_borrows_in_formatting + println!("{:?}", *a); //~ useless_borrows_in_formatting + + // Parenthesized expressions: &(expr) + let n: i32 = 42; + println!("{}", (n)); //~ useless_borrows_in_formatting + println!("{:?}", (n + 1)); //~ useless_borrows_in_formatting + println!("{}", (String::from("paren"))); //~ useless_borrows_in_formatting + + // Block expressions: &{ expr } + println!("{}", { n }); //~ useless_borrows_in_formatting + println!("{:?}", { n + 1 }); //~ useless_borrows_in_formatting + println!("{}", { String::from("block") }); //~ useless_borrows_in_formatting + + let v1 = 42.12345; + let v2 = 20; + let v3 = 10; + println!("{0:1$.2$}", v1, v2, v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$?}", v1, v2, v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + + // Macro wrapping println! - should not lint (println! call is inside macro expansion) + macro_rules! my_println { + ($($args:tt)*) => { + println!($($args)*); + }; + } + my_println!("{}", &n); + + // Arguments coming from a macro - should not lint (& comes from expansion) + macro_rules! make_ref { + ($e:expr) => { + &$e + }; + } + println!("{}", make_ref!(n)); +} diff --git a/tests/ui/useless_borrows_in_formatting.rs b/tests/ui/useless_borrows_in_formatting.rs new file mode 100644 index 000000000000..6d23c9c8e7bb --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.rs @@ -0,0 +1,118 @@ +// When testing or blessing this lint, set TESTNAME so only this test runs: +// TESTNAME=useless_borrows_in_formatting cargo uitest +// TESTNAME=useless_borrows_in_formatting cargo uibless +#![warn(clippy::useless_borrows_in_formatting)] +#![allow(unused, clippy::useless_format)] + +fn main() { + let s: &str = "hello"; + println!("{}", &s); //~ useless_borrows_in_formatting + println!("{:?}", &s); //~ useless_borrows_in_formatting + println!("{}", &&s); //~ useless_borrows_in_formatting + + let string = String::from("world"); + println!("{}", &string); //~ useless_borrows_in_formatting + println!("{:?}", &string); //~ useless_borrows_in_formatting + println!("{}", &&string); //~ useless_borrows_in_formatting + println!("{}", &&string[..2]); //~ useless_borrows_in_formatting + println!("{:?}", &&string[..2]); //~ useless_borrows_in_formatting + // these are ok + println!("{}", &string[..2]); + println!("{:?}", &string[..2]); + + let n: i32 = 42; + println!("{}", &n); //~ useless_borrows_in_formatting + println!("{:?}", &n); //~ useless_borrows_in_formatting + println!("{}", &&n); //~ useless_borrows_in_formatting + + // Reference to slice element + let slice: [i32; 3] = [1, 2, 3]; + println!("{}", &slice[0]); //~ useless_borrows_in_formatting + println!("{:?}", &slice[0]); //~ useless_borrows_in_formatting + println!("{}", &&slice[0]); //~ useless_borrows_in_formatting + + // big array: should not suggest removing & because of the size of the output + println!( + "{:?}", + &[ + //~^ useless_borrows_in_formatting + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ] + ); + + println!("{:?}", &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + //~^ useless_borrows_in_formatting + + let a: [i32; 2] = [1, 2]; + println!("{:016x?}", &[a[0], a[1], a[0], a[1]]); //~ useless_borrows_in_formatting + + // &slice[0..1] with {:?}: inner type [i32] is unsized, so we don't suggest removing & + println!("{:?}", &slice[0..1]); // don't change + println!("{:?}", &&slice[0..1]); //~ useless_borrows_in_formatting + + // Pointer formatting ({:p}): never suggest any changes to it + let x: i32 = 0; + println!("{:p}", &x); // don't change + println!("{:p}", &&x); // should change, but out of scope + + struct Wrap(i32); + let w: Wrap = Wrap(42); + println!("{}", &w.0); //~ useless_borrows_in_formatting + println!("{:?}", &w.0); //~ useless_borrows_in_formatting + println!("{}", &&w.0); //~ useless_borrows_in_formatting + + struct WrapRef<'a>(&'a i32); + let n: i32 = 42; + let w: WrapRef<'_> = WrapRef(&n); + println!("{}", &w.0); //~ useless_borrows_in_formatting + println!("{:?}", &w.0); //~ useless_borrows_in_formatting + println!("{}", &&w.0); //~ useless_borrows_in_formatting + + let a: &mut String = &mut String::from("foo"); + println!("{}", &*a); //~ useless_borrows_in_formatting + println!("{:?}", &*a); //~ useless_borrows_in_formatting + + // Parenthesized expressions: &(expr) + let n: i32 = 42; + println!("{}", &(n)); //~ useless_borrows_in_formatting + println!("{:?}", &(n + 1)); //~ useless_borrows_in_formatting + println!("{}", &(String::from("paren"))); //~ useless_borrows_in_formatting + + // Block expressions: &{ expr } + println!("{}", &{ n }); //~ useless_borrows_in_formatting + println!("{:?}", &{ n + 1 }); //~ useless_borrows_in_formatting + println!("{}", &{ String::from("block") }); //~ useless_borrows_in_formatting + + let v1 = 42.12345; + let v2 = 20; + let v3 = 10; + println!("{0:1$.2$}", &v1, &v2, &v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$?}", &v1, &v2, &v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$}", &v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, &v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, &v3); //~ useless_borrows_in_formatting + + // Macro wrapping println! - should not lint (println! call is inside macro expansion) + macro_rules! my_println { + ($($args:tt)*) => { + println!($($args)*); + }; + } + my_println!("{}", &n); + + // Arguments coming from a macro - should not lint (& comes from expansion) + macro_rules! make_ref { + ($e:expr) => { + &$e + }; + } + println!("{}", make_ref!(n)); +} diff --git a/tests/ui/useless_borrows_in_formatting.stderr b/tests/ui/useless_borrows_in_formatting.stderr new file mode 100644 index 000000000000..e9e028c81ac4 --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.stderr @@ -0,0 +1,266 @@ +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:9:20 + | +LL | println!("{}", &s); + | ^^ help: remove the redundant `&`: `s` + | + = note: `-D clippy::useless-borrows-in-formatting` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_borrows_in_formatting)]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:10:22 + | +LL | println!("{:?}", &s); + | ^^ help: remove the redundant `&`: `s` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:11:20 + | +LL | println!("{}", &&s); + | ^^^ help: remove the redundant `&`: `s` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:14:20 + | +LL | println!("{}", &string); + | ^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:15:22 + | +LL | println!("{:?}", &string); + | ^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:16:20 + | +LL | println!("{}", &&string); + | ^^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:17:20 + | +LL | println!("{}", &&string[..2]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&string[..2]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:18:22 + | +LL | println!("{:?}", &&string[..2]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&string[..2]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:24:20 + | +LL | println!("{}", &n); + | ^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:25:22 + | +LL | println!("{:?}", &n); + | ^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:26:20 + | +LL | println!("{}", &&n); + | ^^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:30:20 + | +LL | println!("{}", &slice[0]); + | ^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:31:22 + | +LL | println!("{:?}", &slice[0]); + | ^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:32:20 + | +LL | println!("{}", &&slice[0]); + | ^^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:37:9 + | +LL | / &[ +LL | | +LL | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, +LL | | 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, +LL | | 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +LL | | ] + | |_________^ + | +help: remove the redundant `&` + | +LL ~ [ +LL + +LL + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, +LL + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, +LL + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +LL + ] + | + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:45:22 + | +LL | println!("{:?}", &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:49:26 + | +LL | println!("{:016x?}", &[a[0], a[1], a[0], a[1]]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `[a[0], a[1], a[0], a[1]]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:53:22 + | +LL | println!("{:?}", &&slice[0..1]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&slice[0..1]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:62:20 + | +LL | println!("{}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:63:22 + | +LL | println!("{:?}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:64:20 + | +LL | println!("{}", &&w.0); + | ^^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:69:20 + | +LL | println!("{}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:70:22 + | +LL | println!("{:?}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:71:20 + | +LL | println!("{}", &&w.0); + | ^^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:74:20 + | +LL | println!("{}", &*a); + | ^^^ help: remove the redundant `&`: `*a` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:75:22 + | +LL | println!("{:?}", &*a); + | ^^^ help: remove the redundant `&`: `*a` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:79:20 + | +LL | println!("{}", &(n)); + | ^^^^ help: remove the redundant `&`: `(n)` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:80:22 + | +LL | println!("{:?}", &(n + 1)); + | ^^^^^^^^ help: remove the redundant `&`: `(n + 1)` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:81:20 + | +LL | println!("{}", &(String::from("paren"))); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `(String::from("paren"))` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:84:20 + | +LL | println!("{}", &{ n }); + | ^^^^^^ help: remove the redundant `&`: `{ n }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:85:22 + | +LL | println!("{:?}", &{ n + 1 }); + | ^^^^^^^^^^ help: remove the redundant `&`: `{ n + 1 }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:86:20 + | +LL | println!("{}", &{ String::from("block") }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `{ String::from("block") }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:27 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:32 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:37 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:28 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:33 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:38 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:99:27 + | +LL | println!("{0:1$.2$}", &v1, v2, v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:100:31 + | +LL | println!("{0:1$.2$}", v1, &v2, v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:101:35 + | +LL | println!("{0:1$.2$}", v1, v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: aborting due to 41 previous errors + diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index d0297ef6bcdc..a22df7013f98 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() { #[allow(const_item_mutation)] fn lint_into_iter_on_const_implementing_iterator() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let _ = NUMBERS.next(); //~^ useless_conversion } fn lint_into_iter_on_const_implementing_iterator_2() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let mut n = NUMBERS; //~^ useless_conversion n.next(); @@ -423,10 +423,8 @@ mod issue11819 { } } -fn issue14739() { - use std::ops::Range; - - const R: Range = 2..7; +fn issue14800() { + const R: std::iter::Empty = std::iter::empty(); R.into_iter().all(|_x| true); // no lint @@ -438,6 +436,33 @@ fn issue14739() { //~^ useless_conversion } +// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` +// will change from producing type `core::ops::Range`, which implements `Iterator`, to +// producing type `core::range::Range`, which implements `IntoIterator`. +// +// Therefore, an `.into_iter()` call that is technically useless today will be useful for +// edition migration or unstable feature testing; do not remove it. +// +// This test case tests that the ranges produced *by range syntax* aren’t linted on, which +// should be true both before and after the expected 2027 edition migration (but after such +// migration, this test will not really be testing anything). +fn do_not_lint_on_ops_range_into_iter_before_method() { + #![allow(clippy::never_loop)] + + // No lint on these + (0..10).into_iter().for_each(drop); + (0..=10).into_iter().for_each(drop); + (0..).into_iter().take(10).for_each(drop); + + // But do still lint on for loops + for _ in (0..10) {} //~ useless_conversion + for _ in (0..=10) {} //~ useless_conversion + for _ in (0..) { + //~^ useless_conversion + break; + } +} + fn issue16165() { macro_rules! mac { (iter $e:expr) => { diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index 20a0f6d72f9a..1f170cf87ac5 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() { #[allow(const_item_mutation)] fn lint_into_iter_on_const_implementing_iterator() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let _ = NUMBERS.into_iter().next(); //~^ useless_conversion } fn lint_into_iter_on_const_implementing_iterator_2() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let mut n = NUMBERS.into_iter(); //~^ useless_conversion n.next(); @@ -423,10 +423,8 @@ pub fn with_reborrow_mut<'a, Y: 'a>(&'a mut self) } } -fn issue14739() { - use std::ops::Range; - - const R: Range = 2..7; +fn issue14800() { + const R: std::iter::Empty = std::iter::empty(); R.into_iter().all(|_x| true); // no lint @@ -438,6 +436,33 @@ fn issue14739() { //~^ useless_conversion } +// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` +// will change from producing type `core::ops::Range`, which implements `Iterator`, to +// producing type `core::range::Range`, which implements `IntoIterator`. +// +// Therefore, an `.into_iter()` call that is technically useless today will be useful for +// edition migration or unstable feature testing; do not remove it. +// +// This test case tests that the ranges produced *by range syntax* aren’t linted on, which +// should be true both before and after the expected 2027 edition migration (but after such +// migration, this test will not really be testing anything). +fn do_not_lint_on_ops_range_into_iter_before_method() { + #![allow(clippy::never_loop)] + + // No lint on these + (0..10).into_iter().for_each(drop); + (0..=10).into_iter().for_each(drop); + (0..).into_iter().take(10).for_each(drop); + + // But do still lint on for loops + for _ in (0..10).into_iter() {} //~ useless_conversion + for _ in (0..=10).into_iter() {} //~ useless_conversion + for _ in (0..).into_iter() { + //~^ useless_conversion + break; + } +} + fn issue16165() { macro_rules! mac { (iter $e:expr) => { diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 18d5c9d28c0d..7042e18a2cad 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -40,13 +40,13 @@ error: useless conversion to the same type: `std::str::Lines<'_>` LL | if Some("ok") == text.lines().into_iter().next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `text.lines()` -error: useless conversion to the same type: `std::ops::Range` +error: useless conversion to the same type: `std::iter::Empty` --> tests/ui/useless_conversion.rs:76:13 | LL | let _ = NUMBERS.into_iter().next(); | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `NUMBERS` -error: useless conversion to the same type: `std::ops::Range` +error: useless conversion to the same type: `std::iter::Empty` --> tests/ui/useless_conversion.rs:82:17 | LL | let mut n = NUMBERS.into_iter(); @@ -421,32 +421,50 @@ LL - takes_into_iter(self.my_field.into_iter()); LL + takes_into_iter(&mut *self.my_field); | -error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:435:5 +error: useless conversion to the same type: `std::iter::Empty` + --> tests/ui/useless_conversion.rs:433:5 | LL | R.into_iter().for_each(|_x| {}); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` -error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:437:13 +error: useless conversion to the same type: `std::iter::Empty` + --> tests/ui/useless_conversion.rs:435:13 | LL | let _ = R.into_iter().map(|_x| 0); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` +error: useless conversion to the same type: `std::ops::Range` + --> tests/ui/useless_conversion.rs:458:14 + | +LL | for _ in (0..10).into_iter() {} + | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..10)` + +error: useless conversion to the same type: `std::ops::RangeInclusive` + --> tests/ui/useless_conversion.rs:459:14 + | +LL | for _ in (0..=10).into_iter() {} + | ^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..=10)` + +error: useless conversion to the same type: `std::ops::RangeFrom` + --> tests/ui/useless_conversion.rs:460:14 + | +LL | for _ in (0..).into_iter() { + | ^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..)` + error: useless conversion to the same type: `std::slice::Iter<'_, i32>` - --> tests/ui/useless_conversion.rs:448:14 + --> tests/ui/useless_conversion.rs:473:14 | LL | for _ in mac!(iter [1, 2]).into_iter() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `mac!(iter [1, 2])` error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:461:27 + --> tests/ui/useless_conversion.rs:486:27 | LL | takes_into_iter_usize(b.into_iter()); | ^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:452:34 + --> tests/ui/useless_conversion.rs:477:34 | LL | fn takes_into_iter_usize(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -457,13 +475,13 @@ LL + takes_into_iter_usize(b); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:470:31 + --> tests/ui/useless_conversion.rs:495:31 | LL | takes_into_iter_usize(b.clone().into_iter()); | ^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:452:34 + --> tests/ui/useless_conversion.rs:477:34 | LL | fn takes_into_iter_usize(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -474,13 +492,13 @@ LL + takes_into_iter_usize(b.clone()); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:478:34 + --> tests/ui/useless_conversion.rs:503:34 | LL | takes_into_iter_usize_result(b.clone().into_iter())?; | ^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:453:41 + --> tests/ui/useless_conversion.rs:478:41 | LL | fn takes_into_iter_usize_result(_: impl IntoIterator) -> Result<(), ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -490,5 +508,5 @@ LL - takes_into_iter_usize_result(b.clone().into_iter())?; LL + takes_into_iter_usize_result(b.clone())?; | -error: aborting due to 48 previous errors +error: aborting due to 51 previous errors diff --git a/tests/ui/zero_offset.rs b/tests/ui/zero_offset.rs index 5a9c3ac9248f..93ed50fde488 100644 --- a/tests/ui/zero_offset.rs +++ b/tests/ui/zero_offset.rs @@ -29,5 +29,18 @@ fn main() { let sized = &1 as *const i32; sized.offset(0); + + let nn = core::ptr::NonNull::<()>::dangling(); + nn.add(0); + //~^ zst_offset + + nn.offset(0); + //~^ zst_offset + + nn.sub(0); + //~^ zst_offset + + let nn_sized = core::ptr::NonNull::::dangling(); + nn_sized.add(0); } } diff --git a/tests/ui/zero_offset.stderr b/tests/ui/zero_offset.stderr index b69c7b92d56a..e78620ddcba4 100644 --- a/tests/ui/zero_offset.stderr +++ b/tests/ui/zero_offset.stderr @@ -48,5 +48,23 @@ error: offset calculation on zero-sized value LL | c.wrapping_sub(0); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 8 previous errors +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:34:9 + | +LL | nn.add(0); + | ^^^^^^^^^ + +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:37:9 + | +LL | nn.offset(0); + | ^^^^^^^^^^^^ + +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:40:9 + | +LL | nn.sub(0); + | ^^^^^^^^^ + +error: aborting due to 11 previous errors diff --git a/triagebot.toml b/triagebot.toml index 8e7c36dac841..869b8d730507 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -93,6 +93,7 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", + "samueltardieu", ] [assign.owners] From 69f70467b4f35c3a1e346b578aba038aea2a6882 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 13 Apr 2026 21:14:29 +1000 Subject: [PATCH 05/36] Eliminate `rustc_lint_defs`' dependency on `rustc_ast`. It currently only depends on two things: - `rustc_ast::AttrId`: this is just a re-export of `rustc_span::AttrId`, so we can import the original instead. - `rustc_ast::AttributeExt`: needed only for the `name` and `id` methods. We can instead pass in the `name` and `id` directly. --- clippy_lints/src/collapsible_if.rs | 2 +- clippy_lints/src/returns/needless_return.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 52e602bbac57..8a12d1093b4b 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -238,7 +238,7 @@ fn check_significant_tokens_and_expect_attrs( }, [attr] - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + if matches!(Level::from_attr(attr.name(), || attr.id()), Some((Level::Expect, _))) && let Some(metas) = attr.meta_item_list() && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() && let [tool, lint_name] = meta_item.path.segments.as_slice() diff --git a/clippy_lints/src/returns/needless_return.rs b/clippy_lints/src/returns/needless_return.rs index 619a70cd8dd1..b9bacc2b73a1 100644 --- a/clippy_lints/src/returns/needless_return.rs +++ b/clippy_lints/src/returns/needless_return.rs @@ -181,7 +181,7 @@ fn check_final_expr<'tcx>( match cx.tcx.hir_attrs(expr.hir_id) { [] => {}, [attr] => { - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + if matches!(Level::from_attr(attr.name(), || attr.id()), Some((Level::Expect, _))) && let metas = attr.meta_item_list() && let Some(lst) = metas && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() From 0c4849ec07560a0cd9abe5f5506b5196aebcf5f6 Mon Sep 17 00:00:00 2001 From: EvoPot Date: Sat, 2 May 2026 06:01:15 +0300 Subject: [PATCH 06/36] Add extra symbol check for `.to_owned()` Previously when adding a suggestion for using `Cow::into_owned()` instead of `ToOwned::to_owned()`, the compiler would just convert the methods `Span` into a `String` and do checks on that `String`. This PR adds an extra guard to that suggestion by checking if the method is `sym::to_owned_method`. --- clippy_utils/src/sym.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 87aac25f5bd1..367ec897dae4 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -606,7 +606,6 @@ macro_rules! generate { to_ne_bytes, to_os_string, to_owned, - to_owned_method, to_path_buf, to_string_method, to_uppercase, From d08d4e702d1804e517985fb91ff12d3cedcd4e70 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 24 Mar 2026 23:00:10 +0100 Subject: [PATCH 07/36] miri: remove retag statements, make typed copies retag implicitly instead --- clippy_lints/src/redundant_clone.rs | 2 +- clippy_utils/src/mir/possible_borrower.rs | 2 +- clippy_utils/src/mir/possible_origin.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index d2d9e1642d44..bfb704dd2171 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -293,7 +293,7 @@ fn find_stmt_assigns_to<'tcx>( })?; match (by_ref, rvalue) { - (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => { + (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place), _)) => { Some(base_local_and_movability(cx, mir, *place)) }, (false, mir::Rvalue::Ref(_, _, place)) => { diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 0ead5944b4b3..cbd55ce9b435 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -155,7 +155,7 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { }; match rvalue { - Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op), + Use(op, _) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op), Aggregate(_, ops) => ops.iter().for_each(visit_op), BinaryOp(_, box (lhs, rhs)) => { visit_op(lhs); diff --git a/clippy_utils/src/mir/possible_origin.rs b/clippy_utils/src/mir/possible_origin.rs index fee22c436b0f..742eedfa225b 100644 --- a/clippy_utils/src/mir/possible_origin.rs +++ b/clippy_utils/src/mir/possible_origin.rs @@ -46,7 +46,7 @@ fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _ mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) | // _2: &mut _; // _3 = move _2 - mir::Rvalue::Use(mir::Operand::Move(borrowed)) | + mir::Rvalue::Use(mir::Operand::Move(borrowed), _) | // _3 = move _2 as &mut _; mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _) => { diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 8f7a140e91a8..c906a1fdba1b 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -134,7 +134,7 @@ fn check_rvalue<'tcx>( }, Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), Rvalue::Repeat(operand, _) - | Rvalue::Use(operand) + | Rvalue::Use(operand, _) | Rvalue::WrapUnsafeBinder(operand, _) | Rvalue::Cast( CastKind::PointerWithExposedProvenance @@ -244,7 +244,6 @@ fn check_statement<'tcx>( // These are all NOPs StatementKind::StorageLive(_) | StatementKind::StorageDead(_) - | StatementKind::Retag { .. } | StatementKind::AscribeUserType(..) | StatementKind::PlaceMention(..) | StatementKind::Coverage(..) From 00cb5cb330ebf1dcf2a3bf8b31c26777d235e81c Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 1 May 2026 23:07:23 +0200 Subject: [PATCH 08/36] Remove more spans from AttributeKind --- clippy_lints/src/format_args.rs | 2 +- clippy_utils/src/lib.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index fd2b9826bb20..7e2379414436 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -716,7 +716,7 @@ fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { }; let selection = SelectionContext::new(&infcx).select(&obligation); let derived = if let Ok(Some(Selection::UserDefined(data))) = selection { - find_attr!(tcx, data.impl_def_id, AutomaticallyDerived(..)) + find_attr!(tcx, data.impl_def_id, AutomaticallyDerived) } else { false }; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e5420f9f9d16..b7c08c380903 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1736,8 +1736,9 @@ pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool { .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_)))) .any(|(id, _)| { find_attr!( - tcx.hir_attrs(tcx.local_def_id_to_hir_id(id.def_id)), - AutomaticallyDerived(..) + tcx, + id.def_id, + AutomaticallyDerived ) }) } @@ -2101,11 +2102,11 @@ pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { } pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { - find_attr!(cx.tcx, crate, NoStd(..)) + find_attr!(cx.tcx, crate, NoStd) } pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { - find_attr!(cx.tcx, crate, NoCore(..)) + find_attr!(cx.tcx, crate, NoCore) } /// Check if parent of a hir node is a trait implementation block. From fa2573464afef499af974d432688c0b5ef67ee60 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 3 May 2026 10:15:07 +0200 Subject: [PATCH 09/36] No longer in vacation --- triagebot.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 869b8d730507..8e7c36dac841 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -93,7 +93,6 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", - "samueltardieu", ] [assign.owners] From 3e0e8955688c52a4c18f158d5f75047fb676779c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 13 Feb 2026 11:25:14 +0000 Subject: [PATCH 10/36] Rip out rustc_layout_scalar_valid_range_* attribute support --- tests/ui/eager_transmute.fixed | 19 ++++++++++-------- tests/ui/eager_transmute.rs | 19 ++++++++++-------- tests/ui/eager_transmute.stderr | 34 ++++++++++++++++----------------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/tests/ui/eager_transmute.fixed b/tests/ui/eager_transmute.fixed index 47a32ec836cc..dbb3fdee43ad 100644 --- a/tests/ui/eager_transmute.fixed +++ b/tests/ui/eager_transmute.fixed @@ -1,8 +1,9 @@ -#![feature(rustc_attrs)] +#![feature(rustc_attrs, pattern_types, pattern_type_macro)] #![warn(clippy::eager_transmute)] #![allow(clippy::transmute_int_to_non_zero, clippy::missing_transmute_annotations)] use std::num::NonZero; +use std::pat::pattern_type; #[repr(u8)] enum Opcode { @@ -77,23 +78,25 @@ unsafe fn f2(op: u8) { } } -#[rustc_layout_scalar_valid_range_end(254)] -struct NonMaxU8(u8); -#[rustc_layout_scalar_valid_range_end(254)] -#[rustc_layout_scalar_valid_range_start(1)] -struct NonZeroNonMaxU8(u8); +struct NonMaxU8(pattern_type!(u8 is 0..=254)); +struct NonZeroNonMaxU8(pattern_type!(u8 is 1..=254)); macro_rules! impls { ($($t:ty),*) => { $( + impl $t { + fn get(&self) -> u8 { + unsafe { std::mem::transmute(self.0) } + } + } impl PartialEq for $t { fn eq(&self, other: &u8) -> bool { - self.0 == *other + self.get() == *other } } impl PartialOrd for $t { fn partial_cmp(&self, other: &u8) -> Option { - self.0.partial_cmp(other) + self.get().partial_cmp(other) } } )* diff --git a/tests/ui/eager_transmute.rs b/tests/ui/eager_transmute.rs index 906cd7bccc86..d44c501467cd 100644 --- a/tests/ui/eager_transmute.rs +++ b/tests/ui/eager_transmute.rs @@ -1,8 +1,9 @@ -#![feature(rustc_attrs)] +#![feature(rustc_attrs, pattern_types, pattern_type_macro)] #![warn(clippy::eager_transmute)] #![allow(clippy::transmute_int_to_non_zero, clippy::missing_transmute_annotations)] use std::num::NonZero; +use std::pat::pattern_type; #[repr(u8)] enum Opcode { @@ -77,23 +78,25 @@ unsafe fn f2(op: u8) { } } -#[rustc_layout_scalar_valid_range_end(254)] -struct NonMaxU8(u8); -#[rustc_layout_scalar_valid_range_end(254)] -#[rustc_layout_scalar_valid_range_start(1)] -struct NonZeroNonMaxU8(u8); +struct NonMaxU8(pattern_type!(u8 is 0..=254)); +struct NonZeroNonMaxU8(pattern_type!(u8 is 1..=254)); macro_rules! impls { ($($t:ty),*) => { $( + impl $t { + fn get(&self) -> u8 { + unsafe { std::mem::transmute(self.0) } + } + } impl PartialEq for $t { fn eq(&self, other: &u8) -> bool { - self.0 == *other + self.get() == *other } } impl PartialOrd for $t { fn partial_cmp(&self, other: &u8) -> Option { - self.0.partial_cmp(other) + self.get().partial_cmp(other) } } )* diff --git a/tests/ui/eager_transmute.stderr b/tests/ui/eager_transmute.stderr index c719ca8adc12..847d05cc5432 100644 --- a/tests/ui/eager_transmute.stderr +++ b/tests/ui/eager_transmute.stderr @@ -1,5 +1,5 @@ error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:21:33 + --> tests/ui/eager_transmute.rs:22:33 | LL | (op < 4).then_some(unsafe { std::mem::transmute(op) }) | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + (op < 4).then(|| unsafe { std::mem::transmute(op) }) | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:28:33 + --> tests/ui/eager_transmute.rs:29:33 | LL | (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:30:33 + --> tests/ui/eager_transmute.rs:31:33 | LL | (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:32:34 + --> tests/ui/eager_transmute.rs:33:34 | LL | (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:35:68 + --> tests/ui/eager_transmute.rs:36:68 | LL | let _: Option = (op > 0 && op < 10).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + let _: Option = (op > 0 && op < 10).then(|| unsafe { std::mem:: | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:37:86 + --> tests/ui/eager_transmute.rs:38:86 | LL | let _: Option = (op > 0 && op < 10 && unrelated == 0).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + let _: Option = (op > 0 && op < 10 && unrelated == 0).then(|| u | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:41:84 + --> tests/ui/eager_transmute.rs:42:84 | LL | let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[0]) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then(|| uns | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:54:70 + --> tests/ui/eager_transmute.rs:55:70 | LL | let _: Option = (1..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + let _: Option = (1..=3).contains(&op).then(|| unsafe { std::mem | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:56:83 + --> tests/ui/eager_transmute.rs:57:83 | LL | let _: Option = ((1..=3).contains(&op) || op == 4).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL + let _: Option = ((1..=3).contains(&op) || op == 4).then(|| unsa | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:58:69 + --> tests/ui/eager_transmute.rs:59:69 | LL | let _: Option = (1..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + let _: Option = (1..3).contains(&op).then(|| unsafe { std::mem: | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:60:68 + --> tests/ui/eager_transmute.rs:61:68 | LL | let _: Option = (1..).contains(&op).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + let _: Option = (1..).contains(&op).then(|| unsafe { std::mem:: | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:62:68 + --> tests/ui/eager_transmute.rs:63:68 | LL | let _: Option = (..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + let _: Option = (..3).contains(&op).then(|| unsafe { std::mem:: | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:64:69 + --> tests/ui/eager_transmute.rs:65:69 | LL | let _: Option = (..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + let _: Option = (..=3).contains(&op).then(|| unsafe { std::mem: | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:75:28 + --> tests/ui/eager_transmute.rs:76:28 | LL | (op < 4).then_some(std::mem::transmute::<_, Opcode>(op)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + (op < 4).then(|| std::mem::transmute::<_, Opcode>(op)); | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:106:62 + --> tests/ui/eager_transmute.rs:109:62 | LL | let _: Option> = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + let _: Option> = (v1 > 0).then(|| unsafe { std::mem::transm | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:113:86 + --> tests/ui/eager_transmute.rs:116:86 | LL | let _: Option = (v2 < NonZero::new(255u8).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -193,7 +193,7 @@ LL + let _: Option = (v2 < NonZero::new(255u8).unwrap()).then(|| u | error: this transmute is always evaluated eagerly, even if the condition is false - --> tests/ui/eager_transmute.rs:120:93 + --> tests/ui/eager_transmute.rs:123:93 | LL | let _: Option = (v2 < NonZero::new(255u8).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ From 7db6bf7f39ee9fe242e11247201ca389d8799d2e Mon Sep 17 00:00:00 2001 From: Gri-ffin Date: Fri, 1 May 2026 16:34:47 +0100 Subject: [PATCH 11/36] fix: [needless_return_with_question_mark] trigger in async functions --- .../needless_return_with_question_mark.rs | 23 ++++++++++++---- .../needless_return_with_question_mark.fixed | 26 +++++++++++++++++++ .../ui/needless_return_with_question_mark.rs | 26 +++++++++++++++++++ .../needless_return_with_question_mark.stderr | 14 +++++++++- 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/returns/needless_return_with_question_mark.rs b/clippy_lints/src/returns/needless_return_with_question_mark.rs index c47a3ef21e86..7c55150db10d 100644 --- a/clippy_lints/src/returns/needless_return_with_question_mark.rs +++ b/clippy_lints/src/returns/needless_return_with_question_mark.rs @@ -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 diff --git a/tests/ui/needless_return_with_question_mark.fixed b/tests/ui/needless_return_with_question_mark.fixed index 2c6faf37717d..8658192a0468 100644 --- a/tests/ui/needless_return_with_question_mark.fixed +++ b/tests/ui/needless_return_with_question_mark.fixed @@ -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 +} diff --git a/tests/ui/needless_return_with_question_mark.rs b/tests/ui/needless_return_with_question_mark.rs index b4411fa19561..64c2d5f404d4 100644 --- a/tests/ui/needless_return_with_question_mark.rs +++ b/tests/ui/needless_return_with_question_mark.rs @@ -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 +} diff --git a/tests/ui/needless_return_with_question_mark.stderr b/tests/ui/needless_return_with_question_mark.stderr index 2af0f46a1ad0..c4ca4003c322 100644 --- a/tests/ui/needless_return_with_question_mark.stderr +++ b/tests/ui/needless_return_with_question_mark.stderr @@ -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 From 21487c13a2b77536e66a6b1a19b4a9d66998779b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 4 May 2026 23:37:19 +0200 Subject: [PATCH 12/36] Update `askama` version to `0.16.0` in `clippy` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 234478eadbf9..a87fdeaf99c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } -askama = { version = "0.15.4", default-features = false, features = ["alloc", "config", "derive"] } +askama = { version = "0.16.0", default-features = false, features = ["alloc", "config", "derive"] } [dev-dependencies.toml] version = "0.9.7" From 7a5e25abc44d6d212c31bcc3914fc18e1653a912 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 23 Feb 2026 00:08:12 +0100 Subject: [PATCH 13/36] New lint: `manual_clear` This proposes replacing calls to `.truncate(0)` for types in the standard library by `.clear()`, as the specialized version might be more efficient. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/manual_clear.rs | 30 +++++++++++ clippy_lints/src/methods/mod.rs | 30 +++++++++++ tests/ui/manual_clear.fixed | 54 ++++++++++++++++++++ tests/ui/manual_clear.rs | 54 ++++++++++++++++++++ tests/ui/manual_clear.stderr | 64 ++++++++++++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 clippy_lints/src/methods/manual_clear.rs create mode 100644 tests/ui/manual_clear.fixed create mode 100644 tests/ui/manual_clear.rs create mode 100644 tests/ui/manual_clear.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 45bfb65b2e67..db8134eff8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6771,6 +6771,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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 441b907eaf2f..520fb8fc2830 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -411,6 +411,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, diff --git a/clippy_lints/src/methods/manual_clear.rs b/clippy_lints/src/methods/manual_clear.rs new file mode 100644 index 000000000000..dbf1c85add55 --- /dev/null +++ b/clippy_lints/src/methods/manual_clear.rs @@ -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, + ); + }); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b647dbdc8468..983778424c0e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -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; @@ -1764,6 +1765,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. @@ -4806,6 +4832,7 @@ ITER_WITH_DRAIN, JOIN_ABSOLUTE_PATHS, LINES_FILTER_MAP_OK, + MANUAL_CLEAR, MANUAL_CONTAINS, MANUAL_C_STR_LITERALS, MANUAL_FILTER_MAP, @@ -5775,6 +5802,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, diff --git a/tests/ui/manual_clear.fixed b/tests/ui/manual_clear.fixed new file mode 100644 index 000000000000..f09d62970e87 --- /dev/null +++ b/tests/ui/manual_clear.fixed @@ -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 = 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); +} diff --git a/tests/ui/manual_clear.rs b/tests/ui/manual_clear.rs new file mode 100644 index 000000000000..5217b9bcf61d --- /dev/null +++ b/tests/ui/manual_clear.rs @@ -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 = 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); +} diff --git a/tests/ui/manual_clear.stderr b/tests/ui/manual_clear.stderr new file mode 100644 index 000000000000..19a323a415f5 --- /dev/null +++ b/tests/ui/manual_clear.stderr @@ -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 + From 9c8be593d0e5dd6354a44848f8d7285105a1b8f2 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Tue, 5 May 2026 22:35:31 +0000 Subject: [PATCH 14/36] fix: `manual_option_zip` FP when the outer param is used in closure --- clippy_lints/src/methods/manual_option_zip.rs | 11 ++--------- tests/ui/manual_option_zip.fixed | 7 +++++++ tests/ui/manual_option_zip.rs | 7 +++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/methods/manual_option_zip.rs b/clippy_lints/src/methods/manual_option_zip.rs index 203957c15658..aa23b9deff45 100644 --- a/clippy_lints/src/methods/manual_option_zip.rs +++ b/clippy_lints/src/methods/manual_option_zip.rs @@ -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) diff --git a/tests/ui/manual_option_zip.fixed b/tests/ui/manual_option_zip.fixed index c0ab66e19169..1f217b14ed15 100644 --- a/tests/ui/manual_option_zip.fixed +++ b/tests/ui/manual_option_zip.fixed @@ -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))); +} diff --git a/tests/ui/manual_option_zip.rs b/tests/ui/manual_option_zip.rs index 578f26cea637..f04f14cb8d2f 100644 --- a/tests/ui/manual_option_zip.rs +++ b/tests/ui/manual_option_zip.rs @@ -116,3 +116,10 @@ fn and_then(self, f: impl FnOnce(i32) -> Option) -> Option { 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))); +} From ce93eb033823e22ce59413bfff01022a7db40b27 Mon Sep 17 00:00:00 2001 From: Ross Sullivan Date: Tue, 5 May 2026 19:08:03 +0900 Subject: [PATCH 15/36] test: Prepare tests for new Cargo build-dir layout --- Cargo.toml | 2 +- tests/compile-test.rs | 62 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 234478eadbf9..f3af37720e7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index c5af6d2d27c4..e5933d7c7a3f 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -89,11 +89,33 @@ fn internal_extern_flags() -> Vec { 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 = 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//build` + let build_dir = { + let mut d = deps_path.to_path_buf(); + d.pop(); // remove `out` + d.pop(); // remove `` + d.pop(); // remove `` + 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//build` dir. +fn discover_out_dirs(dir: &Path) -> Vec { + 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::>() +} From ebc91206e311d7bc37d495a219c93530649bd4ea Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 22:26:35 +0900 Subject: [PATCH 16/36] support `ast::ExprKind::Move` in clippy --- clippy_utils/src/sugg.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 92f08b604ca5..f194103ae2e8 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -231,6 +231,7 @@ pub fn ast( | ast::ExprKind::Loop(..) | ast::ExprKind::MacCall(..) | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Move(..) | ast::ExprKind::Paren(..) | ast::ExprKind::Underscore | ast::ExprKind::Path(..) From 0d1e0952f54e7de5999e3a23b14be2220173f6f2 Mon Sep 17 00:00:00 2001 From: Kokoro2336 <2529677678@qq.com> Date: Fri, 1 May 2026 13:26:24 +0800 Subject: [PATCH 17/36] fix: self deref detecting in check_clone_on_copy. fix: peeling of block. test: supply corner case tests of implicit_return. fix: pass body.value rather than block.expr. fix: special checking for implicit_return. fix: suggestion. refactor: flatten the code structure. refactor: remove lint submission in helper. refactor: call is_lint_allowed when necessary. --- clippy_lints/src/non_canonical_impls.rs | 76 ++++++++++++++++++------ tests/ui/non_canonical_clone_impl.fixed | 45 +++++++++++--- tests/ui/non_canonical_clone_impl.rs | 53 ++++++++++++++--- tests/ui/non_canonical_clone_impl.stderr | 34 +++++++++-- 4 files changed, 166 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 46d1be80b50a..4729270d6a92 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -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 diff --git a/tests/ui/non_canonical_clone_impl.fixed b/tests/ui/non_canonical_clone_impl.fixed index d9be98de69b1..3e2ffaf01468 100644 --- a/tests/ui/non_canonical_clone_impl.fixed +++ b/tests/ui/non_canonical_clone_impl.fixed @@ -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); @@ -104,7 +98,7 @@ impl Copy for Uwu {} 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 {} +} diff --git a/tests/ui/non_canonical_clone_impl.rs b/tests/ui/non_canonical_clone_impl.rs index 3db22bdbcf31..abe388320752 100644 --- a/tests/ui/non_canonical_clone_impl.rs +++ b/tests/ui/non_canonical_clone_impl.rs @@ -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); @@ -118,7 +112,7 @@ impl Copy for Uwu {} 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 {} +} diff --git a/tests/ui/non_canonical_clone_impl.stderr b/tests/ui/non_canonical_clone_impl.stderr index cf36a8f49f81..3741a7dcce86 100644 --- a/tests/ui/non_canonical_clone_impl.stderr +++ b/tests/ui/non_canonical_clone_impl.stderr @@ -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 From 6bc532f0fd8364ad1a9733854af929a1d3b2e0dd Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 26 Feb 2026 23:08:22 +0200 Subject: [PATCH 18/36] Reborrow traits --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index f4ef9f78b35c..0f5e021788c6 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -134,7 +134,7 @@ 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::RawPtr(_, place) => { + 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), From 21ef9baa960ea0509b1a47c65cbf798d853a1f63 Mon Sep 17 00:00:00 2001 From: Zequan Wu Date: Thu, 7 May 2026 14:43:26 -0700 Subject: [PATCH 19/36] test: suppress linker depracation warning --- tests/ui/linking/ld64-cross-compilation.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ui/linking/ld64-cross-compilation.rs b/tests/ui/linking/ld64-cross-compilation.rs index d6c6d1ff91de..0c7f9f9accfa 100644 --- a/tests/ui/linking/ld64-cross-compilation.rs +++ b/tests/ui/linking/ld64-cross-compilation.rs @@ -6,6 +6,8 @@ //@ run-pass //@ only-x86_64-apple-darwin +#![allow(linker_messages)] + fn main() { let dst: Vec = Vec::new(); let len = broken_func(std::hint::black_box(2), dst); From f858a83191fc0f9b3dc6c41dbddfb410fc7def9d Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 8 May 2026 16:30:52 +0800 Subject: [PATCH 20/36] remove dswij from rotation --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 4ea2c1374ea1..3ed0463f8937 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -94,6 +94,7 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", + "dswij", ] [assign.owners] From 9413ccd8da3e724630e61d235e704287a74e3c6f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 11 May 2026 07:53:30 +0200 Subject: [PATCH 21/36] Fix typos in book --- book/src/lints.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/lints.md b/book/src/lints.md index 1dd517976601..55b382c85565 100644 --- a/book/src/lints.md +++ b/book/src/lints.md @@ -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 From a453e8cfcf3ab595cd9e0530563e6e6d2df3d632 Mon Sep 17 00:00:00 2001 From: moses7054 Date: Mon, 11 May 2026 22:08:21 +0530 Subject: [PATCH 22/36] Add inline_trait_bounds lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/inline_trait_bounds.rs | 137 ++++++++++++++++++++ clippy_lints/src/lib.rs | 2 + tests/ui/inline_trait_bounds.fixed | 94 ++++++++++++++ tests/ui/inline_trait_bounds.rs | 94 ++++++++++++++ tests/ui/inline_trait_bounds.stderr | 162 ++++++++++++++++++++++++ 7 files changed, 491 insertions(+) create mode 100644 clippy_lints/src/inline_trait_bounds.rs create mode 100644 tests/ui/inline_trait_bounds.fixed create mode 100644 tests/ui/inline_trait_bounds.rs create mode 100644 tests/ui/inline_trait_bounds.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c3fb84ad9f..f53143e564b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c8f8382dd9e4..4218d1fdc290 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -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, diff --git a/clippy_lints/src/inline_trait_bounds.rs b/clippy_lints/src/inline_trait_bounds.rs new file mode 100644 index 000000000000..99716253c53e --- /dev/null +++ b/clippy_lints/src/inline_trait_bounds.rs @@ -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() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn foo() 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::>(); + + 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}") +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0875982f3bbf..af25b1f7f1aa 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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); diff --git a/tests/ui/inline_trait_bounds.fixed b/tests/ui/inline_trait_bounds.fixed new file mode 100644 index 000000000000..f9cb6df4288b --- /dev/null +++ b/tests/ui/inline_trait_bounds.fixed @@ -0,0 +1,94 @@ +#![warn(clippy::inline_trait_bounds)] + +// Free functions + +fn inline_simple() where T: Clone {} +//~^ inline_trait_bounds + +fn inline_multiple() 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() +//~^ inline_trait_bounds +where + T: core::fmt::Debug, T: Clone, +{ +} + +fn inline_with_const() where T: Clone {} +//~^ inline_trait_bounds + +fn inline_with_return(val: T) -> T where T: Clone { + //~^ inline_trait_bounds + val +} + +// Trait methods + +trait MyTrait { + fn trait_method_inline(&self) where T: Clone; + //~^ inline_trait_bounds + + fn trait_method_default(&self) where T: Clone + Copy {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone; +} + +// Impl methods + +struct MyStruct; + +impl MyStruct { + fn impl_method_inline(&self) where T: Clone {} + //~^ inline_trait_bounds + + fn impl_method_multiple(&self) where T: Clone, U: core::fmt::Debug {} + //~^ inline_trait_bounds +} + +impl MyTrait for MyStruct { + fn trait_method_inline(&self) where T: Clone {} + //~^ inline_trait_bounds + + fn trait_method_default(&self) where T: Clone + Copy {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone, + { + } +} + +// Should NOT lint + +fn where_only() +where + T: Clone, +{ +} + +fn no_bounds() {} + +fn no_generics() {} + +struct InlineStruct(T); + +enum InlineEnum { + A(T), +} + +#[allow(invalid_type_param_default)] +//~v inline_trait_bounds +fn with_default_value(x: T) -> T where T: Clone { + x +} diff --git a/tests/ui/inline_trait_bounds.rs b/tests/ui/inline_trait_bounds.rs new file mode 100644 index 000000000000..86cf740b43f4 --- /dev/null +++ b/tests/ui/inline_trait_bounds.rs @@ -0,0 +1,94 @@ +#![warn(clippy::inline_trait_bounds)] + +// Free functions + +fn inline_simple() {} +//~^ inline_trait_bounds + +fn inline_multiple() {} +//~^ 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() +//~^ inline_trait_bounds +where + T: core::fmt::Debug, +{ +} + +fn inline_with_const() {} +//~^ inline_trait_bounds + +fn inline_with_return(val: T) -> T { + //~^ inline_trait_bounds + val +} + +// Trait methods + +trait MyTrait { + fn trait_method_inline(&self); + //~^ inline_trait_bounds + + fn trait_method_default(&self) {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone; +} + +// Impl methods + +struct MyStruct; + +impl MyStruct { + fn impl_method_inline(&self) {} + //~^ inline_trait_bounds + + fn impl_method_multiple(&self) {} + //~^ inline_trait_bounds +} + +impl MyTrait for MyStruct { + fn trait_method_inline(&self) {} + //~^ inline_trait_bounds + + fn trait_method_default(&self) {} + //~^ inline_trait_bounds + + fn trait_method_where(&self) + where + T: Clone, + { + } +} + +// Should NOT lint + +fn where_only() +where + T: Clone, +{ +} + +fn no_bounds() {} + +fn no_generics() {} + +struct InlineStruct(T); + +enum InlineEnum { + A(T), +} + +#[allow(invalid_type_param_default)] +//~v inline_trait_bounds +fn with_default_value(x: T) -> T { + x +} diff --git a/tests/ui/inline_trait_bounds.stderr b/tests/ui/inline_trait_bounds.stderr new file mode 100644 index 000000000000..730e99b17754 --- /dev/null +++ b/tests/ui/inline_trait_bounds.stderr @@ -0,0 +1,162 @@ +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:5:17 + | +LL | fn inline_simple() {} + | ^^^^^^^^^^ + | + = 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() {} +LL + fn inline_simple() where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:8:19 + | +LL | fn inline_multiple() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_multiple() {} +LL + fn inline_multiple() 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() + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL ~ fn inline_with_where() +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() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_with_const() {} +LL + fn inline_with_const() where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:27:22 + | +LL | fn inline_with_return(val: T) -> T { + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn inline_with_return(val: T) -> T { +LL + fn inline_with_return(val: T) -> T where T: Clone { + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:35:27 + | +LL | fn trait_method_inline(&self); + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_inline(&self); +LL + fn trait_method_inline(&self) where T: Clone; + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:38:28 + | +LL | fn trait_method_default(&self) {} + | ^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_default(&self) {} +LL + fn trait_method_default(&self) where T: Clone + Copy {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:51:26 + | +LL | fn impl_method_inline(&self) {} + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn impl_method_inline(&self) {} +LL + fn impl_method_inline(&self) where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:54:28 + | +LL | fn impl_method_multiple(&self) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn impl_method_multiple(&self) {} +LL + fn impl_method_multiple(&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(&self) {} + | ^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_inline(&self) {} +LL + fn trait_method_inline(&self) where T: Clone {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:62:28 + | +LL | fn trait_method_default(&self) {} + | ^^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn trait_method_default(&self) {} +LL + fn trait_method_default(&self) where T: Clone + Copy {} + | + +error: inline trait bounds used + --> tests/ui/inline_trait_bounds.rs:92:22 + | +LL | fn with_default_value(x: T) -> T { + | ^^^^^^^^^^^^^^^^ + | +help: move bounds to a `where` clause + | +LL - fn with_default_value(x: T) -> T { +LL + fn with_default_value(x: T) -> T where T: Clone { + | + +error: aborting due to 13 previous errors + From 4d9634c764350143cb724316af3d21b8c380cdb2 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 12 May 2026 14:20:21 +0200 Subject: [PATCH 23/36] Update dependencies to remove windows-targets dependency --- clippy_test_deps/Cargo.lock | 108 +++++++++--------------------------- 1 file changed, 25 insertions(+), 83 deletions(-) diff --git a/clippy_test_deps/Cargo.lock b/clippy_test_deps/Cargo.lock index 63e41e8abd10..33ee75ad4945 100644 --- a/clippy_test_deps/Cargo.lock +++ b/clippy_test_deps/Cargo.lock @@ -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" From 2493ef150399adb8ad85234a5ae60de9b8ac2d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 15:59:37 +0200 Subject: [PATCH 24/36] rustdoc: `SpanMapVisitor`: Cache `TypeckResults` --- src/doc/rustdoc/src/unstable-features.md | 9 ++ src/librustdoc/html/render/span_map.rs | 122 +++++++++++------ .../jump-to-def/assoc-items-extra.rs | 20 +++ tests/rustdoc-html/jump-to-def/assoc-items.rs | 128 ++++++++++++------ tests/rustdoc-html/jump-to-def/assoc-types.rs | 20 --- .../rustdoc-html/jump-to-def/no-body-items.rs | 24 ---- .../items-nested-in-bodies.rs | 31 +++++ 7 files changed, 229 insertions(+), 125 deletions(-) create mode 100644 tests/rustdoc-html/jump-to-def/assoc-items-extra.rs delete mode 100644 tests/rustdoc-html/jump-to-def/assoc-types.rs delete mode 100644 tests/rustdoc-html/jump-to-def/no-body-items.rs create mode 100644 tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 1c91ad343b8c..45f79f53f2c5 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -653,6 +653,15 @@ add the `--scrape-tests` flag. This flag enables the generation of links in the source code pages which allow the reader to jump to a type definition. +> [!WARNING] +> In very specific scenarios, enabling this feature may lead to your program getting rejected if you +> rely on rustdoc intentionally not running all semantic analysis passes on function bodies to aid +> with documenting `cfg`-conditional items. +> +> More concretely, rustdoc may choose to type-check bodies if they contain type-dependent paths +> including method calls. This may result in name resolution and type errors getting reported that +> rustdoc would usually suppress. + ### `--test-builder`: `rustc`-like program to build tests * Tracking issue: [#102981](https://github.com/rust-lang/rust/issues/102981) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ff214bad59f1..d314f2b55865 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -1,12 +1,13 @@ use std::path::{Path, PathBuf}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, Visitor, VisitorExt}; use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::{BytePos, ExpnKind}; use crate::clean::{self, PrimitiveType, rustc_span}; @@ -82,7 +83,8 @@ pub(crate) fn collect_spans_and_sources( generate_link_to_definition: bool, ) -> (FxIndexMap, FxHashMap) { if include_sources { - let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; + let mut visitor = + SpanMapVisitor { tcx, maybe_typeck_results: None, matches: FxHashMap::default() }; if generate_link_to_definition { tcx.hir_walk_toplevel_module(&mut visitor); @@ -96,12 +98,34 @@ pub(crate) fn collect_spans_and_sources( struct SpanMapVisitor<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, + pub(crate) maybe_typeck_results: Option>, pub(crate) matches: FxHashMap, } -impl SpanMapVisitor<'_> { +impl<'tcx> SpanMapVisitor<'tcx> { + /// Returns the typeck results of the current body if we're in one. + /// + /// This will typeck the body if it hasn't been already. Since rustdoc intentionally doesn't run + /// all semantic analysis passes on function bodies at the time of writing, this can lead to us + /// "suddenly" rejecting the user's code under `--generate-link-to-definition` while accepting + /// it if that flag isn't passed! So use this method sparingly and think about the consequences + /// including performance! + /// + /// This behavior is documented in the rustdoc book. Ideally, it wouldn't be that way but no + /// good solution has been found so far. Don't think about adding some sort of flag to rustc to + /// suppress diagnostic emission that would be unsound wrt. `ErrorGuaranteed`[^1] and generally + /// be quite hacky! + /// + /// [^1]: Historical context: + /// . + fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { + let results = self.maybe_typeck_results.as_mut()?; + let results = results.cache.get_or_insert_with(|| self.tcx.typeck_body(results.body_id)); + Some(results) + } + /// This function is where we handle `hir::Path` elements and add them into the "span map". - fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) { + fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. // Would be nice to support them too alongside the other `DefKind` @@ -218,18 +242,13 @@ fn handle_macro(&mut self, span: rustc_span::Span) -> bool { } fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, span: Span) { - let tcx = self.tcx; - let body_id = tcx.hir_enclosing_body_owner(hir_id); - // FIXME: this is showing error messages for parts of the code that are not - // compiled (because of cfg)! - // - // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352 - let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); + let Some(typeck_results) = self.maybe_typeck_results() else { return }; + // Interestingly enough, for method calls, we need the whole expression whereas for static // method/function calls, we need the call expression specifically. if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) { let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, tcx)) + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) } else { LinkFromSrc::External(def_id) }; @@ -238,23 +257,6 @@ fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, span: Span) { } } -// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without -// panicking. -fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option { - for (_, node) in tcx.hir_parent_iter(hir_id) { - // FIXME: associated type impl items don't have an associated body, so we don't handle - // them currently. - if let Node::ImplItem(impl_item) = node - && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_)) - { - return None; - } else if let Some((def_id, _)) = node.associated_body() { - return Some(def_id); - } - } - None -} - impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { type NestedFilter = nested_filter::All; @@ -262,7 +264,14 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { self.tcx } - fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) { + fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result { + let maybe_typeck_results = + self.maybe_typeck_results.replace(LazyTypeckResults { body_id, cache: None }); + self.visit_body(self.tcx.hir_body(body_id)); + self.maybe_typeck_results = maybe_typeck_results; + } + + fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { if self.handle_macro(path.span) { return; } @@ -272,25 +281,37 @@ fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { - QPath::TypeRelative(qself, path) => { - if matches!(path.res, Res::Err) { - let tcx = self.tcx; - if let Some(body_id) = hir_enclosing_body_owner(tcx, id) { - let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); - let path = rustc_hir::Path { + QPath::TypeRelative(qself, segment) => { + if let Res::Err = segment.res { + // FIXME: This doesn't work for paths in *types* since HIR ty lowering currently + // doesn't write back the resolution of type-relative paths. Updating it + // to do so should be a simple fix. + // FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, + // rustc currently doesn't keep around that information & thus can't + // provide an API for it. + // `ItemCtxt`s would need a place to write back the resolution of type- + // dependent definitions. Ideally there was some sort of query keyed on + // the `LocalDefId` of the owning item that returns some table with which + // we can map the `HirId` to a `DefId`. + // Of course, we could re-HIR-ty-lower such paths *here* if we were to + // extend the public API of HIR analysis. However, I strongly advise + // against it as it would be too much of a hack. + if let Some(typeck_results) = self.maybe_typeck_results() { + let path = hir::Path { // We change the span to not include parens. - span: path.ident.span, + span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), + // FIXME(fmease): Don't create a path with zero segments! segments: &[], }; self.handle_path(&path, false); } } else { - self.infer_id(path.hir_id, Some(id), path.ident.span.into()); + self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); } rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); - self.visit_path_segment(path); + self.visit_path_segment(segment); } QPath::Resolved(maybe_qself, path) => { self.handle_path(path, true); @@ -323,11 +344,17 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { intravisit::walk_mod(self, m); } - fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into()) } + // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't + // type-relative. In the majority of cases, it's just gonna be a + // `Resolved` path meaning we can end up unnecessarily + // `typeck`'ing the body which is super costly! + // Moreover, if it actually is a type-relative path, we end up + // "resolving" it twice (with slightly different spans). ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), _ => { if self.handle_macro(expr.span) { @@ -340,6 +367,10 @@ fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { } fn visit_item(&mut self, item: &'tcx Item<'tcx>) { + // We're no longer in a body since we've crossed an item boundary. + // Temporarily take away the typeck results which are only valid in bodies. + let maybe_typeck_results = self.maybe_typeck_results.take(); + match item.kind { ItemKind::Static(..) | ItemKind::Const(..) @@ -359,6 +390,15 @@ fn visit_item(&mut self, item: &'tcx Item<'tcx>) { // We already have "visit_mod" above so no need to check it here. | ItemKind::Mod(..) => {} } + intravisit::walk_item(self, item); + + self.maybe_typeck_results = maybe_typeck_results; } } + +/// Lazily computed & cached [`ty::TypeckResults`]. +struct LazyTypeckResults<'tcx> { + body_id: hir::BodyId, + cache: Option<&'tcx ty::TypeckResults<'tcx>>, +} diff --git a/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs b/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs new file mode 100644 index 000000000000..0e15f32048f6 --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/assoc-items-extra.rs @@ -0,0 +1,20 @@ +// Like test `assoc-items.rs` but now utilizing unstable features. +// FIXME: Make use of (m)GCA assoc consts once they no longer ICE! +//@ compile-flags: -Zunstable-options --generate-link-to-definition +#![feature(return_type_notation)] + +//@ has 'src/assoc_items_extra/assoc-items-extra.rs.html' + +trait Trait0 { + fn fn0() -> impl Sized; + fn fn1() -> impl Sized; +} + +fn item() +where + //@ has - '//a[@href="#9"]' 'fn0' + ::fn0(..): Copy, // Item, AssocFn, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#10"]' 'fn1' + T::fn1(..): Copy, // Item, AssocFn, TypeRelative +{} diff --git a/tests/rustdoc-html/jump-to-def/assoc-items.rs b/tests/rustdoc-html/jump-to-def/assoc-items.rs index a434fa7e6053..f176b3962560 100644 --- a/tests/rustdoc-html/jump-to-def/assoc-items.rs +++ b/tests/rustdoc-html/jump-to-def/assoc-items.rs @@ -1,58 +1,106 @@ -// This test ensures that patterns also get a link generated. - //@ compile-flags: -Zunstable-options --generate-link-to-definition -#![crate_name = "foo"] +//@ has 'src/assoc_items/assoc-items.rs.html' -//@ has 'src/foo/assoc-items.rs.html' - -pub trait Trait { - type T; -} -pub trait Another { - type T; - const X: u32; +trait Trait0 { + fn fn0(); + fn fn1(); + fn fn2(); + fn fn3(); + const CT0: usize; + const CT1: usize; + type Ty0; + type Ty1; + type Ty2: Bound0; + type Ty3: Bound0; } -pub struct Foo; - -impl Foo { - pub fn new() -> Self { Foo } +trait Bound0 { + fn fn0(); + const CT0: usize; } -pub struct C; +fn expr() { + //@ has - '//a[@href="#6"]' 'fn0' + let _ = ::fn0; // Expr, AssocFn, Resolved + //@ has - '//a[@href="#7"]' 'fn1' + let _ = T::fn1; // Expr, AssocFn, TypeRelative -impl C { - pub fn wat() {} + //@ has - '//a[@href="#8"]' 'fn2' + let _ = ::fn2(); // Expr, AssocFn, Resolved + //@ has - '//a[@href="#9"]' 'fn3' + let _ = T::fn3(); // Expr, AssocFn, TypeRelative + + //@ has - '//a[@href="#10"]' 'CT0' + let _ = ::CT0; // Expr, AssocConst, Resolved + //@ has - '//a[@href="#11"]' 'CT1' + let _ = ::CT1; // Expr, AssocConst, TypeRelative + + //@ has - '//a[@href="#12"]' 'Ty0' + let _: ::Ty0; // Expr, AssocTy, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#13"]' 'Ty1' + let _: T::Ty1; // Expr, AssocTy, TypeRelative + + //@ has - '//a[@href="#14"]' 'Ty2' + //@ has - '//a[@href="#19"]' 'fn0' + let _ = ::Ty2::fn0(); + + // FIXME: Support this: + //@ !has - '//a[@href="#14"]' 'Ty3' + //@ has - '//a[@href="#20"]' 'CT0' + let _ = T::Ty2::CT0; } -// These two links must not change and in particular must contain `/derive.`! -//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug' -//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq' -#[derive(Debug, PartialEq)] -pub struct Bar; -impl Trait for Bar { - type T = Foo; -} -impl Another for Bar { - type T = C; - const X: u32 = 12; +trait Trait1 { + const CT0: usize; + const CT1: usize; + const CT2: usize; + + fn scope(); } -pub fn bar() { - //@ has - '//a[@href="#20"]' 'new' - ::T::new(); - //@ has - '//a[@href="#26"]' 'wat' - ::T::wat(); - match 12u32 { - //@ has - '//a[@href="#14"]' 'X' - ::X => {} +fn pat() { + //@ has - '//a[@href="#56"]' 'CT0' + if let <() as Trait1>::CT0 = 0 {} // Pat, AssocConst, Resolved + + match 0 { + //@ has - '//a[@href="#57"]' 'CT1' + <() as Trait1>::CT1 => {} // Pat, AssocConst, Resolved _ => {} } } -pub struct Far { - //@ has - '//a[@href="#10"]' 'T' - x: ::T, +impl Trait1 for () { + const CT0: usize = 0; + const CT1: usize = 1; + const CT2: usize = 2; + + fn scope() { + //@ has - '//a[@href="#58"]' 'CT2' + if let Self::CT2 = 0 {} // Pat, AssocConst, TypeRelative + } +} + +trait Trait2 { + const CT0: usize; + type Ty0; + type Ty1; +} + +impl Trait2 for () { + const CT0: usize = 0; + type Ty0 = (); + type Ty1 = (); +} + +struct Item { + //@ has - '//a[@href="#87"]' 'CT0' + f0: [(); <() as Trait2>::CT0], // Item, AssocConst, Resolved + //@ has - '//a[@href="#88"]' 'Ty0' + f1: ::Ty0, // Item, AssocTy, Resolved + // FIXME: Support this: + //@ !has - '//a[@href="#89"]' 'Ty1' + f2: T::Ty1, // Item, AssocTy, TypeRelative } diff --git a/tests/rustdoc-html/jump-to-def/assoc-types.rs b/tests/rustdoc-html/jump-to-def/assoc-types.rs deleted file mode 100644 index f430eaf16389..000000000000 --- a/tests/rustdoc-html/jump-to-def/assoc-types.rs +++ /dev/null @@ -1,20 +0,0 @@ -// This test ensures that associated types don't crash rustdoc jump to def. - -//@ compile-flags: -Zunstable-options --generate-link-to-definition - - -#![crate_name = "foo"] - -//@ has 'src/foo/assoc-types.rs.html' - -pub trait Trait { - type Node; -} - -pub fn y() { - struct X(G); - - impl Trait for X { - type Node = G::Node; - } -} diff --git a/tests/rustdoc-html/jump-to-def/no-body-items.rs b/tests/rustdoc-html/jump-to-def/no-body-items.rs deleted file mode 100644 index e74640072146..000000000000 --- a/tests/rustdoc-html/jump-to-def/no-body-items.rs +++ /dev/null @@ -1,24 +0,0 @@ -// This test ensures that items with no body don't panic when generating -// jump to def links. - -//@ compile-flags: -Zunstable-options --generate-link-to-definition - -#![crate_name = "foo"] - -//@ has 'src/foo/no-body-items.rs.html' - -pub trait A { - type T; - type U; -} - -impl A for () { - type T = Self::U; - type U = (); -} - -pub trait C { - type X; -} - -pub struct F(pub T::X); diff --git a/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs b/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs new file mode 100644 index 000000000000..7d48e885c3f2 --- /dev/null +++ b/tests/rustdoc-ui/generate-link-to-definition/items-nested-in-bodies.rs @@ -0,0 +1,31 @@ +// When trying to resolve a type-relative path (e.g., `T::Item` where `T` is a type param) in an +// item that's nested inside of a body (e.g., of a function or constant), we once tried to look up +// the definition in the `TypeckResults` of the body owner which is wrong and led to compiler ICEs. +// +// We now make sure to invalidate the `TypeckResults` when crossing a body - item border. +// +// For additional context, `TypeckResults` as returned by queries like `typeck` store the typeck +// results of *bodies* only. In item signatures / non-bodies, there's no equivalent at the time of +// writing, so it's impossible to resolve HIR TypeRelative paths (identified by a `HirId`) to their +// definition (`DefId`) in other parts of the compiler / in tools. + +//@ compile-flags: -Zunstable-options --generate-link-to-definition +//@ check-pass +// issue: + +fn scope() { + struct X(T::Item); + + trait Trait { + type Ty; + + fn func() + where + T::Item: Copy + {} + } + + impl Trait for T { + type Ty = T::Item; + } +} From b0eb4ffb00008891ed797eccda532a4246fd9b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:26:46 +0200 Subject: [PATCH 25/36] Don't check if the resolution of `TypeRelative` paths is `Err` because it always is `rustc_resolve` doesn't resolve type-relative paths since that's the job of HIR ty lowering and HIR typeck. `segment.res` comes from `rustc_resolve` and is thus always `Res::Err`. So just try to obtain the `TypeckResults` immediately since they contain the actual resolution as deduced by HIR typeck. --- src/librustdoc/html/render/span_map.rs | 48 ++++++++++++-------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index d314f2b55865..324c79ff2a43 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -282,32 +282,28 @@ fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { match *qpath { QPath::TypeRelative(qself, segment) => { - if let Res::Err = segment.res { - // FIXME: This doesn't work for paths in *types* since HIR ty lowering currently - // doesn't write back the resolution of type-relative paths. Updating it - // to do so should be a simple fix. - // FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, - // rustc currently doesn't keep around that information & thus can't - // provide an API for it. - // `ItemCtxt`s would need a place to write back the resolution of type- - // dependent definitions. Ideally there was some sort of query keyed on - // the `LocalDefId` of the owning item that returns some table with which - // we can map the `HirId` to a `DefId`. - // Of course, we could re-HIR-ty-lower such paths *here* if we were to - // extend the public API of HIR analysis. However, I strongly advise - // against it as it would be too much of a hack. - if let Some(typeck_results) = self.maybe_typeck_results() { - let path = hir::Path { - // We change the span to not include parens. - span: segment.ident.span, - res: typeck_results.qpath_res(qpath, id), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], - }; - self.handle_path(&path, false); - } - } else { - self.infer_id(segment.hir_id, Some(id), segment.ident.span.into()); + // FIXME: This doesn't work for paths in *types* since HIR ty lowering currently + // doesn't write back the resolution of type-relative paths. Updating it to + // do so should be a simple fix. + // FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, rustc + // currently doesn't keep around that information & thus can't provide an API + // for it. + // `ItemCtxt`s would need a place to write back the resolution of type- + // dependent definitions. Ideally there was some sort of query keyed on the + // `LocalDefId` of the owning item that returns some table with which we can + // map the `HirId` to a `DefId`. + // Of course, we could re-HIR-ty-lower such paths *here* if we were to extend + // the public API of HIR analysis. However, I strongly advise against it as + // it would be too much of a hack. + if let Some(typeck_results) = self.maybe_typeck_results() { + let path = hir::Path { + // We change the span to not include parens. + span: segment.ident.span, + res: typeck_results.qpath_res(qpath, id), + // FIXME(fmease): Don't create a path with zero segments! + segments: &[], + }; + self.handle_path(&path, false); } rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); From f5ad60c7daa7280f55cf9bc9eaee83de402bc946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 16:49:39 +0200 Subject: [PATCH 26/36] Inline non-self-descriptive fn `infer_id` --- src/librustdoc/html/render/span_map.rs | 53 +++++++++++--------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 324c79ff2a43..6c847effe0b3 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -124,6 +124,14 @@ fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { Some(results) } + fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { + if def_id.is_local() { + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) + } else { + LinkFromSrc::External(def_id) + } + } + /// This function is where we handle `hir::Path` elements and add them into the "span map". fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { @@ -131,11 +139,6 @@ fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { // Would be nice to support them too alongside the other `DefKind` // (such as primitive types!). Res::Def(kind, def_id) if kind != DefKind::TyParam => { - let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, self.tcx)) - } else { - LinkFromSrc::External(def_id) - }; // In case the path ends with generics, we remove them from the span. let span = if only_use_last_segment && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) @@ -156,7 +159,7 @@ fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { }) .unwrap_or(path.span) }; - self.matches.insert(span.into(), link); + self.matches.insert(span.into(), self.link_for_def(def_id)); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { let path_span = if only_use_last_segment @@ -240,21 +243,6 @@ fn handle_macro(&mut self, span: rustc_span::Span) -> bool { self.matches.insert(new_span.into(), link_from_src); true } - - fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option, span: Span) { - let Some(typeck_results) = self.maybe_typeck_results() else { return }; - - // Interestingly enough, for method calls, we need the whole expression whereas for static - // method/function calls, we need the call expression specifically. - if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) { - let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, self.tcx)) - } else { - LinkFromSrc::External(def_id) - }; - self.matches.insert(span, link); - } - } } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -341,23 +329,26 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - match expr.kind { - ExprKind::MethodCall(segment, ..) => { - self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into()) + let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { + // Exprs *have* to exist in a body, so typeck results should always be available. + let typeck_results = self.maybe_typeck_results().unwrap(); + if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { + self.matches.insert(span.into(), self.link_for_def(def_id)); } + }; + + match expr.kind { + ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't // type-relative. In the majority of cases, it's just gonna be a // `Resolved` path meaning we can end up unnecessarily // `typeck`'ing the body which is super costly! // Moreover, if it actually is a type-relative path, we end up // "resolving" it twice (with slightly different spans). - ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), - _ => { - if self.handle_macro(expr.span) { - // We don't want to go deeper into the macro. - return; - } - } + ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + // We don't want to go deeper into the macro. + _ if self.handle_macro(expr.span) => return, + _ => {} } intravisit::walk_expr(self, expr); } From bfcaf513b4819cfd3dd2b3e930dc702db05e7f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 18:23:50 +0200 Subject: [PATCH 27/36] Don't create a path with zero segments --- src/librustdoc/html/render/span_map.rs | 43 +++++++++----------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 6c847effe0b3..824eb758937f 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -135,37 +135,26 @@ fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { /// This function is where we handle `hir::Path` elements and add them into the "span map". fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { match path.res { - // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. - // Would be nice to support them too alongside the other `DefKind` - // (such as primitive types!). - Res::Def(kind, def_id) if kind != DefKind::TyParam => { + // FIXME: Properly support type parameters. Note they resolve just fine. The issue is + // that our highlighter would then also linkify their *definition site* for some reason + // linking them to themselves. Const parameters don't exhibit this issue. + Res::Def(DefKind::TyParam, _) => {} + Res::Def(_, def_id) => { + // The segments can be empty for `use *;` in a non-crate-root scope in Rust 2015. + let span = path.segments.last().map_or(path.span, |seg| seg.ident.span); // In case the path ends with generics, we remove them from the span. - let span = if only_use_last_segment - && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) - { - path_span + let span = if only_use_last_segment { + span } else { - path.segments - .last() - .map(|last| { - // In `use` statements, the included item is not in the path segments. - // However, it doesn't matter because you can't have generics on `use` - // statements. - if path.span.contains(last.ident.span) { - path.span.with_hi(last.ident.span.hi()) - } else { - path.span - } - }) - .unwrap_or(path.span) + // In `use` statements, the included item is not in the path segments. However, + // it doesn't matter because you can't have generics on `use` statements. + if path.span.contains(span) { path.span.with_hi(span.hi()) } else { path.span } }; self.matches.insert(span.into(), self.link_for_def(def_id)); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { - let path_span = if only_use_last_segment - && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) - { - path_span + let path_span = if only_use_last_segment { + path.segments.last().unwrap().ident.span } else { path.span }; @@ -176,7 +165,6 @@ fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { self.matches .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); } - Res::Err => {} _ => {} } } @@ -288,8 +276,7 @@ fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Spa // We change the span to not include parens. span: segment.ident.span, res: typeck_results.qpath_res(qpath, id), - // FIXME(fmease): Don't create a path with zero segments! - segments: &[], + segments: std::slice::from_ref(segment), }; self.handle_path(&path, false); } From efaf441d7b37a28462fd006d8ca64271c8cbb0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 May 2026 19:55:04 +0200 Subject: [PATCH 28/36] Don't special-case `ExprKind::Call` Don't unnecessarily try to obtain the type-dependent definition of callees in `visit_expr`, just let `visit_qpath` handle callees. This means that for callees that are * `Resolved` paths (the majority of callees) we don't try to `typeck` the enclosing body which should improve perf if the body doesn't contain any type-dependent definitions. * actually `TypeRelative` paths we don't resolve them twice (with slightly different spans) --- src/librustdoc/html/render/span_map.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 824eb758937f..863ce3a79f4e 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -316,23 +316,14 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - let mut handle_type_dependent_def = |hir_id: HirId, span: rustc_span::Span| { - // Exprs *have* to exist in a body, so typeck results should always be available. - let typeck_results = self.maybe_typeck_results().unwrap(); - if let Some(def_id) = typeck_results.type_dependent_def_id(hir_id) { - self.matches.insert(span.into(), self.link_for_def(def_id)); - } - }; - match expr.kind { - ExprKind::MethodCall(seg, ..) => handle_type_dependent_def(expr.hir_id, seg.ident.span), - // FIXME(fmease): We needlessly request `TypeckResults` even if the callee isn't - // type-relative. In the majority of cases, it's just gonna be a - // `Resolved` path meaning we can end up unnecessarily - // `typeck`'ing the body which is super costly! - // Moreover, if it actually is a type-relative path, we end up - // "resolving" it twice (with slightly different spans). - ExprKind::Call(callee, ..) => handle_type_dependent_def(callee.hir_id, callee.span), + ExprKind::MethodCall(segment, ..) => { + // Exprs *have* to exist in a body, so typeck results should always be available. + let typeck_results = self.maybe_typeck_results().unwrap(); + if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { + self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); + } + } // We don't want to go deeper into the macro. _ if self.handle_macro(expr.span) => return, _ => {} From e205784d77f5e84e5505b2b77536ad2d89961ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 13 May 2026 03:50:04 +0200 Subject: [PATCH 29/36] Don't try to resolve type-dependent paths in anon consts --- src/librustdoc/html/render/span_map.rs | 16 +++++++++++++--- .../generate-link-to-definition/anon-consts.rs | 11 +++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 863ce3a79f4e..517b538d1bfd 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -247,6 +247,16 @@ fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result { self.maybe_typeck_results = maybe_typeck_results; } + fn visit_anon_const(&mut self, ct: &'tcx hir::AnonConst) { + // FIXME: Typeck'ing anon consts leads to ICEs in rustc if the parent body wasn't typeck'ed + // yet. See #156418. Figure out what the best and proper solution for this is. Until + // then, let's prevent `typeck` from being called on anon consts by not setting + // `maybe_typeck_results` to `Some(_)`. + let maybe_typeck_results = self.maybe_typeck_results.take(); + self.visit_body(self.tcx.hir_body(ct.body)); + self.maybe_typeck_results = maybe_typeck_results; + } + fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { if self.handle_macro(path.span) { return; @@ -318,9 +328,9 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { - // Exprs *have* to exist in a body, so typeck results should always be available. - let typeck_results = self.maybe_typeck_results().unwrap(); - if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { + if let Some(typeck_results) = self.maybe_typeck_results() + && let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) + { self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); } } diff --git a/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs b/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs new file mode 100644 index 000000000000..3a774b0d9ca9 --- /dev/null +++ b/tests/rustdoc-ui/generate-link-to-definition/anon-consts.rs @@ -0,0 +1,11 @@ +// Ensure that we don't crash on anonymous constants. +// FIXME: Ideally, we would actually support linkifying type-dependent paths in +// anon consts but for now that's disabled until we figure out what the +// proper solution is implementation-wise. +// issue: +//@ check-pass + +fn scope() { + struct Hold; + let _ = X::<{ 0usize.saturating_add(1) }>; +} From 2c30279ff69c568248a341bdbd77a0ee24afcc15 Mon Sep 17 00:00:00 2001 From: John Millikin Date: Wed, 13 May 2026 19:24:01 +0900 Subject: [PATCH 30/36] Add `send_process_group_signal` to existing `unix_send_signal` feature This function wraps POSIX `killpg()`, and on Linux additionally may be implemented by `pidfd_send_signal`. --- library/std/src/os/unix/process.rs | 36 +++++++++++++++++++ library/std/src/sys/pal/unix/linux/pidfd.rs | 15 ++++++++ library/std/src/sys/process/unix/fuchsia.rs | 5 +++ library/std/src/sys/process/unix/unix.rs | 13 +++++++ .../std/src/sys/process/unix/unsupported.rs | 4 +++ library/std/src/sys/process/unix/vxworks.rs | 8 +++++ 6 files changed, 81 insertions(+) diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index a739d6ad2a90..e0bc415f9e3e 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -420,6 +420,38 @@ pub trait ChildExt: Sealed { /// } /// ``` fn send_signal(&self, signal: i32) -> io::Result<()>; + + /// Sends a signal to a child process's process group. + /// + /// # Errors + /// + /// This function will return an error if the signal is invalid or if the + /// child process does not have a process group. The integer values + /// associated with signals are implementation-specific, so it's encouraged + /// to use a crate that provides posix bindings. + /// + /// # Examples + /// + /// ```rust + /// #![feature(unix_send_signal)] + /// + /// use std::{io, os::unix::process::{ChildExt, CommandExt}, process::{Command, Stdio}}; + /// + /// use libc::SIGTERM; + /// + /// fn main() -> io::Result<()> { + /// # if cfg!(not(all(target_vendor = "apple", not(target_os = "macos")))) { + /// let child = Command::new("cat") + /// .stdin(Stdio::piped()) + /// .process_group(0) + /// .spawn()?; + /// child.send_process_group_signal(SIGTERM)?; + /// # } + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_send_signal", issue = "141975")] + fn send_process_group_signal(&self, signal: i32) -> io::Result<()>; } #[unstable(feature = "unix_send_signal", issue = "141975")] @@ -427,6 +459,10 @@ impl ChildExt for process::Child { fn send_signal(&self, signal: i32) -> io::Result<()> { self.handle.send_signal(signal) } + + fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { + self.handle.send_process_group_signal(signal) + } } #[stable(feature = "process_extensions", since = "1.2.0")] diff --git a/library/std/src/sys/pal/unix/linux/pidfd.rs b/library/std/src/sys/pal/unix/linux/pidfd.rs index 671046949aa1..ac376c1a81af 100644 --- a/library/std/src/sys/pal/unix/linux/pidfd.rs +++ b/library/std/src/sys/pal/unix/linux/pidfd.rs @@ -95,6 +95,21 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { .map(drop) } + pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { + // since kernel 6.9 + // https://lore.kernel.org/all/20240210-chihuahua-hinzog-3945b6abd44a@brauner/ + cvt(unsafe { + libc::syscall( + libc::SYS_pidfd_send_signal, + self.0.as_raw_fd(), + signal, + crate::ptr::null::<()>(), + libc::PIDFD_SIGNAL_PROCESS_GROUP, + ) + }) + .map(drop) + } + pub fn wait(&self) -> io::Result { let r = self.waitid(libc::WEXITED)?; match r { diff --git a/library/std/src/sys/process/unix/fuchsia.rs b/library/std/src/sys/process/unix/fuchsia.rs index 3fae5ec1468b..65ef7bf8c60c 100644 --- a/library/std/src/sys/process/unix/fuchsia.rs +++ b/library/std/src/sys/process/unix/fuchsia.rs @@ -158,6 +158,11 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> { unimplemented!() } + pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> { + // Fuchsia doesn't have a direct equivalent for signals + unimplemented!() + } + pub fn wait(&mut self) -> io::Result { let mut proc_info: zx_info_process_t = Default::default(); let mut actual: size_t = 0; diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index a68be2543bc2..d476072a6449 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -1002,6 +1002,19 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { cvt(unsafe { libc::kill(self.pid, signal) }).map(drop) } + pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { + // See note in `send_signal` regarding recycled PIDs. + if self.status.is_some() { + return Ok(()); + } + #[cfg(target_os = "linux")] + if let Some(pid_fd) = self.pidfd.as_ref() { + // The `PIDFD_SIGNAL_PROCESS_GROUP` flag requires kernel >= 6.9 + return pid_fd.send_process_group_signal(signal); + } + cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop) + } + pub fn wait(&mut self) -> io::Result { use crate::sys::cvt_r; if let Some(status) = self.status { diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index 9bda394f2465..17421d1e2e35 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -49,6 +49,10 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> { unsupported() } + pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> { + unsupported() + } + pub fn wait(&mut self) -> io::Result { unsupported() } diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index 346ca6d74c9b..c5acff2bdd3c 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -161,6 +161,14 @@ pub fn send_signal(&self, signal: i32) -> io::Result<()> { } } + pub fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { + // See note in `send_signal` regarding recycled PIDs. + if self.status.is_some() { + return Ok(()); + } + cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop) + } + pub fn wait(&mut self) -> io::Result { use crate::sys::cvt_r; if let Some(status) = self.status { From 6e99c6851628fd40b08cdc4a6128bcd4a6aeb7cf Mon Sep 17 00:00:00 2001 From: cyrgani Date: Wed, 13 May 2026 10:55:37 +0000 Subject: [PATCH 31/36] use `deref_patterns` in `rustdoc` --- src/librustdoc/clean/cfg.rs | 8 +- src/librustdoc/clean/mod.rs | 9 +- src/librustdoc/clean/types.rs | 29 +++-- src/librustdoc/fold.rs | 2 +- src/librustdoc/formats/cache.rs | 118 +++++++++---------- src/librustdoc/formats/item_type.rs | 2 +- src/librustdoc/formats/renderer.rs | 4 +- src/librustdoc/html/format.rs | 6 +- src/librustdoc/html/render/context.rs | 2 +- src/librustdoc/html/render/mod.rs | 7 +- src/librustdoc/html/render/print_item.rs | 15 ++- src/librustdoc/html/render/sidebar.rs | 2 +- src/librustdoc/json/conversions.rs | 2 +- src/librustdoc/lib.rs | 2 +- src/librustdoc/passes/collect_trait_impls.rs | 4 +- src/librustdoc/passes/propagate_stability.rs | 5 +- 16 files changed, 111 insertions(+), 106 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index ec3407d361eb..5dc9f17c15fd 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -149,7 +149,7 @@ fn should_append_only_to_description(&self) -> bool { | CfgEntry::All(..) | CfgEntry::NameValue { .. } | CfgEntry::Version(..) - | CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true, + | CfgEntry::Not(CfgEntry::NameValue { .. }, _) => true, CfgEntry::Not(..) | CfgEntry::Bool(..) => false, } } @@ -386,7 +386,7 @@ fn display_sub_cfgs( impl fmt::Display for Display<'_> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { - CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => { + CfgEntry::Not(CfgEntry::Any(sub_cfgs, _), _) => { let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " }; fmt.write_str("neither ")?; @@ -399,10 +399,10 @@ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { }) .joined(separator, fmt) } - CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => { + CfgEntry::Not(simple @ CfgEntry::NameValue { .. }, _) => { write!(fmt, "non-{}", Display(simple, self.1)) } - CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)), + CfgEntry::Not(c, _) => write!(fmt, "not ({})", Display(c, self.1)), CfgEntry::Any(sub_cfgs, _) => { let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " }; diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index f020a26a23bc..d50e4dd80557 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1472,11 +1472,8 @@ fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool { generics.where_predicates.retain_mut(|pred| match *pred { WherePredicate::BoundPredicate { ty: - QPath(box QPathData { - ref assoc, - ref self_type, - trait_: Some(ref trait_), - .. + QPath(QPathData { + ref assoc, ref self_type, trait_: Some(ref trait_), .. }), bounds: ref mut pred_bounds, .. @@ -2786,7 +2783,7 @@ fn add_without_unwanted_attributes<'hir>( hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => { attrs.push((Cow::Borrowed(attr), import_parent)); } - hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { + hir::Attribute::Parsed(AttributeKind::Doc(d)) => { // Remove attributes from `normal` that should not be inherited by `use` re-export. let DocAttribute { first_span: _, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 05d4888c110c..7b7fbffbe906 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -491,7 +491,7 @@ pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool { /// Returns true if item is an associated function with a `self` parameter. pub(crate) fn has_self_param(&self) -> bool { - if let ItemKind::MethodItem(box Function { decl, .. }, _) = &self.inner.kind { + if let ItemKind::MethodItem(Function { decl, .. }, _) = &self.inner.kind { decl.receiver_type().is_some() } else { false @@ -505,8 +505,8 @@ pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Option { }; match kind { ItemKind::ModuleItem(Module { span, .. }) => Some(*span), - ItemKind::ImplItem(box Impl { kind: ImplKind::Auto, .. }) => None, - ItemKind::ImplItem(box Impl { kind: ImplKind::Blanket(_), .. }) => { + ItemKind::ImplItem(Impl { kind: ImplKind::Auto, .. }) => None, + ItemKind::ImplItem(Impl { kind: ImplKind::Blanket(_), .. }) => { if let ItemId::Blanket { impl_id, .. } = self.item_id { Some(rustc_span(impl_id, tcx)) } else { @@ -667,16 +667,21 @@ pub(crate) fn is_variant(&self) -> bool { self.type_() == ItemType::Variant } pub(crate) fn is_associated_type(&self) -> bool { - matches!(self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..))) + matches!(self.kind, AssocTypeItem(..) | StrippedItem(AssocTypeItem(..))) } pub(crate) fn is_required_associated_type(&self) -> bool { - matches!(self.kind, RequiredAssocTypeItem(..) | StrippedItem(box RequiredAssocTypeItem(..))) + matches!(self.kind, RequiredAssocTypeItem(..) | StrippedItem(RequiredAssocTypeItem(..))) } pub(crate) fn is_associated_const(&self) -> bool { - matches!(self.kind, ProvidedAssocConstItem(..) | ImplAssocConstItem(..) | StrippedItem(box (ProvidedAssocConstItem(..) | ImplAssocConstItem(..)))) + matches!( + self.kind, + ProvidedAssocConstItem(..) + | ImplAssocConstItem(..) + | StrippedItem(ProvidedAssocConstItem(..) | ImplAssocConstItem(..)) + ) } pub(crate) fn is_required_associated_const(&self) -> bool { - matches!(self.kind, RequiredAssocConstItem(..) | StrippedItem(box RequiredAssocConstItem(..))) + matches!(self.kind, RequiredAssocConstItem(..) | StrippedItem(RequiredAssocConstItem(..))) } pub(crate) fn is_method(&self) -> bool { self.type_() == ItemType::Method @@ -1508,9 +1513,9 @@ pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool { pub(crate) fn primitive_type(&self) -> Option { match *self { - Primitive(p) | BorrowedRef { type_: box Primitive(p), .. } => Some(p), - Slice(..) | BorrowedRef { type_: box Slice(..), .. } => Some(PrimitiveType::Slice), - Array(..) | BorrowedRef { type_: box Array(..), .. } => Some(PrimitiveType::Array), + Primitive(p) | BorrowedRef { type_: Primitive(p), .. } => Some(p), + Slice(..) | BorrowedRef { type_: Slice(..), .. } => Some(PrimitiveType::Slice), + Array(..) | BorrowedRef { type_: Array(..), .. } => Some(PrimitiveType::Array), Tuple(ref tys) => { if tys.is_empty() { Some(PrimitiveType::Unit) @@ -1590,7 +1595,7 @@ pub(crate) fn def_id(&self, cache: &Cache) -> Option { Type::Path { path } => return Some(path.def_id()), DynTrait(bounds, _) => return bounds.first().map(|b| b.trait_.def_id()), Primitive(p) => return cache.primitive_locations.get(p).cloned(), - BorrowedRef { type_: box Generic(..), .. } => PrimitiveType::Reference, + BorrowedRef { type_: Generic(..), .. } => PrimitiveType::Reference, BorrowedRef { type_, .. } => return type_.def_id(cache), Tuple(tys) => { if tys.is_empty() { @@ -1605,7 +1610,7 @@ pub(crate) fn def_id(&self, cache: &Cache) -> Option { Type::Pat(..) => PrimitiveType::Pat, Type::FieldOf(..) => PrimitiveType::FieldOf, RawPointer(..) => PrimitiveType::RawPointer, - QPath(box QPathData { self_type, .. }) => return self_type.def_id(cache), + QPath(QPathData { self_type, .. }) => return self_type.def_id(cache), Generic(_) | SelfTy | Infer | ImplTrait(_) | UnsafeBinder(_) => return None, }; Primitive(t).def_id(cache) diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index 8b9db4638e47..3890eccc402a 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -105,7 +105,7 @@ fn fold_inner_recur(&mut self, kind: ItemKind) -> ItemKind { /// don't override! fn fold_item_recur(&mut self, mut item: Item) -> Item { item.inner.kind = match item.inner.kind { - StrippedItem(box i) => StrippedItem(Box::new(self.fold_inner_recur(i))), + StrippedItem(i) => StrippedItem(Box::new(self.fold_inner_recur(*i))), _ => self.fold_inner_recur(item.inner.kind), }; item diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 18b00224dfcb..0312f8909c03 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -248,7 +248,7 @@ fn fold_item(&mut self, item: clean::Item) -> Option { // If this is a stripped module, // we don't want it or its children in the search index. let orig_stripped_mod = match item.kind { - clean::StrippedItem(box clean::ModuleItem(..)) => { + clean::StrippedItem(clean::ModuleItem(..)) => { mem::replace(&mut self.cache.stripped_mod, true) } _ => self.cache.stripped_mod, @@ -409,69 +409,69 @@ fn is_from_private_dep(tcx: TyCtxt<'_>, cache: &Cache, def_id: DefId) -> bool { // Once we've recursively found all the generics, hoard off all the // implementations elsewhere. - let ret = if let clean::Item { - inner: box clean::ItemInner { kind: clean::ImplItem(ref i), .. }, - } = item - { - // Figure out the id of this impl. This may map to a - // primitive rather than always to a struct/enum. - // Note: matching twice to restrict the lifetime of the `i` borrow. - let mut dids = FxIndexSet::default(); - match i.for_ { - clean::Type::Path { ref path } - | clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. } => { - dids.insert(path.def_id()); - if let Some(generics) = path.generics() - && let ty::Adt(adt, _) = self - .tcx - .type_of(path.def_id()) - .instantiate_identity() - .skip_norm_wip() - .kind() - && adt.is_fundamental() - { - for ty in generics { - dids.extend(ty.def_id(self.cache)); + let ret = + if let clean::Item { inner: clean::ItemInner { kind: clean::ImplItem(ref i), .. } } = + item + { + // Figure out the id of this impl. This may map to a + // primitive rather than always to a struct/enum. + // Note: matching twice to restrict the lifetime of the `i` borrow. + let mut dids = FxIndexSet::default(); + match i.for_ { + clean::Type::Path { ref path } + | clean::BorrowedRef { type_: clean::Type::Path { ref path }, .. } => { + dids.insert(path.def_id()); + if let Some(generics) = path.generics() + && let ty::Adt(adt, _) = self + .tcx + .type_of(path.def_id()) + .instantiate_identity() + .skip_norm_wip() + .kind() + && adt.is_fundamental() + { + for ty in generics { + dids.extend(ty.def_id(self.cache)); + } } } - } - clean::DynTrait(ref bounds, _) - | clean::BorrowedRef { type_: box clean::DynTrait(ref bounds, _), .. } => { - dids.insert(bounds[0].trait_.def_id()); - } - ref t => { - let did = t - .primitive_type() - .and_then(|t| self.cache.primitive_locations.get(&t).cloned()); + clean::DynTrait(ref bounds, _) + | clean::BorrowedRef { type_: clean::DynTrait(ref bounds, _), .. } => { + dids.insert(bounds[0].trait_.def_id()); + } + ref t => { + let did = t + .primitive_type() + .and_then(|t| self.cache.primitive_locations.get(&t).cloned()); - dids.extend(did); - } - } - - if let Some(trait_) = &i.trait_ - && let Some(generics) = trait_.generics() - { - for bound in generics { - dids.extend(bound.def_id(self.cache)); - } - } - let impl_item = Impl { impl_item: item }; - let impl_did = impl_item.def_id(); - let trait_did = impl_item.trait_did(); - if trait_did.is_none_or(|d| self.cache.traits.contains_key(&d)) { - for did in dids { - if self.impl_ids.entry(did).or_default().insert(impl_did) { - self.cache.impls.entry(did).or_default().push(impl_item.clone()); + dids.extend(did); } } + + if let Some(trait_) = &i.trait_ + && let Some(generics) = trait_.generics() + { + for bound in generics { + dids.extend(bound.def_id(self.cache)); + } + } + let impl_item = Impl { impl_item: item }; + let impl_did = impl_item.def_id(); + let trait_did = impl_item.trait_did(); + if trait_did.is_none_or(|d| self.cache.traits.contains_key(&d)) { + for did in dids { + if self.impl_ids.entry(did).or_default().insert(impl_did) { + self.cache.impls.entry(did).or_default().push(impl_item.clone()); + } + } + } else { + let trait_did = trait_did.expect("no trait did"); + self.cache.orphan_trait_impls.push((trait_did, dids, impl_item)); + } + None } else { - let trait_did = trait_did.expect("no trait did"); - self.cache.orphan_trait_impls.push((trait_did, dids, impl_item)); - } - None - } else { - Some(item) - }; + Some(item) + }; if pushed { self.cache.stack.pop().expect("stack already empty"); @@ -655,7 +655,7 @@ enum ParentStackItem { impl ParentStackItem { fn new(item: &clean::Item) -> Self { match &item.kind { - clean::ItemKind::ImplItem(box clean::Impl { for_, trait_, generics, kind, .. }) => { + clean::ItemKind::ImplItem(clean::Impl { for_, trait_, generics, kind, .. }) => { ParentStackItem::Impl { for_: for_.clone(), trait_: trait_.clone(), diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index eb3492e4625b..73b85d292361 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -106,7 +106,7 @@ fn visit_u64(self, v: u64) -> Result { impl<'a> From<&'a clean::Item> for ItemType { fn from(item: &'a clean::Item) -> ItemType { let kind = match &item.kind { - clean::StrippedItem(box item) => item, + clean::StrippedItem(item) => item, kind => kind, }; diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 5c458232f8f9..bb2ccf0d4a35 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -75,8 +75,8 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>( prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string()); cx.mod_item_in(item)?; - let (clean::StrippedItem(box clean::ModuleItem(ref module)) - | clean::ModuleItem(ref module)) = item.inner.kind + let (clean::StrippedItem(clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) = + item.inner.kind else { unreachable!() }; diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0694dd05cd39..35212d480cfd 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -961,7 +961,7 @@ fn fmt_type( } } }, - clean::Slice(box clean::Generic(name)) => { + clean::Slice(clean::Generic(name)) => { primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) } clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f), @@ -974,7 +974,7 @@ fn fmt_type( fmt::Display::fmt(&print_type(t, cx), f)?; write!(f, ", {field})") } - clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link( + clean::Array(clean::Generic(name), n) if !f.alternate() => primitive_link( f, PrimitiveType::Array, format_args!("[{name}; {n}]", n = Escape(n)), @@ -1280,7 +1280,7 @@ fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt:: if let Some(self_ty) = parameter.to_receiver() { match self_ty { clean::SelfTy => f.write_str("self"), - clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { + clean::BorrowedRef { lifetime, mutability, type_: clean::SelfTy } => { f.write_str(if f.alternate() { "&" } else { "&" })?; if let Some(lt) = lifetime { write!(f, "{lt} ", lt = print_lifetime(lt))?; diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 6c02eecbd06e..038eb49407f8 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -832,7 +832,7 @@ fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error> { // Render sidebar-items.js used throughout this module. if !self.info.render_redirect_pages { - let (clean::StrippedItem(box clean::ModuleItem(ref module)) + let (clean::StrippedItem(clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) = item.kind else { unreachable!() diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8600fccdbe62..20bbf9655978 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -809,7 +809,8 @@ fn document_full_inner( } let kind = match &item.kind { - clean::ItemKind::StrippedItem(box kind) | kind => kind, + clean::ItemKind::StrippedItem(kind) => kind, + kind => kind, }; if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { @@ -1582,7 +1583,7 @@ fn render_deref_methods( .items .iter() .find_map(|item| match item.kind { - clean::AssocTypeItem(box ref t, _) => Some(match *t { + clean::AssocTypeItem(ref t, _) => Some(match *t { clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), @@ -2709,7 +2710,7 @@ fn collect_paths_for_type(first_ty: &clean::Type, cache: &Cache) -> Vec clean::Type::BorrowedRef { type_, .. } => { work.push_back(type_); } - clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => { + clean::Type::QPath(clean::QPathData { self_type, trait_, .. }) => { work.push_back(self_type); if let Some(trait_) = trait_ { process_path(trait_.def_id()); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 48108097864a..870c60a98481 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1524,9 +1524,8 @@ fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt: fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Display { fmt::from_fn(|f| { if !s.is_empty() - && s.iter().all(|field| { - matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) - }) + && s.iter() + .all(|field| matches!(field.kind, clean::StrippedItem(clean::StructFieldItem(..)))) { return f.write_str("/* private fields */"); } @@ -1534,7 +1533,7 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa s.iter() .map(|ty| { fmt::from_fn(|f| match ty.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"), + clean::StrippedItem(clean::StructFieldItem(_)) => f.write_str("_"), clean::StructFieldItem(ref ty) => write!(f, "{}", print_type(ty, cx)), _ => unreachable!(), }) @@ -1852,7 +1851,7 @@ fn item_variants( )?; for field in fields { match field.kind { - clean::StrippedItem(box clean::StructFieldItem(_)) => {} + clean::StrippedItem(clean::StructFieldItem(_)) => {} clean::StructFieldItem(ref ty) => { let id = cx.derive_id(format!( "variant.{}.field.{}", @@ -2355,7 +2354,7 @@ fn render_implementor( // full path, for example in `std::iter::ExactSizeIterator` let use_absolute = match implementor.inner_impl().for_ { clean::Type::Path { ref path, .. } - | clean::BorrowedRef { type_: box clean::Type::Path { ref path, .. }, .. } + | clean::BorrowedRef { type_: clean::Type::Path { ref path, .. }, .. } if !path.is_assoc_ty() => { implementor_dups[&path.last()].1 @@ -2551,7 +2550,7 @@ fn render_struct_fields( w.write_str("(")?; if !fields.is_empty() && fields.iter().all(|field| { - matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) + matches!(field.kind, clean::StrippedItem(clean::StructFieldItem(..))) }) { write!(w, "/* private fields */")?; @@ -2561,7 +2560,7 @@ fn render_struct_fields( w.write_str(", ")?; } match field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => { + clean::StrippedItem(clean::StructFieldItem(..)) => { write!(w, "_")?; } clean::StructFieldItem(ref ty) => { diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 360f8bdf642e..817e8144bbb4 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -526,7 +526,7 @@ fn sidebar_deref_methods<'a>( debug!("found Deref: {impl_:?}"); if let Some((target, real_target)) = impl_.inner_impl().items.iter().find_map(|item| match item.kind { - clean::AssocTypeItem(box ref t, _) => Some(match *t { + clean::AssocTypeItem(ref t, _) => Some(match *t { clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_), _ => (&t.type_, &t.type_), }), diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 623ad55f6e99..a0140402de42 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -232,7 +232,7 @@ fn from_clean(arg: &clean::GenericArg, renderer: &JsonRenderer<'_>) -> Self { match arg { Lifetime(l) => GenericArg::Lifetime(l.into_json(renderer)), Type(t) => GenericArg::Type(t.into_json(renderer)), - Const(box c) => GenericArg::Const(c.into_json(renderer)), + Const(c) => GenericArg::Const(c.into_json(renderer)), Infer => GenericArg::Infer, } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index c8aa197cabe1..2fff3adfa546 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -5,7 +5,7 @@ )] #![feature(ascii_char)] #![feature(ascii_char_variants)] -#![feature(box_patterns)] +#![feature(deref_patterns)] #![feature(file_buffered)] #![feature(formatting_options)] #![feature(iter_intersperse)] diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 762516874c0a..9d603117ea5a 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -155,7 +155,7 @@ fn add_deref_target( // scan through included items ahead of time to splice in Deref targets to the "valid" sets for it in new_items_external.iter().chain(new_items_local.iter()) { - if let ImplItem(box Impl { ref for_, ref trait_, ref items, polarity, .. }) = it.kind + if let ImplItem(Impl { ref for_, ref trait_, ref items, polarity, .. }) = it.kind && trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() && polarity != ty::ImplPolarity::Negative && cleaner.keep_impl(for_, true) @@ -195,7 +195,7 @@ fn add_deref_target( // Filter out external items that are not needed new_items_external.retain(|it| { - if let ImplItem(box Impl { ref for_, ref trait_, ref kind, .. }) = it.kind { + if let ImplItem(Impl { ref for_, ref trait_, ref kind, .. }) = it.kind { cleaner.keep_impl( for_, trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(), diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index c8691fd012bf..6700ca649d7b 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -69,7 +69,10 @@ fn fold_item(&mut self, mut item: Item) -> Option { item_stability }; - let (ItemKind::StrippedItem(box kind) | kind) = &item.kind; + let kind = match &item.kind { + ItemKind::StrippedItem(kind) => kind, + kind => kind, + }; match kind { ItemKind::ExternCrateItem { .. } | ItemKind::ImportItem(..) From 7bf5fe7bf84f5b94e5970de18543abac0579209c Mon Sep 17 00:00:00 2001 From: John Millikin Date: Wed, 13 May 2026 19:52:56 +0900 Subject: [PATCH 32/36] Add `ChildExt::kill_process_group` This function wraps POSIX `killpg(pid, SIGKILL)`, and on Linux additionally may be implemented by `pidfd_send_signal`. --- library/std/src/os/unix/process.rs | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index e0bc415f9e3e..a0defc39ac82 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -452,6 +452,36 @@ pub trait ChildExt: Sealed { /// ``` #[unstable(feature = "unix_send_signal", issue = "141975")] fn send_process_group_signal(&self, signal: i32) -> io::Result<()>; + + /// Forces the child process's process group to exit. + /// + /// This is analogous to [`Child::kill`] but applies to every process in + /// the child process's process group. + /// + /// Use [`CommandExt::process_group`] to assign a child process to an + /// existing process group, or to make it the leader of a new process group. + /// By default spawned processes are in the parent's process group. + /// + /// # Examples + /// + /// ```rust + /// #![feature(unix_kill_process_group)] + /// + /// use std::{os::unix::process::{ChildExt, CommandExt}, process::{Command, Stdio}}; + /// + /// fn main() -> std::io::Result<()> { + /// let mut child = Command::new("cat") + /// .stdin(Stdio::piped()) + /// .process_group(0) + /// .spawn()?; + /// child.kill_process_group()?; + /// Ok(()) + /// } + /// ``` + /// + /// [`Child::kill`]: process::Child::kill + #[unstable(feature = "unix_kill_process_group", issue = "156537")] + fn kill_process_group(&mut self) -> io::Result<()>; } #[unstable(feature = "unix_send_signal", issue = "141975")] @@ -463,6 +493,10 @@ fn send_signal(&self, signal: i32) -> io::Result<()> { fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { self.handle.send_process_group_signal(signal) } + + fn kill_process_group(&mut self) -> io::Result<()> { + self.handle.send_process_group_signal(libc::SIGKILL) + } } #[stable(feature = "process_extensions", since = "1.2.0")] From 3d0ee528cb125e58710f1d56fbb4b1516f877ee3 Mon Sep 17 00:00:00 2001 From: Bryanskiy Date: Fri, 8 May 2026 14:48:57 +0300 Subject: [PATCH 33/36] Privacy: move macros handling to early stage --- compiler/rustc_middle/src/middle/privacy.rs | 8 +- compiler/rustc_middle/src/ty/mod.rs | 6 + compiler/rustc_privacy/src/lib.rs | 201 ++---------------- .../src/effective_visibilities.rs | 146 ++++++++++++- compiler/rustc_resolve/src/lib.rs | 4 + 5 files changed, 178 insertions(+), 187 deletions(-) diff --git a/compiler/rustc_middle/src/middle/privacy.rs b/compiler/rustc_middle/src/middle/privacy.rs index 0359264d3f78..5bf4bbe79a9a 100644 --- a/compiler/rustc_middle/src/middle/privacy.rs +++ b/compiler/rustc_middle/src/middle/privacy.rs @@ -65,6 +65,10 @@ fn at_level_mut(&mut self, level: Level) -> &mut Visibility { } } + pub fn public_at_level(&self) -> Option { + Level::all_levels().into_iter().find(|&level| self.is_public_at_level(level)) + } + pub fn is_public_at_level(&self, level: Level) -> bool { self.at_level(level).is_public() } @@ -120,9 +124,7 @@ pub fn is_directly_public(&self, id: LocalDefId) -> bool { } pub fn public_at_level(&self, id: LocalDefId) -> Option { - self.effective_vis(id).and_then(|effective_vis| { - Level::all_levels().into_iter().find(|&level| effective_vis.is_public_at_level(level)) - }) + self.effective_vis(id).and_then(|effective_vis| effective_vis.public_at_level()) } pub fn update_root(&mut self) { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 93233efae665..7044dc54b5a3 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -179,6 +179,12 @@ pub struct ResolverGlobalCtxt { /// Item with a given `LocalDefId` was defined during macro expansion with ID `ExpnId`. pub expn_that_defined: UnordMap, pub effective_visibilities: EffectiveVisibilities, + // FIXME: This table contains ADTs reachable from macro 2.0. + // Currently, reachability of a definition from a macro is determined by nominal visibility + // (see `compute_effective_visibilities`). This is incorrect and leads to the necessity + // of traversing ADT fields in `rustc_privacy`. Remove this workaround once the + // correct reachability logic is implemented for macros. + pub macro_reachable_adts: FxIndexMap>, pub extern_crate_map: UnordMap, pub maybe_unused_trait_imports: FxIndexSet, pub module_children: LocalDefIdMap>, diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index d92944d1b31f..6a792e5a803b 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -15,7 +15,6 @@ ItemIsPrivate, PrivateInterfacesOrBoundsLint, ReportEffectiveVisibility, UnnameableTypesLint, UnnamedItemIsPrivate, }; -use rustc_ast::MacroDef; use rustc_ast::visit::{VisitorResult, try_visit}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::intern::Interned; @@ -34,7 +33,6 @@ }; use rustc_middle::{bug, span_bug}; use rustc_session::lint; -use rustc_span::hygiene::Transparency; use rustc_span::{Ident, Span, Symbol, sym}; use tracing::debug; @@ -419,22 +417,8 @@ fn new_min( /// The embargo visitor, used to determine the exports of the AST. struct EmbargoVisitor<'tcx> { tcx: TyCtxt<'tcx>, - /// Effective visibilities for reachable nodes. effective_visibilities: EffectiveVisibilities, - /// A set of pairs corresponding to modules, where the first module is - /// reachable via a macro that's defined in the second module. This cannot - /// be represented as reachable because it can't handle the following case: - /// - /// pub mod n { // Should be `Public` - /// pub(crate) mod p { // Should *not* be accessible - /// pub fn f() -> i32 { 12 } // Must be `Reachable` - /// } - /// } - /// pub macro m() { - /// n::p::f() - /// } - macro_reachable: FxHashSet<(LocalModDefId, LocalModDefId)>, /// Has something changed in the level map? changed: bool, } @@ -509,161 +493,6 @@ fn reach_through_impl_trait( level: Level::ReachableThroughImplTrait, } } - - // We have to make sure that the items that macros might reference - // are reachable, since they might be exported transitively. - fn update_reachability_from_macro( - &mut self, - local_def_id: LocalDefId, - md: &MacroDef, - macro_ev: EffectiveVisibility, - ) { - // Non-opaque macros cannot make other items more accessible than they already are. - let hir_id = self.tcx.local_def_id_to_hir_id(local_def_id); - let attrs = self.tcx.hir_attrs(hir_id); - - if find_attr!(attrs, RustcMacroTransparency(x) => *x) - .unwrap_or(Transparency::fallback(md.macro_rules)) - != Transparency::Opaque - { - return; - } - - let macro_module_def_id = self.tcx.local_parent(local_def_id); - if self.tcx.def_kind(macro_module_def_id) != DefKind::Mod { - // The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252). - return; - } - // FIXME(typed_def_id): Introduce checked constructors that check def_kind. - let macro_module_def_id = LocalModDefId::new_unchecked(macro_module_def_id); - - if self.effective_visibilities.public_at_level(local_def_id).is_none() { - return; - } - - // Since we are starting from an externally visible module, - // all the parents in the loop below are also guaranteed to be modules. - let mut module_def_id = macro_module_def_id; - loop { - let changed_reachability = - self.update_macro_reachable(module_def_id, macro_module_def_id, macro_ev); - if changed_reachability || module_def_id == LocalModDefId::CRATE_DEF_ID { - break; - } - module_def_id = LocalModDefId::new_unchecked(self.tcx.local_parent(module_def_id)); - } - } - - /// Updates the item as being reachable through a macro defined in the given - /// module. Returns `true` if the level has changed. - fn update_macro_reachable( - &mut self, - module_def_id: LocalModDefId, - defining_mod: LocalModDefId, - macro_ev: EffectiveVisibility, - ) -> bool { - if self.macro_reachable.insert((module_def_id, defining_mod)) { - for child in self.tcx.module_children_local(module_def_id.to_local_def_id()) { - if let Res::Def(def_kind, def_id) = child.res - && let Some(def_id) = def_id.as_local() - && child.vis.is_accessible_from(defining_mod, self.tcx) - { - let vis = self.tcx.local_visibility(def_id); - self.update_macro_reachable_def(def_id, def_kind, vis, defining_mod, macro_ev); - } - } - true - } else { - false - } - } - - fn update_macro_reachable_def( - &mut self, - def_id: LocalDefId, - def_kind: DefKind, - vis: ty::Visibility, - module: LocalModDefId, - macro_ev: EffectiveVisibility, - ) { - self.update(def_id, macro_ev, Level::Reachable); - match def_kind { - // No type privacy, so can be directly marked as reachable. - DefKind::Const { .. } - | DefKind::Static { .. } - | DefKind::TraitAlias - | DefKind::TyAlias => { - if vis.is_accessible_from(module, self.tcx) { - self.update(def_id, macro_ev, Level::Reachable); - } - } - - // Hygiene isn't really implemented for `macro_rules!` macros at the - // moment. Accordingly, marking them as reachable is unwise. `macro` macros - // have normal hygiene, so we can treat them like other items without type - // privacy and mark them reachable. - DefKind::Macro(_) => { - let item = self.tcx.hir_expect_item(def_id); - if let hir::ItemKind::Macro(_, MacroDef { macro_rules: false, .. }, _) = item.kind { - if vis.is_accessible_from(module, self.tcx) { - self.update(def_id, macro_ev, Level::Reachable); - } - } - } - - // We can't use a module name as the final segment of a path, except - // in use statements. Since re-export checking doesn't consider - // hygiene these don't need to be marked reachable. The contents of - // the module, however may be reachable. - DefKind::Mod => { - if vis.is_accessible_from(module, self.tcx) { - self.update_macro_reachable( - LocalModDefId::new_unchecked(def_id), - module, - macro_ev, - ); - } - } - - DefKind::Struct | DefKind::Union => { - // While structs and unions have type privacy, their fields do not. - let struct_def = self.tcx.adt_def(def_id); - for field in &struct_def.non_enum_variant().fields { - let def_id = field.did.expect_local(); - let field_vis = self.tcx.local_visibility(def_id); - if field_vis.is_accessible_from(module, self.tcx) { - self.reach(def_id, macro_ev).ty(); - } - } - } - - // These have type privacy, so are not reachable unless they're - // public, or are not namespaced at all. - DefKind::AssocConst { .. } - | DefKind::AssocTy - | DefKind::ConstParam - | DefKind::Ctor(_, _) - | DefKind::Enum - | DefKind::ForeignTy - | DefKind::Fn - | DefKind::OpaqueTy - | DefKind::AssocFn - | DefKind::Trait - | DefKind::TyParam - | DefKind::Variant - | DefKind::LifetimeParam - | DefKind::ExternCrate - | DefKind::Use - | DefKind::ForeignMod - | DefKind::AnonConst - | DefKind::InlineConst - | DefKind::Field - | DefKind::GlobalAsm - | DefKind::Impl { .. } - | DefKind::Closure - | DefKind::SyntheticCoroutineBody => (), - } - } } impl<'tcx> EmbargoVisitor<'tcx> { @@ -689,13 +518,8 @@ fn check_def_id(&mut self, owner_id: OwnerId) { DefKind::Use | DefKind::ExternCrate | DefKind::GlobalAsm => {} // The interface is empty, and all nested items are processed by `check_def_id`. DefKind::Mod => {} - DefKind::Macro { .. } => { - if let Some(item_ev) = item_ev { - let (_, macro_def, _) = - self.tcx.hir_expect_item(owner_id.def_id).expect_macro(); - self.update_reachability_from_macro(owner_id.def_id, macro_def, item_ev); - } - } + // Effective visibilities for macros are processed earlier. + DefKind::Macro { .. } => {} DefKind::ForeignTy | DefKind::Const { .. } | DefKind::Static { .. } @@ -1815,7 +1639,6 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { let mut visitor = EmbargoVisitor { tcx, effective_visibilities: tcx.resolutions(()).effective_visibilities.clone(), - macro_reachable: Default::default(), changed: false, }; @@ -1872,6 +1695,26 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { visitor.changed = false; } + // FIXME: remove this once proper support for defs reachability from macros is implemented. + // See `ResolverGlobalCtxt::macro_reachable_adts` comment. + for (&adt_def_id, macro_mods) in &tcx.resolutions(()).macro_reachable_adts { + let struct_def = tcx.adt_def(adt_def_id); + let Some(struct_ev) = visitor.effective_visibilities.effective_vis(adt_def_id).copied() + else { + continue; + }; + for field in &struct_def.non_enum_variant().fields { + let def_id = field.did.expect_local(); + let field_vis = tcx.local_visibility(def_id); + + for ¯o_mod in macro_mods { + if field_vis.is_accessible_from(macro_mod, tcx) { + visitor.reach(def_id, struct_ev).ty(); + } + } + } + } + let crate_items = tcx.hir_crate_items(()); loop { for id in crate_items.free_items() { diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index f3b47f04c90a..e25a57cee406 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -1,11 +1,13 @@ use std::mem; use rustc_ast::visit::Visitor; -use rustc_ast::{Crate, EnumDef, ast, visit}; +use rustc_ast::{Attribute, Crate, EnumDef, ast, visit}; use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level}; use rustc_middle::ty::Visibility; +use rustc_span::sym; use tracing::info; use crate::{Decl, DeclKind, Resolver}; @@ -34,6 +36,19 @@ pub(crate) struct EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { import_effective_visibilities: EffectiveVisibilities>, // It's possible to recalculate this at any point, but it's relatively expensive. current_private_vis: Visibility, + /// A set of pairs corresponding to modules, where the first module is + /// reachable via a macro that's defined in the second module. This cannot + /// be represented as reachable because it can't handle the following case: + /// + /// pub mod n { // Should be `Public` + /// pub(crate) mod p { // Should *not* be accessible + /// pub fn f() -> i32 { 12 } // Must be `Reachable` + /// } + /// } + /// pub macro m() { + /// n::p::f() + /// } + macro_reachable: FxHashSet<(LocalDefId, LocalDefId)>, changed: bool, } @@ -71,6 +86,7 @@ pub(crate) fn compute_effective_visibilities<'c>( def_effective_visibilities: Default::default(), import_effective_visibilities: Default::default(), current_private_vis: Visibility::Restricted(CRATE_DEF_ID), + macro_reachable: Default::default(), changed: true, }; @@ -210,6 +226,123 @@ fn update_field(&mut self, def_id: LocalDefId, parent_id: LocalDefId) { let nominal_vis = self.r.tcx.local_visibility(def_id); self.update_def(def_id, nominal_vis, ParentId::Def(parent_id), self.current_private_vis); } + + fn update_macro(&mut self, def_id: LocalDefId, inherited_effective_vis: EffectiveVisibility) { + let max_vis = Some(self.r.tcx.local_visibility(def_id)); + let priv_vis = if def_id == CRATE_DEF_ID { + Visibility::Restricted(CRATE_DEF_ID) + } else { + self.r.private_vis_def(def_id) + }; + self.changed |= self.def_effective_visibilities.update( + def_id, + max_vis, + priv_vis, + inherited_effective_vis, + Level::Reachable, + self.r.tcx, + ); + } + + // We have to make sure that the items that macros might reference + // are reachable, since they might be exported transitively. + fn update_reachability_from_macro( + &mut self, + local_def_id: LocalDefId, + md: &ast::MacroDef, + attrs: &[Attribute], + ) { + // Non-opaque macros cannot make other items more accessible than they already are. + if rustc_ast::attr::find_by_name(attrs, sym::rustc_macro_transparency) + .map_or(md.macro_rules, |attr| attr.value_str() != Some(sym::opaque)) + { + return; + } + + let macro_module_def_id = self.r.tcx.local_parent(local_def_id); + if self.r.tcx.def_kind(macro_module_def_id) != DefKind::Mod { + // The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252). + return; + } + + let Some(macro_ev) = self + .def_effective_visibilities + .effective_vis(local_def_id) + .filter(|ev| ev.public_at_level().is_some()) + .copied() + else { + return; + }; + + // Since we are starting from an externally visible module, + // all the parents in the loop below are also guaranteed to be modules. + let mut module_def_id = macro_module_def_id; + loop { + let changed_reachability = + self.update_macro_reachable(module_def_id, macro_module_def_id, macro_ev); + if changed_reachability || module_def_id == CRATE_DEF_ID { + break; + } + module_def_id = self.r.tcx.local_parent(module_def_id); + } + } + + /// Updates the item as being reachable through a macro defined in the given + /// module. Returns `true` if the level has changed. + fn update_macro_reachable( + &mut self, + module_def_id: LocalDefId, + defining_mod: LocalDefId, + macro_ev: EffectiveVisibility, + ) -> bool { + if self.macro_reachable.insert((module_def_id, defining_mod)) { + let module = self.r.expect_module(module_def_id.to_def_id()); + for (_, name_resolution) in self.r.resolutions(module).borrow().iter() { + let Some(decl) = name_resolution.borrow().best_decl() else { + continue; + }; + + if let Res::Def(def_kind, def_id) = decl.res() + && let Some(def_id) = def_id.as_local() + // FIXME: defs should be checked with `EffectiveVisibilities::is_reachable`. + && decl.vis().is_accessible_from(defining_mod, self.r.tcx) + { + let vis = self.r.tcx.local_visibility(def_id); + self.update_macro_reachable_def(def_id, def_kind, vis, defining_mod, macro_ev); + } + } + true + } else { + false + } + } + + fn update_macro_reachable_def( + &mut self, + def_id: LocalDefId, + def_kind: DefKind, + vis: Visibility, + module: LocalDefId, + macro_ev: EffectiveVisibility, + ) { + self.update_macro(def_id, macro_ev); + + match def_kind { + DefKind::Mod => { + if vis.is_accessible_from(module, self.r.tcx) { + self.update_macro_reachable(def_id, module, macro_ev); + } + } + DefKind::Struct | DefKind::Union => { + self.r + .macro_reachable_adts + .entry(def_id) + .or_insert_with(Default::default) + .insert(module); + } + _ => {} + } + } } impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { @@ -217,7 +350,7 @@ fn visit_item(&mut self, item: &'a ast::Item) { let def_id = self.r.local_def_id(item.id); // Update effective visibilities of nested items. // If it's a mod, also make the visitor walk all of its items - match item.kind { + match &item.kind { // Resolved in rustc_privacy when types are available ast::ItemKind::Impl(..) => return, @@ -234,7 +367,7 @@ fn visit_item(&mut self, item: &'a ast::Item) { self.current_private_vis = prev_private_vis; } - ast::ItemKind::Enum(_, _, EnumDef { ref variants }) => { + ast::ItemKind::Enum(_, _, EnumDef { variants }) => { self.set_bindings_effective_visibilities(def_id); for variant in variants { let variant_def_id = self.r.local_def_id(variant.id); @@ -244,7 +377,7 @@ fn visit_item(&mut self, item: &'a ast::Item) { } } - ast::ItemKind::Struct(_, _, ref def) | ast::ItemKind::Union(_, _, ref def) => { + ast::ItemKind::Struct(_, _, def) | ast::ItemKind::Union(_, _, def) => { for field in def.fields() { self.update_field(self.r.local_def_id(field.id), def_id); } @@ -254,6 +387,10 @@ fn visit_item(&mut self, item: &'a ast::Item) { self.set_bindings_effective_visibilities(def_id); } + ast::ItemKind::MacroDef(_, macro_def) => { + self.update_reachability_from_macro(def_id, macro_def, &item.attrs); + } + ast::ItemKind::ExternCrate(..) | ast::ItemKind::Use(..) | ast::ItemKind::Static(..) @@ -262,7 +399,6 @@ fn visit_item(&mut self, item: &'a ast::Item) { | ast::ItemKind::GlobalAsm(..) | ast::ItemKind::TyAlias(..) | ast::ItemKind::TraitAlias(..) - | ast::ItemKind::MacroDef(..) | ast::ItemKind::ForeignMod(..) | ast::ItemKind::Fn(..) | ast::ItemKind::Delegation(..) => return, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 44a72fec56b9..ccb7ffbaba53 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1531,6 +1531,8 @@ pub struct Resolver<'ra, 'tcx> { stripped_cfg_items: Vec> = Vec::new(), effective_visibilities: EffectiveVisibilities, + macro_reachable_adts: FxIndexMap>, + doc_link_resolutions: FxIndexMap, doc_link_traits_in_scope: FxIndexMap>, all_macro_rules: UnordSet = Default::default(), @@ -1853,6 +1855,7 @@ pub fn new( confused_type_with_std_module: Default::default(), stripped_cfg_items: Default::default(), effective_visibilities: Default::default(), + macro_reachable_adts: Default::default(), doc_link_resolutions: Default::default(), doc_link_traits_in_scope: Default::default(), current_crate_outer_attr_insert_span, @@ -1963,6 +1966,7 @@ pub fn into_outputs(self) -> ResolverOutputs<'tcx> { expn_that_defined, visibilities_for_hashing: self.visibilities_for_hashing, effective_visibilities, + macro_reachable_adts: self.macro_reachable_adts, extern_crate_map, module_children: self.module_children, ambig_module_children: self.ambig_module_children, From 0f05543124345e3d0b75538538782b5f8dbf6ee6 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Wed, 13 May 2026 19:18:47 +0200 Subject: [PATCH 34/36] Bump nightly version -> 2026-05-13 --- clippy_utils/README.md | 2 +- rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/README.md b/clippy_utils/README.md index de7afb7cf13a..716b13a5e8f0 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2026-04-30 +nightly-2026-05-13 ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9992299153e2..397173e05f8f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -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" From 530c69bbc2c84b3def01a748722331ce9fa52c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 8 May 2026 22:43:48 +0000 Subject: [PATCH 35/36] Do not index past end of buffer when checking heuristic in error index syntax highlighter When checking whether the current token is a function indentifier by inspecting the syntax, do not attempt to access past the end of the code buffer. --- compiler/rustc_driver_impl/src/highlighter.rs | 2 +- .../explanation-code-rendering-edge-case-regression.rs | 3 +++ .../explanation-code-rendering-edge-case-regression.stdout | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/ui/explain/explanation-code-rendering-edge-case-regression.rs create mode 100644 tests/ui/explain/explanation-code-rendering-edge-case-regression.stdout diff --git a/compiler/rustc_driver_impl/src/highlighter.rs b/compiler/rustc_driver_impl/src/highlighter.rs index 70b73cc29b72..16b8f58bca1e 100644 --- a/compiler/rustc_driver_impl/src/highlighter.rs +++ b/compiler/rustc_driver_impl/src/highlighter.rs @@ -106,7 +106,7 @@ pub fn highlight_rustc_lexer(&mut self, code: &str, buf: &mut Vec) -> io::Re // This heuristic test is to detect if the identifier is // a function call. If it is, then the function identifier is // colored differently. - if code[*len_accum..*len_accum + len + 1].ends_with('(') { + if code[*len_accum + len..].starts_with('(') { style = style.fg_color(Some(Color::Ansi(FUNCTION_COLOR))); } // The `derive` keyword is colored differently. diff --git a/tests/ui/explain/explanation-code-rendering-edge-case-regression.rs b/tests/ui/explain/explanation-code-rendering-edge-case-regression.rs new file mode 100644 index 000000000000..0d12c71bb873 --- /dev/null +++ b/tests/ui/explain/explanation-code-rendering-edge-case-regression.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --explain E0602 --color always --error-format=human +//@ check-pass +// Ensure that we trigger the condition for #156326 without ICEing. diff --git a/tests/ui/explain/explanation-code-rendering-edge-case-regression.stdout b/tests/ui/explain/explanation-code-rendering-edge-case-regression.stdout new file mode 100644 index 000000000000..962d12921434 --- /dev/null +++ b/tests/ui/explain/explanation-code-rendering-edge-case-regression.stdout @@ -0,0 +1,7 @@ +An unknown or invalid lint was used on the command line. + +Erroneous code example: + +rustc -D bogus rust_file.rs + +Maybe you just misspelled the lint name or the lint doesn't exist anymore. Either way, try to update/remove it in order to fix the error. From 4e3fe3ed281cd9f7623e435cee1c9e8016d64105 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Wed, 13 May 2026 19:53:10 +0200 Subject: [PATCH 36/36] Update Cargo.lock --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b409832c7063..0f6368e4497c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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",