From 60c059114c3c1d2bcbe90c71f308b32f7cea1b07 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Wed, 13 Dec 2023 11:09:08 +0800 Subject: [PATCH 01/73] [`default_numeric_fallback`]: don't lint on return and macro calls with stated type --- clippy_lints/src/default_numeric_fallback.rs | 41 +++++++++++++++++--- tests/ui/default_numeric_fallback_f64.fixed | 2 +- tests/ui/default_numeric_fallback_f64.stderr | 8 +--- tests/ui/default_numeric_fallback_i32.fixed | 34 +++++++++++++++- tests/ui/default_numeric_fallback_i32.rs | 32 +++++++++++++++ tests/ui/default_numeric_fallback_i32.stderr | 26 +++++++++---- 6 files changed, 121 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 64a924a776a65..712bc075650fa 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -4,7 +4,7 @@ use clippy_utils::{get_parent_node, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor}; -use rustc_hir::{Body, Expr, ExprKind, HirId, ItemKind, Lit, Node, Stmt, StmtKind}; +use rustc_hir::{Block, Body, Expr, ExprKind, FnRetTy, HirId, ItemKind, Lit, Node, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty}; @@ -122,13 +122,42 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { match &expr.kind { + ExprKind::Block( + Block { + stmts, expr: Some(_), .. + }, + _, + ) => { + if let Some(parent) = self.cx.tcx.hir().find_parent(expr.hir_id) + && let Some(fn_sig) = parent.fn_sig() + && let FnRetTy::Return(_ty) = fn_sig.decl.output + { + // We cannot check the exact type since it's a `hir::Ty`` which does not implement `is_numeric` + self.ty_bounds.push(ExplicitTyBound(true)); + for stmt in *stmts { + self.visit_stmt(stmt); + } + self.ty_bounds.pop(); + // Ignore return expr since we know its type was inferred from return ty + return; + } + }, + + // Ignore return expr since we know its type was inferred from return ty + ExprKind::Ret(_) => return, + ExprKind::Call(func, args) => { if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) { for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) { - // Push found arg type, then visit arg. - self.ty_bounds.push((*bound).into()); - self.visit_expr(expr); - self.ty_bounds.pop(); + // If is from macro, try to use last bound type (typically pushed when visiting stmt), + // otherwise push found arg type, then visit arg, + if expr.span.from_expansion() { + self.visit_expr(expr); + } else { + self.ty_bounds.push((*bound).into()); + self.visit_expr(expr); + self.ty_bounds.pop(); + } } return; } @@ -137,7 +166,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { 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_binder(); - for (expr, bound) in iter::zip(std::iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) { + 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); self.ty_bounds.pop(); diff --git a/tests/ui/default_numeric_fallback_f64.fixed b/tests/ui/default_numeric_fallback_f64.fixed index 9072d23356343..e62dc8b255e7c 100644 --- a/tests/ui/default_numeric_fallback_f64.fixed +++ b/tests/ui/default_numeric_fallback_f64.fixed @@ -75,7 +75,7 @@ mod function_def { fn ret_f64() -> f64 { // Even though the output type is specified, // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. - 1.0_f64 + 1. } fn test() { diff --git a/tests/ui/default_numeric_fallback_f64.stderr b/tests/ui/default_numeric_fallback_f64.stderr index 7ea2e3e681947..e81aafdb3f3ef 100644 --- a/tests/ui/default_numeric_fallback_f64.stderr +++ b/tests/ui/default_numeric_fallback_f64.stderr @@ -85,12 +85,6 @@ error: default numeric fallback might occur LL | let y = 1.; | ^^ help: consider adding suffix: `1.0_f64` -error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:78:9 - | -LL | 1. - | ^^ help: consider adding suffix: `1.0_f64` - error: default numeric fallback might occur --> $DIR/default_numeric_fallback_f64.rs:84:27 | @@ -147,5 +141,5 @@ LL | inline!(let x = 22.;); | = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 24 previous errors +error: aborting due to 23 previous errors diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index 920cd9f8f7715..115cd30a6b391 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -76,7 +76,7 @@ mod function_def { fn ret_i32() -> i32 { // Even though the output type is specified, // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. - 1_i32 + 1 } fn test() { @@ -186,4 +186,36 @@ fn check_expect_suppression() { let x = 21; } +mod type_already_infered { + // Should NOT lint if bound to return type + fn ret_i32() -> i32 { + 1 + } + + // Should NOT lint if bound to return type + fn ret_if_i32(b: bool) -> i32 { + if b { 100 } else { 0 } + } + + // Should NOT lint if bound to return type + fn ret_i32_tuple() -> (i32, i32) { + (0, 1) + } + + // Should NOT lint if bound to return type + fn ret_stmt(b: bool) -> (i32, i32) { + if b { + return (0, 1); + } + (0, 0) + } + + #[allow(clippy::useless_vec)] + fn vec_macro() { + // Should NOT lint in `vec!` call if the type was already stated + let data_i32: Vec = vec![1, 2, 3]; + let data_i32 = vec![1_i32, 2_i32, 3_i32]; + } +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index bdb7b5f47bca0..a4abfc11320ce 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -186,4 +186,36 @@ fn check_expect_suppression() { let x = 21; } +mod type_already_infered { + // Should NOT lint if bound to return type + fn ret_i32() -> i32 { + 1 + } + + // Should NOT lint if bound to return type + fn ret_if_i32(b: bool) -> i32 { + if b { 100 } else { 0 } + } + + // Should NOT lint if bound to return type + fn ret_i32_tuple() -> (i32, i32) { + (0, 1) + } + + // Should NOT lint if bound to return type + fn ret_stmt(b: bool) -> (i32, i32) { + if b { + return (0, 1); + } + (0, 0) + } + + #[allow(clippy::useless_vec)] + fn vec_macro() { + // Should NOT lint in `vec!` call if the type was already stated + let data_i32: Vec = vec![1, 2, 3]; + let data_i32 = vec![1, 2, 3]; + } +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.stderr b/tests/ui/default_numeric_fallback_i32.stderr index b03b8b84c3324..0da9b77f32e36 100644 --- a/tests/ui/default_numeric_fallback_i32.stderr +++ b/tests/ui/default_numeric_fallback_i32.stderr @@ -97,12 +97,6 @@ error: default numeric fallback might occur LL | let y = 1; | ^ help: consider adding suffix: `1_i32` -error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:79:9 - | -LL | 1 - | ^ help: consider adding suffix: `1_i32` - error: default numeric fallback might occur --> $DIR/default_numeric_fallback_i32.rs:85:27 | @@ -159,5 +153,23 @@ LL | inline!(let x = 22;); | = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 26 previous errors +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback_i32.rs:217:29 + | +LL | let data_i32 = vec![1, 2, 3]; + | ^ help: consider adding suffix: `1_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback_i32.rs:217:32 + | +LL | let data_i32 = vec![1, 2, 3]; + | ^ help: consider adding suffix: `2_i32` + +error: default numeric fallback might occur + --> $DIR/default_numeric_fallback_i32.rs:217:35 + | +LL | let data_i32 = vec![1, 2, 3]; + | ^ help: consider adding suffix: `3_i32` + +error: aborting due to 28 previous errors From eae2317977acb0d42a93740ae93122a1287d8090 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Thu, 14 Dec 2023 09:27:01 +0800 Subject: [PATCH 02/73] improve [`cast_sign_loss`], to skip warning on mathmatical expression that is always positive --- clippy_lints/src/casts/cast_sign_loss.rs | 141 +++++++++++++++++++---- tests/ui/cast.rs | 49 ++++++++ tests/ui/cast.stderr | 74 +++++++++++- 3 files changed, 238 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index bd12ee406284b..1df5a25f674d7 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,12 +1,14 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{method_chain_args, sext}; -use rustc_hir::{Expr, ExprKind}; +use clippy_utils::{clip, method_chain_args, sext}; +use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, UintTy}; use super::CAST_SIGN_LOSS; +const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; + pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { if should_lint(cx, cast_op, cast_from, cast_to) { span_lint( @@ -25,33 +27,28 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast return false; } - // Don't lint for positive constants. - let const_val = constant(cx, cx.typeck_results(), cast_op); - if let Some(Constant::Int(n)) = const_val - && let ty::Int(ity) = *cast_from.kind() - && sext(cx.tcx, n, ity) >= 0 - { + // Don't lint if `cast_op` is known to be positive. + if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) { return false; } - // Don't lint for the result of methods that always return non-negative values. - if let ExprKind::MethodCall(path, ..) = cast_op.kind { - let mut method_name = path.ident.name.as_str(); - let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; - - if method_name == "unwrap" - && let Some(arglist) = method_chain_args(cast_op, &["unwrap"]) - && let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind - { - method_name = inner_path.ident.name.as_str(); - } - - if allowed_methods.iter().any(|&name| method_name == name) { - return false; - } + let (mut uncertain_count, mut negative_count) = (0, 0); + // Peel off possible binary expressions, e.g. x * x * y => [x, x, y] + let Some(exprs) = exprs_with_selected_binop_peeled(cast_op) else { + // Assume cast sign lose if we cannot determine the sign of `cast_op` + return true; + }; + for expr in exprs { + let ty = cx.typeck_results().expr_ty(expr); + match expr_sign(cx, expr, ty) { + Sign::Negative => negative_count += 1, + Sign::Uncertain => uncertain_count += 1, + Sign::ZeroOrPositive => (), + }; } - true + // Lint if there are odd number of uncertain or negative results + uncertain_count % 2 == 1 || negative_count % 2 == 1 }, (false, true) => !cast_to.is_signed(), @@ -59,3 +56,97 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast (_, _) => false, } } + +fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option { + if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + && let ty::Int(ity) = *ty.kind() + { + return Some(sext(cx.tcx, n, ity)); + } + None +} + +enum Sign { + ZeroOrPositive, + Negative, + Uncertain, +} + +fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign { + // Try evaluate this expr first to see if it's positive + if let Some(val) = get_const_int_eval(cx, expr, ty) { + return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative }; + } + // Calling on methods that always return non-negative values. + if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind { + let mut method_name = path.ident.name.as_str(); + + if method_name == "unwrap" + && let Some(arglist) = method_chain_args(expr, &["unwrap"]) + && let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind + { + method_name = inner_path.ident.name.as_str(); + } + + if method_name == "pow" + && let [arg] = args + { + return pow_call_result_sign(cx, caller, arg); + } else if METHODS_RET_POSITIVE.iter().any(|&name| method_name == name) { + return Sign::ZeroOrPositive; + } + } + + Sign::Uncertain +} + +/// Return the sign of the `pow` call's result. +/// +/// If the caller is a positive number, the result is always positive, +/// If the `power_of` is a even number, the result is always positive as well, +/// Otherwise a [`Sign::Uncertain`] will be returned. +fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign { + let caller_ty = cx.typeck_results().expr_ty(caller); + if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty) + && caller_val >= 0 + { + return Sign::ZeroOrPositive; + } + + if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of) + && clip(cx.tcx, n, UintTy::U32) % 2 == 0 + { + return Sign::ZeroOrPositive; + } + + Sign::Uncertain +} + +/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`], +/// which the result could always be positive under certain condition. +/// +/// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will +/// return `None` +fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option>> { + #[inline] + fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> { + match expr.kind { + ExprKind::Binary(op, lhs, rhs) => { + if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) { + collect_operands(lhs, operands); + operands.push(rhs); + } else { + // Things are complicated when there are other binary ops exist, + // abort checking by returning `None` for now. + return None; + } + }, + _ => operands.push(expr), + } + Some(()) + } + + let mut res = vec![]; + collect_operands(expr, &mut res)?; + Some(res) +} diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 1ca18170f8a0e..e9476c80ccb81 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -365,3 +365,52 @@ fn avoid_subtract_overflow(q: u32) { fn issue11426() { (&42u8 >> 0xa9008fb6c9d81e42_0e25730562a601c8_u128) as usize; } + +fn issue11642() { + fn square(x: i16) -> u32 { + let x = x as i32; + (x * x) as u32; + x.pow(2) as u32; + (-2_i32).pow(2) as u32 + } + + let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; + + (-2_i32).pow(3) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + + let x: i32 = 10; + (x * x) as u32; + (x * x * x) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + + let y: i16 = -2; + (y * y * y * y * -2) as u16; + //~^ ERROR: casting `i16` to `u16` may lose the sign of the value + (y * y * y * y * 2) as u16; + (y * y * y * 2) as u16; + //~^ ERROR: casting `i16` to `u16` may lose the sign of the value + (y * y * y * -2) as u16; + //~^ ERROR: casting `i16` to `u16` may lose the sign of the value + + fn foo(a: i32, b: i32, c: i32) -> u32 { + (a * a * b * b * c * c) as u32; + (a * b * c) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a * -b * c) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a * b * c * c) as u32; + (a * -2) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a * b * c * -2) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a / b) as u32; + (a / b * c) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a / b + b * c) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + a.pow(3) as u32; + //~^ ERROR: casting `i32` to `u32` may lose the sign of the value + (a.abs() * b.pow(2) / c.abs()) as u32 + } +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index bc74f7b728e86..4e37af7f37891 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -444,5 +444,77 @@ help: ... or use `try_from` and handle the error accordingly LL | let c = u8::try_from(q / 1000); | ~~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 51 previous errors +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:379:5 + | +LL | (-2_i32).pow(3) as u32; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:384:5 + | +LL | (x * x * x) as u32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting `i16` to `u16` may lose the sign of the value + --> $DIR/cast.rs:388:5 + | +LL | (y * y * y * y * -2) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i16` to `u16` may lose the sign of the value + --> $DIR/cast.rs:391:5 + | +LL | (y * y * y * 2) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i16` to `u16` may lose the sign of the value + --> $DIR/cast.rs:393:5 + | +LL | (y * y * y * -2) as u16; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:398:9 + | +LL | (a * b * c) as u32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:400:9 + | +LL | (a * -b * c) as u32; + | ^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:403:9 + | +LL | (a * -2) as u32; + | ^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:405:9 + | +LL | (a * b * c * -2) as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:408:9 + | +LL | (a / b * c) as u32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:410:9 + | +LL | (a / b + b * c) as u32; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:412:9 + | +LL | a.pow(3) as u32; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 63 previous errors From 4cea5a8f33715bfe38824c798a5d75218cbfec04 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 16 Dec 2023 14:02:50 +0100 Subject: [PATCH 03/73] Do not suggest `[T; n]` instead of `vec![T; n]` if `T` is not `Copy` --- clippy_lints/src/vec.rs | 17 ++++++----------- tests/ui/vec.fixed | 7 +++++++ tests/ui/vec.rs | 7 +++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 5e13c73f03591..2c33c93412a3d 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -11,8 +11,8 @@ use clippy_utils::{get_parent_expr, higher, is_trait_method}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::{sym, DesugaringKind, Span}; @@ -79,7 +79,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { // this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..]; && local.ty.is_none() && let PatKind::Binding(_, id, ..) = local.pat.kind - && is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(expr.peel_borrows()))) { let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| { // allow indexing into a vec and some set of allowed method calls that exist on slices, too @@ -185,6 +184,11 @@ impl UselessVec { let snippet = match *vec_args { higher::VecArgs::Repeat(elem, len) => { if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) { + // vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also + if !is_copy(cx, cx.typeck_results().expr_ty(elem)) { + return; + } + #[expect(clippy::cast_possible_truncation)] if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack { return; @@ -241,12 +245,3 @@ fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 { let ty = cx.typeck_results().expr_ty_adjusted(expr); cx.layout_of(ty).map_or(0, |l| l.size.bytes()) } - -/// Returns the item type of the vector (i.e., the `T` in `Vec`). -fn vec_type(ty: Ty<'_>) -> Ty<'_> { - if let ty::Adt(_, args) = ty.kind() { - args.type_at(0) - } else { - panic!("The type of `vec!` is a not a struct?"); - } -} diff --git a/tests/ui/vec.fixed b/tests/ui/vec.fixed index 81b8bd7da775e..b318fd42f7ca2 100644 --- a/tests/ui/vec.fixed +++ b/tests/ui/vec.fixed @@ -210,3 +210,10 @@ fn issue11861() { // should not lint m!(vec![1]); } + +fn issue_11958() { + fn f(_s: &[String]) {} + + // should not lint, `String` is not `Copy` + f(&vec!["test".to_owned(); 2]); +} diff --git a/tests/ui/vec.rs b/tests/ui/vec.rs index 5aca9b2925c71..08ad6efa37f2d 100644 --- a/tests/ui/vec.rs +++ b/tests/ui/vec.rs @@ -210,3 +210,10 @@ fn issue11861() { // should not lint m!(vec![1]); } + +fn issue_11958() { + fn f(_s: &[String]) {} + + // should not lint, `String` is not `Copy` + f(&vec!["test".to_owned(); 2]); +} From 1c431f4da96fe701388f9ca7ecff1020b402664a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:36:12 +0100 Subject: [PATCH 04/73] Move check for `PartialEq` in `UNCONDITIONAL_RECURSION` lint into its own function --- clippy_lints/src/unconditional_recursion.rs | 137 +++++++++++--------- 1 file changed, 75 insertions(+), 62 deletions(-) diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index b1fa30aa06820..570770523ebcb 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -8,6 +8,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; +use rustc_span::symbol::Ident; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -55,6 +56,76 @@ fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(path_res(cx, expr), Res::Local(_)) } +fn check_partial_eq( + cx: &LateContext<'_>, + body: &Body<'_>, + method_span: Span, + method_def_id: LocalDefId, + name: Ident, +) { + let args = cx + .tcx + .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) + .inputs(); + // That has two arguments. + if let [self_arg, other_arg] = args + && let Some(self_arg) = get_ty_def_id(*self_arg) + && let Some(other_arg) = get_ty_def_id(*other_arg) + // The two arguments are of the same type. + && self_arg == other_arg + && let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id) + && let Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(impl_), + owner_id, + .. + }), + )) = cx.tcx.hir().parent_iter(hir_id).next() + // We exclude `impl` blocks generated from rustc's proc macros. + && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + // It is a implementation of a trait. + && let Some(trait_) = impl_.of_trait + && let Some(trait_def_id) = trait_.trait_def_id() + // The trait is `PartialEq`. + && Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"]) + { + let to_check_op = if name.name == sym::eq { + BinOpKind::Eq + } else { + BinOpKind::Ne + }; + let expr = body.value.peel_blocks(); + let is_bad = match expr.kind { + ExprKind::Binary(op, left, right) if op.node == to_check_op => is_local(cx, left) && is_local(cx, right), + ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { + if is_local(cx, receiver) + && is_local(cx, &arg) + && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && trait_id == trait_def_id + { + true + } else { + false + } + }, + _ => false, + }; + if is_bad { + span_lint_and_then( + cx, + UNCONDITIONAL_RECURSION, + method_span, + "function cannot return without recursing", + |diag| { + diag.span_note(expr.span, "recursive call site"); + }, + ); + } + } +} + impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { #[allow(clippy::unnecessary_def_path)] fn check_fn( @@ -64,70 +135,12 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { _decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, method_span: Span, - def_id: LocalDefId, + method_def_id: LocalDefId, ) { // If the function is a method... - if let FnKind::Method(name, _) = kind - // That has two arguments. - && let [self_arg, other_arg] = cx - .tcx - .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(def_id).skip_binder()) - .inputs() - && let Some(self_arg) = get_ty_def_id(*self_arg) - && let Some(other_arg) = get_ty_def_id(*other_arg) - // The two arguments are of the same type. - && self_arg == other_arg - && let hir_id = cx.tcx.local_def_id_to_hir_id(def_id) - && let Some(( - _, - Node::Item(Item { - kind: ItemKind::Impl(impl_), - owner_id, - .. - }), - )) = cx.tcx.hir().parent_iter(hir_id).next() - // We exclude `impl` blocks generated from rustc's proc macros. - && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) - // It is a implementation of a trait. - && let Some(trait_) = impl_.of_trait - && let Some(trait_def_id) = trait_.trait_def_id() - // The trait is `PartialEq`. - && Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"]) - { - let to_check_op = if name.name == sym::eq { - BinOpKind::Eq - } else { - BinOpKind::Ne - }; - let expr = body.value.peel_blocks(); - let is_bad = match expr.kind { - ExprKind::Binary(op, left, right) if op.node == to_check_op => { - is_local(cx, left) && is_local(cx, right) - }, - ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { - if is_local(cx, receiver) - && is_local(cx, &arg) - && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) - && trait_id == trait_def_id - { - true - } else { - false - } - }, - _ => false, - }; - if is_bad { - span_lint_and_then( - cx, - UNCONDITIONAL_RECURSION, - method_span, - "function cannot return without recursing", - |diag| { - diag.span_note(expr.span, "recursive call site"); - }, - ); + if let FnKind::Method(name, _) = kind { + if name.name == sym::eq || name.name == sym::ne { + check_partial_eq(cx, body, method_span, method_def_id, name); } } } From 4ab8cdd5b9f715938f5d4f1c4c6a87b4ea400ce5 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 19 Dec 2023 22:52:04 -0500 Subject: [PATCH 05/73] Hide foreign `#[doc(hidden)]` paths in import suggestions --- tests/ui/crashes/ice-6252.stderr | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ui/crashes/ice-6252.stderr b/tests/ui/crashes/ice-6252.stderr index f929bec9583c5..f6d0976091c53 100644 --- a/tests/ui/crashes/ice-6252.stderr +++ b/tests/ui/crashes/ice-6252.stderr @@ -8,8 +8,6 @@ help: consider importing one of these items | LL + use core::marker::PhantomData; | -LL + use serde::__private::PhantomData; - | LL + use std::marker::PhantomData; | From a9baf344b8563995caecb8343fa7ac8a7cd26cc7 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 25 Dec 2023 09:39:32 +0800 Subject: [PATCH 06/73] remove obsolete test comments --- tests/ui/default_numeric_fallback_f64.fixed | 2 -- tests/ui/default_numeric_fallback_f64.rs | 2 -- tests/ui/default_numeric_fallback_f64.stderr | 18 +++++++-------- tests/ui/default_numeric_fallback_i32.fixed | 2 -- tests/ui/default_numeric_fallback_i32.rs | 2 -- tests/ui/default_numeric_fallback_i32.stderr | 24 ++++++++++---------- 6 files changed, 21 insertions(+), 29 deletions(-) diff --git a/tests/ui/default_numeric_fallback_f64.fixed b/tests/ui/default_numeric_fallback_f64.fixed index e62dc8b255e7c..00f0d76443401 100644 --- a/tests/ui/default_numeric_fallback_f64.fixed +++ b/tests/ui/default_numeric_fallback_f64.fixed @@ -73,8 +73,6 @@ mod nested_local { mod function_def { fn ret_f64() -> f64 { - // Even though the output type is specified, - // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. 1. } diff --git a/tests/ui/default_numeric_fallback_f64.rs b/tests/ui/default_numeric_fallback_f64.rs index 256b94f6c0589..942cedac27537 100644 --- a/tests/ui/default_numeric_fallback_f64.rs +++ b/tests/ui/default_numeric_fallback_f64.rs @@ -73,8 +73,6 @@ mod nested_local { mod function_def { fn ret_f64() -> f64 { - // Even though the output type is specified, - // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. 1. } diff --git a/tests/ui/default_numeric_fallback_f64.stderr b/tests/ui/default_numeric_fallback_f64.stderr index e81aafdb3f3ef..c95679c9eb81b 100644 --- a/tests/ui/default_numeric_fallback_f64.stderr +++ b/tests/ui/default_numeric_fallback_f64.stderr @@ -86,55 +86,55 @@ LL | let y = 1.; | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:84:27 + --> $DIR/default_numeric_fallback_f64.rs:82:27 | LL | let f = || -> _ { 1. }; | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:88:29 + --> $DIR/default_numeric_fallback_f64.rs:86:29 | LL | let f = || -> f64 { 1. }; | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:102:21 + --> $DIR/default_numeric_fallback_f64.rs:100:21 | LL | generic_arg(1.); | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:105:32 + --> $DIR/default_numeric_fallback_f64.rs:103:32 | LL | let x: _ = generic_arg(1.); | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:123:28 + --> $DIR/default_numeric_fallback_f64.rs:121:28 | LL | GenericStruct { x: 1. }; | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:126:36 + --> $DIR/default_numeric_fallback_f64.rs:124:36 | LL | let _ = GenericStruct { x: 1. }; | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:144:24 + --> $DIR/default_numeric_fallback_f64.rs:142:24 | LL | GenericEnum::X(1.); | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:164:23 + --> $DIR/default_numeric_fallback_f64.rs:162:23 | LL | s.generic_arg(1.); | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:174:25 + --> $DIR/default_numeric_fallback_f64.rs:172:25 | LL | inline!(let x = 22.;); | ^^^ help: consider adding suffix: `22.0_f64` diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index 115cd30a6b391..9ef92d8f279a9 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -74,8 +74,6 @@ mod nested_local { mod function_def { fn ret_i32() -> i32 { - // Even though the output type is specified, - // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. 1 } diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index a4abfc11320ce..4b54b8d84587e 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -74,8 +74,6 @@ mod nested_local { mod function_def { fn ret_i32() -> i32 { - // Even though the output type is specified, - // this unsuffixed literal is linted to reduce heuristics and keep codebase simple. 1 } diff --git a/tests/ui/default_numeric_fallback_i32.stderr b/tests/ui/default_numeric_fallback_i32.stderr index 0da9b77f32e36..7663977fb65a4 100644 --- a/tests/ui/default_numeric_fallback_i32.stderr +++ b/tests/ui/default_numeric_fallback_i32.stderr @@ -98,55 +98,55 @@ LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:85:27 + --> $DIR/default_numeric_fallback_i32.rs:83:27 | LL | let f = || -> _ { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:89:29 + --> $DIR/default_numeric_fallback_i32.rs:87:29 | LL | let f = || -> i32 { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:103:21 + --> $DIR/default_numeric_fallback_i32.rs:101:21 | LL | generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:106:32 + --> $DIR/default_numeric_fallback_i32.rs:104:32 | LL | let x: _ = generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:124:28 + --> $DIR/default_numeric_fallback_i32.rs:122:28 | LL | GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:127:36 + --> $DIR/default_numeric_fallback_i32.rs:125:36 | LL | let _ = GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:145:24 + --> $DIR/default_numeric_fallback_i32.rs:143:24 | LL | GenericEnum::X(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:165:23 + --> $DIR/default_numeric_fallback_i32.rs:163:23 | LL | s.generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:175:25 + --> $DIR/default_numeric_fallback_i32.rs:173:25 | LL | inline!(let x = 22;); | ^^ help: consider adding suffix: `22_i32` @@ -154,19 +154,19 @@ LL | inline!(let x = 22;); = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:217:29 + --> $DIR/default_numeric_fallback_i32.rs:215:29 | LL | let data_i32 = vec![1, 2, 3]; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:217:32 + --> $DIR/default_numeric_fallback_i32.rs:215:32 | LL | let data_i32 = vec![1, 2, 3]; | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:217:35 + --> $DIR/default_numeric_fallback_i32.rs:215:35 | LL | let data_i32 = vec![1, 2, 3]; | ^ help: consider adding suffix: `3_i32` From a578b9ba0b13e5dde95d2f09aab1b9d5ae699cb6 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 25 Dec 2023 14:28:01 +0800 Subject: [PATCH 07/73] make [`mutex_atomic`] more type aware --- clippy_lints/src/mutex_atomic.rs | 26 +++++++++++++++++++++++--- tests/ui/mutex_atomic.rs | 19 +++++++++++++++++-- tests/ui/mutex_atomic.stderr | 30 +++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index a23e12f7a18c2..4ae4fc9b09616 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -6,7 +6,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::is_type_diagnostic_item; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, IntTy, Ty, UintTy}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -105,8 +105,28 @@ impl<'tcx> LateLintPass<'tcx> for Mutex { fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { match ty.kind() { ty::Bool => Some("AtomicBool"), - ty::Uint(_) => Some("AtomicUsize"), - ty::Int(_) => Some("AtomicIsize"), + ty::Uint(uint_ty) => { + match uint_ty { + UintTy::U8 => Some("AtomicU8"), + UintTy::U16 => Some("AtomicU16"), + UintTy::U32 => Some("AtomicU32"), + UintTy::U64 => Some("AtomicU64"), + UintTy::Usize => Some("AtomicUsize"), + // There's no `AtomicU128`. + UintTy::U128 => None, + } + }, + ty::Int(int_ty) => { + match int_ty { + IntTy::I8 => Some("AtomicI8"), + IntTy::I16 => Some("AtomicI16"), + IntTy::I32 => Some("AtomicI32"), + IntTy::I64 => Some("AtomicI64"), + IntTy::Isize => Some("AtomicIsize"), + // There's no `AtomicI128`. + IntTy::I128 => None, + } + }, ty::RawPtr(_) => Some("AtomicPtr"), _ => None, } diff --git a/tests/ui/mutex_atomic.rs b/tests/ui/mutex_atomic.rs index 198b95d8c9486..3a51538b74240 100644 --- a/tests/ui/mutex_atomic.rs +++ b/tests/ui/mutex_atomic.rs @@ -18,9 +18,24 @@ fn main() { Mutex::new(&mut x as *mut u32); //~^ ERROR: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want Mutex::new(0u32); - //~^ ERROR: consider using an `AtomicUsize` instead of a `Mutex` here; if you just wan + //~^ ERROR: consider using an `AtomicU32` instead of a `Mutex` here; if you just wan //~| NOTE: `-D clippy::mutex-integer` implied by `-D warnings` Mutex::new(0i32); - //~^ ERROR: consider using an `AtomicIsize` instead of a `Mutex` here; if you just wan + //~^ ERROR: consider using an `AtomicI32` instead of a `Mutex` here; if you just wan Mutex::new(0f32); // there are no float atomics, so this should not lint + Mutex::new(0u8); + //~^ ERROR: consider using an `AtomicU8` instead of a `Mutex` here; if you just wan + Mutex::new(0i16); + //~^ ERROR: consider using an `AtomicI16` instead of a `Mutex` here; if you just wan + let _x: Mutex = Mutex::new(0); + //~^ ERROR: consider using an `AtomicI8` instead of a `Mutex` here; if you just wan + const X: i64 = 0; + Mutex::new(X); + //~^ ERROR: consider using an `AtomicI64` instead of a `Mutex` here; if you just wan + + // there are no 128 atomics, so these two should not lint + { + Mutex::new(0u128); + let _x: Mutex = Mutex::new(0); + } } diff --git a/tests/ui/mutex_atomic.stderr b/tests/ui/mutex_atomic.stderr index 483e1ce15f6fb..91f73d30b5386 100644 --- a/tests/ui/mutex_atomic.stderr +++ b/tests/ui/mutex_atomic.stderr @@ -31,7 +31,7 @@ error: consider using an `AtomicPtr` instead of a `Mutex` here; if you just want LL | Mutex::new(&mut x as *mut u32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: consider using an `AtomicUsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` +error: consider using an `AtomicU32` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` --> $DIR/mutex_atomic.rs:20:5 | LL | Mutex::new(0u32); @@ -40,11 +40,35 @@ LL | Mutex::new(0u32); = note: `-D clippy::mutex-integer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutex_integer)]` -error: consider using an `AtomicIsize` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` +error: consider using an `AtomicI32` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` --> $DIR/mutex_atomic.rs:23:5 | LL | Mutex::new(0i32); | ^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: consider using an `AtomicU8` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:26:5 + | +LL | Mutex::new(0u8); + | ^^^^^^^^^^^^^^^ + +error: consider using an `AtomicI16` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:28:5 + | +LL | Mutex::new(0i16); + | ^^^^^^^^^^^^^^^^ + +error: consider using an `AtomicI8` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:30:25 + | +LL | let _x: Mutex = Mutex::new(0); + | ^^^^^^^^^^^^^ + +error: consider using an `AtomicI64` instead of a `Mutex` here; if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + --> $DIR/mutex_atomic.rs:33:5 + | +LL | Mutex::new(X); + | ^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors From 4c64d7cea2377a0ee00c021f65f5754db6ef63c0 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 26 Dec 2023 18:43:16 -0500 Subject: [PATCH 08/73] ci: Update to `actions/checkout@v4` from `v3`. This also updates the book's section on CI for the same thing. changelog: none --- .github/workflows/clippy.yml | 2 +- .github/workflows/clippy_bors.yml | 10 +++++----- .github/workflows/clippy_dev.yml | 2 +- .github/workflows/deploy.yml | 4 ++-- .github/workflows/remark.yml | 2 +- book/src/continuous_integration/github_actions.md | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 99d80bec0255f..5ba960db66dde 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -38,7 +38,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install toolchain run: rustup show active-toolchain diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 73c2555074286..012797e5ca72a 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -26,7 +26,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} @@ -72,7 +72,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install i686 dependencies if: matrix.host == 'i686-unknown-linux-gnu' @@ -151,7 +151,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install toolchain run: rustup show active-toolchain @@ -175,7 +175,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install toolchain run: rustup show active-toolchain @@ -231,7 +231,7 @@ jobs: github_token: "${{ secrets.github_token }}" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install toolchain run: rustup show active-toolchain diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index 0f0e3f2db925c..37f18a4c08740 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -24,7 +24,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Run - name: Build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 999ee7acfe761..94f494b65c47c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,10 +21,10 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ env.TARGET_BRANCH }} path: 'out' diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 30bd476332f75..05e1b3b9202b4 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -16,7 +16,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v3 diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md index 339287a7dd95f..b588c8f0f02c4 100644 --- a/book/src/continuous_integration/github_actions.md +++ b/book/src/continuous_integration/github_actions.md @@ -15,7 +15,7 @@ jobs: clippy_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run Clippy run: cargo clippy --all-targets --all-features ``` From c4a80f2e3e8375ff54826840e2e2a5c53a20e2ce Mon Sep 17 00:00:00 2001 From: Yuxiang Qiu Date: Fri, 24 Nov 2023 20:56:57 -0500 Subject: [PATCH 09/73] feat: add `manual_is_variant_and` lint --- CHANGELOG.md | 1 + clippy_config/src/msrvs.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lines_filter_map_ok.rs | 3 +- .../src/methods/manual_is_variant_and.rs | 59 +++++++++++++ clippy_lints/src/methods/mod.rs | 36 +++++++- .../src/methods/option_map_unwrap_or.rs | 2 +- .../src/methods/path_ends_with_ext.rs | 2 +- tests/ui/manual_is_variant_and.fixed | 51 ++++++++++++ tests/ui/manual_is_variant_and.rs | 57 +++++++++++++ tests/ui/manual_is_variant_and.stderr | 82 +++++++++++++++++++ 11 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 clippy_lints/src/methods/manual_is_variant_and.rs create mode 100644 tests/ui/manual_is_variant_and.fixed create mode 100644 tests/ui/manual_is_variant_and.rs create mode 100644 tests/ui/manual_is_variant_and.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index e01c09b295c5a..054d9390dd201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5293,6 +5293,7 @@ Released 2018-09-13 [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check [`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite [`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite +[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and [`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else [`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 8dec477cfdd6c..773cce36df456 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -17,7 +17,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } - 1,70,0 { OPTION_IS_SOME_AND, BINARY_HEAP_RETAIN } + 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN } 1,68,0 { PATH_MAIN_SEPARATOR_STR } 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index a00e2d3e0445e..1db94ad3045bc 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -385,6 +385,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::JOIN_ABSOLUTE_PATHS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, + crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_NEXT_BACK_INFO, crate::methods::MANUAL_OK_OR_INFO, crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO, diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs index 8a0955147bb54..29957e423b0b9 100644 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -96,8 +96,7 @@ fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> boo ExprKind::Path(qpath) => cx .qpath_res(qpath, fm_arg.hir_id) .opt_def_id() - .map(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)) - .unwrap_or_default(), + .is_some_and(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)), // Detect `|x| x.ok()` ExprKind::Closure(Closure { body, .. }) => { if let Body { diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs new file mode 100644 index 0000000000000..d29acd4622a0b --- /dev/null +++ b/clippy_lints/src/methods/manual_is_variant_and.rs @@ -0,0 +1,59 @@ +use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_span::{sym, Span}; + +use super::MANUAL_IS_VARIANT_AND; + +pub(super) fn check<'tcx>( + cx: &LateContext<'_>, + expr: &'tcx rustc_hir::Expr<'_>, + map_recv: &'tcx rustc_hir::Expr<'_>, + map_arg: &'tcx rustc_hir::Expr<'_>, + map_span: Span, + msrv: &Msrv, +) { + // Don't lint if: + + // 1. the `expr` is generated by a macro + if expr.span.from_expansion() { + return; + } + + // 2. the caller of `map()` is neither `Option` nor `Result` + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result); + if !is_option && !is_result { + return; + } + + // 3. the caller of `unwrap_or_default` is neither `Option` nor `Result` + if !cx.typeck_results().expr_ty(expr).is_bool() { + return; + } + + // 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND` + if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) { + return; + } + + let lint_msg = if is_option { + "called `map().unwrap_or_default()` on an `Option` value" + } else { + "called `map().unwrap_or_default()` on a `Result` value" + }; + let suggestion = if is_option { "is_some_and" } else { "is_ok_and" }; + + span_lint_and_sugg( + cx, + MANUAL_IS_VARIANT_AND, + expr.span.with_lo(map_span.lo()), + lint_msg, + "use", + format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")), + Applicability::MachineApplicable, + ); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 25b1ea526e2e3..0cde17ef5ad5e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -51,6 +51,7 @@ mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; +mod manual_is_variant_and; mod manual_next_back; mod manual_ok_or; mod manual_saturating_arithmetic; @@ -3829,6 +3830,32 @@ declare_clippy_lint! { "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" } +declare_clippy_lint! { + /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// + /// ### Why is this bad? + /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. + /// + /// ### Example + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.map(|a| a > 10).unwrap_or_default(); + /// result.map(|a| a > 10).unwrap_or_default(); + /// ``` + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.is_some_and(|a| a > 10); + /// result.is_ok_and(|a| a > 10); + /// ``` + #[clippy::version = "1.76.0"] + pub MANUAL_IS_VARIANT_AND, + pedantic, + "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3983,6 +4010,7 @@ impl_lint_pass!(Methods => [ RESULT_FILTER_MAP, ITER_FILTER_IS_SOME, ITER_FILTER_IS_OK, + MANUAL_IS_VARIANT_AND, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4664,7 +4692,13 @@ impl Methods { } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, - ("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => { + ("unwrap_or_default", []) => { + if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) { + manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv); + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + ("unwrap_unchecked" | "unwrap_err_unchecked", []) => { unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, ("unwrap_or_else", [u_arg]) => { diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 63e64a5b35d05..35deabe49b636 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -72,7 +72,7 @@ pub(super) fn check<'tcx>( } // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead - let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND) + let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) && matches!(&unwrap_arg.kind, ExprKind::Lit(lit) if matches!(lit.node, rustc_ast::LitKind::Bool(false))); diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints/src/methods/path_ends_with_ext.rs index 094ead9f4ad87..29f44ec2a4d46 100644 --- a/clippy_lints/src/methods/path_ends_with_ext.rs +++ b/clippy_lints/src/methods/path_ends_with_ext.rs @@ -33,7 +33,7 @@ pub(super) fn check( && path.chars().all(char::is_alphanumeric) { let mut sugg = snippet(cx, recv.span, "..").into_owned(); - if msrv.meets(msrvs::OPTION_IS_SOME_AND) { + if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) { let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#); } else { let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#); diff --git a/tests/ui/manual_is_variant_and.fixed b/tests/ui/manual_is_variant_and.fixed new file mode 100644 index 0000000000000..8c34b51103c13 --- /dev/null +++ b/tests/ui/manual_is_variant_and.fixed @@ -0,0 +1,51 @@ +//@aux-build:option_helpers.rs +#![warn(clippy::manual_is_variant_and)] + +#[macro_use] +extern crate option_helpers; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or_default()` use. + // Single line case. + let _ = opt.is_some_and(|x| x > 1); + // Multi-line cases. + let _ = opt.is_some_and(|x| { + x > 1 + }); + let _ = opt.is_some_and(|x| x > 1); + let _ = opt + .is_some_and(|x| x > 1); + + // won't fix because the return type of the closure is not `bool` + let _ = opt.map(|x| x + 1).unwrap_or_default(); + + let opt2 = Some('a'); + let _ = opt2.is_some_and(char::is_alphanumeric); // should lint + let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint +} + +#[rustfmt::skip] +fn result_methods() { + let res: Result = Ok(1); + + // multi line cases + let _ = res.is_ok_and(|x| { + x > 1 + }); + let _ = res.is_ok_and(|x| x > 1); + + // won't fix because the return type of the closure is not `bool` + let _ = res.map(|x| x + 1).unwrap_or_default(); + + let res2: Result = Ok('a'); + let _ = res2.is_ok_and(char::is_alphanumeric); // should lint + let _ = opt_map!(res2, |x| x == 'a').unwrap_or_default(); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/tests/ui/manual_is_variant_and.rs b/tests/ui/manual_is_variant_and.rs new file mode 100644 index 0000000000000..25b2489d94211 --- /dev/null +++ b/tests/ui/manual_is_variant_and.rs @@ -0,0 +1,57 @@ +//@aux-build:option_helpers.rs +#![warn(clippy::manual_is_variant_and)] + +#[macro_use] +extern crate option_helpers; + +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or_default()` use. + // Single line case. + let _ = opt.map(|x| x > 1) + // Should lint even though this call is on a separate line. + .unwrap_or_default(); + // Multi-line cases. + let _ = opt.map(|x| { + x > 1 + } + ).unwrap_or_default(); + let _ = opt.map(|x| x > 1).unwrap_or_default(); + let _ = opt + .map(|x| x > 1) + .unwrap_or_default(); + + // won't fix because the return type of the closure is not `bool` + let _ = opt.map(|x| x + 1).unwrap_or_default(); + + let opt2 = Some('a'); + let _ = opt2.map(char::is_alphanumeric).unwrap_or_default(); // should lint + let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint +} + +#[rustfmt::skip] +fn result_methods() { + let res: Result = Ok(1); + + // multi line cases + let _ = res.map(|x| { + x > 1 + } + ).unwrap_or_default(); + let _ = res.map(|x| x > 1) + .unwrap_or_default(); + + // won't fix because the return type of the closure is not `bool` + let _ = res.map(|x| x + 1).unwrap_or_default(); + + let res2: Result = Ok('a'); + let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint + let _ = opt_map!(res2, |x| x == 'a').unwrap_or_default(); // should not lint +} + +fn main() { + option_methods(); + result_methods(); +} diff --git a/tests/ui/manual_is_variant_and.stderr b/tests/ui/manual_is_variant_and.stderr new file mode 100644 index 0000000000000..c243de098dc67 --- /dev/null +++ b/tests/ui/manual_is_variant_and.stderr @@ -0,0 +1,82 @@ +error: called `map().unwrap_or_default()` on an `Option` value + --> $DIR/manual_is_variant_and.rs:13:17 + | +LL | let _ = opt.map(|x| x > 1) + | _________________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or_default(); + | |____________________________^ help: use: `is_some_and(|x| x > 1)` + | + = note: `-D clippy::manual-is-variant-and` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_is_variant_and)]` + +error: called `map().unwrap_or_default()` on an `Option` value + --> $DIR/manual_is_variant_and.rs:17:17 + | +LL | let _ = opt.map(|x| { + | _________________^ +LL | | x > 1 +LL | | } +LL | | ).unwrap_or_default(); + | |_________________________^ + | +help: use + | +LL ~ let _ = opt.is_some_and(|x| { +LL + x > 1 +LL ~ }); + | + +error: called `map().unwrap_or_default()` on an `Option` value + --> $DIR/manual_is_variant_and.rs:21:17 + | +LL | let _ = opt.map(|x| x > 1).unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_some_and(|x| x > 1)` + +error: called `map().unwrap_or_default()` on an `Option` value + --> $DIR/manual_is_variant_and.rs:23:10 + | +LL | .map(|x| x > 1) + | __________^ +LL | | .unwrap_or_default(); + | |____________________________^ help: use: `is_some_and(|x| x > 1)` + +error: called `map().unwrap_or_default()` on an `Option` value + --> $DIR/manual_is_variant_and.rs:30:18 + | +LL | let _ = opt2.map(char::is_alphanumeric).unwrap_or_default(); // should lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_some_and(char::is_alphanumeric)` + +error: called `map().unwrap_or_default()` on a `Result` value + --> $DIR/manual_is_variant_and.rs:39:17 + | +LL | let _ = res.map(|x| { + | _________________^ +LL | | x > 1 +LL | | } +LL | | ).unwrap_or_default(); + | |_________________________^ + | +help: use + | +LL ~ let _ = res.is_ok_and(|x| { +LL + x > 1 +LL ~ }); + | + +error: called `map().unwrap_or_default()` on a `Result` value + --> $DIR/manual_is_variant_and.rs:43:17 + | +LL | let _ = res.map(|x| x > 1) + | _________________^ +LL | | .unwrap_or_default(); + | |____________________________^ help: use: `is_ok_and(|x| x > 1)` + +error: called `map().unwrap_or_default()` on a `Result` value + --> $DIR/manual_is_variant_and.rs:50:18 + | +LL | let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_ok_and(char::is_alphanumeric)` + +error: aborting due to 8 previous errors + From e5c9fb68279322000536166a4a82a754904e52f7 Mon Sep 17 00:00:00 2001 From: Florian Brucker Date: Wed, 27 Dec 2023 22:15:17 +0100 Subject: [PATCH 10/73] 11973: Don't escape `"` in `'"'` --- clippy_lints/src/methods/utils.rs | 1 + tests/ui/single_char_pattern.fixed | 2 ++ tests/ui/single_char_pattern.rs | 2 ++ tests/ui/single_char_pattern.stderr | 26 ++++++++++++++++---------- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 9ad4250a141f0..3a0305b4d553e 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -74,6 +74,7 @@ pub(super) fn get_hint_if_single_char_arg( match ch { "'" => "\\'", r"\" => "\\\\", + "\\\"" => "\"", // no need to escape `"` in `'"'` _ => ch, } ); diff --git a/tests/ui/single_char_pattern.fixed b/tests/ui/single_char_pattern.fixed index 79e7eda40703b..9573fdbcfde33 100644 --- a/tests/ui/single_char_pattern.fixed +++ b/tests/ui/single_char_pattern.fixed @@ -42,6 +42,8 @@ fn main() { x.split('\n'); x.split('\''); x.split('\''); + // Issue #11973: Don't escape `"` in `'"'` + x.split('"'); let h = HashSet::::new(); h.contains("X"); // should not warn diff --git a/tests/ui/single_char_pattern.rs b/tests/ui/single_char_pattern.rs index 81962c0a6e931..8a04480dbc643 100644 --- a/tests/ui/single_char_pattern.rs +++ b/tests/ui/single_char_pattern.rs @@ -42,6 +42,8 @@ fn main() { x.split("\n"); x.split("'"); x.split("\'"); + // Issue #11973: Don't escape `"` in `'"'` + x.split("\""); let h = HashSet::::new(); h.contains("X"); // should not warn diff --git a/tests/ui/single_char_pattern.stderr b/tests/ui/single_char_pattern.stderr index 6e57ab3489f12..781ab316d9de3 100644 --- a/tests/ui/single_char_pattern.stderr +++ b/tests/ui/single_char_pattern.stderr @@ -182,58 +182,64 @@ LL | x.split("\'"); | ^^^^ help: try using a `char` instead: `'\''` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:49:31 + --> $DIR/single_char_pattern.rs:46:13 + | +LL | x.split("\""); + | ^^^^ help: try using a `char` instead: `'"'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:51:31 | LL | x.replace(';', ",").split(","); // issue #2978 | ^^^ help: try using a `char` instead: `','` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:50:19 + --> $DIR/single_char_pattern.rs:52:19 | LL | x.starts_with("\x03"); // issue #2996 | ^^^^^^ help: try using a `char` instead: `'\x03'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:57:13 + --> $DIR/single_char_pattern.rs:59:13 | LL | x.split(r"a"); | ^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:58:13 + --> $DIR/single_char_pattern.rs:60:13 | LL | x.split(r#"a"#); | ^^^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:59:13 + --> $DIR/single_char_pattern.rs:61:13 | LL | x.split(r###"a"###); | ^^^^^^^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:60:13 + --> $DIR/single_char_pattern.rs:62:13 | LL | x.split(r###"'"###); | ^^^^^^^^^^ help: try using a `char` instead: `'\''` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:61:13 + --> $DIR/single_char_pattern.rs:63:13 | LL | x.split(r###"#"###); | ^^^^^^^^^^ help: try using a `char` instead: `'#'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:63:13 + --> $DIR/single_char_pattern.rs:65:13 | LL | x.split(r#"\"#); | ^^^^^^ help: try using a `char` instead: `'\\'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:64:13 + --> $DIR/single_char_pattern.rs:66:13 | LL | x.split(r"\"); | ^^^^ help: try using a `char` instead: `'\\'` -error: aborting due to 39 previous errors +error: aborting due to 40 previous errors From affde93041299c195649ca2c076bafb744b55c7e Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Wed, 27 Dec 2023 23:38:20 +0100 Subject: [PATCH 11/73] lint on nested binary operations involving local and handle projections --- clippy_lints/src/transmute/eager_transmute.rs | 37 ++++++++++++-- tests/ui/eager_transmute.fixed | 22 +++++++- tests/ui/eager_transmute.rs | 22 +++++++- tests/ui/eager_transmute.stderr | 51 +++++++++++++++---- 4 files changed, 117 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 01a23c515f57d..7539b6af38597 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_normalizable; -use clippy_utils::{path_to_local, path_to_local_id}; +use clippy_utils::{eq_expr_value, path_to_local}; use rustc_abi::WrappingRange; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Node}; @@ -25,6 +25,36 @@ fn range_fully_contained(from: WrappingRange, to: WrappingRange) -> bool { to.contains(from.start) && to.contains(from.end) } +/// Checks if a given expression is a binary operation involving a local variable or is made up of +/// other (nested) binary expressions involving the local. There must be at least one local +/// reference that is the same as `local_expr`. +/// +/// This is used as a heuristic to detect if a variable +/// is checked to be within the valid range of a transmuted type. +/// All of these would return true: +/// * `x < 4` +/// * `x < 4 && x > 1` +/// * `x.field < 4 && x.field > 1` (given `x.field`) +/// * `x.field < 4 && unrelated()` +fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Binary(_, lhs, rhs) => { + binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs) + }, + _ => eq_expr_value(cx, local_expr, expr), + } +} + +/// Checks if an expression is a path to a local variable (with optional projections), e.g. +/// `x.field[0].field2` would return true. +fn is_local_with_projections(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(_) => path_to_local(expr).is_some(), + ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr), + _ => false, + } +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, @@ -36,9 +66,8 @@ pub(super) fn check<'tcx>( && let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind && cx.typeck_results().expr_ty(receiver).is_bool() && path.ident.name == sym!(then_some) - && let ExprKind::Binary(_, lhs, rhs) = receiver.kind - && let Some(local_id) = path_to_local(transmutable) - && (path_to_local_id(lhs, local_id) || path_to_local_id(rhs, local_id)) + && is_local_with_projections(transmutable) + && binops_with_local(cx, transmutable, receiver) && is_normalizable(cx, cx.param_env, from_ty) && is_normalizable(cx, cx.param_env, to_ty) // we only want to lint if the target type has a niche that is larger than the one of the source type diff --git a/tests/ui/eager_transmute.fixed b/tests/ui/eager_transmute.fixed index e06aa2cc9fd77..58c6ee914c6f9 100644 --- a/tests/ui/eager_transmute.fixed +++ b/tests/ui/eager_transmute.fixed @@ -12,16 +12,36 @@ enum Opcode { Div = 3, } +struct Data { + foo: &'static [u8], + bar: &'static [u8], +} + fn int_to_opcode(op: u8) -> Option { (op < 4).then(|| unsafe { std::mem::transmute(op) }) } -fn f(op: u8, unrelated: u8) { +fn f(op: u8, op2: Data, unrelated: u8) { true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + + let _: Option = (op > 0 && op < 10).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = (op > 0 && op < 10 && unrelated == 0).then(|| unsafe { std::mem::transmute(op) }); + + // lint even when the transmutable goes through field/array accesses + let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then(|| unsafe { std::mem::transmute(op2.foo[0]) }); + + // don't lint: wrong index used in the transmute + let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[1]) }); + + // don't lint: no check for the transmutable in the condition + let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op2.bar[0]) }); + + // don't lint: wrong variable + let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) }); } unsafe fn f2(op: u8) { diff --git a/tests/ui/eager_transmute.rs b/tests/ui/eager_transmute.rs index 89ccdf583f395..2af311d6c5120 100644 --- a/tests/ui/eager_transmute.rs +++ b/tests/ui/eager_transmute.rs @@ -12,16 +12,36 @@ enum Opcode { Div = 3, } +struct Data { + foo: &'static [u8], + bar: &'static [u8], +} + fn int_to_opcode(op: u8) -> Option { (op < 4).then_some(unsafe { std::mem::transmute(op) }) } -fn f(op: u8, unrelated: u8) { +fn f(op: u8, op2: Data, unrelated: u8) { true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + + let _: Option = (op > 0 && op < 10).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = (op > 0 && op < 10 && unrelated == 0).then_some(unsafe { std::mem::transmute(op) }); + + // lint even when the transmutable goes through field/array accesses + let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[0]) }); + + // don't lint: wrong index used in the transmute + let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[1]) }); + + // don't lint: no check for the transmutable in the condition + let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op2.bar[0]) }); + + // don't lint: wrong variable + let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) }); } unsafe fn f2(op: u8) { diff --git a/tests/ui/eager_transmute.stderr b/tests/ui/eager_transmute.stderr index 5eb163c5fcbb9..65dd6815c3ad3 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 - --> $DIR/eager_transmute.rs:16:33 + --> $DIR/eager_transmute.rs:21:33 | LL | (op < 4).then_some(unsafe { std::mem::transmute(op) }) | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | (op < 4).then(|| unsafe { std::mem::transmute(op) }) | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:22:33 + --> $DIR/eager_transmute.rs:27:33 | LL | (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:23:33 + --> $DIR/eager_transmute.rs:28:33 | LL | (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +34,7 @@ LL | (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:24:34 + --> $DIR/eager_transmute.rs:29:34 | LL | (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,40 @@ LL | (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:28:24 + --> $DIR/eager_transmute.rs:31:68 + | +LL | let _: Option = (op > 0 && op < 10).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (op > 0 && op < 10).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:32:86 + | +LL | let _: Option = (op > 0 && op < 10 && unrelated == 0).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (op > 0 && op < 10 && unrelated == 0).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:35:84 + | +LL | let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[0]) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (op2.foo[0] > 0 && op2.foo[0] < 10).then(|| unsafe { std::mem::transmute(op2.foo[0]) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:48:24 | LL | (op < 4).then_some(std::mem::transmute::<_, Opcode>(op)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +89,7 @@ LL | (op < 4).then(|| std::mem::transmute::<_, Opcode>(op)); | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:57:60 + --> $DIR/eager_transmute.rs:77:60 | LL | let _: Option = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,7 +100,7 @@ LL | let _: Option = (v1 > 0).then(|| unsafe { std::mem::transmut | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:63:86 + --> $DIR/eager_transmute.rs:83:86 | LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -78,7 +111,7 @@ LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| u | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:69:93 + --> $DIR/eager_transmute.rs:89:93 | LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,5 +121,5 @@ help: consider using `bool::then` to only transmute if the condition holds LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); | ~~~~ ++ -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors From 6c28f0765ea6e7c819292e1ed800895aac9f00ae Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 28 Dec 2023 05:27:56 +0100 Subject: [PATCH 12/73] recognize `contains` calls on ranges --- clippy_lints/src/transmute/eager_transmute.rs | 16 ++++ tests/ui/eager_transmute.fixed | 13 ++++ tests/ui/eager_transmute.rs | 13 ++++ tests/ui/eager_transmute.stderr | 76 +++++++++++++++++-- 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 7539b6af38597..c44f5150dd1a9 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -36,11 +36,27 @@ fn range_fully_contained(from: WrappingRange, to: WrappingRange) -> bool { /// * `x < 4 && x > 1` /// * `x.field < 4 && x.field > 1` (given `x.field`) /// * `x.field < 4 && unrelated()` +/// * `(1..=3).contains(&x)` fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_>) -> bool { match expr.kind { ExprKind::Binary(_, lhs, rhs) => { binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs) }, + ExprKind::MethodCall(path, receiver, [arg], _) + if path.ident.name == sym!(contains) + // ... `contains` called on some kind of range + && let Some(receiver_adt) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() + && let lang_items = cx.tcx.lang_items() + && [ + lang_items.range_from_struct(), + lang_items.range_inclusive_struct(), + lang_items.range_struct(), + lang_items.range_to_inclusive_struct(), + 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, local_expr, expr), } } diff --git a/tests/ui/eager_transmute.fixed b/tests/ui/eager_transmute.fixed index 58c6ee914c6f9..bece09bba1abd 100644 --- a/tests/ui/eager_transmute.fixed +++ b/tests/ui/eager_transmute.fixed @@ -42,6 +42,19 @@ fn f(op: u8, op2: Data, unrelated: u8) { // don't lint: wrong variable let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) }); + + // range contains checks + let _: Option = (1..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = ((1..=3).contains(&op) || op == 4).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = (1..3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = (1..).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = (..3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + let _: Option = (..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + + // unrelated binding in contains + let _: Option = (1..=3) + .contains(&unrelated) + .then_some(unsafe { std::mem::transmute(op) }); } unsafe fn f2(op: u8) { diff --git a/tests/ui/eager_transmute.rs b/tests/ui/eager_transmute.rs index 2af311d6c5120..a82bd578f76cc 100644 --- a/tests/ui/eager_transmute.rs +++ b/tests/ui/eager_transmute.rs @@ -42,6 +42,19 @@ fn f(op: u8, op2: Data, unrelated: u8) { // don't lint: wrong variable let _: Option = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) }); + + // range contains checks + let _: Option = (1..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = ((1..=3).contains(&op) || op == 4).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = (1..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = (1..).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = (..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + let _: Option = (..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + + // unrelated binding in contains + let _: Option = (1..=3) + .contains(&unrelated) + .then_some(unsafe { std::mem::transmute(op) }); } unsafe fn f2(op: u8) { diff --git a/tests/ui/eager_transmute.stderr b/tests/ui/eager_transmute.stderr index 65dd6815c3ad3..5f42eec544fae 100644 --- a/tests/ui/eager_transmute.stderr +++ b/tests/ui/eager_transmute.stderr @@ -78,7 +78,73 @@ 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 - --> $DIR/eager_transmute.rs:48:24 + --> $DIR/eager_transmute.rs:47:70 + | +LL | let _: Option = (1..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (1..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:48:83 + | +LL | let _: Option = ((1..=3).contains(&op) || op == 4).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = ((1..=3).contains(&op) || op == 4).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:49:69 + | +LL | let _: Option = (1..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (1..3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:50:68 + | +LL | let _: Option = (1..).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (1..).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:51:68 + | +LL | let _: Option = (..3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (..3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:52:69 + | +LL | let _: Option = (..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:61:24 | LL | (op < 4).then_some(std::mem::transmute::<_, Opcode>(op)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +155,7 @@ LL | (op < 4).then(|| std::mem::transmute::<_, Opcode>(op)); | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:77:60 + --> $DIR/eager_transmute.rs:90:60 | LL | let _: Option = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -100,7 +166,7 @@ LL | let _: Option = (v1 > 0).then(|| unsafe { std::mem::transmut | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:83:86 + --> $DIR/eager_transmute.rs:96:86 | LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,7 +177,7 @@ LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| u | ~~~~ ++ error: this transmute is always evaluated eagerly, even if the condition is false - --> $DIR/eager_transmute.rs:89:93 + --> $DIR/eager_transmute.rs:102:93 | LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -121,5 +187,5 @@ help: consider using `bool::then` to only transmute if the condition holds LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); | ~~~~ ++ -error: aborting due to 11 previous errors +error: aborting due to 17 previous errors From 99b4330f5cdcf258a8146950d33426c6bb442abc Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Dec 2023 01:52:10 +0000 Subject: [PATCH 13/73] Remove movability from TyKind::Coroutine --- clippy_lints/src/doc/missing_headers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs index 4cbfa97a8a35f..26f120cb33f03 100644 --- a/clippy_lints/src/doc/missing_headers.rs +++ b/clippy_lints/src/doc/missing_headers.rs @@ -72,7 +72,7 @@ pub fn check( && let body = cx.tcx.hir().body(body_id) && let ret_ty = typeck.expr_ty(body.value) && implements_trait(cx, ret_ty, future, &[]) - && let ty::Coroutine(_, subs, _) = ret_ty.kind() + && let ty::Coroutine(_, subs) = ret_ty.kind() && is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result) { span_lint( From b8aab4bd1dbb842ca79e9c6c1db663935a0b044e Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Thu, 28 Dec 2023 09:40:01 -0800 Subject: [PATCH 14/73] Polish `missing_enforced_import_renames` documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes a typo in the name of the lint (`enforce-import-renames` instead of `enforced-import-renames`). * Copyedit “Why” paragraph. * Make the example configuration use a multi-line list, since it is not particularly expected that a real project will have *exactly one* rename to enforce (and the old formatting had unbalanced whitespace). --- clippy_lints/src/missing_enforced_import_rename.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index c1f6c71a63e68..c6c188854fd91 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -13,21 +13,23 @@ use rustc_span::Symbol; declare_clippy_lint! { /// ### What it does /// Checks for imports that do not rename the item as specified - /// in the `enforce-import-renames` config option. + /// in the `enforced-import-renames` config option. /// /// Note: Even though this lint is warn-by-default, it will only trigger if - /// import renames are defined in the clippy.toml file. + /// import renames are defined in the `clippy.toml` file. /// /// ### Why is this bad? - /// Consistency is important, if a project has defined import - /// renames they should be followed. More practically, some item names are too - /// vague outside of their defining scope this can enforce a more meaningful naming. + /// Consistency is important; if a project has defined import renames, then they should be + /// followed. More practically, some item names are too vague outside of their defining scope, + /// in which case this can enforce a more meaningful naming. /// /// ### Example /// An example clippy.toml configuration: /// ```toml /// # clippy.toml - /// enforced-import-renames = [ { path = "serde_json::Value", rename = "JsonValue" }] + /// enforced-import-renames = [ + /// { path = "serde_json::Value", rename = "JsonValue" }, + /// ] /// ``` /// /// ```rust,ignore From 15b1edb2092efef167b375ac4a88429a90714210 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 28 Dec 2023 19:33:07 +0100 Subject: [PATCH 15/73] Merge commit 'ac4c2094a6030530661bee3876e0228ddfeb6b8b' into clippy-subtree-sync --- CHANGELOG.md | 68 ++++- Cargo.toml | 2 +- book/src/lint_configuration.md | 2 +- clippy_config/Cargo.toml | 2 +- clippy_config/src/conf.rs | 5 +- clippy_config/src/msrvs.rs | 7 +- clippy_lints/Cargo.toml | 2 +- clippy_lints/src/assertions_on_constants.rs | 15 +- clippy_lints/src/async_yields_async.rs | 4 +- clippy_lints/src/declared_lints.rs | 5 + .../src/default_constructed_unit_structs.rs | 2 +- clippy_lints/src/format_push_string.rs | 2 +- clippy_lints/src/implied_bounds_in_impls.rs | 2 +- clippy_lints/src/indexing_slicing.rs | 18 +- clippy_lints/src/ineffective_open_options.rs | 2 +- clippy_lints/src/iter_without_into_iter.rs | 4 +- clippy_lints/src/len_zero.rs | 4 +- clippy_lints/src/lib.rs | 3 + clippy_lints/src/loops/infinite_loop.rs | 2 +- clippy_lints/src/loops/manual_flatten.rs | 2 +- .../src/loops/while_let_on_iterator.rs | 2 +- clippy_lints/src/manual_async_fn.rs | 24 +- clippy_lints/src/manual_hash_one.rs | 2 +- clippy_lints/src/manual_let_else.rs | 2 +- clippy_lints/src/matches/collapsible_match.rs | 4 +- clippy_lints/src/matches/manual_utils.rs | 4 +- clippy_lints/src/matches/mod.rs | 22 +- .../src/matches/redundant_pattern_match.rs | 130 ++++++--- clippy_lints/src/methods/filter_map.rs | 89 +++++-- clippy_lints/src/methods/iter_filter.rs | 87 ++++++ clippy_lints/src/methods/mod.rs | 101 ++++++- .../src/methods/unnecessary_to_owned.rs | 56 ++++ clippy_lints/src/needless_bool.rs | 38 ++- clippy_lints/src/option_if_let_else.rs | 1 + clippy_lints/src/question_mark.rs | 30 ++- clippy_lints/src/redundant_async_block.rs | 16 +- clippy_lints/src/redundant_closure_call.rs | 5 +- clippy_lints/src/transmute/eager_transmute.rs | 74 ++++++ clippy_lints/src/transmute/mod.rs | 61 ++++- clippy_lints/src/unconditional_recursion.rs | 134 ++++++++++ .../src/undocumented_unsafe_blocks.rs | 4 +- clippy_lints/src/uninhabited_references.rs | 2 +- clippy_lints/src/utils/author.rs | 1 + clippy_utils/Cargo.toml | 2 +- clippy_utils/src/ast_utils.rs | 1 + clippy_utils/src/attrs.rs | 11 +- clippy_utils/src/higher.rs | 20 +- clippy_utils/src/visitors.rs | 5 +- declare_clippy_lint/Cargo.toml | 2 +- rust-toolchain | 2 +- .../ui-internal/disallow_struct_span_lint.rs | 4 +- .../disallow_struct_span_lint.stderr | 8 +- tests/ui-toml/suppress_lint_in_const/test.rs | 3 +- .../suppress_lint_in_const/test.stderr | 18 +- tests/ui/assertions_on_constants.rs | 7 + tests/ui/assertions_on_constants.stderr | 10 +- tests/ui/author/loop.rs | 6 +- tests/ui/bool_comparison.fixed | 9 + tests/ui/bool_comparison.rs | 9 + tests/ui/bool_comparison.stderr | 20 +- .../branches_sharing_code/shared_at_bottom.rs | 8 +- .../shared_at_bottom.stderr | 18 +- tests/ui/collapsible_if.fixed | 3 +- tests/ui/collapsible_if.rs | 3 +- tests/ui/collapsible_if.stderr | 18 +- tests/ui/crashes/ice-11939.rs | 14 + tests/ui/crashes/ice-5497.rs | 2 + tests/ui/crashes/ice-5497.stderr | 2 +- tests/ui/doc/doc-fixable.fixed | 2 +- tests/ui/doc/doc-fixable.rs | 2 +- tests/ui/eager_transmute.fixed | 72 +++++ tests/ui/eager_transmute.rs | 72 +++++ tests/ui/eager_transmute.stderr | 92 +++++++ tests/ui/get_unwrap.fixed | 3 +- tests/ui/get_unwrap.rs | 3 +- tests/ui/get_unwrap.stderr | 62 ++--- tests/ui/if_then_some_else_none.rs | 1 + tests/ui/if_then_some_else_none.stderr | 10 +- tests/ui/indexing_slicing_index.rs | 3 + tests/ui/indexing_slicing_index.stderr | 35 ++- tests/ui/infinite_loops.stderr | 30 +-- tests/ui/iter_filter_is_ok.fixed | 26 ++ tests/ui/iter_filter_is_ok.rs | 26 ++ tests/ui/iter_filter_is_ok.stderr | 23 ++ tests/ui/iter_filter_is_some.fixed | 27 ++ tests/ui/iter_filter_is_some.rs | 27 ++ tests/ui/iter_filter_is_some.stderr | 23 ++ tests/ui/manual_let_else_question_mark.fixed | 21 ++ tests/ui/manual_let_else_question_mark.rs | 23 ++ tests/ui/manual_let_else_question_mark.stderr | 10 +- tests/ui/needless_if.fixed | 1 + tests/ui/needless_if.rs | 1 + tests/ui/needless_if.stderr | 14 +- tests/ui/nonminimal_bool.rs | 7 +- tests/ui/nonminimal_bool.stderr | 26 +- tests/ui/option_filter_map.fixed | 6 + tests/ui/option_filter_map.rs | 8 + tests/ui/option_filter_map.stderr | 16 +- tests/ui/redundant_async_block.fixed | 8 +- tests/ui/redundant_async_block.rs | 8 +- ...dundant_pattern_matching_if_let_true.fixed | 38 +++ .../redundant_pattern_matching_if_let_true.rs | 38 +++ ...undant_pattern_matching_if_let_true.stderr | 47 ++++ .../redundant_pattern_matching_ipaddr.fixed | 6 + tests/ui/redundant_pattern_matching_ipaddr.rs | 6 + .../redundant_pattern_matching_ipaddr.stderr | 44 +-- .../ui/redundant_pattern_matching_poll.fixed | 6 + tests/ui/redundant_pattern_matching_poll.rs | 6 + .../ui/redundant_pattern_matching_poll.stderr | 44 +-- tests/ui/result_filter_map.fixed | 27 ++ tests/ui/result_filter_map.rs | 35 +++ tests/ui/result_filter_map.stderr | 41 +++ tests/ui/unconditional_recursion.rs | 163 ++++++++++++ tests/ui/unconditional_recursion.stderr | 251 ++++++++++++++++++ tests/ui/unnecessary_to_owned_on_split.fixed | 21 ++ tests/ui/unnecessary_to_owned_on_split.rs | 21 ++ tests/ui/unnecessary_to_owned_on_split.stderr | 53 ++++ util/gh-pages/index.html | 70 ++++- 118 files changed, 2531 insertions(+), 321 deletions(-) create mode 100644 clippy_lints/src/methods/iter_filter.rs create mode 100644 clippy_lints/src/transmute/eager_transmute.rs create mode 100644 clippy_lints/src/unconditional_recursion.rs create mode 100644 tests/ui/crashes/ice-11939.rs create mode 100644 tests/ui/eager_transmute.fixed create mode 100644 tests/ui/eager_transmute.rs create mode 100644 tests/ui/eager_transmute.stderr create mode 100644 tests/ui/iter_filter_is_ok.fixed create mode 100644 tests/ui/iter_filter_is_ok.rs create mode 100644 tests/ui/iter_filter_is_ok.stderr create mode 100644 tests/ui/iter_filter_is_some.fixed create mode 100644 tests/ui/iter_filter_is_some.rs create mode 100644 tests/ui/iter_filter_is_some.stderr create mode 100644 tests/ui/redundant_pattern_matching_if_let_true.fixed create mode 100644 tests/ui/redundant_pattern_matching_if_let_true.rs create mode 100644 tests/ui/redundant_pattern_matching_if_let_true.stderr create mode 100644 tests/ui/result_filter_map.fixed create mode 100644 tests/ui/result_filter_map.rs create mode 100644 tests/ui/result_filter_map.stderr create mode 100644 tests/ui/unconditional_recursion.rs create mode 100644 tests/ui/unconditional_recursion.stderr create mode 100644 tests/ui/unnecessary_to_owned_on_split.fixed create mode 100644 tests/ui/unnecessary_to_owned_on_split.rs create mode 100644 tests/ui/unnecessary_to_owned_on_split.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 70aff7f60e059..f82421a687b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,68 @@ document. ## Unreleased / Beta / In Rust Nightly -[7671c283...master](https://github.com/rust-lang/rust-clippy/compare/7671c283...master) +[09ac14c9...master](https://github.com/rust-lang/rust-clippy/compare/09ac14c9...master) + +## Rust 1.75 + +Current stable, released 2023-12-28 + +[View all 69 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-09-25T11%3A47%3A47Z..2023-11-02T16%3A41%3A59Z+base%3Amaster) + +### New Lints + +* [`unused_enumerate_index`] + [#10404](https://github.com/rust-lang/rust-clippy/pull/10404) +* [`unnecessary_fallible_conversions`] + [#11669](https://github.com/rust-lang/rust-clippy/pull/11669) +* [`waker_clone_wake`] + [#11698](https://github.com/rust-lang/rust-clippy/pull/11698) +* [`struct_field_names`] + [#11496](https://github.com/rust-lang/rust-clippy/pull/11496) +* [`into_iter_without_iter`] + [#11587](https://github.com/rust-lang/rust-clippy/pull/11587) +* [`iter_without_into_iter`] + [#11527](https://github.com/rust-lang/rust-clippy/pull/11527) +* [`manual_hash_one`] + [#11556](https://github.com/rust-lang/rust-clippy/pull/11556) + + +### Moves and Deprecations + +* Moved [`read_zero_byte_vec`] to `nursery` (Now allow-by-default) + [#11727](https://github.com/rust-lang/rust-clippy/pull/11727) +* Moved [`missing_enforced_import_renames`] to `style` (Now warn-by-default) + [#11539](https://github.com/rust-lang/rust-clippy/pull/11539) +* Moved [`needless_raw_string_hashes`] to `pedantic` (Now allow-by-default) + [#11415](https://github.com/rust-lang/rust-clippy/pull/11415) +* Moved [`needless_pass_by_ref_mut`] to `nursery` (Now allow-by-default) + [#11596](https://github.com/rust-lang/rust-clippy/pull/11596) + +### Enhancements + +* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: Now check the + [`ignore-interior-mutability`] config value + [#11678](https://github.com/rust-lang/rust-clippy/pull/11678) + +### Suggestion Fixes/Improvements + +* [`items_after_test_module`]: The suggestion is now machine-applicable + [#11611](https://github.com/rust-lang/rust-clippy/pull/11611) + +### ICE Fixes + +* [`redundant_locals`]: No longer crashes if variables are rebound above macros + [#11623](https://github.com/rust-lang/rust-clippy/pull/11623) +* [`implicit_hasher`]: No longer lints inside macros, which could cause ICEs + [#11593](https://github.com/rust-lang/rust-clippy/pull/11593) + +### Documentation Improvements + +* `cargo clippy --help` now uses colors for readability :tada: ## Rust 1.74 -Current stable, released 2023-11-16 +Released 2023-11-16 [View all 94 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-08-11T15%3A29%3A18Z..2023-09-25T08%3A48%3A22Z+base%3Amaster) @@ -51,7 +108,7 @@ Current stable, released 2023-11-16 ### Enhancements * [`undocumented_unsafe_blocks`]: The config values [`accept-comment-above-statement`] and - [`accept-comment-above-attributes`] to `true` by default + [`accept-comment-above-attributes`] are now `true` by default [#11170](https://github.com/rust-lang/rust-clippy/pull/11170) * [`explicit_iter_loop`]: Added [`enforce-iter-loop-reborrow`] to disable reborrow linting by default [#11418](https://github.com/rust-lang/rust-clippy/pull/11418) @@ -5044,6 +5101,7 @@ Released 2018-09-13 [`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod [`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument [`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec +[`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute [`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else [`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop [`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum @@ -5177,6 +5235,8 @@ Released 2018-09-13 [`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module [`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect [`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count +[`iter_filter_is_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_ok +[`iter_filter_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_some [`iter_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map [`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop [`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice @@ -5470,6 +5530,7 @@ Released 2018-09-13 [`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization [`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs [`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used +[`result_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_filter_map [`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn @@ -5582,6 +5643,7 @@ Released 2018-09-13 [`type_id_on_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_id_on_box [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction +[`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks [`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops [`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc diff --git a/Cargo.toml b/Cargo.toml index f6084a4627266..eda20531e40f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.76" +version = "0.1.77" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 2bb89321cefe6..7c9a8eb1bfba1 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -212,7 +212,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 20f313201276b..74b8e5eaa1c41 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.76" +version = "0.1.77" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 3cf4377002a86..a4f368397cede 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -27,7 +27,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", - "WebGL", + "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", @@ -640,7 +640,8 @@ impl Conf { } }, Err(error) => { - sess.dcx().err(format!("error finding Clippy's configuration file: {error}")); + sess.dcx() + .err(format!("error finding Clippy's configuration file: {error}")); }, } diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 13e61e5a0326c..471ad73e2074d 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -41,6 +41,7 @@ msrv_aliases! { 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } 1,34,0 { TRY_FROM } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } + 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL } 1,27,0 { ITERATOR_TRY_FOLD } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } @@ -106,7 +107,8 @@ impl Msrv { if let Some(msrv_attr) = msrv_attrs.next() { if let Some(duplicate) = msrv_attrs.last() { - sess.dcx().struct_span_err(duplicate.span, "`clippy::msrv` is defined multiple times") + sess.dcx() + .struct_span_err(duplicate.span, "`clippy::msrv` is defined multiple times") .span_note(msrv_attr.span, "first definition found here") .emit(); } @@ -116,7 +118,8 @@ impl Msrv { return Some(version); } - sess.dcx().span_err(msrv_attr.span, format!("`{msrv}` is not a valid Rust version")); + sess.dcx() + .span_err(msrv_attr.span, format!("`{msrv}` is not a valid Rust version")); } else { sess.dcx().span_err(msrv_attr.span, "bad clippy attribute"); } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index ad8b7ded46b96..8cba35f3d871c 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.76" +version = "0.1.77" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index a15ec199a28a9..1e327f7a6dfb8 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,7 +1,7 @@ -use clippy_utils::consts::{constant, Constant}; +use clippy_utils::consts::{constant_with_source, Constant, ConstantSource}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; -use rustc_hir::Expr; +use rustc_hir::{Expr, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -42,9 +42,18 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return; }; - let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { + let Some((Constant::Bool(val), source)) = constant_with_source(cx, cx.typeck_results(), condition) else { return; }; + if let ConstantSource::Constant = source + && let Some(node) = cx.tcx.hir().find_parent(e.hir_id) + && let Node::Item(Item { + kind: ItemKind::Const(..), + .. + }) = node + { + return; + } if val { span_lint_and_help( cx, diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index c965341d3fdf6..bb08ac7508bc2 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -2,9 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; -use rustc_hir::{ - Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath, -}; +use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 1220eb8901305..eae9dfac064e8 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -369,6 +369,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::ITERATOR_STEP_BY_ZERO_INFO, crate::methods::ITER_CLONED_COLLECT_INFO, crate::methods::ITER_COUNT_INFO, + crate::methods::ITER_FILTER_IS_OK_INFO, + crate::methods::ITER_FILTER_IS_SOME_INFO, crate::methods::ITER_KV_MAP_INFO, crate::methods::ITER_NEXT_SLICE_INFO, crate::methods::ITER_NTH_INFO, @@ -419,6 +421,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::REDUNDANT_AS_STR_INFO, crate::methods::REPEAT_ONCE_INFO, + crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, crate::methods::SEARCH_IS_SOME_INFO, crate::methods::SEEK_FROM_CURRENT_INFO, @@ -650,6 +653,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO, crate::transmute::CROSSPOINTER_TRANSMUTE_INFO, + crate::transmute::EAGER_TRANSMUTE_INFO, crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO, crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO, crate::transmute::TRANSMUTE_FLOAT_TO_INT_INFO, @@ -676,6 +680,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::types::REDUNDANT_ALLOCATION_INFO, crate::types::TYPE_COMPLEXITY_INFO, crate::types::VEC_BOX_INFO, + crate::unconditional_recursion::UNCONDITIONAL_RECURSION_INFO, crate::undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS_INFO, crate::undocumented_unsafe_blocks::UNNECESSARY_SAFETY_COMMENT_INFO, crate::unicode::INVISIBLE_CHARACTERS_INFO, diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index 9ce5acfbcc212..71e1a25c2bce4 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -42,7 +42,7 @@ declare_clippy_lint! { #[clippy::version = "1.71.0"] pub DEFAULT_CONSTRUCTED_UNIT_STRUCTS, complexity, - "unit structs can be contructed without calling `default`" + "unit structs can be constructed without calling `default`" } declare_lint_pass!(DefaultConstructedUnitStructs => [DEFAULT_CONSTRUCTED_UNIT_STRUCTS]); diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 3901dd984f9d6..2b08437d82711 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -57,7 +57,7 @@ fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { Some(higher::IfLetOrMatch::Match(_, arms, MatchSource::Normal)) => { arms.iter().any(|arm| is_format(cx, arm.body)) }, - Some(higher::IfLetOrMatch::IfLet(_, _, then, r#else)) => { + Some(higher::IfLetOrMatch::IfLet(_, _, then, r#else, _)) => { is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e)) }, _ => false, diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index 0f5a9ea5d84be..fab8ffedb9493 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -158,7 +158,7 @@ fn try_resolve_type<'tcx>( /// This function tries to, for all generic type parameters in a supertrait predicate `trait ...: /// GenericTrait`, check if the substituted type in the implied-by bound matches with what's -/// subtituted in the implied bound. +/// substituted in the implied bound. /// /// Consider this example. /// ```rust,ignore diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 0ae03d101ab58..391db0b0df726 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -170,7 +170,23 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { return; } // Index is a constant uint. - if constant(cx, cx.typeck_results(), index).is_some() { + if let Some(constant) = constant(cx, cx.typeck_results(), index) { + // only `usize` index is legal in rust array index + // leave other type to rustc + if let Constant::Int(off) = constant + && let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind() + && *utype == ty::UintTy::Usize + && let ty::Array(_, s) = ty.kind() + && let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) + { + // get constant offset and check whether it is in bounds + let off = usize::try_from(off).unwrap(); + let size = usize::try_from(size).unwrap(); + + if off >= size { + span_lint(cx, OUT_OF_BOUNDS_INDEXING, expr.span, "index is out of bounds"); + } + } // Let rustc's `const_err` lint handle constant `usize` indexing on arrays. return; } diff --git a/clippy_lints/src/ineffective_open_options.rs b/clippy_lints/src/ineffective_open_options.rs index 955f90d426248..af5b1f9573991 100644 --- a/clippy_lints/src/ineffective_open_options.rs +++ b/clippy_lints/src/ineffective_open_options.rs @@ -16,7 +16,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// `.append(true)` already enables `write(true)`, making this one - /// superflous. + /// superfluous. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 3a5756482a125..c9dc48668f29d 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -49,7 +49,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.74.0"] + #[clippy::version = "1.75.0"] pub ITER_WITHOUT_INTO_ITER, pedantic, "implementing `iter(_mut)` without an associated `IntoIterator for (&|&mut) Type` impl" @@ -101,7 +101,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.74.0"] + #[clippy::version = "1.75.0"] pub INTO_ITER_WITHOUT_ITER, pedantic, "implementing `IntoIterator for (&|&mut) Type` without an inherent `iter(_mut)` method" diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 8c032b1702360..c09d3d1862b0c 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -8,8 +8,8 @@ use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, - ImplicitSelfKind, Item, ItemKind, Mutability, Node, PatKind, PathSegment, PrimTy, QPath, TraitItemRef, - TyKind, TypeBindingKind, OpaqueTyOrigin, + ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatKind, PathSegment, PrimTy, QPath, + TraitItemRef, TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 7758d6a58e648..755a4ff525d2e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -22,6 +22,7 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) extern crate pulldown_cmark; +extern crate rustc_abi; extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; @@ -327,6 +328,7 @@ mod trait_bounds; mod transmute; mod tuple_array_conversions; mod types; +mod unconditional_recursion; mod undocumented_unsafe_blocks; mod unicode; mod uninhabited_references; @@ -1078,6 +1080,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity)); store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)); + store.register_late_pass(|_| Box::new(unconditional_recursion::UnconditionalRecursion)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index 9b88dd76e5cc5..5e099f1e76f86 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -49,7 +49,7 @@ pub(super) fn check<'tcx>( if let FnRetTy::DefaultReturn(ret_span) = parent_fn_ret { diag.span_suggestion( ret_span, - "if this is intentional, consider specifing `!` as function return", + "if this is intentional, consider specifying `!` as function return", " -> !", Applicability::MaybeIncorrect, ); diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index a726b11695637..36de9021f4996 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -20,7 +20,7 @@ pub(super) fn check<'tcx>( span: Span, ) { let inner_expr = peel_blocks_with_stmt(body); - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None }) + if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None, .. }) = higher::IfLet::hir(cx, inner_expr) // Ensure match_expr in `if let` statement is the same as the pat from the for-loop && let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 21b9efba54c7f..d070ee7498561 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -14,7 +14,7 @@ use rustc_span::symbol::sym; use rustc_span::Symbol; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr) + if let Some(higher::WhileLet { if_then, let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) // check for `Some(..)` pattern && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index 9ba1d3afcbe9d..4cd5f3b81e526 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -3,9 +3,9 @@ use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Block, Body, Closure, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, FnRetTy, - GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, - TypeBindingKind, ClosureKind, + Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, + FnRetTy, GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, + TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -172,23 +172,13 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt)) } -fn desugared_async_block<'tcx>( - cx: &LateContext<'tcx>, - block: &'tcx Block<'tcx>, -) -> Option<&'tcx Body<'tcx>> { +fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { if let Some(Expr { - kind: - ExprKind::Closure(&Closure { - kind: - ClosureKind::Coroutine(CoroutineKind::Desugared( - CoroutineDesugaring::Async, - CoroutineSource::Block, - )), - body, - .. - }), + kind: ExprKind::Closure(&Closure { kind, body, .. }), .. }) = block.expr + && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, CoroutineSource::Block)) = + kind { return Some(cx.tcx.hir().body(body)); } diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs index 252b3a83a18f0..73687fbbe54e1 100644 --- a/clippy_lints/src/manual_hash_one.rs +++ b/clippy_lints/src/manual_hash_one.rs @@ -40,7 +40,7 @@ declare_clippy_lint! { /// /// let hash = s.hash_one(&value); /// ``` - #[clippy::version = "1.74.0"] + #[clippy::version = "1.75.0"] pub MANUAL_HASH_ONE, complexity, "manual implementations of `BuildHasher::hash_one`" diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 92dc4d57ab146..fdf8fa4e2771f 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -61,7 +61,7 @@ impl<'tcx> QuestionMark { && let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init) { match if_let_or_match { - IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => { + IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else, ..) => { if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then) && let Some(if_else) = if_else && is_never_expr(cx, if_else).is_some() diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index 48fc5746b3cd0..91e6ca7fa8bc1 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -41,7 +41,7 @@ fn check_arm<'tcx>( let inner_expr = peel_blocks_with_stmt(outer_then_body); if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr) && let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner { - IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)), + IfLetOrMatch::IfLet(scrutinee, pat, _, els, _) => Some((scrutinee, pat, els)), IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none()) // if there are more than two arms, collapsing would be non-trivial // one of the arms must be "wild-like" @@ -75,7 +75,7 @@ fn check_arm<'tcx>( ) // ...or anywhere in the inner expression && match inner { - IfLetOrMatch::IfLet(_, _, body, els) => { + IfLetOrMatch::IfLet(_, _, body, els, _) => { !is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id)) }, IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)), diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 0627e458dfe0f..152aba99ce9b1 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -63,9 +63,7 @@ where return None; } - let Some(some_expr) = get_some_expr_fn(cx, some_pat, some_expr, expr_ctxt) else { - return None; - }; + let some_expr = get_some_expr_fn(cx, some_pat, some_expr, expr_ctxt)?; // These two lints will go back and forth with each other. if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 4c7568f39b45d..50494f4819f44 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -469,15 +469,15 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Lint for redundant pattern matching over `Result`, `Option`, - /// `std::task::Poll` or `std::net::IpAddr` + /// `std::task::Poll`, `std::net::IpAddr` or `bool`s /// /// ### Why is this bad? /// It's more concise and clear to just use the proper - /// utility function + /// utility function or using the condition directly /// /// ### Known problems - /// This will change the drop order for the matched type. Both `if let` and - /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the + /// For suggestions involving bindings in patterns, this will change the drop order for the matched type. + /// Both `if let` and `while let` will drop the value at the end of the block, both `if` and `while` will drop the /// value before entering the block. For most types this change will not matter, but for a few /// types this will not be an acceptable change (e.g. locks). See the /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about @@ -499,6 +499,10 @@ declare_clippy_lint! { /// Ok(_) => true, /// Err(_) => false, /// }; + /// + /// let cond = true; + /// if let true = cond {} + /// matches!(cond, true); /// ``` /// /// The more idiomatic use would be: @@ -515,6 +519,10 @@ declare_clippy_lint! { /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} /// Ok::(42).is_ok(); + /// + /// let cond = true; + /// if cond {} + /// cond; /// ``` #[clippy::version = "1.31.0"] pub REDUNDANT_PATTERN_MATCHING, @@ -1019,8 +1027,11 @@ impl<'tcx> LateLintPass<'tcx> for Matches { let from_expansion = expr.span.from_expansion(); if let ExprKind::Match(ex, arms, source) = expr.kind { - if is_direct_expn_of(expr.span, "matches").is_some() { + if is_direct_expn_of(expr.span, "matches").is_some() + && let [arm, _] = arms + { redundant_pattern_match::check_match(cx, expr, ex, arms); + redundant_pattern_match::check_matches_true(cx, expr, arm, ex); } if source == MatchSource::Normal && !is_span_match(cx, expr.span) { @@ -1104,6 +1115,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if_let.let_pat, if_let.let_expr, if_let.if_else.is_some(), + if_let.let_span, ); needless_match::check_if_let(cx, expr, &if_let); } diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 2582f7edcf66d..a4acdfb1db4e1 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -1,7 +1,7 @@ use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, walk_span_to_context}; -use clippy_utils::sugg::Sugg; +use clippy_utils::sugg::{make_unop, Sugg}; use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr}; use clippy_utils::{higher, is_expn_of, is_trait_method}; @@ -12,13 +12,20 @@ use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, use rustc_hir::{Arm, Expr, ExprKind, Guard, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; -use rustc_span::{sym, Symbol}; +use rustc_span::{sym, Span, Symbol}; use std::fmt::Write; use std::ops::ControlFlow; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { - find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); + if let Some(higher::WhileLet { + let_pat, + let_expr, + let_span, + .. + }) = higher::WhileLet::hir(expr) + { + find_method_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); + find_if_let_true(cx, let_pat, let_expr, let_span); } } @@ -28,8 +35,73 @@ pub(super) fn check_if_let<'tcx>( pat: &'tcx Pat<'_>, scrutinee: &'tcx Expr<'_>, has_else: bool, + let_span: Span, +) { + find_if_let_true(cx, pat, scrutinee, let_span); + find_method_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else); +} + +/// Looks for: +/// * `matches!(expr, true)` +pub fn check_matches_true<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + arm: &'tcx Arm<'_>, + scrutinee: &'tcx Expr<'_>, +) { + find_match_true( + cx, + arm.pat, + scrutinee, + expr.span.source_callsite(), + "using `matches!` to pattern match a bool", + ); +} + +/// Looks for any of: +/// * `if let true = ...` +/// * `if let false = ...` +/// * `while let true = ...` +fn find_if_let_true<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, scrutinee: &'tcx Expr<'_>, let_span: Span) { + find_match_true(cx, pat, scrutinee, let_span, "using `if let` to pattern match a bool"); +} + +/// Common logic between `find_if_let_true` and `check_matches_true` +fn find_match_true<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + scrutinee: &'tcx Expr<'_>, + span: Span, + message: &str, ) { - find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else); + if let PatKind::Lit(lit) = pat.kind + && let ExprKind::Lit(lit) = lit.kind + && let LitKind::Bool(pat_is_true) = lit.node + { + let mut applicability = Applicability::MachineApplicable; + + let mut sugg = Sugg::hir_with_context( + cx, + scrutinee, + scrutinee.span.source_callsite().ctxt(), + "..", + &mut applicability, + ); + + if !pat_is_true { + sugg = make_unop("!", sugg); + } + + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN_MATCHING, + span, + message, + "consider using the condition directly", + sugg.to_string(), + applicability, + ); + } } // Extract the generic arguments out of a type @@ -56,9 +128,7 @@ fn find_method_and_type<'tcx>( if is_wildcard || is_rest { let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); - let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { - return None; - }; + let id = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id))?; let lang_items = cx.tcx.lang_items(); if Some(id) == lang_items.result_ok_variant() { Some(("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))) @@ -100,7 +170,7 @@ fn find_method_and_type<'tcx>( } } -fn find_sugg_for_if_let<'tcx>( +fn find_method_sugg_for_if_let<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, let_pat: &Pat<'_>, @@ -341,31 +411,25 @@ fn get_good_method<'tcx>( path_left: &QPath<'_>, ) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { if let Some(name) = get_ident(path_left) { - return match name.as_str() { - "Ok" => { - find_good_method_for_matches_macro(cx, arms, path_left, Item::Lang(ResultOk), "is_ok()", "is_err()") - }, - "Err" => { - find_good_method_for_matches_macro(cx, arms, path_left, Item::Lang(ResultErr), "is_err()", "is_ok()") - }, - "Some" => find_good_method_for_matches_macro( - cx, - arms, - path_left, - Item::Lang(OptionSome), - "is_some()", - "is_none()", - ), - "None" => find_good_method_for_matches_macro( - cx, - arms, - path_left, - Item::Lang(OptionNone), - "is_none()", - "is_some()", - ), - _ => None, + let (expected_item_left, should_be_left, should_be_right) = match name.as_str() { + "Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"), + "Err" => (Item::Lang(ResultErr), "is_err()", "is_ok()"), + "Some" => (Item::Lang(OptionSome), "is_some()", "is_none()"), + "None" => (Item::Lang(OptionNone), "is_none()", "is_some()"), + "Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"), + "Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"), + "V4" => (Item::Diag(sym::IpAddr, sym!(V4)), "is_ipv4()", "is_ipv6()"), + "V6" => (Item::Diag(sym::IpAddr, sym!(V6)), "is_ipv6()", "is_ipv4()"), + _ => return None, }; + return find_good_method_for_matches_macro( + cx, + arms, + path_left, + expected_item_left, + should_be_left, + should_be_right, + ); } None } diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 844ab40cab1a0..7cfd3d346b638 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -14,7 +14,7 @@ use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::Span; use std::borrow::Cow; -use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP}; +use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP}; fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { match &expr.kind { @@ -22,6 +22,7 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> hir::ExprKind::Path(QPath::Resolved(_, segments)) => { segments.segments.last().unwrap().ident.name == method_name }, + hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(body.value); @@ -46,6 +47,9 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool { is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) } +fn is_ok_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool { + is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_ok)) +} #[derive(Debug, Copy, Clone)] enum OffendingFilterExpr<'tcx> { @@ -186,7 +190,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> { match higher::IfLetOrMatch::parse(cx, map_body.value) { // For `if let` we want to check that the variant matching arm references the local created by // its pattern - Some(higher::IfLetOrMatch::IfLet(sc, pat, then, Some(else_))) + Some(higher::IfLetOrMatch::IfLet(sc, pat, then, Some(else_), ..)) if let Some((ident, span)) = expr_uses_local(pat, then) => { (sc, else_, ident, span) @@ -273,6 +277,18 @@ fn is_filter_some_map_unwrap( (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) } +/// is `filter(|x| x.is_ok()).map(|x| x.unwrap())` +fn is_filter_ok_map_unwrap( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + filter_arg: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, +) -> bool { + // result has no filter, so we only check for iterators + let iterator = is_trait_method(cx, expr, sym::Iterator); + iterator && is_ok_filter_map(cx, filter_arg, map_arg) +} + /// lint use of `filter().map()` or `find().map()` for `Iterators` #[allow(clippy::too_many_arguments)] pub(super) fn check( @@ -300,30 +316,21 @@ pub(super) fn check( return; } - if is_trait_method(cx, map_recv, sym::Iterator) - - // filter(|x| ...is_some())... - && let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind - && let filter_body = cx.tcx.hir().body(filter_body_id) - && let [filter_param] = filter_body.params - // optional ref pattern: `filter(|&x| ..)` - && let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { - (ref_pat, true) - } else { - (filter_param.pat, false) - } - - && let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind - && let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id) + if is_filter_ok_map_unwrap(cx, expr, filter_arg, map_arg) { + span_lint_and_sugg( + cx, + RESULT_FILTER_MAP, + filter_span.with_hi(expr.span.hi()), + "`filter` for `Ok` followed by `unwrap`", + "consider using `flatten` instead", + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(), + Applicability::MachineApplicable, + ); - && let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind - && let map_body = cx.tcx.hir().body(map_body_id) - && let [map_param] = map_body.params - && let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind + return; + } - && let Some(check_result) = - offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref) - { + if let Some((map_param_ident, check_result)) = is_find_or_filter(cx, 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) @@ -395,6 +402,40 @@ pub(super) fn check( } } +fn is_find_or_filter<'a>( + cx: &LateContext<'a>, + map_recv: &hir::Expr<'_>, + filter_arg: &hir::Expr<'_>, + map_arg: &hir::Expr<'_>, +) -> Option<(Ident, CheckResult<'a>)> { + if is_trait_method(cx, map_recv, sym::Iterator) + // filter(|x| ...is_some())... + && let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind + && let filter_body = cx.tcx.hir().body(filter_body_id) + && let [filter_param] = filter_body.params + // optional ref pattern: `filter(|&x| ..)` + && let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + (ref_pat, true) + } else { + (filter_param.pat, false) + } + + && let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind + && let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id) + + && let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind + && let map_body = cx.tcx.hir().body(map_body_id) + && let [map_param] = map_body.params + && 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) + { + return Some((map_param_ident, check_result)); + } + None +} + fn acceptable_methods(method: &PathSegment<'_>) -> bool { let methods: [Symbol; 8] = [ sym::clone, diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs new file mode 100644 index 0000000000000..ade8e3155fae3 --- /dev/null +++ b/clippy_lints/src/methods/iter_filter.rs @@ -0,0 +1,87 @@ +use rustc_lint::{LateContext, LintContext}; + +use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline}; +use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::QPath; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; +use std::borrow::Cow; + +fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { + match &expr.kind { + hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, + hir::ExprKind::Path(QPath::Resolved(_, segments)) => { + segments.segments.last().unwrap().ident.name == method_name + }, + hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, + hir::ExprKind::Closure(&hir::Closure { body, .. }) => { + let body = cx.tcx.hir().body(body); + let closure_expr = peel_blocks(body.value); + let arg_id = body.params[0].pat.hir_id; + match closure_expr.kind { + hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => { + if ident.name == method_name + && let hir::ExprKind::Path(path) = &receiver.kind + && let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id) + { + return arg_id == *local; + } + false + }, + _ => false, + } + }, + _ => false, + } +} + +fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) { + is_method(cx, parent_expr, rustc_span::sym::map) + } else { + false + } +} + +#[allow(clippy::too_many_arguments)] +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) { + let is_iterator = is_trait_method(cx, expr, sym::Iterator); + let parent_is_not_map = !parent_is_map(cx, expr); + + if is_iterator + && parent_is_not_map + && is_method(cx, filter_arg, sym!(is_some)) + && !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi())) + { + span_lint_and_sugg( + cx, + ITER_FILTER_IS_SOME, + filter_span.with_hi(expr.span.hi()), + "`filter` for `is_some` on iterator over `Option`", + "consider using `flatten` instead", + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(), + Applicability::HasPlaceholders, + ); + } + if is_iterator + && parent_is_not_map + && is_method(cx, filter_arg, sym!(is_ok)) + && !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi())) + { + span_lint_and_sugg( + cx, + ITER_FILTER_IS_OK, + filter_span.with_hi(expr.span.hi()), + "`filter` for `is_ok` on iterator over `Result`s", + "consider using `flatten` instead", + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(), + Applicability::HasPlaceholders, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b4f60ffadd773..25b1ea526e2e3 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -38,6 +38,7 @@ mod into_iter_on_ref; mod is_digit_ascii_radix; mod iter_cloned_collect; mod iter_count; +mod iter_filter; mod iter_kv_map; mod iter_next_slice; mod iter_nth; @@ -1175,7 +1176,8 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for indirect collection of populated `Option` + /// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may + /// be replaced with a `.flatten()` call. /// /// ### Why is this bad? /// `Option` is like a collection of 0-1 things, so `flatten` @@ -3752,6 +3754,81 @@ declare_clippy_lint! { "using `Option.map_or(Err(_), Ok)`, which is more succinctly expressed as `Option.ok_or(_)`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may + /// be replaced with a `.flatten()` call. + /// + /// ### Why is this bad? + /// `Result` implements `IntoIterator`. This means that `Result` can be flattened + /// automatically without suspicious-looking `unwrap` calls. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::empty::>().filter(Result::is_ok).map(Result::unwrap); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::empty::>().flatten(); + /// ``` + #[clippy::version = "1.76.0"] + pub RESULT_FILTER_MAP, + complexity, + "filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it appects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of the `Option`. The simplification + /// results in more readable and succint code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// // example code where clippy issues a warning + /// vec![Some(1)].into_iter().filter(Option::is_some); + /// + /// ``` + /// Use instead: + /// ```no_run + /// // example code which does not raise clippy warning + /// vec![Some(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.76.0"] + pub ITER_FILTER_IS_SOME, + pedantic, + "filtering an iterator over `Option`s for `Some` can be achieved with `flatten`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it appects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of `Result`. The simplification + /// results in more readable and succint code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// // example code where clippy issues a warning + /// vec![Ok::(1)].into_iter().filter(Result::is_ok); + /// + /// ``` + /// Use instead: + /// ```no_run + /// // example code which does not raise clippy warning + /// vec![Ok::(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.76.0"] + pub ITER_FILTER_IS_OK, + pedantic, + "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3903,6 +3980,9 @@ impl_lint_pass!(Methods => [ UNNECESSARY_FALLIBLE_CONVERSIONS, JOIN_ABSOLUTE_PATHS, OPTION_MAP_OR_ERR_OK, + RESULT_FILTER_MAP, + ITER_FILTER_IS_SOME, + ITER_FILTER_IS_OK, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4232,7 +4312,24 @@ impl Methods { string_extend_chars::check(cx, expr, recv, arg); extend_with_drain::check(cx, expr, recv, arg); }, - (name @ ("filter" | "find"), [arg]) => { + ("filter", [arg]) => { + if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { + // if `arg` has side-effect, the semantic will change + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::FixClosure(name, arg), + false, + ); + } + if self.msrv.meets(msrvs::ITER_FLATTEN) { + // use the sourcemap to get the span of the closure + iter_filter::check(cx, expr, arg, span); + } + }, + ("find", [arg]) => { if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { // if `arg` has side-effect, the semantic will change iter_overeager_cloned::check( diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index c4775b6bd0430..637368e936185 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -7,6 +7,7 @@ use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, Node}; use rustc_hir_typeck::{FnCtxt, Inherited}; @@ -37,6 +38,9 @@ pub fn check<'tcx>( if is_cloned_or_copied(cx, method_name, method_def_id) { unnecessary_iter_cloned::check(cx, expr, method_name, receiver); } else if is_to_owned_like(cx, expr, method_name, method_def_id) { + if check_split_call_arg(cx, expr, method_name, receiver) { + return; + } // At this point, we know the call is of a `to_owned`-like function. The functions // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary // based on its context, that is, whether it is a referent in an `AddrOf` expression, an @@ -233,6 +237,58 @@ fn check_into_iter_call_arg( false } +/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its +/// call of a `to_owned`-like function is unnecessary. +fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) + && let Some((fn_name, argument_expr)) = get_fn_name_and_arg(cx, parent) + && fn_name.as_str() == "split" + && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) + && let Some(arg_snippet) = snippet_opt(cx, argument_expr.span) + { + // The next suggestion may be incorrect because the removal of the `to_owned`-like + // function could cause the iterator to hold a reference to a resource that is used + // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148. + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{method_name}`"), + "use", + format!("{receiver_snippet}.split({arg_snippet})"), + Applicability::MaybeIncorrect, + ); + return true; + } + false +} + +fn get_fn_name_and_arg<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(Symbol, Expr<'tcx>)> { + match &expr.kind { + ExprKind::MethodCall(path, _, [arg_expr], ..) => Some((path.ident.name, *arg_expr)), + ExprKind::Call( + Expr { + kind: ExprKind::Path(qpath), + hir_id: path_hir_id, + .. + }, + [arg_expr], + ) => { + // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or + // deref to fn pointers, dyn Fn, impl Fn - #8850 + if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, def_id) = + cx.typeck_results().qpath_res(qpath, *path_hir_id) + && let Some(fn_name) = cx.tcx.opt_item_name(def_id) + { + Some((fn_name, *arg_expr)) + } else { + None + } + }, + _ => None, + } +} + /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call /// of a `to_owned`-like function is unnecessary. fn check_other_call_arg<'tcx>( diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 218ca5e80f380..bf27adb45afac 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -340,6 +340,12 @@ fn check_comparison<'a, 'tcx>( } if l_ty.is_bool() && r_ty.is_bool() { let mut applicability = Applicability::MachineApplicable; + // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, + // calling `source_callsite` make sure macros are handled correctly, see issue #9907 + let binop_span = left_side + .span + .source_callsite() + .with_hi(right_side.span.source_callsite().hi()); if op.node == BinOpKind::Eq { let expression_info = one_side_is_unary_not(left_side, right_side); @@ -347,13 +353,23 @@ fn check_comparison<'a, 'tcx>( span_lint_and_sugg( cx, BOOL_COMPARISON, - e.span, + binop_span, "this comparison might be written more concisely", "try simplifying it as shown", format!( "{} != {}", - snippet_with_applicability(cx, expression_info.left_span, "..", &mut applicability), - snippet_with_applicability(cx, expression_info.right_span, "..", &mut applicability) + snippet_with_applicability( + cx, + expression_info.left_span.source_callsite(), + "..", + &mut applicability + ), + snippet_with_applicability( + cx, + expression_info.right_span.source_callsite(), + "..", + &mut applicability + ) ), applicability, ); @@ -362,16 +378,16 @@ fn check_comparison<'a, 'tcx>( match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { (Some(true), None) => left_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, e, right_side, applicability, m, h); + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); }), (None, Some(true)) => right_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, e, left_side, applicability, m, h); + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); }), (Some(false), None) => left_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, e, right_side, applicability, m, h); + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); }), (None, Some(false)) => right_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, e, left_side, applicability, m, h); + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); }), (None, None) => no_literal.map_or((), |(h, m)| { let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); @@ -379,7 +395,7 @@ fn check_comparison<'a, 'tcx>( span_lint_and_sugg( cx, BOOL_COMPARISON, - e.span, + binop_span, m, "try simplifying it as shown", h(left_side, right_side).to_string(), @@ -394,17 +410,17 @@ fn check_comparison<'a, 'tcx>( fn suggest_bool_comparison<'a, 'tcx>( cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, + span: Span, expr: &Expr<'_>, mut app: Applicability, message: &str, conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, ) { - let hint = Sugg::hir_with_context(cx, expr, e.span.ctxt(), "..", &mut app); + let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); span_lint_and_sugg( cx, BOOL_COMPARISON, - e.span, + span, message, "try simplifying it as shown", conv_hint(hint).to_string(), diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 89e4e3c740d5e..556c493d36c87 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -238,6 +238,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> let_expr, if_then, if_else: Some(if_else), + .. }) = higher::IfLet::hir(cx, expr) && !cx.typeck_results().expr_ty(expr).is_unit() && !is_else_clause(cx.tcx, expr) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index fc5835408a92a..509d9483e1d7a 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -8,7 +8,7 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, - peel_blocks_with_stmt, + peel_blocks_with_stmt, span_contains_comment, }; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -96,6 +96,24 @@ enum IfBlockType<'hir> { ), } +fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> { + if let Block { + stmts: &[], + expr: Some(els), + .. + } = block + { + Some(els) + } else if let [stmt] = block.stmts + && let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::Ret(..) = expr.kind + { + Some(expr) + } else { + None + } +} + fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { if let StmtKind::Local(Local { pat, @@ -103,12 +121,9 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { els: Some(els), .. }) = stmt.kind - && let Block { - stmts: &[], - expr: Some(els), - .. - } = els - && let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, els) + && let Some(ret) = find_let_else_ret_expression(els) + && let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret) + && !span_contains_comment(cx.tcx.sess.source_map(), els.span) { let mut applicability = Applicability::MaybeIncorrect; let init_expr_str = snippet_with_applicability(cx, init_expr.span, "..", &mut applicability); @@ -256,6 +271,7 @@ impl QuestionMark { let_expr, if_then, if_else, + .. }) = higher::IfLet::hir(cx, expr) && !is_else_clause(cx.tcx, expr) && let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind diff --git a/clippy_lints/src/redundant_async_block.rs b/clippy_lints/src/redundant_async_block.rs index b50141f048c5b..0ed957f1f2fb3 100644 --- a/clippy_lints/src/redundant_async_block.rs +++ b/clippy_lints/src/redundant_async_block.rs @@ -3,9 +3,12 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::peel_blocks; use clippy_utils::source::{snippet, walk_span_to_context}; +use clippy_utils::ty::implements_trait; use clippy_utils::visitors::for_each_expr; use rustc_errors::Applicability; -use rustc_hir::{Closure, ClosureKind, CoroutineKind, CoroutineSource, CoroutineDesugaring, Expr, ExprKind, MatchSource}; +use rustc_hir::{ + Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, MatchSource, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::UpvarCapture; @@ -49,6 +52,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock { let Some(expr) = desugar_await(peel_blocks(body_expr)) && // The await prefix must not come from a macro as its content could change in the future. expr.span.eq_ctxt(body_expr.span) && + // The await prefix must implement Future, as implementing IntoFuture is not enough. + let Some(future_trait) = cx.tcx.lang_items().future_trait() && + implements_trait(cx, cx.typeck_results().expr_ty(expr), future_trait, &[]) && // An async block does not have immediate side-effects from a `.await` point-of-view. (!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) && let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt()) @@ -71,7 +77,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock { fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { if let ExprKind::Closure(Closure { body, def_id, kind, .. }) = expr.kind && let body = cx.tcx.hir().body(*body) - && matches!(kind, ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, CoroutineSource::Block))) + && matches!( + kind, + ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block + )) + ) { cx.typeck_results() .closure_min_captures diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 16c929edb928f..cde08dfcc748d 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -5,7 +5,7 @@ use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor}; -use rustc_hir::{intravisit as hir_visit, CoroutineKind, CoroutineSource, CoroutineDesugaring, Node, ClosureKind}; +use rustc_hir::{intravisit as hir_visit, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; @@ -66,7 +66,8 @@ impl<'tcx> Visitor<'tcx> for ReturnVisitor { fn is_async_closure(body: &hir::Body<'_>) -> bool { if let hir::ExprKind::Closure(innermost_closure_generated_by_desugar) = body.value.kind // checks whether it is `async || whatever_expression` - && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, CoroutineSource::Closure)) = innermost_closure_generated_by_desugar.kind + && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, CoroutineSource::Closure)) + = innermost_closure_generated_by_desugar.kind { true } else { diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs new file mode 100644 index 0000000000000..01a23c515f57d --- /dev/null +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -0,0 +1,74 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_normalizable; +use clippy_utils::{path_to_local, path_to_local_id}; +use rustc_abi::WrappingRange; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; + +use super::EAGER_TRANSMUTE; + +fn peel_parent_unsafe_blocks<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) { + match parent { + Node::Block(_) => {}, + Node::Expr(e) if let ExprKind::Block(..) = e.kind => {}, + Node::Expr(e) => return Some(e), + _ => break, + } + } + None +} + +fn range_fully_contained(from: WrappingRange, to: WrappingRange) -> bool { + to.contains(from.start) && to.contains(from.end) +} + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + transmutable: &'tcx Expr<'tcx>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, +) -> bool { + if let Some(then_some_call) = peel_parent_unsafe_blocks(cx, expr) + && let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind + && cx.typeck_results().expr_ty(receiver).is_bool() + && path.ident.name == sym!(then_some) + && let ExprKind::Binary(_, lhs, rhs) = receiver.kind + && let Some(local_id) = path_to_local(transmutable) + && (path_to_local_id(lhs, local_id) || path_to_local_id(rhs, local_id)) + && is_normalizable(cx, cx.param_env, from_ty) + && is_normalizable(cx, cx.param_env, to_ty) + // we only want to lint if the target type has a niche that is larger than the one of the source type + // e.g. `u8` to `NonZeroU8` should lint, but `NonZeroU8` to `u8` should not + && let Ok(from_layout) = cx.tcx.layout_of(cx.param_env.and(from_ty)) + && let Ok(to_layout) = cx.tcx.layout_of(cx.param_env.and(to_ty)) + && match (from_layout.largest_niche, to_layout.largest_niche) { + (Some(from_niche), Some(to_niche)) => !range_fully_contained(from_niche.valid_range, to_niche.valid_range), + (None, Some(_)) => true, + (_, None) => false, + } + { + span_lint_and_then( + cx, + EAGER_TRANSMUTE, + expr.span, + "this transmute is always evaluated eagerly, even if the condition is false", + |diag| { + diag.multipart_suggestion( + "consider using `bool::then` to only transmute if the condition holds", + vec![ + (path.ident.span, "then".into()), + (arg.span.shrink_to_lo(), "|| ".into()), + ], + Applicability::MaybeIncorrect, + ); + }, + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 95a92afea6681..06de7a1103166 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -1,4 +1,5 @@ mod crosspointer_transmute; +mod eager_transmute; mod transmute_float_to_int; mod transmute_int_to_bool; mod transmute_int_to_char; @@ -463,6 +464,62 @@ declare_clippy_lint! { "transmute results in a null function pointer, which is undefined behavior" } +declare_clippy_lint! { + /// ### What it does + /// Checks for integer validity checks, followed by a transmute that is (incorrectly) evaluated + /// eagerly (e.g. using `bool::then_some`). + /// + /// ### Why is this bad? + /// Eager evaluation means that the `transmute` call is executed regardless of whether the condition is true or false. + /// This can introduce unsoundness and other subtle bugs. + /// + /// ### Example + /// Consider the following function which is meant to convert an unsigned integer to its enum equivalent via transmute. + /// + /// ```no_run + /// #[repr(u8)] + /// enum Opcode { + /// Add = 0, + /// Sub = 1, + /// Mul = 2, + /// Div = 3 + /// } + /// + /// fn int_to_opcode(op: u8) -> Option { + /// (op < 4).then_some(unsafe { std::mem::transmute(op) }) + /// } + /// ``` + /// This may appear fine at first given that it checks that the `u8` is within the validity range of the enum, + /// *however* the transmute is evaluated eagerly, meaning that it executes even if `op >= 4`! + /// + /// This makes the function unsound, because it is possible for the caller to cause undefined behavior + /// (creating an enum with an invalid bitpattern) entirely in safe code only by passing an incorrect value, + /// which is normally only a bug that is possible in unsafe code. + /// + /// One possible way in which this can go wrong practically is that the compiler sees it as: + /// ```rust,ignore (illustrative) + /// let temp: Foo = unsafe { std::mem::transmute(op) }; + /// (0 < 4).then_some(temp) + /// ``` + /// and optimizes away the `(0 < 4)` check based on the assumption that since a `Foo` was created from `op` with the validity range `0..3`, + /// it is **impossible** for this condition to be false. + /// + /// In short, it is possible for this function to be optimized in a way that makes it [never return `None`](https://godbolt.org/z/ocrcenevq), + /// even if passed the value `4`. + /// + /// This can be avoided by instead using lazy evaluation. For the example above, this should be written: + /// ```rust,ignore (illustrative) + /// fn int_to_opcode(op: u8) -> Option { + /// (op < 4).then(|| unsafe { std::mem::transmute(op) }) + /// ^^^^ ^^ `bool::then` only executes the closure if the condition is true! + /// } + /// ``` + #[clippy::version = "1.76.0"] + pub EAGER_TRANSMUTE, + correctness, + "eager evaluation of `transmute`" +} + pub struct Transmute { msrv: Msrv, } @@ -484,6 +541,7 @@ impl_lint_pass!(Transmute => [ TRANSMUTE_UNDEFINED_REPR, TRANSMUTING_NULL, TRANSMUTE_NULL_TO_FN, + EAGER_TRANSMUTE, ]); impl Transmute { #[must_use] @@ -530,7 +588,8 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context) | (unsound_collection_transmute::check(cx, e, from_ty, to_ty) - || transmute_undefined_repr::check(cx, e, from_ty, to_ty)); + || transmute_undefined_repr::check(cx, e, from_ty, to_ty)) + | (eager_transmute::check(cx, e, arg, from_ty, to_ty)); if !linted { transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, from_ty_adjusted, to_ty, arg); diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs new file mode 100644 index 0000000000000..b1fa30aa06820 --- /dev/null +++ b/clippy_lints/src/unconditional_recursion.rs @@ -0,0 +1,134 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{get_trait_def_id, path_res}; +use rustc_ast::BinOpKind; +use rustc_hir::def::Res; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::declare_lint_pass; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// ### What it does + /// Checks that there isn't an infinite recursion in `PartialEq` trait + /// implementation. + /// + /// ### Why is this bad? + /// This is a hard to find infinite recursion which will crashing any code + /// using it. + /// + /// ### Example + /// ```no_run + /// enum Foo { + /// A, + /// B, + /// } + /// + /// impl PartialEq for Foo { + /// fn eq(&self, other: &Self) -> bool { + /// self == other // bad! + /// } + /// } + /// ``` + /// Use instead: + /// + /// In such cases, either use `#[derive(PartialEq)]` or don't implement it. + #[clippy::version = "1.76.0"] + pub UNCONDITIONAL_RECURSION, + suspicious, + "detect unconditional recursion in some traits implementation" +} + +declare_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]); + +fn get_ty_def_id(ty: Ty<'_>) -> Option { + match ty.peel_refs().kind() { + ty::Adt(adt, _) => Some(adt.did()), + ty::Foreign(def_id) => Some(*def_id), + _ => None, + } +} + +fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(path_res(cx, expr), Res::Local(_)) +} + +impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { + #[allow(clippy::unnecessary_def_path)] + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _decl: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + method_span: Span, + def_id: LocalDefId, + ) { + // If the function is a method... + if let FnKind::Method(name, _) = kind + // That has two arguments. + && let [self_arg, other_arg] = cx + .tcx + .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(def_id).skip_binder()) + .inputs() + && let Some(self_arg) = get_ty_def_id(*self_arg) + && let Some(other_arg) = get_ty_def_id(*other_arg) + // The two arguments are of the same type. + && self_arg == other_arg + && let hir_id = cx.tcx.local_def_id_to_hir_id(def_id) + && let Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(impl_), + owner_id, + .. + }), + )) = cx.tcx.hir().parent_iter(hir_id).next() + // We exclude `impl` blocks generated from rustc's proc macros. + && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + // It is a implementation of a trait. + && let Some(trait_) = impl_.of_trait + && let Some(trait_def_id) = trait_.trait_def_id() + // The trait is `PartialEq`. + && Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"]) + { + let to_check_op = if name.name == sym::eq { + BinOpKind::Eq + } else { + BinOpKind::Ne + }; + let expr = body.value.peel_blocks(); + let is_bad = match expr.kind { + ExprKind::Binary(op, left, right) if op.node == to_check_op => { + is_local(cx, left) && is_local(cx, right) + }, + ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { + if is_local(cx, receiver) + && is_local(cx, &arg) + && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && trait_id == trait_def_id + { + true + } else { + false + } + }, + _ => false, + }; + if is_bad { + span_lint_and_then( + cx, + UNCONDITIONAL_RECURSION, + method_span, + "function cannot return without recursing", + |diag| { + diag.span_note(expr.span, "recursive call site"); + }, + ); + } + } + } +} diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 7a6549a7c54a6..e5bc3b5a25f8e 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -680,9 +680,7 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos }) .filter(|(_, text)| !text.is_empty()); - let Some((line_start, line)) = lines.next() else { - return None; - }; + let (line_start, line) = lines.next()?; // Check for a sequence of line comments. if line.starts_with("//") { let (mut line, mut line_start) = (line, line_start); diff --git a/clippy_lints/src/uninhabited_references.rs b/clippy_lints/src/uninhabited_references.rs index d41576cadadf5..903593ecfd7df 100644 --- a/clippy_lints/src/uninhabited_references.rs +++ b/clippy_lints/src/uninhabited_references.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.76.0"] pub UNINHABITED_REFERENCES, - suspicious, + nursery, "reference to uninhabited type" } diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index df715b12dea7e..8817e46b3c8c0 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -351,6 +351,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { let_pat, let_expr, if_then, + .. }) = higher::WhileLet::hir(expr.value) { bind!(self, let_pat, let_expr, if_then); diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 5d23326cec89b..b8869eedf52c2 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.76" +version = "0.1.77" edition = "2021" publish = false diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 7fe76b946a4b9..adc35bd82ae39 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -134,6 +134,7 @@ pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool { } } +#[allow(clippy::too_many_lines)] // Just a big match statement pub fn eq_expr(l: &Expr, r: &Expr) -> bool { use ExprKind::*; if !over(&l.attrs, &r.attrs, eq_attr) { diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 46c96fad884f5..db80e07ca1c4d 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -76,12 +76,14 @@ pub fn get_attr<'a>( }) .map_or_else( || { - sess.dcx().span_err(attr_segments[1].ident.span, "usage of unknown attribute"); + sess.dcx() + .span_err(attr_segments[1].ident.span, "usage of unknown attribute"); false }, |deprecation_status| { - let mut diag = - sess.dcx().struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute"); + let mut diag = sess + .dcx() + .struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute"); match *deprecation_status { DeprecationStatus::Deprecated => { diag.emit(); @@ -132,7 +134,8 @@ pub fn get_unique_attr<'a>( let mut unique_attr: Option<&ast::Attribute> = None; for attr in get_attr(sess, attrs, name) { if let Some(duplicate) = unique_attr { - sess.dcx().struct_span_err(attr.span, format!("`{name}` is defined multiple times")) + sess.dcx() + .struct_span_err(attr.span, format!("`{name}` is defined multiple times")) .span_note(duplicate.span, "first definition found here") .emit(); } else { diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 3135a033648cc..ba682813dadf8 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -91,6 +91,9 @@ pub struct IfLet<'hir> { pub if_then: &'hir Expr<'hir>, /// `if let` else expression pub if_else: Option<&'hir Expr<'hir>>, + /// `if let PAT = EXPR` + /// ^^^^^^^^^^^^^^ + pub let_span: Span, } impl<'hir> IfLet<'hir> { @@ -99,9 +102,10 @@ impl<'hir> IfLet<'hir> { if let ExprKind::If( Expr { kind: - ExprKind::Let(hir::Let { + ExprKind::Let(&hir::Let { pat: let_pat, init: let_expr, + span: let_span, .. }), .. @@ -129,6 +133,7 @@ impl<'hir> IfLet<'hir> { let_expr, if_then, if_else, + let_span, }); } None @@ -146,6 +151,9 @@ pub enum IfLetOrMatch<'hir> { &'hir Pat<'hir>, &'hir Expr<'hir>, Option<&'hir Expr<'hir>>, + /// `if let PAT = EXPR` + /// ^^^^^^^^^^^^^^ + Span, ), } @@ -160,7 +168,8 @@ impl<'hir> IfLetOrMatch<'hir> { let_pat, if_then, if_else, - }| { Self::IfLet(let_expr, let_pat, if_then, if_else) }, + let_span, + }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) }, ), } } @@ -353,6 +362,9 @@ pub struct WhileLet<'hir> { pub let_expr: &'hir Expr<'hir>, /// `while let` loop body pub if_then: &'hir Expr<'hir>, + /// `while let PAT = EXPR` + /// ^^^^^^^^^^^^^^ + pub let_span: Span, } impl<'hir> WhileLet<'hir> { @@ -367,9 +379,10 @@ impl<'hir> WhileLet<'hir> { ExprKind::If( Expr { kind: - ExprKind::Let(hir::Let { + ExprKind::Let(&hir::Let { pat: let_pat, init: let_expr, + span: let_span, .. }), .. @@ -390,6 +403,7 @@ impl<'hir> WhileLet<'hir> { let_pat, let_expr, if_then, + let_span, }); } None diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index d752fe7d97ebf..ebc38e531fe6e 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -316,10 +316,7 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> is_const: bool, } impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - fn nested_visit_map(&mut self) -> Self::Map { - self.cx.tcx.hir() - } + type NestedFilter = rustc_hir::intravisit::nested_filter::None; fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if !self.is_const { diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index af123e107d5c7..5aaafb4172144 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.76" +version = "0.1.77" edition = "2021" publish = false diff --git a/rust-toolchain b/rust-toolchain index d575da6dece85..5a2526fd2675f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-12-16" +channel = "nightly-2023-12-28" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui-internal/disallow_struct_span_lint.rs b/tests/ui-internal/disallow_struct_span_lint.rs index 3155c0235ffd0..c81d54918cbbb 100644 --- a/tests/ui-internal/disallow_struct_span_lint.rs +++ b/tests/ui-internal/disallow_struct_span_lint.rs @@ -11,7 +11,7 @@ use rustc_lint::{Lint, LintContext}; use rustc_middle::ty::TyCtxt; pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into, msg: impl Into) { - cx.struct_span_lint(lint, span, msg, |b| b); + cx.struct_span_lint(lint, span, msg, |_| {}); } pub fn b( @@ -21,7 +21,7 @@ pub fn b( span: impl Into, msg: impl Into, ) { - tcx.struct_span_lint_hir(lint, hir_id, span, msg, |b| b); + tcx.struct_span_lint_hir(lint, hir_id, span, msg, |_| {}); } fn main() {} diff --git a/tests/ui-internal/disallow_struct_span_lint.stderr b/tests/ui-internal/disallow_struct_span_lint.stderr index 76c487fb1352e..7d424124f2b12 100644 --- a/tests/ui-internal/disallow_struct_span_lint.stderr +++ b/tests/ui-internal/disallow_struct_span_lint.stderr @@ -1,8 +1,8 @@ error: use of a disallowed method `rustc_lint::context::LintContext::struct_span_lint` --> $DIR/disallow_struct_span_lint.rs:14:5 | -LL | cx.struct_span_lint(lint, span, msg, |b| b); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | cx.struct_span_lint(lint, span, msg, |_| {}); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::disallowed-methods` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` @@ -10,8 +10,8 @@ LL | cx.struct_span_lint(lint, span, msg, |b| b); error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::struct_span_lint_hir` --> $DIR/disallow_struct_span_lint.rs:24:5 | -LL | tcx.struct_span_lint_hir(lint, hir_id, span, msg, |b| b); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | tcx.struct_span_lint_hir(lint, hir_id, span, msg, |_| {}); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 2 previous errors diff --git a/tests/ui-toml/suppress_lint_in_const/test.rs b/tests/ui-toml/suppress_lint_in_const/test.rs index 17c1b03d88c61..3edb3a10b7695 100644 --- a/tests/ui-toml/suppress_lint_in_const/test.rs +++ b/tests/ui-toml/suppress_lint_in_const/test.rs @@ -7,7 +7,8 @@ unconditional_panic, clippy::no_effect, clippy::unnecessary_operation, - clippy::useless_vec + clippy::useless_vec, + clippy::out_of_bounds_indexing )] const ARR: [i32; 2] = [1, 2]; diff --git a/tests/ui-toml/suppress_lint_in_const/test.stderr b/tests/ui-toml/suppress_lint_in_const/test.stderr index f8ace7995939a..84e7eff45573e 100644 --- a/tests/ui-toml/suppress_lint_in_const/test.stderr +++ b/tests/ui-toml/suppress_lint_in_const/test.stderr @@ -1,17 +1,17 @@ error[E0080]: evaluation of `main::{constant#3}` failed - --> $DIR/test.rs:37:14 + --> $DIR/test.rs:38:14 | LL | const { &ARR[idx4()] }; // Ok, should not produce stderr, since `suppress-restriction-lint-in-const` is set true. | ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4 note: erroneous constant encountered - --> $DIR/test.rs:37:5 + --> $DIR/test.rs:38:5 | LL | const { &ARR[idx4()] }; // Ok, should not produce stderr, since `suppress-restriction-lint-in-const` is set true. | ^^^^^^^^^^^^^^^^^^^^^^ error: indexing may panic - --> $DIR/test.rs:28:5 + --> $DIR/test.rs:29:5 | LL | x[index]; | ^^^^^^^^ @@ -21,7 +21,7 @@ LL | x[index]; = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` error: indexing may panic - --> $DIR/test.rs:46:5 + --> $DIR/test.rs:47:5 | LL | v[0]; | ^^^^ @@ -29,7 +29,7 @@ LL | v[0]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> $DIR/test.rs:47:5 + --> $DIR/test.rs:48:5 | LL | v[10]; | ^^^^^ @@ -37,7 +37,7 @@ LL | v[10]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> $DIR/test.rs:48:5 + --> $DIR/test.rs:49:5 | LL | v[1 << 3]; | ^^^^^^^^^ @@ -45,7 +45,7 @@ LL | v[1 << 3]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> $DIR/test.rs:54:5 + --> $DIR/test.rs:55:5 | LL | v[N]; | ^^^^ @@ -53,7 +53,7 @@ LL | v[N]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> $DIR/test.rs:55:5 + --> $DIR/test.rs:56:5 | LL | v[M]; | ^^^^ @@ -61,7 +61,7 @@ LL | v[M]; = help: consider using `.get(n)` or `.get_mut(n)` instead error[E0080]: evaluation of constant value failed - --> $DIR/test.rs:15:24 + --> $DIR/test.rs:16:24 | LL | const REF_ERR: &i32 = &ARR[idx4()]; // Ok, let rustc handle const contexts. | ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4 diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index 10809a6d247a2..1309ae45d0ae5 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -45,4 +45,11 @@ fn main() { const CFG_FLAG: &bool = &cfg!(feature = "hey"); assert!(!CFG_FLAG); + + const _: () = assert!(true); + //~^ ERROR: `assert!(true)` will be optimized out by the compiler + + // Don't lint if the value is dependent on a defined constant: + const N: usize = 1024; + const _: () = assert!(N.is_power_of_two()); } diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 780d1fe1c8a7a..099be4ed35542 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -72,5 +72,13 @@ LL | debug_assert!(true); | = help: remove it -error: aborting due to 9 previous errors +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:49:19 + | +LL | const _: () = assert!(true); + | ^^^^^^^^^^^^^ + | + = help: remove it + +error: aborting due to 10 previous errors diff --git a/tests/ui/author/loop.rs b/tests/ui/author/loop.rs index d6de21631e2ba..ff5b610011795 100644 --- a/tests/ui/author/loop.rs +++ b/tests/ui/author/loop.rs @@ -1,5 +1,9 @@ #![feature(stmt_expr_attributes)] -#![allow(clippy::never_loop, clippy::while_immutable_condition)] +#![allow( + clippy::never_loop, + clippy::while_immutable_condition, + clippy::redundant_pattern_matching +)] fn main() { #[clippy::author] diff --git a/tests/ui/bool_comparison.fixed b/tests/ui/bool_comparison.fixed index e3f2ca72d1cbd..02f1d09b83395 100644 --- a/tests/ui/bool_comparison.fixed +++ b/tests/ui/bool_comparison.fixed @@ -165,3 +165,12 @@ fn issue3973() { if is_debug == m!(func) {} if m!(func) == is_debug {} } + +#[allow(clippy::unnecessary_cast)] +fn issue9907() { + let _ = (1 >= 2) as usize; + let _ = (!m!(func)) as usize; + // This is not part of the issue, but an unexpected found when fixing the issue, + // the provided span was inside of macro rather than the macro callsite. + let _ = ((1 < 2) != m!(func)) as usize; +} diff --git a/tests/ui/bool_comparison.rs b/tests/ui/bool_comparison.rs index d1bc20d6831a5..5ef696d855eca 100644 --- a/tests/ui/bool_comparison.rs +++ b/tests/ui/bool_comparison.rs @@ -165,3 +165,12 @@ fn issue3973() { if is_debug == m!(func) {} if m!(func) == is_debug {} } + +#[allow(clippy::unnecessary_cast)] +fn issue9907() { + let _ = ((1 < 2) == false) as usize; + let _ = (false == m!(func)) as usize; + // This is not part of the issue, but an unexpected found when fixing the issue, + // the provided span was inside of macro rather than the macro callsite. + let _ = ((1 < 2) == !m!(func)) as usize; +} diff --git a/tests/ui/bool_comparison.stderr b/tests/ui/bool_comparison.stderr index 4560df6d4cdbd..6907dc0523fec 100644 --- a/tests/ui/bool_comparison.stderr +++ b/tests/ui/bool_comparison.stderr @@ -133,5 +133,23 @@ error: equality checks against true are unnecessary LL | if m!(func) == true {} | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` -error: aborting due to 22 previous errors +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:171:14 + | +LL | let _ = ((1 < 2) == false) as usize; + | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `1 >= 2` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:172:14 + | +LL | let _ = (false == m!(func)) as usize; + | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + +error: this comparison might be written more concisely + --> $DIR/bool_comparison.rs:175:14 + | +LL | let _ = ((1 < 2) == !m!(func)) as usize; + | ^^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `(1 < 2) != m!(func)` + +error: aborting due to 25 previous errors diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.rs b/tests/ui/branches_sharing_code/shared_at_bottom.rs index d102efa7a5880..549908b8770e3 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_bottom.rs @@ -1,6 +1,10 @@ #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] -#![allow(dead_code)] -#![allow(clippy::equatable_if_let, clippy::uninlined_format_args)] +#![allow( + clippy::equatable_if_let, + clippy::uninlined_format_args, + clippy::redundant_pattern_matching, + dead_code +)] //@no-rustfix // This tests the branches_sharing_code lint at the end of blocks diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_bottom.stderr index d00717befc103..8223df0fe7bab 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -1,5 +1,5 @@ error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:31:5 + --> $DIR/shared_at_bottom.rs:35:5 | LL | / let result = false; LL | | @@ -26,7 +26,7 @@ LL ~ result; | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:51:5 + --> $DIR/shared_at_bottom.rs:55:5 | LL | / println!("Same end of block"); LL | | @@ -40,7 +40,7 @@ LL + println!("Same end of block"); | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:69:5 + --> $DIR/shared_at_bottom.rs:73:5 | LL | / println!( LL | | @@ -61,7 +61,7 @@ LL + ); | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:82:9 + --> $DIR/shared_at_bottom.rs:86:9 | LL | / println!("Hello World"); LL | | @@ -75,7 +75,7 @@ LL + println!("Hello World"); | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:99:5 + --> $DIR/shared_at_bottom.rs:103:5 | LL | / let later_used_value = "A string value"; LL | | @@ -94,7 +94,7 @@ LL + println!("{}", later_used_value); | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:113:5 + --> $DIR/shared_at_bottom.rs:117:5 | LL | / let simple_examples = "I now identify as a &str :)"; LL | | @@ -112,7 +112,7 @@ LL + println!("This is the new simple_example: {}", simple_examples); | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:179:5 + --> $DIR/shared_at_bottom.rs:183:5 | LL | / x << 2 LL | | @@ -128,7 +128,7 @@ LL ~ x << 2; | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:188:5 + --> $DIR/shared_at_bottom.rs:192:5 | LL | / x * 4 LL | | @@ -144,7 +144,7 @@ LL + x * 4 | error: all if blocks contain the same code at the end - --> $DIR/shared_at_bottom.rs:202:44 + --> $DIR/shared_at_bottom.rs:206:44 | LL | if x == 17 { b = 1; a = 0x99; } else { a = 0x99; } | ^^^^^^^^^^^ diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index fff6bfcc753a9..44b0b6e739156 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -3,7 +3,8 @@ clippy::equatable_if_let, clippy::needless_if, clippy::nonminimal_bool, - clippy::eq_op + clippy::eq_op, + clippy::redundant_pattern_matching )] #[rustfmt::skip] diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index 70bfea231ae43..563a273dcddda 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -3,7 +3,8 @@ clippy::equatable_if_let, clippy::needless_if, clippy::nonminimal_bool, - clippy::eq_op + clippy::eq_op, + clippy::redundant_pattern_matching )] #[rustfmt::skip] diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr index e8a36bf48f11b..16df3e433db49 100644 --- a/tests/ui/collapsible_if.stderr +++ b/tests/ui/collapsible_if.stderr @@ -1,5 +1,5 @@ error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:14:5 + --> $DIR/collapsible_if.rs:15:5 | LL | / if x == "hello" { LL | | if y == "world" { @@ -18,7 +18,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:20:5 + --> $DIR/collapsible_if.rs:21:5 | LL | / if x == "hello" || x == "world" { LL | | if y == "world" || y == "hello" { @@ -35,7 +35,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:26:5 + --> $DIR/collapsible_if.rs:27:5 | LL | / if x == "hello" && x == "world" { LL | | if y == "world" || y == "hello" { @@ -52,7 +52,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:32:5 + --> $DIR/collapsible_if.rs:33:5 | LL | / if x == "hello" || x == "world" { LL | | if y == "world" && y == "hello" { @@ -69,7 +69,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:38:5 + --> $DIR/collapsible_if.rs:39:5 | LL | / if x == "hello" && x == "world" { LL | | if y == "world" && y == "hello" { @@ -86,7 +86,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:44:5 + --> $DIR/collapsible_if.rs:45:5 | LL | / if 42 == 1337 { LL | | if 'a' != 'A' { @@ -103,7 +103,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:100:5 + --> $DIR/collapsible_if.rs:101:5 | LL | / if x == "hello" { LL | | if y == "world" { // Collapsible @@ -120,7 +120,7 @@ LL + } | error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:159:5 + --> $DIR/collapsible_if.rs:160:5 | LL | / if matches!(true, true) { LL | | if matches!(true, true) {} @@ -128,7 +128,7 @@ LL | | } | |_____^ help: collapse nested if block: `if matches!(true, true) && matches!(true, true) {}` error: this `if` statement can be collapsed - --> $DIR/collapsible_if.rs:164:5 + --> $DIR/collapsible_if.rs:165:5 | LL | / if matches!(true, true) && truth() { LL | | if matches!(true, true) {} diff --git a/tests/ui/crashes/ice-11939.rs b/tests/ui/crashes/ice-11939.rs new file mode 100644 index 0000000000000..5e7193b682646 --- /dev/null +++ b/tests/ui/crashes/ice-11939.rs @@ -0,0 +1,14 @@ +#![allow(clippy::unit_arg, clippy::no_effect)] + +const fn v(_: ()) {} + +fn main() { + if true { + v({ + [0; 1 + 1]; + }); + Some(()) + } else { + None + }; +} diff --git a/tests/ui/crashes/ice-5497.rs b/tests/ui/crashes/ice-5497.rs index f77f691c19276..fe8d640c4707a 100644 --- a/tests/ui/crashes/ice-5497.rs +++ b/tests/ui/crashes/ice-5497.rs @@ -1,4 +1,6 @@ // reduced from rustc issue-69020-assoc-const-arith-overflow.rs +#![allow(clippy::out_of_bounds_indexing)] + pub fn main() {} pub trait Foo { diff --git a/tests/ui/crashes/ice-5497.stderr b/tests/ui/crashes/ice-5497.stderr index ee69f3379b6a1..3efaf05827e80 100644 --- a/tests/ui/crashes/ice-5497.stderr +++ b/tests/ui/crashes/ice-5497.stderr @@ -1,5 +1,5 @@ error: this operation will panic at runtime - --> $DIR/ice-5497.rs:9:22 + --> $DIR/ice-5497.rs:11:22 | LL | const OOB: i32 = [1][1] + T::OOB; | ^^^^^^ index out of bounds: the length is 1 but the index is 1 diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 708ac666675bb..bff46e55722ba 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -65,7 +65,7 @@ fn test_units() { /// OAuth GraphQL /// OCaml /// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS -/// WebGL +/// WebGL WebGL2 WebGPU /// TensorFlow /// TrueType /// iOS macOS FreeBSD diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 040d6352c5255..4e162a97dee9b 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -65,7 +65,7 @@ fn test_units() { /// OAuth GraphQL /// OCaml /// OpenGL OpenMP OpenSSH OpenSSL OpenStreetMap OpenDNS -/// WebGL +/// WebGL WebGL2 WebGPU /// TensorFlow /// TrueType /// iOS macOS FreeBSD diff --git a/tests/ui/eager_transmute.fixed b/tests/ui/eager_transmute.fixed new file mode 100644 index 0000000000000..e06aa2cc9fd77 --- /dev/null +++ b/tests/ui/eager_transmute.fixed @@ -0,0 +1,72 @@ +#![feature(rustc_attrs)] +#![warn(clippy::eager_transmute)] +#![allow(clippy::transmute_int_to_non_zero)] + +use std::num::NonZeroU8; + +#[repr(u8)] +enum Opcode { + Add = 0, + Sub = 1, + Mul = 2, + Div = 3, +} + +fn int_to_opcode(op: u8) -> Option { + (op < 4).then(|| unsafe { std::mem::transmute(op) }) +} + +fn f(op: u8, unrelated: u8) { + true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); +} + +unsafe fn f2(op: u8) { + (op < 4).then(|| std::mem::transmute::<_, Opcode>(op)); +} + +#[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); + +macro_rules! impls { + ($($t:ty),*) => { + $( + impl PartialEq for $t { + fn eq(&self, other: &u8) -> bool { + self.0 == *other + } + } + impl PartialOrd for $t { + fn partial_cmp(&self, other: &u8) -> Option { + self.0.partial_cmp(other) + } + } + )* + }; +} +impls!(NonMaxU8, NonZeroNonMaxU8); + +fn niche_tests(v1: u8, v2: NonZeroU8, v3: NonZeroNonMaxU8) { + // u8 -> NonZeroU8, do lint + let _: Option = (v1 > 0).then(|| unsafe { std::mem::transmute(v1) }); + + // NonZeroU8 -> u8, don't lint, target type has no niche and therefore a higher validity range + let _: Option = (v2 > NonZeroU8::new(1).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); + + // NonZeroU8 -> NonMaxU8, do lint, different niche + let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); + + // NonZeroNonMaxU8 -> NonMaxU8, don't lint, target type has more validity + let _: Option = (v3 < 255).then_some(unsafe { std::mem::transmute(v2) }); + + // NonZeroU8 -> NonZeroNonMaxU8, do lint, target type has less validity + let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); +} + +fn main() {} diff --git a/tests/ui/eager_transmute.rs b/tests/ui/eager_transmute.rs new file mode 100644 index 0000000000000..89ccdf583f395 --- /dev/null +++ b/tests/ui/eager_transmute.rs @@ -0,0 +1,72 @@ +#![feature(rustc_attrs)] +#![warn(clippy::eager_transmute)] +#![allow(clippy::transmute_int_to_non_zero)] + +use std::num::NonZeroU8; + +#[repr(u8)] +enum Opcode { + Add = 0, + Sub = 1, + Mul = 2, + Div = 3, +} + +fn int_to_opcode(op: u8) -> Option { + (op < 4).then_some(unsafe { std::mem::transmute(op) }) +} + +fn f(op: u8, unrelated: u8) { + true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); +} + +unsafe fn f2(op: u8) { + (op < 4).then_some(std::mem::transmute::<_, Opcode>(op)); +} + +#[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); + +macro_rules! impls { + ($($t:ty),*) => { + $( + impl PartialEq for $t { + fn eq(&self, other: &u8) -> bool { + self.0 == *other + } + } + impl PartialOrd for $t { + fn partial_cmp(&self, other: &u8) -> Option { + self.0.partial_cmp(other) + } + } + )* + }; +} +impls!(NonMaxU8, NonZeroNonMaxU8); + +fn niche_tests(v1: u8, v2: NonZeroU8, v3: NonZeroNonMaxU8) { + // u8 -> NonZeroU8, do lint + let _: Option = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) }); + + // NonZeroU8 -> u8, don't lint, target type has no niche and therefore a higher validity range + let _: Option = (v2 > NonZeroU8::new(1).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); + + // NonZeroU8 -> NonMaxU8, do lint, different niche + let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); + + // NonZeroNonMaxU8 -> NonMaxU8, don't lint, target type has more validity + let _: Option = (v3 < 255).then_some(unsafe { std::mem::transmute(v2) }); + + // NonZeroU8 -> NonZeroNonMaxU8, do lint, target type has less validity + let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); +} + +fn main() {} diff --git a/tests/ui/eager_transmute.stderr b/tests/ui/eager_transmute.stderr new file mode 100644 index 0000000000000..5eb163c5fcbb9 --- /dev/null +++ b/tests/ui/eager_transmute.stderr @@ -0,0 +1,92 @@ +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:16:33 + | +LL | (op < 4).then_some(unsafe { std::mem::transmute(op) }) + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::eager-transmute` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::eager_transmute)]` +help: consider using `bool::then` to only transmute if the condition holds + | +LL | (op < 4).then(|| unsafe { std::mem::transmute(op) }) + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:22:33 + | +LL | (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:23:33 + | +LL | (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:24:34 + | +LL | (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:28:24 + | +LL | (op < 4).then_some(std::mem::transmute::<_, Opcode>(op)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | (op < 4).then(|| std::mem::transmute::<_, Opcode>(op)); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:57:60 + | +LL | let _: Option = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (v1 > 0).then(|| unsafe { std::mem::transmute(v1) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:63:86 + | +LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); + | ~~~~ ++ + +error: this transmute is always evaluated eagerly, even if the condition is false + --> $DIR/eager_transmute.rs:69:93 + | +LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) }); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `bool::then` to only transmute if the condition holds + | +LL | let _: Option = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) }); + | ~~~~ ++ + +error: aborting due to 8 previous errors + diff --git a/tests/ui/get_unwrap.fixed b/tests/ui/get_unwrap.fixed index d5a4309db5945..62beb195939c6 100644 --- a/tests/ui/get_unwrap.fixed +++ b/tests/ui/get_unwrap.fixed @@ -2,7 +2,8 @@ unused_mut, clippy::from_iter_instead_of_collect, clippy::get_first, - clippy::useless_vec + clippy::useless_vec, + clippy::out_of_bounds_indexing )] #![warn(clippy::unwrap_used)] #![deny(clippy::get_unwrap)] diff --git a/tests/ui/get_unwrap.rs b/tests/ui/get_unwrap.rs index 5a9ad204f7025..1e09ff5c67e51 100644 --- a/tests/ui/get_unwrap.rs +++ b/tests/ui/get_unwrap.rs @@ -2,7 +2,8 @@ unused_mut, clippy::from_iter_instead_of_collect, clippy::get_first, - clippy::useless_vec + clippy::useless_vec, + clippy::out_of_bounds_indexing )] #![warn(clippy::unwrap_used)] #![deny(clippy::get_unwrap)] diff --git a/tests/ui/get_unwrap.stderr b/tests/ui/get_unwrap.stderr index 384860ea116e9..700f3cfec4e0b 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -1,17 +1,17 @@ error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:36:17 + --> $DIR/get_unwrap.rs:37:17 | LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&boxed_slice[1]` | note: the lint level is defined here - --> $DIR/get_unwrap.rs:8:9 + --> $DIR/get_unwrap.rs:9:9 | LL | #![deny(clippy::get_unwrap)] | ^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:36:17 + --> $DIR/get_unwrap.rs:37:17 | LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -22,13 +22,13 @@ LL | let _ = boxed_slice.get(1).unwrap(); = help: to override `-D warnings` add `#[allow(clippy::unwrap_used)]` error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:37:17 + --> $DIR/get_unwrap.rs:38:17 | LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&some_slice[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:37:17 + --> $DIR/get_unwrap.rs:38:17 | LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,13 +37,13 @@ LL | let _ = some_slice.get(0).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:38:17 + --> $DIR/get_unwrap.rs:39:17 | LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&some_vec[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:38:17 + --> $DIR/get_unwrap.rs:39:17 | LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,13 +52,13 @@ LL | let _ = some_vec.get(0).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:39:17 + --> $DIR/get_unwrap.rs:40:17 | LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&some_vecdeque[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:39:17 + --> $DIR/get_unwrap.rs:40:17 | LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,13 +67,13 @@ LL | let _ = some_vecdeque.get(0).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:40:17 + --> $DIR/get_unwrap.rs:41:17 | LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&some_hashmap[&1]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:40:17 + --> $DIR/get_unwrap.rs:41:17 | LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,13 +82,13 @@ LL | let _ = some_hashmap.get(&1).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:41:17 + --> $DIR/get_unwrap.rs:42:17 | LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&some_btreemap[&1]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:41:17 + --> $DIR/get_unwrap.rs:42:17 | LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ LL | let _ = some_btreemap.get(&1).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:45:21 + --> $DIR/get_unwrap.rs:46:21 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `boxed_slice[1]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:45:22 + --> $DIR/get_unwrap.rs:46:22 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,13 +112,13 @@ LL | let _: u8 = *boxed_slice.get(1).unwrap(); = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:50:9 + --> $DIR/get_unwrap.rs:51:9 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `boxed_slice[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:50:10 + --> $DIR/get_unwrap.rs:51:10 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -127,13 +127,13 @@ LL | *boxed_slice.get_mut(0).unwrap() = 1; = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:51:9 + --> $DIR/get_unwrap.rs:52:9 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_slice[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:51:10 + --> $DIR/get_unwrap.rs:52:10 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -142,13 +142,13 @@ LL | *some_slice.get_mut(0).unwrap() = 1; = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:52:9 + --> $DIR/get_unwrap.rs:53:9 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:52:10 + --> $DIR/get_unwrap.rs:53:10 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,13 +157,13 @@ LL | *some_vec.get_mut(0).unwrap() = 1; = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:53:9 + --> $DIR/get_unwrap.rs:54:9 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vecdeque[0]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:53:10 + --> $DIR/get_unwrap.rs:54:10 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -172,13 +172,13 @@ LL | *some_vecdeque.get_mut(0).unwrap() = 1; = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:65:17 + --> $DIR/get_unwrap.rs:66:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec[0..1]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:65:17 + --> $DIR/get_unwrap.rs:66:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -187,13 +187,13 @@ LL | let _ = some_vec.get(0..1).unwrap().to_vec(); = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:66:17 + --> $DIR/get_unwrap.rs:67:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `some_vec[0..1]` error: used `unwrap()` on an `Option` value - --> $DIR/get_unwrap.rs:66:17 + --> $DIR/get_unwrap.rs:67:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -202,25 +202,25 @@ LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:76:24 + --> $DIR/get_unwrap.rs:77:24 | LL | let _x: &i32 = f.get(1 + 2).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `&f[1 + 2]` error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:79:18 + --> $DIR/get_unwrap.rs:80:18 | LL | let _x = f.get(1 + 2).unwrap().to_string(); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `f[1 + 2]` error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:82:18 + --> $DIR/get_unwrap.rs:83:18 | LL | let _x = f.get(1 + 2).unwrap().abs(); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `f[1 + 2]` error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:99:33 + --> $DIR/get_unwrap.rs:100:33 | LL | let b = rest.get_mut(linidx(j, k) - linidx(i, k) - 1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut rest[linidx(j, k) - linidx(i, k) - 1]` diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 77abd663e0a6e..abc92459148eb 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -1,4 +1,5 @@ #![warn(clippy::if_then_some_else_none)] +#![allow(clippy::redundant_pattern_matching)] fn main() { // Should issue an error. diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index 5c97b06da15a8..9b3d65cc803ac 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -1,5 +1,5 @@ error: this could be simplified with `bool::then` - --> $DIR/if_then_some_else_none.rs:5:13 + --> $DIR/if_then_some_else_none.rs:6:13 | LL | let _ = if foo() { | _____________^ @@ -16,7 +16,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::if_then_some_else_none)]` error: this could be simplified with `bool::then` - --> $DIR/if_then_some_else_none.rs:14:13 + --> $DIR/if_then_some_else_none.rs:15:13 | LL | let _ = if matches!(true, true) { | _____________^ @@ -31,7 +31,7 @@ LL | | }; = help: consider using `bool::then` like: `matches!(true, true).then(|| { /* snippet */ matches!(true, false) })` error: this could be simplified with `bool::then_some` - --> $DIR/if_then_some_else_none.rs:24:28 + --> $DIR/if_then_some_else_none.rs:25:28 | LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); = help: consider using `bool::then_some` like: `(o < 32).then_some(o)` error: this could be simplified with `bool::then_some` - --> $DIR/if_then_some_else_none.rs:29:13 + --> $DIR/if_then_some_else_none.rs:30:13 | LL | let _ = if !x { Some(0) } else { None }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -47,7 +47,7 @@ LL | let _ = if !x { Some(0) } else { None }; = help: consider using `bool::then_some` like: `(!x).then_some(0)` error: this could be simplified with `bool::then` - --> $DIR/if_then_some_else_none.rs:85:13 + --> $DIR/if_then_some_else_none.rs:86:13 | LL | let _ = if foo() { | _____________^ diff --git a/tests/ui/indexing_slicing_index.rs b/tests/ui/indexing_slicing_index.rs index f0da5dfc60bd6..1ac0bb11014aa 100644 --- a/tests/ui/indexing_slicing_index.rs +++ b/tests/ui/indexing_slicing_index.rs @@ -74,4 +74,7 @@ fn main() { //~^ ERROR: indexing may panic v[M]; //~^ ERROR: indexing may panic + + let slice = &x; + let _ = x[4]; } diff --git a/tests/ui/indexing_slicing_index.stderr b/tests/ui/indexing_slicing_index.stderr index 1c34875d2b8ab..6d64fa1e6cf89 100644 --- a/tests/ui/indexing_slicing_index.stderr +++ b/tests/ui/indexing_slicing_index.stderr @@ -38,6 +38,21 @@ LL | x[index]; | = help: consider using `.get(n)` or `.get_mut(n)` instead +error: index is out of bounds + --> $DIR/indexing_slicing_index.rs:32:5 + | +LL | x[4]; + | ^^^^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::out_of_bounds_indexing)]` + +error: index is out of bounds + --> $DIR/indexing_slicing_index.rs:34:5 + | +LL | x[1 << 3]; + | ^^^^^^^^^ + error: indexing may panic --> $DIR/indexing_slicing_index.rs:45:14 | @@ -56,6 +71,12 @@ LL | const { &ARR[idx4()] }; = help: consider using `.get(n)` or `.get_mut(n)` instead = note: the suggestion might not be applicable in constant blocks +error: index is out of bounds + --> $DIR/indexing_slicing_index.rs:55:5 + | +LL | y[4]; + | ^^^^ + error: indexing may panic --> $DIR/indexing_slicing_index.rs:58:5 | @@ -80,6 +101,12 @@ LL | v[1 << 3]; | = help: consider using `.get(n)` or `.get_mut(n)` instead +error: index is out of bounds + --> $DIR/indexing_slicing_index.rs:70:5 + | +LL | x[N]; + | ^^^^ + error: indexing may panic --> $DIR/indexing_slicing_index.rs:73:5 | @@ -96,12 +123,18 @@ LL | v[M]; | = help: consider using `.get(n)` or `.get_mut(n)` instead +error: index is out of bounds + --> $DIR/indexing_slicing_index.rs:79:13 + | +LL | let _ = x[4]; + | ^^^^ + error[E0080]: evaluation of constant value failed --> $DIR/indexing_slicing_index.rs:16:24 | LL | const REF_ERR: &i32 = &ARR[idx4()]; // Ok, let rustc handle const contexts. | ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4 -error: aborting due to 12 previous errors +error: aborting due to 17 previous errors For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index f58b3cebbc34d..771fbfa44ee9f 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -9,7 +9,7 @@ LL | | } | = note: `-D clippy::infinite-loop` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::infinite_loop)]` -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn no_break() -> ! { | ++++ @@ -26,7 +26,7 @@ LL | | do_something(); LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn all_inf() -> ! { | ++++ @@ -43,7 +43,7 @@ LL | | } LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn all_inf() -> ! { | ++++ @@ -57,7 +57,7 @@ LL | | do_something(); LL | | } | |_____________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn all_inf() -> ! { | ++++ @@ -84,7 +84,7 @@ LL | | do_something(); LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn no_break_never_ret_noise() -> ! { | ++++ @@ -101,7 +101,7 @@ LL | | } LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn break_inner_but_not_outer_1(cond: bool) -> ! { | ++++ @@ -118,7 +118,7 @@ LL | | } LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn break_inner_but_not_outer_2(cond: bool) -> ! { | ++++ @@ -132,7 +132,7 @@ LL | | do_something(); LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn break_outer_but_not_inner() -> ! { | ++++ @@ -149,7 +149,7 @@ LL | | } LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn break_wrong_loop(cond: bool) -> ! { | ++++ @@ -166,7 +166,7 @@ LL | | } LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn match_like() -> ! { | ++++ @@ -180,7 +180,7 @@ LL | | let _x = matches!(result, Ok(v) if v != 0).then_some(0); LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn match_like() -> ! { | ++++ @@ -197,7 +197,7 @@ LL | | }); LL | | } | |_____^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn match_like() -> ! { | ++++ @@ -211,7 +211,7 @@ LL | | do_something(); LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn problematic_trait_method() -> ! { | ++++ @@ -225,7 +225,7 @@ LL | | do_something(); LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | fn could_be_problematic() -> ! { | ++++ @@ -239,7 +239,7 @@ LL | | do_something(); LL | | } | |_________^ | -help: if this is intentional, consider specifing `!` as function return +help: if this is intentional, consider specifying `!` as function return | LL | let _loop_forever = || -> ! { | ++++ diff --git a/tests/ui/iter_filter_is_ok.fixed b/tests/ui/iter_filter_is_ok.fixed new file mode 100644 index 0000000000000..a5ca41528afd5 --- /dev/null +++ b/tests/ui/iter_filter_is_ok.fixed @@ -0,0 +1,26 @@ +#![warn(clippy::iter_filter_is_ok)] + +fn main() { + let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + + #[rustfmt::skip] + let _ = vec![Ok(1), Err(2)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + + // Don't lint below + let mut counter = 0; + let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { + counter += 1; + o.is_ok() + }); + let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { + // Roses are red, + // Violets are blue, + // `Err` is not an `Option`, + // and this doesn't ryme + o.is_ok() + }); +} diff --git a/tests/ui/iter_filter_is_ok.rs b/tests/ui/iter_filter_is_ok.rs new file mode 100644 index 0000000000000..e4e73f5ada1c3 --- /dev/null +++ b/tests/ui/iter_filter_is_ok.rs @@ -0,0 +1,26 @@ +#![warn(clippy::iter_filter_is_ok)] + +fn main() { + let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok); + //~^ HELP: consider using `flatten` instead + let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok()); + //~^ HELP: consider using `flatten` instead + + #[rustfmt::skip] + let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() }); + //~^ HELP: consider using `flatten` instead + + // Don't lint below + let mut counter = 0; + let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { + counter += 1; + o.is_ok() + }); + let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { + // Roses are red, + // Violets are blue, + // `Err` is not an `Option`, + // and this doesn't ryme + o.is_ok() + }); +} diff --git a/tests/ui/iter_filter_is_ok.stderr b/tests/ui/iter_filter_is_ok.stderr new file mode 100644 index 0000000000000..f3acbe38d8a8c --- /dev/null +++ b/tests/ui/iter_filter_is_ok.stderr @@ -0,0 +1,23 @@ +error: `filter` for `is_ok` on iterator over `Result`s + --> $DIR/iter_filter_is_ok.rs:4:52 + | +LL | let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + | + = note: `-D clippy::iter-filter-is-ok` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::iter_filter_is_ok)]` + +error: `filter` for `is_ok` on iterator over `Result`s + --> $DIR/iter_filter_is_ok.rs:6:52 + | +LL | let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok()); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + +error: `filter` for `is_ok` on iterator over `Result`s + --> $DIR/iter_filter_is_ok.rs:10:45 + | +LL | let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/iter_filter_is_some.fixed b/tests/ui/iter_filter_is_some.fixed new file mode 100644 index 0000000000000..c3fa93f0ab218 --- /dev/null +++ b/tests/ui/iter_filter_is_some.fixed @@ -0,0 +1,27 @@ +#![warn(clippy::iter_filter_is_some)] + +fn main() { + let _ = vec![Some(1)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + let _ = vec![Some(1)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + + #[rustfmt::skip] + let _ = vec![Some(1)].into_iter().flatten(); + //~^ HELP: consider using `flatten` instead + + // Don't lint below + let mut counter = 0; + let _ = vec![Some(1)].into_iter().filter(|o| { + counter += 1; + o.is_some() + }); + + let _ = vec![Some(1)].into_iter().filter(|o| { + // Roses are red, + // Violets are blue, + // `Err` is not an `Option`, + // and this doesn't ryme + o.is_some() + }); +} diff --git a/tests/ui/iter_filter_is_some.rs b/tests/ui/iter_filter_is_some.rs new file mode 100644 index 0000000000000..b023776abe46e --- /dev/null +++ b/tests/ui/iter_filter_is_some.rs @@ -0,0 +1,27 @@ +#![warn(clippy::iter_filter_is_some)] + +fn main() { + let _ = vec![Some(1)].into_iter().filter(Option::is_some); + //~^ HELP: consider using `flatten` instead + let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()); + //~^ HELP: consider using `flatten` instead + + #[rustfmt::skip] + let _ = vec![Some(1)].into_iter().filter(|o| { o.is_some() }); + //~^ HELP: consider using `flatten` instead + + // Don't lint below + let mut counter = 0; + let _ = vec![Some(1)].into_iter().filter(|o| { + counter += 1; + o.is_some() + }); + + let _ = vec![Some(1)].into_iter().filter(|o| { + // Roses are red, + // Violets are blue, + // `Err` is not an `Option`, + // and this doesn't ryme + o.is_some() + }); +} diff --git a/tests/ui/iter_filter_is_some.stderr b/tests/ui/iter_filter_is_some.stderr new file mode 100644 index 0000000000000..1f2b10036fef6 --- /dev/null +++ b/tests/ui/iter_filter_is_some.stderr @@ -0,0 +1,23 @@ +error: `filter` for `is_some` on iterator over `Option` + --> $DIR/iter_filter_is_some.rs:4:39 + | +LL | let _ = vec![Some(1)].into_iter().filter(Option::is_some); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + | + = note: `-D clippy::iter-filter-is-some` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::iter_filter_is_some)]` + +error: `filter` for `is_some` on iterator over `Option` + --> $DIR/iter_filter_is_some.rs:6:39 + | +LL | let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + +error: `filter` for `is_some` on iterator over `Option` + --> $DIR/iter_filter_is_some.rs:10:39 + | +LL | let _ = vec![Some(1)].into_iter().filter(|o| { o.is_some() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/manual_let_else_question_mark.fixed b/tests/ui/manual_let_else_question_mark.fixed index b555186cc22a4..6b29ce7598573 100644 --- a/tests/ui/manual_let_else_question_mark.fixed +++ b/tests/ui/manual_let_else_question_mark.fixed @@ -60,3 +60,24 @@ fn foo() -> Option<()> { Some(()) } + +// lint not just `return None`, but also `return None;` (note the semicolon) +fn issue11993(y: Option) -> Option { + let x = y?; + + // don't lint: more than one statement in the else body + let Some(x) = y else { + todo!(); + return None; + }; + + let Some(x) = y else { + // Roses are red, + // violets are blue, + // please keep this comment, + // it's art, you know? + return None; + }; + + None +} diff --git a/tests/ui/manual_let_else_question_mark.rs b/tests/ui/manual_let_else_question_mark.rs index 5852c7094a4f4..e92c4c1375e52 100644 --- a/tests/ui/manual_let_else_question_mark.rs +++ b/tests/ui/manual_let_else_question_mark.rs @@ -65,3 +65,26 @@ fn foo() -> Option<()> { Some(()) } + +// lint not just `return None`, but also `return None;` (note the semicolon) +fn issue11993(y: Option) -> Option { + let Some(x) = y else { + return None; + }; + + // don't lint: more than one statement in the else body + let Some(x) = y else { + todo!(); + return None; + }; + + let Some(x) = y else { + // Roses are red, + // violets are blue, + // please keep this comment, + // it's art, you know? + return None; + }; + + None +} diff --git a/tests/ui/manual_let_else_question_mark.stderr b/tests/ui/manual_let_else_question_mark.stderr index bf0b1bbf0dd3b..dec6947697a7e 100644 --- a/tests/ui/manual_let_else_question_mark.stderr +++ b/tests/ui/manual_let_else_question_mark.stderr @@ -53,5 +53,13 @@ error: this could be rewritten as `let...else` LL | let v = if let Some(v_some) = g() { v_some } else { return None }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `let Some(v) = g() else { return None };` -error: aborting due to 6 previous errors +error: this `let...else` may be rewritten with the `?` operator + --> $DIR/manual_let_else_question_mark.rs:71:5 + | +LL | / let Some(x) = y else { +LL | | return None; +LL | | }; + | |______^ help: replace it with: `let x = y?;` + +error: aborting due to 7 previous errors diff --git a/tests/ui/needless_if.fixed b/tests/ui/needless_if.fixed index 1086ae2c984f8..79e33a7218b7e 100644 --- a/tests/ui/needless_if.fixed +++ b/tests/ui/needless_if.fixed @@ -10,6 +10,7 @@ clippy::nonminimal_bool, clippy::short_circuit_statement, clippy::unnecessary_operation, + clippy::redundant_pattern_matching, unused )] #![warn(clippy::needless_if)] diff --git a/tests/ui/needless_if.rs b/tests/ui/needless_if.rs index 131cceaf712fc..2c135fb22bf87 100644 --- a/tests/ui/needless_if.rs +++ b/tests/ui/needless_if.rs @@ -10,6 +10,7 @@ clippy::nonminimal_bool, clippy::short_circuit_statement, clippy::unnecessary_operation, + clippy::redundant_pattern_matching, unused )] #![warn(clippy::needless_if)] diff --git a/tests/ui/needless_if.stderr b/tests/ui/needless_if.stderr index c3e83c0f1f593..9a911b4dbaccc 100644 --- a/tests/ui/needless_if.stderr +++ b/tests/ui/needless_if.stderr @@ -1,5 +1,5 @@ error: this `if` branch is empty - --> $DIR/needless_if.rs:26:5 + --> $DIR/needless_if.rs:27: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_if)]` error: this `if` branch is empty - --> $DIR/needless_if.rs:28:5 + --> $DIR/needless_if.rs:29:5 | LL | if maybe_side_effect() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();` error: this `if` branch is empty - --> $DIR/needless_if.rs:33:5 + --> $DIR/needless_if.rs:34:5 | LL | / if { LL | | return; @@ -29,7 +29,7 @@ LL + }); | error: this `if` branch is empty - --> $DIR/needless_if.rs:49:5 + --> $DIR/needless_if.rs:50:5 | LL | / if { LL | | if let true = true @@ -54,19 +54,19 @@ LL + } && true); | error: this `if` branch is empty - --> $DIR/needless_if.rs:93:5 + --> $DIR/needless_if.rs:94:5 | LL | if { maybe_side_effect() } {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });` error: this `if` branch is empty - --> $DIR/needless_if.rs:95:5 + --> $DIR/needless_if.rs:96:5 | LL | if { maybe_side_effect() } && true {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);` error: this `if` branch is empty - --> $DIR/needless_if.rs:99:5 + --> $DIR/needless_if.rs:100:5 | LL | if true {} | ^^^^^^^^^^ help: you can remove it: `true;` diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index da7876e772ee9..4d48ef14d31ce 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -1,6 +1,11 @@ //@no-rustfix: overlapping suggestions #![feature(lint_reasons)] -#![allow(unused, clippy::diverging_sub_expression, clippy::needless_if)] +#![allow( + unused, + clippy::diverging_sub_expression, + clippy::needless_if, + clippy::redundant_pattern_matching +)] #![warn(clippy::nonminimal_bool)] #![allow(clippy::useless_vec)] diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index deae389dbefad..fd1568d94e3ec 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -1,5 +1,5 @@ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:13:13 + --> $DIR/nonminimal_bool.rs:18:13 | LL | let _ = !true; | ^^^^^ help: try: `false` @@ -8,43 +8,43 @@ LL | let _ = !true; = help: to override `-D warnings` add `#[allow(clippy::nonminimal_bool)]` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:16:13 + --> $DIR/nonminimal_bool.rs:21:13 | LL | let _ = !false; | ^^^^^^ help: try: `true` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:18:13 + --> $DIR/nonminimal_bool.rs:23:13 | LL | let _ = !!a; | ^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:20:13 + --> $DIR/nonminimal_bool.rs:25:13 | LL | let _ = false || a; | ^^^^^^^^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:25:13 + --> $DIR/nonminimal_bool.rs:30:13 | LL | let _ = !(!a && b); | ^^^^^^^^^^ help: try: `a || !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:27:13 + --> $DIR/nonminimal_bool.rs:32:13 | LL | let _ = !(!a || b); | ^^^^^^^^^^ help: try: `a && !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:29:13 + --> $DIR/nonminimal_bool.rs:34:13 | LL | let _ = !a && !(b && c); | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:38:13 + --> $DIR/nonminimal_bool.rs:43:13 | LL | let _ = a == b && c == 5 && a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,7 +57,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:40:13 + --> $DIR/nonminimal_bool.rs:45:13 | LL | let _ = a == b || c == 5 || a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -70,7 +70,7 @@ LL | let _ = a == b || c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:42:13 + --> $DIR/nonminimal_bool.rs:47:13 | LL | let _ = a == b && c == 5 && b == a; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,7 +83,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:44:13 + --> $DIR/nonminimal_bool.rs:49:13 | LL | let _ = a != b || !(a != b || c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL | let _ = a != b || c != d; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:46:13 + --> $DIR/nonminimal_bool.rs:51:13 | LL | let _ = a != b && !(a != b && c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL | let _ = a != b && c != d; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:77:8 + --> $DIR/nonminimal_bool.rs:82:8 | LL | if matches!(true, true) && true { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(true, true)` diff --git a/tests/ui/option_filter_map.fixed b/tests/ui/option_filter_map.fixed index ee004c0e194bb..b1c3cfa48a446 100644 --- a/tests/ui/option_filter_map.fixed +++ b/tests/ui/option_filter_map.fixed @@ -3,12 +3,18 @@ fn main() { let _ = Some(Some(1)).flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(Some(1)).flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(1).map(odds_out).flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(1).map(odds_out).flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![Some(1)].into_iter().flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![Some(1)].into_iter().flatten(); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![1] .into_iter() .map(odds_out) diff --git a/tests/ui/option_filter_map.rs b/tests/ui/option_filter_map.rs index eae2fa176a890..2550b9cd2b369 100644 --- a/tests/ui/option_filter_map.rs +++ b/tests/ui/option_filter_map.rs @@ -3,21 +3,29 @@ fn main() { let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(Some(1)).filter(|o| o.is_some()).map(|o| o.unwrap()); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(1).map(odds_out).filter(Option::is_some).map(Option::unwrap); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = Some(1).map(odds_out).filter(|o| o.is_some()).map(|o| o.unwrap()); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![Some(1)].into_iter().filter(Option::is_some).map(Option::unwrap); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()).map(|o| o.unwrap()); + //~^ ERROR: `filter` for `Some` followed by `unwrap` let _ = vec![1] .into_iter() .map(odds_out) .filter(Option::is_some) + //~^ ERROR: `filter` for `Some` followed by `unwrap` .map(Option::unwrap); let _ = vec![1] .into_iter() .map(odds_out) .filter(|o| o.is_some()) + //~^ ERROR: `filter` for `Some` followed by `unwrap` .map(|o| o.unwrap()); } diff --git a/tests/ui/option_filter_map.stderr b/tests/ui/option_filter_map.stderr index 148f9d02f5e16..6a0fc10822bdf 100644 --- a/tests/ui/option_filter_map.stderr +++ b/tests/ui/option_filter_map.stderr @@ -8,48 +8,50 @@ LL | let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap); = help: to override `-D warnings` add `#[allow(clippy::option_filter_map)]` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:6:27 + --> $DIR/option_filter_map.rs:7:27 | LL | let _ = Some(Some(1)).filter(|o| o.is_some()).map(|o| o.unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:7:35 + --> $DIR/option_filter_map.rs:9:35 | LL | let _ = Some(1).map(odds_out).filter(Option::is_some).map(Option::unwrap); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:8:35 + --> $DIR/option_filter_map.rs:11:35 | LL | let _ = Some(1).map(odds_out).filter(|o| o.is_some()).map(|o| o.unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:10:39 + --> $DIR/option_filter_map.rs:14:39 | LL | let _ = vec![Some(1)].into_iter().filter(Option::is_some).map(Option::unwrap); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:11:39 + --> $DIR/option_filter_map.rs:16:39 | LL | let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()).map(|o| o.unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:15:10 + --> $DIR/option_filter_map.rs:21:10 | LL | .filter(Option::is_some) | __________^ +LL | | LL | | .map(Option::unwrap); | |____________________________^ help: consider using `flatten` instead: `flatten()` error: `filter` for `Some` followed by `unwrap` - --> $DIR/option_filter_map.rs:20:10 + --> $DIR/option_filter_map.rs:27:10 | LL | .filter(|o| o.is_some()) | __________^ +LL | | LL | | .map(|o| o.unwrap()); | |____________________________^ help: consider using `flatten` instead: `flatten()` diff --git a/tests/ui/redundant_async_block.fixed b/tests/ui/redundant_async_block.fixed index d492ea1be753a..a1875c1c06e1a 100644 --- a/tests/ui/redundant_async_block.fixed +++ b/tests/ui/redundant_async_block.fixed @@ -1,7 +1,7 @@ #![allow(unused, clippy::manual_async_fn)] #![warn(clippy::redundant_async_block)] -use std::future::Future; +use std::future::{Future, IntoFuture}; async fn func1(n: usize) -> usize { n + 1 @@ -189,3 +189,9 @@ fn await_from_macro_deep() -> impl Future { // or return different things depending on its argument async { mac!(async { 42 }) } } + +// Issue 11959 +fn from_into_future(a: impl IntoFuture) -> impl Future { + // Do not lint: `a` is not equivalent to this expression + async { a.await } +} diff --git a/tests/ui/redundant_async_block.rs b/tests/ui/redundant_async_block.rs index dd96e14100682..bb43403a043ed 100644 --- a/tests/ui/redundant_async_block.rs +++ b/tests/ui/redundant_async_block.rs @@ -1,7 +1,7 @@ #![allow(unused, clippy::manual_async_fn)] #![warn(clippy::redundant_async_block)] -use std::future::Future; +use std::future::{Future, IntoFuture}; async fn func1(n: usize) -> usize { n + 1 @@ -189,3 +189,9 @@ fn await_from_macro_deep() -> impl Future { // or return different things depending on its argument async { mac!(async { 42 }) } } + +// Issue 11959 +fn from_into_future(a: impl IntoFuture) -> impl Future { + // Do not lint: `a` is not equivalent to this expression + async { a.await } +} diff --git a/tests/ui/redundant_pattern_matching_if_let_true.fixed b/tests/ui/redundant_pattern_matching_if_let_true.fixed new file mode 100644 index 0000000000000..6d91067893405 --- /dev/null +++ b/tests/ui/redundant_pattern_matching_if_let_true.fixed @@ -0,0 +1,38 @@ +#![warn(clippy::redundant_pattern_matching)] +#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] + +macro_rules! condition { + () => { + true + }; +} + +macro_rules! lettrue { + (if) => { + if let true = true {} + }; + (while) => { + while let true = true {} + }; +} + +fn main() { + let mut k = 5; + + if k > 1 {} + if !(k > 5) {} + if k > 1 {} + if let (true, true) = (k > 1, k > 2) {} + while k > 1 { + k += 1; + } + while condition!() { + k += 1; + } + + k > 5; + !(k > 5); + // Whole loop is from a macro expansion, don't lint: + lettrue!(if); + lettrue!(while); +} diff --git a/tests/ui/redundant_pattern_matching_if_let_true.rs b/tests/ui/redundant_pattern_matching_if_let_true.rs new file mode 100644 index 0000000000000..a82e673982a38 --- /dev/null +++ b/tests/ui/redundant_pattern_matching_if_let_true.rs @@ -0,0 +1,38 @@ +#![warn(clippy::redundant_pattern_matching)] +#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] + +macro_rules! condition { + () => { + true + }; +} + +macro_rules! lettrue { + (if) => { + if let true = true {} + }; + (while) => { + while let true = true {} + }; +} + +fn main() { + let mut k = 5; + + if let true = k > 1 {} + if let false = k > 5 {} + if let (true) = k > 1 {} + if let (true, true) = (k > 1, k > 2) {} + while let true = k > 1 { + k += 1; + } + while let true = condition!() { + k += 1; + } + + matches!(k > 5, true); + matches!(k > 5, false); + // Whole loop is from a macro expansion, don't lint: + lettrue!(if); + lettrue!(while); +} diff --git a/tests/ui/redundant_pattern_matching_if_let_true.stderr b/tests/ui/redundant_pattern_matching_if_let_true.stderr new file mode 100644 index 0000000000000..211a332d79a66 --- /dev/null +++ b/tests/ui/redundant_pattern_matching_if_let_true.stderr @@ -0,0 +1,47 @@ +error: using `if let` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:22:8 + | +LL | if let true = k > 1 {} + | ^^^^^^^^^^^^^^^^ help: consider using the condition directly: `k > 1` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]` + +error: using `if let` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:23:8 + | +LL | if let false = k > 5 {} + | ^^^^^^^^^^^^^^^^^ help: consider using the condition directly: `!(k > 5)` + +error: using `if let` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:24:8 + | +LL | if let (true) = k > 1 {} + | ^^^^^^^^^^^^^^^^^^ help: consider using the condition directly: `k > 1` + +error: using `if let` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:26:11 + | +LL | while let true = k > 1 { + | ^^^^^^^^^^^^^^^^ help: consider using the condition directly: `k > 1` + +error: using `if let` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:29:11 + | +LL | while let true = condition!() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the condition directly: `condition!()` + +error: using `matches!` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:33:5 + | +LL | matches!(k > 5, true); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using the condition directly: `k > 5` + +error: using `matches!` to pattern match a bool + --> $DIR/redundant_pattern_matching_if_let_true.rs:34:5 + | +LL | matches!(k > 5, false); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using the condition directly: `!(k > 5)` + +error: aborting due to 7 previous errors + diff --git a/tests/ui/redundant_pattern_matching_ipaddr.fixed b/tests/ui/redundant_pattern_matching_ipaddr.fixed index 70dd9fc250f77..429d33118a58f 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.fixed +++ b/tests/ui/redundant_pattern_matching_ipaddr.fixed @@ -18,6 +18,12 @@ fn main() { if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + // Issue 6459 + if V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + + // Issue 6459 + if V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + while V4(Ipv4Addr::LOCALHOST).is_ipv4() {} while V6(Ipv6Addr::LOCALHOST).is_ipv6() {} diff --git a/tests/ui/redundant_pattern_matching_ipaddr.rs b/tests/ui/redundant_pattern_matching_ipaddr.rs index 6e2a2f7b6d244..e7136b72c20c8 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.rs +++ b/tests/ui/redundant_pattern_matching_ipaddr.rs @@ -18,6 +18,12 @@ fn main() { if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} + // Issue 6459 + if matches!(V4(Ipv4Addr::LOCALHOST), V4(_)) {} + + // Issue 6459 + if matches!(V6(Ipv6Addr::LOCALHOST), V6(_)) {} + while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} diff --git a/tests/ui/redundant_pattern_matching_ipaddr.stderr b/tests/ui/redundant_pattern_matching_ipaddr.stderr index d36129a2bee95..54cefa5e82b1c 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.stderr +++ b/tests/ui/redundant_pattern_matching_ipaddr.stderr @@ -20,19 +20,31 @@ LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} | -------^^^^^-------------------------- help: try: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:21:15 + --> $DIR/redundant_pattern_matching_ipaddr.rs:22:8 + | +LL | if matches!(V4(Ipv4Addr::LOCALHOST), V4(_)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `V4(Ipv4Addr::LOCALHOST).is_ipv4()` + +error: redundant pattern matching, consider using `is_ipv6()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:25:8 + | +LL | if matches!(V6(Ipv6Addr::LOCALHOST), V6(_)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `V6(Ipv6Addr::LOCALHOST).is_ipv6()` + +error: redundant pattern matching, consider using `is_ipv4()` + --> $DIR/redundant_pattern_matching_ipaddr.rs:27:15 | LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} | ----------^^^^^-------------------------- help: try: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:23:15 + --> $DIR/redundant_pattern_matching_ipaddr.rs:29:15 | LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} | ----------^^^^^-------------------------- help: try: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:33:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:39:5 | LL | / match V4(Ipv4Addr::LOCALHOST) { LL | | V4(_) => true, @@ -41,7 +53,7 @@ LL | | }; | |_____^ help: try: `V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:38:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:44:5 | LL | / match V4(Ipv4Addr::LOCALHOST) { LL | | V4(_) => false, @@ -50,7 +62,7 @@ LL | | }; | |_____^ help: try: `V4(Ipv4Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:43:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:49:5 | LL | / match V6(Ipv6Addr::LOCALHOST) { LL | | V4(_) => false, @@ -59,7 +71,7 @@ LL | | }; | |_____^ help: try: `V6(Ipv6Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:48:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:54:5 | LL | / match V6(Ipv6Addr::LOCALHOST) { LL | | V4(_) => true, @@ -68,49 +80,49 @@ LL | | }; | |_____^ help: try: `V6(Ipv6Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:53:20 + --> $DIR/redundant_pattern_matching_ipaddr.rs:59:20 | LL | let _ = if let V4(_) = V4(Ipv4Addr::LOCALHOST) { | -------^^^^^-------------------------- help: try: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:61:20 + --> $DIR/redundant_pattern_matching_ipaddr.rs:67:20 | LL | let _ = if let V4(_) = gen_ipaddr() { | -------^^^^^--------------- help: try: `if gen_ipaddr().is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:63:19 + --> $DIR/redundant_pattern_matching_ipaddr.rs:69:19 | LL | } else if let V6(_) = gen_ipaddr() { | -------^^^^^--------------- help: try: `if gen_ipaddr().is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:75:12 + --> $DIR/redundant_pattern_matching_ipaddr.rs:81:12 | LL | if let V4(_) = V4(Ipv4Addr::LOCALHOST) {} | -------^^^^^-------------------------- help: try: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:77:12 + --> $DIR/redundant_pattern_matching_ipaddr.rs:83:12 | LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {} | -------^^^^^-------------------------- help: try: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:79:15 + --> $DIR/redundant_pattern_matching_ipaddr.rs:85:15 | LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {} | ----------^^^^^-------------------------- help: try: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:81:15 + --> $DIR/redundant_pattern_matching_ipaddr.rs:87:15 | LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {} | ----------^^^^^-------------------------- help: try: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()` error: redundant pattern matching, consider using `is_ipv4()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:83:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:89:5 | LL | / match V4(Ipv4Addr::LOCALHOST) { LL | | V4(_) => true, @@ -119,7 +131,7 @@ LL | | }; | |_____^ help: try: `V4(Ipv4Addr::LOCALHOST).is_ipv4()` error: redundant pattern matching, consider using `is_ipv6()` - --> $DIR/redundant_pattern_matching_ipaddr.rs:88:5 + --> $DIR/redundant_pattern_matching_ipaddr.rs:94:5 | LL | / match V6(Ipv6Addr::LOCALHOST) { LL | | V4(_) => false, @@ -127,5 +139,5 @@ LL | | V6(_) => true, LL | | }; | |_____^ help: try: `V6(Ipv6Addr::LOCALHOST).is_ipv6()` -error: aborting due to 18 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/redundant_pattern_matching_poll.fixed b/tests/ui/redundant_pattern_matching_poll.fixed index 718c2f8ea3ddd..08d83f87e4c39 100644 --- a/tests/ui/redundant_pattern_matching_poll.fixed +++ b/tests/ui/redundant_pattern_matching_poll.fixed @@ -22,6 +22,12 @@ fn main() { bar(); } + // Issue 6459 + if Ready(42).is_ready() {} + + // Issue 6459 + if Pending::<()>.is_pending() {} + while Ready(42).is_ready() {} while Ready(42).is_pending() {} diff --git a/tests/ui/redundant_pattern_matching_poll.rs b/tests/ui/redundant_pattern_matching_poll.rs index daa4761aff5e3..7bc2b3be4d346 100644 --- a/tests/ui/redundant_pattern_matching_poll.rs +++ b/tests/ui/redundant_pattern_matching_poll.rs @@ -22,6 +22,12 @@ fn main() { bar(); } + // Issue 6459 + if matches!(Ready(42), Ready(_)) {} + + // Issue 6459 + if matches!(Pending::<()>, Pending) {} + while let Ready(_) = Ready(42) {} while let Pending = Ready(42) {} diff --git a/tests/ui/redundant_pattern_matching_poll.stderr b/tests/ui/redundant_pattern_matching_poll.stderr index c010c3c44375a..de64331b4e8cf 100644 --- a/tests/ui/redundant_pattern_matching_poll.stderr +++ b/tests/ui/redundant_pattern_matching_poll.stderr @@ -20,25 +20,37 @@ LL | if let Ready(_) = Ready(42) { | -------^^^^^^^^------------ help: try: `if Ready(42).is_ready()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:25:15 + --> $DIR/redundant_pattern_matching_poll.rs:26:8 + | +LL | if matches!(Ready(42), Ready(_)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ready(42).is_ready()` + +error: redundant pattern matching, consider using `is_pending()` + --> $DIR/redundant_pattern_matching_poll.rs:29:8 + | +LL | if matches!(Pending::<()>, Pending) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Pending::<()>.is_pending()` + +error: redundant pattern matching, consider using `is_ready()` + --> $DIR/redundant_pattern_matching_poll.rs:31:15 | LL | while let Ready(_) = Ready(42) {} | ----------^^^^^^^^------------ help: try: `while Ready(42).is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:27:15 + --> $DIR/redundant_pattern_matching_poll.rs:33:15 | LL | while let Pending = Ready(42) {} | ----------^^^^^^^------------ help: try: `while Ready(42).is_pending()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:29:15 + --> $DIR/redundant_pattern_matching_poll.rs:35:15 | LL | while let Pending = Pending::<()> {} | ----------^^^^^^^---------------- help: try: `while Pending::<()>.is_pending()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:35:5 + --> $DIR/redundant_pattern_matching_poll.rs:41:5 | LL | / match Ready(42) { LL | | Ready(_) => true, @@ -47,7 +59,7 @@ LL | | }; | |_____^ help: try: `Ready(42).is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:40:5 + --> $DIR/redundant_pattern_matching_poll.rs:46:5 | LL | / match Pending::<()> { LL | | Ready(_) => false, @@ -56,7 +68,7 @@ LL | | }; | |_____^ help: try: `Pending::<()>.is_pending()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:45:13 + --> $DIR/redundant_pattern_matching_poll.rs:51:13 | LL | let _ = match Pending::<()> { | _____________^ @@ -66,49 +78,49 @@ LL | | }; | |_____^ help: try: `Pending::<()>.is_pending()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:51:20 + --> $DIR/redundant_pattern_matching_poll.rs:57:20 | LL | let _ = if let Ready(_) = poll { true } else { false }; | -------^^^^^^^^------- help: try: `if poll.is_ready()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:55:20 + --> $DIR/redundant_pattern_matching_poll.rs:61:20 | LL | let _ = if let Ready(_) = gen_poll() { | -------^^^^^^^^------------- help: try: `if gen_poll().is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:57:19 + --> $DIR/redundant_pattern_matching_poll.rs:63:19 | LL | } else if let Pending = gen_poll() { | -------^^^^^^^------------- help: try: `if gen_poll().is_pending()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:73:12 + --> $DIR/redundant_pattern_matching_poll.rs:79:12 | LL | if let Ready(_) = Ready(42) {} | -------^^^^^^^^------------ help: try: `if Ready(42).is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:75:12 + --> $DIR/redundant_pattern_matching_poll.rs:81:12 | LL | if let Pending = Pending::<()> {} | -------^^^^^^^---------------- help: try: `if Pending::<()>.is_pending()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:77:15 + --> $DIR/redundant_pattern_matching_poll.rs:83:15 | LL | while let Ready(_) = Ready(42) {} | ----------^^^^^^^^------------ help: try: `while Ready(42).is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:79:15 + --> $DIR/redundant_pattern_matching_poll.rs:85:15 | LL | while let Pending = Pending::<()> {} | ----------^^^^^^^---------------- help: try: `while Pending::<()>.is_pending()` error: redundant pattern matching, consider using `is_ready()` - --> $DIR/redundant_pattern_matching_poll.rs:81:5 + --> $DIR/redundant_pattern_matching_poll.rs:87:5 | LL | / match Ready(42) { LL | | Ready(_) => true, @@ -117,7 +129,7 @@ LL | | }; | |_____^ help: try: `Ready(42).is_ready()` error: redundant pattern matching, consider using `is_pending()` - --> $DIR/redundant_pattern_matching_poll.rs:86:5 + --> $DIR/redundant_pattern_matching_poll.rs:92:5 | LL | / match Pending::<()> { LL | | Ready(_) => false, @@ -125,5 +137,5 @@ LL | | Pending => true, LL | | }; | |_____^ help: try: `Pending::<()>.is_pending()` -error: aborting due to 18 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/result_filter_map.fixed b/tests/ui/result_filter_map.fixed new file mode 100644 index 0000000000000..e8943dbc72da5 --- /dev/null +++ b/tests/ui/result_filter_map.fixed @@ -0,0 +1,27 @@ +#![warn(clippy::result_filter_map)] +#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)] + +fn odds_out(x: i32) -> Result { + if x % 2 == 0 { Ok(x) } else { Err(()) } +} + +fn main() { + // Unlike the Option version, Result does not have a filter operation => we will check for iterators + // only + let _ = vec![Ok(1) as Result] + .into_iter() + .flatten(); + + let _ = vec![Ok(1) as Result] + .into_iter() + .flatten(); + + let _ = vec![1] + .into_iter() + .map(odds_out) + .flatten(); + let _ = vec![1] + .into_iter() + .map(odds_out) + .flatten(); +} diff --git a/tests/ui/result_filter_map.rs b/tests/ui/result_filter_map.rs new file mode 100644 index 0000000000000..bfe47ffcf380c --- /dev/null +++ b/tests/ui/result_filter_map.rs @@ -0,0 +1,35 @@ +#![warn(clippy::result_filter_map)] +#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)] + +fn odds_out(x: i32) -> Result { + if x % 2 == 0 { Ok(x) } else { Err(()) } +} + +fn main() { + // Unlike the Option version, Result does not have a filter operation => we will check for iterators + // only + let _ = vec![Ok(1) as Result] + .into_iter() + .filter(Result::is_ok) + //~^ ERROR: `filter` for `Ok` followed by `unwrap` + .map(Result::unwrap); + + let _ = vec![Ok(1) as Result] + .into_iter() + .filter(|o| o.is_ok()) + //~^ ERROR: `filter` for `Ok` followed by `unwrap` + .map(|o| o.unwrap()); + + let _ = vec![1] + .into_iter() + .map(odds_out) + .filter(Result::is_ok) + //~^ ERROR: `filter` for `Ok` followed by `unwrap` + .map(Result::unwrap); + let _ = vec![1] + .into_iter() + .map(odds_out) + .filter(|o| o.is_ok()) + //~^ ERROR: `filter` for `Ok` followed by `unwrap` + .map(|o| o.unwrap()); +} diff --git a/tests/ui/result_filter_map.stderr b/tests/ui/result_filter_map.stderr new file mode 100644 index 0000000000000..4687794949d34 --- /dev/null +++ b/tests/ui/result_filter_map.stderr @@ -0,0 +1,41 @@ +error: `filter` for `Ok` followed by `unwrap` + --> $DIR/result_filter_map.rs:13:10 + | +LL | .filter(Result::is_ok) + | __________^ +LL | | +LL | | .map(Result::unwrap); + | |____________________________^ help: consider using `flatten` instead: `flatten()` + | + = note: `-D clippy::result-filter-map` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::result_filter_map)]` + +error: `filter` for `Ok` followed by `unwrap` + --> $DIR/result_filter_map.rs:19:10 + | +LL | .filter(|o| o.is_ok()) + | __________^ +LL | | +LL | | .map(|o| o.unwrap()); + | |____________________________^ help: consider using `flatten` instead: `flatten()` + +error: `filter` for `Ok` followed by `unwrap` + --> $DIR/result_filter_map.rs:26:10 + | +LL | .filter(Result::is_ok) + | __________^ +LL | | +LL | | .map(Result::unwrap); + | |____________________________^ help: consider using `flatten` instead: `flatten()` + +error: `filter` for `Ok` followed by `unwrap` + --> $DIR/result_filter_map.rs:32:10 + | +LL | .filter(|o| o.is_ok()) + | __________^ +LL | | +LL | | .map(|o| o.unwrap()); + | |____________________________^ help: consider using `flatten` instead: `flatten()` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/unconditional_recursion.rs b/tests/ui/unconditional_recursion.rs new file mode 100644 index 0000000000000..1169118de83b5 --- /dev/null +++ b/tests/ui/unconditional_recursion.rs @@ -0,0 +1,163 @@ +//@no-rustfix + +#![warn(clippy::unconditional_recursion)] +#![allow(clippy::partialeq_ne_impl)] + +enum Foo { + A, + B, +} + +impl PartialEq for Foo { + fn ne(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self != other + } + fn eq(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self == other + } +} + +enum Foo2 { + A, + B, +} + +impl PartialEq for Foo2 { + fn ne(&self, other: &Self) -> bool { + self != &Foo2::B // no error here + } + fn eq(&self, other: &Self) -> bool { + self == &Foo2::B // no error here + } +} + +enum Foo3 { + A, + B, +} + +impl PartialEq for Foo3 { + fn ne(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self.ne(other) + } + fn eq(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self.eq(other) + } +} + +enum Foo4 { + A, + B, +} + +impl PartialEq for Foo4 { + fn ne(&self, other: &Self) -> bool { + self.eq(other) // no error + } + fn eq(&self, other: &Self) -> bool { + self.ne(other) // no error + } +} + +enum Foo5 { + A, + B, +} + +impl Foo5 { + fn a(&self) -> bool { + true + } +} + +impl PartialEq for Foo5 { + fn ne(&self, other: &Self) -> bool { + self.a() // no error + } + fn eq(&self, other: &Self) -> bool { + self.a() // no error + } +} + +struct S; + +// Check the order doesn't matter. +impl PartialEq for S { + fn ne(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + other != self + } + fn eq(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + other == self + } +} + +struct S2; + +// Check that if the same element is compared, it's also triggering the lint. +impl PartialEq for S2 { + fn ne(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + other != other + } + fn eq(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + other == other + } +} + +struct S3; + +impl PartialEq for S3 { + fn ne(&self, _other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self != self + } + fn eq(&self, _other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + self == self + } +} + +// There should be no warning here! +#[derive(PartialEq)] +enum E { + A, + B, +} + +#[derive(PartialEq)] +struct Bar(T); + +struct S4; + +impl PartialEq for S4 { + fn eq(&self, other: &Self) -> bool { + // No warning here. + Bar(self) == Bar(other) + } +} + +macro_rules! impl_partial_eq { + ($ty:ident) => { + impl PartialEq for $ty { + fn eq(&self, other: &Self) -> bool { + self == other + } + } + }; +} + +struct S5; + +impl_partial_eq!(S5); +//~^ ERROR: function cannot return without recursing + +fn main() { + // test code goes here +} diff --git a/tests/ui/unconditional_recursion.stderr b/tests/ui/unconditional_recursion.stderr new file mode 100644 index 0000000000000..1fb01c00f1952 --- /dev/null +++ b/tests/ui/unconditional_recursion.stderr @@ -0,0 +1,251 @@ +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:42:5 + | +LL | fn ne(&self, other: &Self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing +LL | +LL | self.ne(other) + | -------------- recursive call site + | + = help: a `loop` may express intention better if this is on purpose + = note: `-D unconditional-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unconditional_recursion)]` + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:46:5 + | +LL | fn eq(&self, other: &Self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing +LL | +LL | self.eq(other) + | -------------- recursive call site + | + = help: a `loop` may express intention better if this is on purpose + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:12:5 + | +LL | / fn ne(&self, other: &Self) -> bool { +LL | | +LL | | self != other +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:14:9 + | +LL | self != other + | ^^^^^^^^^^^^^ + = note: `-D clippy::unconditional-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unconditional_recursion)]` + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:16:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | +LL | | self == other +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:18:9 + | +LL | self == other + | ^^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:42:5 + | +LL | / fn ne(&self, other: &Self) -> bool { +LL | | +LL | | self.ne(other) +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:44:9 + | +LL | self.ne(other) + | ^^^^^^^^^^^^^^ + +error: parameter is only used in recursion + --> $DIR/unconditional_recursion.rs:42:18 + | +LL | fn ne(&self, other: &Self) -> bool { + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_other` + | +note: parameter used here + --> $DIR/unconditional_recursion.rs:44:17 + | +LL | self.ne(other) + | ^^^^^ + = note: `-D clippy::only-used-in-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::only_used_in_recursion)]` + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:46:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | +LL | | self.eq(other) +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:48:9 + | +LL | self.eq(other) + | ^^^^^^^^^^^^^^ + +error: parameter is only used in recursion + --> $DIR/unconditional_recursion.rs:46:18 + | +LL | fn eq(&self, other: &Self) -> bool { + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_other` + | +note: parameter used here + --> $DIR/unconditional_recursion.rs:48:17 + | +LL | self.eq(other) + | ^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:90:5 + | +LL | / fn ne(&self, other: &Self) -> bool { +LL | | +LL | | other != self +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:92:9 + | +LL | other != self + | ^^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:94:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | +LL | | other == self +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:96:9 + | +LL | other == self + | ^^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:104:5 + | +LL | / fn ne(&self, other: &Self) -> bool { +LL | | +LL | | other != other +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:106:9 + | +LL | other != other + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/unconditional_recursion.rs:106:9 + | +LL | other != other + | ^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:108:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | +LL | | other == other +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:110:9 + | +LL | other == other + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/unconditional_recursion.rs:110:9 + | +LL | other == other + | ^^^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:117:5 + | +LL | / fn ne(&self, _other: &Self) -> bool { +LL | | +LL | | self != self +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:119:9 + | +LL | self != self + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/unconditional_recursion.rs:119:9 + | +LL | self != self + | ^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:121:5 + | +LL | / fn eq(&self, _other: &Self) -> bool { +LL | | +LL | | self == self +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:123:9 + | +LL | self == self + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/unconditional_recursion.rs:123:9 + | +LL | self == self + | ^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:149:13 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | self == other +LL | | } + | |_____________^ +... +LL | impl_partial_eq!(S5); + | -------------------- in this macro invocation + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:150:17 + | +LL | self == other + | ^^^^^^^^^^^^^ +... +LL | impl_partial_eq!(S5); + | -------------------- in this macro invocation + = note: this error originates in the macro `impl_partial_eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 19 previous errors + diff --git a/tests/ui/unnecessary_to_owned_on_split.fixed b/tests/ui/unnecessary_to_owned_on_split.fixed new file mode 100644 index 0000000000000..53dc3c43e2f86 --- /dev/null +++ b/tests/ui/unnecessary_to_owned_on_split.fixed @@ -0,0 +1,21 @@ +#[allow(clippy::single_char_pattern)] + +fn main() { + let _ = "a".split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` + let _ = "a".split("a").next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` + let _ = "a".split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + let _ = "a".split("a").next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + + let _ = [1].split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_vec` + let _ = [1].split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_vec` + let _ = [1].split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + let _ = [1].split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` +} diff --git a/tests/ui/unnecessary_to_owned_on_split.rs b/tests/ui/unnecessary_to_owned_on_split.rs new file mode 100644 index 0000000000000..62400e7eee17b --- /dev/null +++ b/tests/ui/unnecessary_to_owned_on_split.rs @@ -0,0 +1,21 @@ +#[allow(clippy::single_char_pattern)] + +fn main() { + let _ = "a".to_string().split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` + let _ = "a".to_string().split("a").next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` + let _ = "a".to_owned().split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + let _ = "a".to_owned().split("a").next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + + let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_vec` + let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_vec` + let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` + let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); + //~^ ERROR: unnecessary use of `to_owned` +} diff --git a/tests/ui/unnecessary_to_owned_on_split.stderr b/tests/ui/unnecessary_to_owned_on_split.stderr new file mode 100644 index 0000000000000..cfb3766d15ed6 --- /dev/null +++ b/tests/ui/unnecessary_to_owned_on_split.stderr @@ -0,0 +1,53 @@ +error: unnecessary use of `to_string` + --> $DIR/unnecessary_to_owned_on_split.rs:4:13 + | +LL | let _ = "a".to_string().split('a').next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split('a')` + | + = note: `-D clippy::unnecessary-to-owned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_to_owned)]` + +error: unnecessary use of `to_string` + --> $DIR/unnecessary_to_owned_on_split.rs:6:13 + | +LL | let _ = "a".to_string().split("a").next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split("a")` + +error: unnecessary use of `to_owned` + --> $DIR/unnecessary_to_owned_on_split.rs:8:13 + | +LL | let _ = "a".to_owned().split('a').next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split('a')` + +error: unnecessary use of `to_owned` + --> $DIR/unnecessary_to_owned_on_split.rs:10:13 + | +LL | let _ = "a".to_owned().split("a").next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split("a")` + +error: unnecessary use of `to_vec` + --> $DIR/unnecessary_to_owned_on_split.rs:13:13 + | +LL | let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` + +error: unnecessary use of `to_vec` + --> $DIR/unnecessary_to_owned_on_split.rs:15:13 + | +LL | let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` + +error: unnecessary use of `to_owned` + --> $DIR/unnecessary_to_owned_on_split.rs:17:13 + | +LL | let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` + +error: unnecessary use of `to_owned` + --> $DIR/unnecessary_to_owned_on_split.rs:19:13 + | +LL | let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` + +error: aborting due to 8 previous errors + diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 99e211654d19c..a5274eeb349d1 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -566,8 +566,74 @@

- - Fork me on GitHub + + + From 3fceca23bb64e304df56b3bd86d26790b5301bdf Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 29 Dec 2023 05:16:53 +0000 Subject: [PATCH 16/73] Remove mitigations for incorrect node args --- clippy_lints/src/methods/unnecessary_to_owned.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 637368e936185..5f69cf6cd7ada 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -445,11 +445,12 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< { let bound_fn_sig = cx.tcx.fn_sig(callee_def_id); let fn_sig = bound_fn_sig.skip_binder(); - if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id) + if let Some(arg_index) = recv + .into_iter() + .chain(call_args) + .position(|arg| arg.hir_id == expr.hir_id) && let param_ty = fn_sig.input(arg_index).skip_binder() - && let ty::Param(ParamTy { index: param_index , ..}) = *param_ty.kind() - // https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021 - && (param_index as usize) < call_generic_args.len() + && let ty::Param(ParamTy { index: param_index, .. }) = *param_ty.kind() { if fn_sig .skip_binder() From fa7dd1c4e0106ce0346236801653188689dbaa43 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 17 Dec 2023 15:55:06 -0500 Subject: [PATCH 17/73] add new lint, pub_underscore_fields - add a new late pass lint, with config options - add ui tests for both variations of config option - update CHANGELOG.md github feedback bump version to 1.77 and run cargo collect-metadata Change `,` to `;` in `conf.rs` --- CHANGELOG.md | 2 + book/src/lint_configuration.md | 10 +++ clippy_config/src/conf.rs | 7 +- clippy_config/src/types.rs | 6 ++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 7 ++ clippy_lints/src/pub_underscore_fields.rs | 83 +++++++++++++++++++ .../all_pub_fields/clippy.toml | 1 + .../exported/clippy.toml | 1 + ...ub_underscore_fields.all_pub_fields.stderr | 60 ++++++++++++++ .../pub_underscore_fields.exported.stderr | 12 +++ .../pub_underscore_fields.rs | 66 +++++++++++++++ .../toml_unknown_key/conf_unknown_key.stderr | 2 + 13 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 clippy_lints/src/pub_underscore_fields.rs create mode 100644 tests/ui-toml/pub_underscore_fields/all_pub_fields/clippy.toml create mode 100644 tests/ui-toml/pub_underscore_fields/exported/clippy.toml create mode 100644 tests/ui-toml/pub_underscore_fields/pub_underscore_fields.all_pub_fields.stderr create mode 100644 tests/ui-toml/pub_underscore_fields/pub_underscore_fields.exported.stderr create mode 100644 tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f82421a687b82..0ac7297f8f1b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5483,6 +5483,7 @@ Released 2018-09-13 [`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq [`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast [`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names +[`pub_underscore_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields [`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use [`pub_with_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_with_shorthand [`pub_without_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_without_shorthand @@ -5810,4 +5811,5 @@ Released 2018-09-13 [`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles [`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow [`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items +[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 7c9a8eb1bfba1..3b62ae0524ab0 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -805,3 +805,13 @@ for _ in &mut *rmvec {} * [`missing_errors_doc`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc) +## `pub-underscore-fields-behavior` + + +**Default Value:** `"PublicallyExported"` + +--- +**Affected lints:** +* [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields) + + diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 67ea0af07e6fe..77d7f1760c996 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -1,5 +1,5 @@ use crate::msrvs::Msrv; -use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, Rename}; +use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename}; use crate::ClippyConfiguration; use rustc_data_structures::fx::FxHashSet; use rustc_session::Session; @@ -547,6 +547,11 @@ define_Conf! { /// /// Whether to also run the listed lints on private items. (check_private_items: bool = false), + /// Lint: PUB_UNDERSCORE_FIELDS + /// + /// Lint "public" fields in a struct that are prefixed with an underscore based on their + /// exported visibility; or whether they are marked as "pub". + (pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PublicallyExported), } /// Search for the configuration file. diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index df48cc3f5e391..baee09629ac44 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -126,3 +126,9 @@ unimplemented_serialize! { Rename, MacroMatcher, } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum PubUnderscoreFieldsBehaviour { + PublicallyExported, + AllPubFields, +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index eae9dfac064e8..9dd22135d95cc 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -576,6 +576,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::ptr::MUT_FROM_REF_INFO, crate::ptr::PTR_ARG_INFO, crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, + crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO, crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, crate::question_mark_used::QUESTION_MARK_USED_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 755a4ff525d2e..712fe73f6a16d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -272,6 +272,7 @@ mod permissions_set_readonly_false; mod precedence; mod ptr; mod ptr_offset_with_cast; +mod pub_underscore_fields; mod pub_use; mod question_mark; mod question_mark_used; @@ -571,6 +572,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { verbose_bit_mask_threshold, warn_on_all_wildcard_imports, check_private_items, + pub_underscore_fields_behavior, blacklisted_names: _, cyclomatic_complexity_threshold: _, @@ -1081,6 +1083,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)); store.register_late_pass(|_| Box::new(unconditional_recursion::UnconditionalRecursion)); + store.register_late_pass(move |_| { + Box::new(pub_underscore_fields::PubUnderscoreFields { + behavior: pub_underscore_fields_behavior, + }) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/pub_underscore_fields.rs b/clippy_lints/src/pub_underscore_fields.rs new file mode 100644 index 0000000000000..00465ce43813e --- /dev/null +++ b/clippy_lints/src/pub_underscore_fields.rs @@ -0,0 +1,83 @@ +use clippy_config::types::PubUnderscoreFieldsBehaviour; +use clippy_utils::attrs::is_doc_hidden; +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_path_lang_item; +use rustc_hir::{FieldDef, Item, ItemKind, LangItem}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks whether any field of the struct is prefixed with an `_` (underscore) and also marked + /// `pub` (public) + /// + /// ### Why is this bad? + /// Fields prefixed with an `_` are inferred as unused, which suggests it should not be marked + /// as `pub`, because marking it as `pub` infers it will be used. + /// + /// ### Example + /// ```rust + /// struct FileHandle { + /// pub _descriptor: usize, + /// } + /// ``` + /// Use instead: + /// ```rust + /// struct FileHandle { + /// _descriptor: usize, + /// } + /// ``` + /// + /// OR + /// + /// ```rust + /// struct FileHandle { + /// pub descriptor: usize, + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub PUB_UNDERSCORE_FIELDS, + pedantic, + "struct field prefixed with underscore and marked public" +} + +pub struct PubUnderscoreFields { + pub behavior: PubUnderscoreFieldsBehaviour, +} +impl_lint_pass!(PubUnderscoreFields => [PUB_UNDERSCORE_FIELDS]); + +impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + // This lint only pertains to structs. + let ItemKind::Struct(variant_data, _) = &item.kind else { + return; + }; + + let is_visible = |field: &FieldDef<'_>| match self.behavior { + PubUnderscoreFieldsBehaviour::PublicallyExported => cx.effective_visibilities.is_reachable(field.def_id), + PubUnderscoreFieldsBehaviour::AllPubFields => { + // If there is a visibility span then the field is marked pub in some way. + !field.vis_span.is_empty() + }, + }; + + for field in variant_data.fields() { + // Only pertains to fields that start with an underscore, and are public. + if field.ident.as_str().starts_with('_') && is_visible(field) + // We ignore fields that have `#[doc(hidden)]`. + && !is_doc_hidden(cx.tcx.hir().attrs(field.hir_id)) + // We ignore fields that are `PhantomData`. + && !is_path_lang_item(cx, field.ty, LangItem::PhantomData) + { + span_lint_and_help( + cx, + PUB_UNDERSCORE_FIELDS, + field.vis_span.to(field.ident.span), + "field marked as public but also inferred as unused because it's prefixed with `_`", + None, + "consider removing the underscore, or making the field private", + ); + } + } + } +} diff --git a/tests/ui-toml/pub_underscore_fields/all_pub_fields/clippy.toml b/tests/ui-toml/pub_underscore_fields/all_pub_fields/clippy.toml new file mode 100644 index 0000000000000..95835d101b1d4 --- /dev/null +++ b/tests/ui-toml/pub_underscore_fields/all_pub_fields/clippy.toml @@ -0,0 +1 @@ +pub-underscore-fields-behavior = "AllPubFields" \ No newline at end of file diff --git a/tests/ui-toml/pub_underscore_fields/exported/clippy.toml b/tests/ui-toml/pub_underscore_fields/exported/clippy.toml new file mode 100644 index 0000000000000..94a0d3554bc7b --- /dev/null +++ b/tests/ui-toml/pub_underscore_fields/exported/clippy.toml @@ -0,0 +1 @@ +pub-underscore-fields-behavior = "PublicallyExported" \ No newline at end of file diff --git a/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.all_pub_fields.stderr b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.all_pub_fields.stderr new file mode 100644 index 0000000000000..c6bd499fd8cb3 --- /dev/null +++ b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.all_pub_fields.stderr @@ -0,0 +1,60 @@ +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:15:9 + | +LL | pub _b: u8, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + = note: `-D clippy::pub-underscore-fields` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]` + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:23:13 + | +LL | pub(in crate::inner) _f: Option<()>, + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:27:13 + | +LL | pub _g: String, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:34:9 + | +LL | pub _a: usize, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:41:9 + | +LL | pub _c: i64, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:44:9 + | +LL | pub _e: Option, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:57:9 + | +LL | pub(crate) _b: Option, + | ^^^^^^^^^^^^^ + | + = help: consider removing the underscore, or making the field private + +error: aborting due to 7 previous errors + diff --git a/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.exported.stderr b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.exported.stderr new file mode 100644 index 0000000000000..b76f6b3d38822 --- /dev/null +++ b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.exported.stderr @@ -0,0 +1,12 @@ +error: field marked as public but also inferred as unused because it's prefixed with `_` + --> $DIR/pub_underscore_fields.rs:15:9 + | +LL | pub _b: u8, + | ^^^^^^ + | + = help: consider removing the underscore, or making the field private + = note: `-D clippy::pub-underscore-fields` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs new file mode 100644 index 0000000000000..1d8fee7daad2d --- /dev/null +++ b/tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs @@ -0,0 +1,66 @@ +//@revisions: exported all_pub_fields +//@[all_pub_fields] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/all_pub_fields +//@[exported] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/exported + +#![allow(unused)] +#![warn(clippy::pub_underscore_fields)] + +use std::marker::PhantomData; + +pub mod inner { + use std::marker; + + pub struct PubSuper { + pub(super) a: usize, + pub _b: u8, + _c: i32, + pub _mark: marker::PhantomData, + } + + mod inner2 { + pub struct PubModVisibility { + pub(in crate::inner) e: bool, + pub(in crate::inner) _f: Option<()>, + } + + struct PrivateStructPubField { + pub _g: String, + } + } +} + +fn main() { + pub struct StructWithOneViolation { + pub _a: usize, + } + + // should handle structs with multiple violations + pub struct StructWithMultipleViolations { + a: u8, + _b: usize, + pub _c: i64, + #[doc(hidden)] + pub d: String, + pub _e: Option, + } + + // shouldn't warn on anonymous fields + pub struct AnonymousFields(pub usize, i32); + + // don't warn on empty structs + pub struct Empty1; + pub struct Empty2(); + pub struct Empty3 {}; + + pub struct PubCrate { + pub(crate) a: String, + pub(crate) _b: Option, + } + + // shouldn't warn on fields named pub + pub struct NamedPub { + r#pub: bool, + _pub: String, + pub(crate) _mark: PhantomData, + } +} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 12828cf9dec5f..ed4fb84df1ae1 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -49,6 +49,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect missing-docs-in-crate-items msrv pass-by-value-size-limit + pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold @@ -124,6 +125,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect missing-docs-in-crate-items msrv pass-by-value-size-limit + pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold From 1ee9993a962d9f2990bbdac91c792ca893f10b54 Mon Sep 17 00:00:00 2001 From: Aneesh Kadiyala <143342960+ARandomDev99@users.noreply.github.com> Date: Fri, 29 Dec 2023 19:23:31 +0530 Subject: [PATCH 18/73] add new lint, empty_enum_variants_with_brackets - Add a new early pass lint. - Add relevant UI tests. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 3 +- ...ith_brackets.rs => empty_with_brackets.rs} | 56 ++++++++++++++++++- clippy_lints/src/lib.rs | 4 +- .../empty_enum_variants_with_brackets.fixed | 27 +++++++++ tests/ui/empty_enum_variants_with_brackets.rs | 27 +++++++++ .../empty_enum_variants_with_brackets.stderr | 36 ++++++++++++ 7 files changed, 148 insertions(+), 6 deletions(-) rename clippy_lints/src/{empty_structs_with_brackets.rs => empty_with_brackets.rs} (62%) create mode 100644 tests/ui/empty_enum_variants_with_brackets.fixed create mode 100644 tests/ui/empty_enum_variants_with_brackets.rs create mode 100644 tests/ui/empty_enum_variants_with_brackets.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac7297f8f1b3..931517e72f6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5105,6 +5105,7 @@ Released 2018-09-13 [`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else [`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop [`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum +[`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets [`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments [`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr [`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 9dd22135d95cc..b606bb4aa33d6 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -150,7 +150,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, crate::empty_enum::EMPTY_ENUM_INFO, - crate::empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO, + crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, + crate::empty_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO, crate::endian_bytes::BIG_ENDIAN_BYTES_INFO, crate::endian_bytes::HOST_ENDIAN_BYTES_INFO, crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO, diff --git a/clippy_lints/src/empty_structs_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs similarity index 62% rename from clippy_lints/src/empty_structs_with_brackets.rs rename to clippy_lints/src/empty_with_brackets.rs index 3cf67b3ecbfa4..52a88324fc3ce 100644 --- a/clippy_lints/src/empty_structs_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; -use rustc_ast::ast::{Item, ItemKind, VariantData}; +use rustc_ast::ast::{Item, ItemKind, Variant, VariantData}; use rustc_errors::Applicability; use rustc_lexer::TokenKind; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -27,9 +27,38 @@ declare_clippy_lint! { restriction, "finds struct declarations with empty brackets" } -declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]); -impl EarlyLintPass for EmptyStructsWithBrackets { +declare_clippy_lint! { + /// ### What it does + /// Finds enum variants without fields that are declared with empty brackets. + /// + /// ### Why is this bad? + /// Empty brackets while defining enum variants are redundant and can be omitted. + /// + /// ### Example + /// ```no_run + /// enum MyEnum { + /// HasData(u8), + /// HasNoData(), // redundant parentheses + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// enum MyEnum { + /// HasData(u8), + /// HasNoData, + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub EMPTY_ENUM_VARIANTS_WITH_BRACKETS, + restriction, + "finds enum variants with empty brackets" +} + +declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]); + +impl EarlyLintPass for EmptyWithBrackets { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { let span_after_ident = item.span.with_lo(item.ident.span.hi()); @@ -53,6 +82,27 @@ impl EarlyLintPass for EmptyStructsWithBrackets { ); } } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) { + let span_after_ident = variant.span.with_lo(variant.ident.span.hi()); + + if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) { + span_lint_and_then( + cx, + EMPTY_ENUM_VARIANTS_WITH_BRACKETS, + span_after_ident, + "enum variant has empty brackets", + |diagnostic| { + diagnostic.span_suggestion_hidden( + span_after_ident, + "remove the brackets", + "", + Applicability::MachineApplicable, + ); + }, + ); + } + } } fn has_no_ident_token(braces_span_str: &str) -> bool { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 712fe73f6a16d..dc24243b57479 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -115,7 +115,7 @@ mod duplicate_mod; mod else_if_without_else; mod empty_drop; mod empty_enum; -mod empty_structs_with_brackets; +mod empty_with_brackets; mod endian_bytes; mod entry; mod enum_clike; @@ -949,7 +949,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }) }); store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); - store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets)); + store.register_early_pass(|| Box::new(empty_with_brackets::EmptyWithBrackets)); store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); diff --git a/tests/ui/empty_enum_variants_with_brackets.fixed b/tests/ui/empty_enum_variants_with_brackets.fixed new file mode 100644 index 0000000000000..1a5e78dd47fef --- /dev/null +++ b/tests/ui/empty_enum_variants_with_brackets.fixed @@ -0,0 +1,27 @@ +#![warn(clippy::empty_enum_variants_with_brackets)] +#![allow(dead_code)] + +pub enum PublicTestEnum { + NonEmptyBraces { x: i32, y: i32 }, // No error + NonEmptyParentheses(i32, i32), // No error + EmptyBraces, //~ ERROR: enum variant has empty brackets + EmptyParentheses, //~ ERROR: enum variant has empty brackets +} + +enum TestEnum { + NonEmptyBraces { x: i32, y: i32 }, // No error + NonEmptyParentheses(i32, i32), // No error + EmptyBraces, //~ ERROR: enum variant has empty brackets + EmptyParentheses, //~ ERROR: enum variant has empty brackets + AnotherEnum, // No error +} + +enum TestEnumWithFeatures { + NonEmptyBraces { + #[cfg(feature = "thisisneverenabled")] + x: i32, + }, // No error + NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error +} + +fn main() {} diff --git a/tests/ui/empty_enum_variants_with_brackets.rs b/tests/ui/empty_enum_variants_with_brackets.rs new file mode 100644 index 0000000000000..ca20b969a240a --- /dev/null +++ b/tests/ui/empty_enum_variants_with_brackets.rs @@ -0,0 +1,27 @@ +#![warn(clippy::empty_enum_variants_with_brackets)] +#![allow(dead_code)] + +pub enum PublicTestEnum { + NonEmptyBraces { x: i32, y: i32 }, // No error + NonEmptyParentheses(i32, i32), // No error + EmptyBraces {}, //~ ERROR: enum variant has empty brackets + EmptyParentheses(), //~ ERROR: enum variant has empty brackets +} + +enum TestEnum { + NonEmptyBraces { x: i32, y: i32 }, // No error + NonEmptyParentheses(i32, i32), // No error + EmptyBraces {}, //~ ERROR: enum variant has empty brackets + EmptyParentheses(), //~ ERROR: enum variant has empty brackets + AnotherEnum, // No error +} + +enum TestEnumWithFeatures { + NonEmptyBraces { + #[cfg(feature = "thisisneverenabled")] + x: i32, + }, // No error + NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error +} + +fn main() {} diff --git a/tests/ui/empty_enum_variants_with_brackets.stderr b/tests/ui/empty_enum_variants_with_brackets.stderr new file mode 100644 index 0000000000000..6b2f286147fab --- /dev/null +++ b/tests/ui/empty_enum_variants_with_brackets.stderr @@ -0,0 +1,36 @@ +error: enum variant has empty brackets + --> $DIR/empty_enum_variants_with_brackets.rs:7:16 + | +LL | EmptyBraces {}, + | ^^^ + | + = note: `-D clippy::empty-enum-variants-with-brackets` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::empty_enum_variants_with_brackets)]` + = help: remove the brackets + +error: enum variant has empty brackets + --> $DIR/empty_enum_variants_with_brackets.rs:8:21 + | +LL | EmptyParentheses(), + | ^^ + | + = help: remove the brackets + +error: enum variant has empty brackets + --> $DIR/empty_enum_variants_with_brackets.rs:14:16 + | +LL | EmptyBraces {}, + | ^^^ + | + = help: remove the brackets + +error: enum variant has empty brackets + --> $DIR/empty_enum_variants_with_brackets.rs:15:21 + | +LL | EmptyParentheses(), + | ^^ + | + = help: remove the brackets + +error: aborting due to 4 previous errors + From ca40b0c284a54676ee9e9b560f5ea43b51dfbfbc Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 30 Dec 2023 01:58:30 +0800 Subject: [PATCH 19/73] fix/issue#11243: allow 3-digit-grouped binary in non_octal_unix_permissions --- .../src/non_octal_unix_permissions.rs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 49e9e2c00ccb0..7143ac9cfc174 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -1,6 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::{match_def_path, paths}; +use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -39,6 +41,24 @@ declare_clippy_lint! { declare_lint_pass!(NonOctalUnixPermissions => [NON_OCTAL_UNIX_PERMISSIONS]); +fn check_binary_unix_permissions(lit_kind: &LitKind, snip: &str) -> bool { + // support binary unix permissions + if let Some(num_lit) = NumericLiteral::from_lit_kind(snip, lit_kind) { + if num_lit.radix != Radix::Binary { + return false; + } + + let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); + // check whether is binary format unix permissions + if group_sizes.len() != 3 { + return false; + } + group_sizes.iter().all(|len| *len == 3) + } else { + false + } +} + impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { match &expr.kind { @@ -51,26 +71,22 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { )) || (path.ident.name == sym!(set_mode) && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) - && let ExprKind::Lit(_) = param.kind + && let ExprKind::Lit(lit_kind) = param.kind && param.span.eq_ctxt(expr.span) + && let Some(snip) = snippet_opt(cx, param.span) + && !(snip.starts_with("0o") || check_binary_unix_permissions(&lit_kind.node, &snip)) { - let Some(snip) = snippet_opt(cx, param.span) else { - return; - }; - - if !snip.starts_with("0o") { - show_error(cx, param); - } + show_error(cx, param); } }, ExprKind::Call(func, [param]) => { if let ExprKind::Path(ref path) = func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE) - && let ExprKind::Lit(_) = param.kind + && let ExprKind::Lit(lit_kind) = param.kind && param.span.eq_ctxt(expr.span) && let Some(snip) = snippet_opt(cx, param.span) - && !snip.starts_with("0o") + && !(snip.starts_with("0o") || check_binary_unix_permissions(&lit_kind.node, &snip)) { show_error(cx, param); } From 95f8f1eed26c52f3885a0d429976cf1de084e267 Mon Sep 17 00:00:00 2001 From: mikkoglinn678 <66647152+mikkoglinn678@users.noreply.github.com> Date: Fri, 29 Dec 2023 20:46:37 -0600 Subject: [PATCH 20/73] fix typo in creating early lint pass --- clippy_dev/src/new_lint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 31a42734c1310..5d9cde06cd86b 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -67,7 +67,7 @@ pub fn create( if pass == "early" { println!( "\n\ - NOTE: Use a late pass unless you need something specific from\ + NOTE: Use a late pass unless you need something specific from\n\ an early pass, as they lack many features and utilities" ); } From 0848e120b266517c661aed984838e4e6ca5afdd6 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 30 Dec 2023 04:11:12 +0100 Subject: [PATCH 21/73] add expansion checks to `iter_without_into_iter` and `into_iter_without_iter` --- clippy_lints/src/iter_without_into_iter.rs | 9 +++-- tests/ui/into_iter_without_iter.rs | 39 +++++++++++++++++++++ tests/ui/into_iter_without_iter.stderr | 38 ++++++++++++++++---- tests/ui/iter_without_into_iter.rs | 31 +++++++++++++++++ tests/ui/iter_without_into_iter.stderr | 40 +++++++++++++++++----- 5 files changed, 140 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index c9dc48668f29d..903d3a2ab8960 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -5,7 +5,8 @@ use clippy_utils::ty::{implements_trait, make_normalized_projection}; use rustc_ast::Mutability; use rustc_errors::Applicability; use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, ItemKind, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; use rustc_span::{sym, Symbol}; @@ -152,7 +153,8 @@ fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol impl LateLintPass<'_> for IterWithoutIntoIter { fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { - if let ItemKind::Impl(imp) = item.kind + if !in_external_macro(cx.sess(), item.span) + && let ItemKind::Impl(imp) = item.kind && let TyKind::Ref(_, self_ty_without_ref) = &imp.self_ty.kind && let Some(trait_ref) = imp.of_trait && trait_ref @@ -219,7 +221,8 @@ impl {self_ty_without_ref} {{ _ => return, }; - if let ImplItemKind::Fn(sig, _) = item.kind + if !in_external_macro(cx.sess(), item.span) + && let ImplItemKind::Fn(sig, _) = item.kind && let FnRetTy::Return(ret) = sig.decl.output && is_nameable_in_impl_trait(ret) && cx.tcx.generics_of(item_did).params.is_empty() diff --git a/tests/ui/into_iter_without_iter.rs b/tests/ui/into_iter_without_iter.rs index 448d0114dff82..c8b9076041a31 100644 --- a/tests/ui/into_iter_without_iter.rs +++ b/tests/ui/into_iter_without_iter.rs @@ -1,5 +1,7 @@ //@no-rustfix +//@aux-build:proc_macros.rs #![warn(clippy::into_iter_without_iter)] +extern crate proc_macros; use std::iter::IntoIterator; @@ -111,6 +113,43 @@ impl IntoIterator for &Alias { } } +// Fine to lint, the impls comes from a local macro. +pub struct Issue12037; +macro_rules! generate_impl { + () => { + impl<'a> IntoIterator for &'a Issue12037 { + type IntoIter = std::slice::Iter<'a, u8>; + type Item = &'a u8; + fn into_iter(self) -> Self::IntoIter { + todo!() + } + } + }; +} +generate_impl!(); + +// Impl comes from an external crate +proc_macros::external! { + pub struct ImplWithForeignSpan; + impl<'a> IntoIterator for &'a ImplWithForeignSpan { + type IntoIter = std::slice::Iter<'a, u8>; + type Item = &'a u8; + fn into_iter(self) -> Self::IntoIter { + todo!() + } + } +} + +pub struct Allowed; +#[allow(clippy::into_iter_without_iter)] +impl<'a> IntoIterator for &'a Allowed { + type IntoIter = std::slice::Iter<'a, u8>; + type Item = &'a u8; + fn into_iter(self) -> Self::IntoIter { + todo!() + } +} + fn main() {} pub mod issue11635 { diff --git a/tests/ui/into_iter_without_iter.stderr b/tests/ui/into_iter_without_iter.stderr index 70f3f82a936c6..a232c7cecc577 100644 --- a/tests/ui/into_iter_without_iter.stderr +++ b/tests/ui/into_iter_without_iter.stderr @@ -1,5 +1,5 @@ error: `IntoIterator` implemented for a reference type without an `iter` method - --> $DIR/into_iter_without_iter.rs:7:1 + --> $DIR/into_iter_without_iter.rs:9:1 | LL | / impl<'a> IntoIterator for &'a S1 { LL | | @@ -23,7 +23,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> $DIR/into_iter_without_iter.rs:15:1 + --> $DIR/into_iter_without_iter.rs:17:1 | LL | / impl<'a> IntoIterator for &'a mut S1 { LL | | @@ -45,7 +45,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter` method - --> $DIR/into_iter_without_iter.rs:25:1 + --> $DIR/into_iter_without_iter.rs:27:1 | LL | / impl<'a, T> IntoIterator for &'a S2 { LL | | @@ -67,7 +67,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> $DIR/into_iter_without_iter.rs:33:1 + --> $DIR/into_iter_without_iter.rs:35:1 | LL | / impl<'a, T> IntoIterator for &'a mut S2 { LL | | @@ -89,7 +89,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> $DIR/into_iter_without_iter.rs:84:1 + --> $DIR/into_iter_without_iter.rs:86:1 | LL | / impl<'a, T> IntoIterator for &mut S4<'a, T> { LL | | @@ -110,5 +110,31 @@ LL + } LL + } | -error: aborting due to 5 previous errors +error: `IntoIterator` implemented for a reference type without an `iter` method + --> $DIR/into_iter_without_iter.rs:120:9 + | +LL | / impl<'a> IntoIterator for &'a Issue12037 { +LL | | type IntoIter = std::slice::Iter<'a, u8>; +LL | | type Item = &'a u8; +LL | | fn into_iter(self) -> Self::IntoIter { +LL | | todo!() +LL | | } +LL | | } + | |_________^ +... +LL | generate_impl!(); + | ---------------- in this macro invocation + | + = note: this error originates in the macro `generate_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider implementing `iter` + | +LL ~ +LL + impl Issue12037 { +LL + fn iter(&self) -> std::slice::Iter<'a, u8> { +LL + <&Self as IntoIterator>::into_iter(self) +LL + } +LL + } + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/iter_without_into_iter.rs b/tests/ui/iter_without_into_iter.rs index 29f526b455cb9..3054d848efb7d 100644 --- a/tests/ui/iter_without_into_iter.rs +++ b/tests/ui/iter_without_into_iter.rs @@ -1,5 +1,7 @@ //@no-rustfix +//@aux-build:proc_macros.rs #![warn(clippy::iter_without_into_iter)] +extern crate proc_macros; pub struct S1; impl S1 { @@ -121,4 +123,33 @@ impl S12 { } } +pub struct Issue12037; +macro_rules! generate_impl { + () => { + impl Issue12037 { + fn iter(&self) -> std::slice::Iter<'_, u8> { + todo!() + } + } + }; +} +generate_impl!(); + +proc_macros::external! { + pub struct ImplWithForeignSpan; + impl ImplWithForeignSpan { + fn iter(&self) -> std::slice::Iter<'_, u8> { + todo!() + } + } +} + +pub struct Allowed; +impl Allowed { + #[allow(clippy::iter_without_into_iter)] + pub fn iter(&self) -> std::slice::Iter<'_, u8> { + todo!() + } +} + fn main() {} diff --git a/tests/ui/iter_without_into_iter.stderr b/tests/ui/iter_without_into_iter.stderr index af5afd47bfc41..4cf20e2aa56c9 100644 --- a/tests/ui/iter_without_into_iter.stderr +++ b/tests/ui/iter_without_into_iter.stderr @@ -1,5 +1,5 @@ error: `iter` method without an `IntoIterator` impl for `&S1` - --> $DIR/iter_without_into_iter.rs:6:5 + --> $DIR/iter_without_into_iter.rs:8:5 | LL | / pub fn iter(&self) -> std::slice::Iter<'_, u8> { LL | | @@ -22,7 +22,7 @@ LL + } | error: `iter_mut` method without an `IntoIterator` impl for `&mut S1` - --> $DIR/iter_without_into_iter.rs:10:5 + --> $DIR/iter_without_into_iter.rs:12:5 | LL | / pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, u8> { LL | | @@ -43,7 +43,7 @@ LL + } | error: `iter` method without an `IntoIterator` impl for `&S3<'a>` - --> $DIR/iter_without_into_iter.rs:26:5 + --> $DIR/iter_without_into_iter.rs:28:5 | LL | / pub fn iter(&self) -> std::slice::Iter<'_, u8> { LL | | @@ -64,7 +64,7 @@ LL + } | error: `iter_mut` method without an `IntoIterator` impl for `&mut S3<'a>` - --> $DIR/iter_without_into_iter.rs:30:5 + --> $DIR/iter_without_into_iter.rs:32:5 | LL | / pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, u8> { LL | | @@ -85,7 +85,7 @@ LL + } | error: `iter` method without an `IntoIterator` impl for `&S8` - --> $DIR/iter_without_into_iter.rs:67:5 + --> $DIR/iter_without_into_iter.rs:69:5 | LL | / pub fn iter(&self) -> std::slice::Iter<'static, T> { LL | | todo!() @@ -105,7 +105,7 @@ LL + } | error: `iter` method without an `IntoIterator` impl for `&S9` - --> $DIR/iter_without_into_iter.rs:75:5 + --> $DIR/iter_without_into_iter.rs:77:5 | LL | / pub fn iter(&self) -> std::slice::Iter<'_, T> { LL | | @@ -126,7 +126,7 @@ LL + } | error: `iter_mut` method without an `IntoIterator` impl for `&mut S9` - --> $DIR/iter_without_into_iter.rs:79:5 + --> $DIR/iter_without_into_iter.rs:81:5 | LL | / pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> { LL | | @@ -146,5 +146,29 @@ LL + } LL + } | -error: aborting due to 7 previous errors +error: `iter` method without an `IntoIterator` impl for `&Issue12037` + --> $DIR/iter_without_into_iter.rs:130:13 + | +LL | / fn iter(&self) -> std::slice::Iter<'_, u8> { +LL | | todo!() +LL | | } + | |_____________^ +... +LL | generate_impl!(); + | ---------------- in this macro invocation + | + = note: this error originates in the macro `generate_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider implementing `IntoIterator` for `&Issue12037` + | +LL ~ +LL + impl IntoIterator for &Issue12037 { +LL + type IntoIter = std::slice::Iter<'_, u8>; +LL + type Item = &u8; +LL + fn into_iter(self) -> Self::IntoIter { +LL + self.iter() +LL + } +LL + } + | + +error: aborting due to 8 previous errors From c2b3f5c767e69a3788efb01633ae53f6adfcd6dd Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Sat, 30 Dec 2023 13:25:33 +0200 Subject: [PATCH 22/73] `identity_op` correctly suggests a deference for coerced references When `identity_op` identifies a `no_op`, provides a suggestion, it also checks the type of the type of the variable. If the variable is a reference that's been coerced into a value, e.g. ``` let x = &0i32; let _ = x + 0; ``` the suggestion will now use a derefence. This is done by identifying whether the variable is a reference to an integral value, and then whether it gets dereferenced. changelog: false positive: [`identity_op`]: corrected suggestion for reference coerced to value. fixes: #12050 --- clippy_lints/src/operators/identity_op.rs | 208 +++++++++++++--------- tests/ui/identity_op.fixed | 94 +++++++++- tests/ui/identity_op.rs | 94 +++++++++- tests/ui/identity_op.stderr | 154 +++++++++++----- 4 files changed, 426 insertions(+), 124 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 8ecb038627f5a..f671517c13461 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -18,82 +18,118 @@ pub(crate) fn check<'tcx>( right: &'tcx Expr<'_>, ) { if !is_allowed(cx, op, left, right) { - match op { - BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - check_op( - cx, - left, - 0, - expr.span, - peel_hir_expr_refs(right).0.span, - needs_parenthesis(cx, expr, right), - ); - check_op( - cx, - right, - 0, - expr.span, - peel_hir_expr_refs(left).0.span, - Parens::Unneeded, - ); - }, - BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - check_op( - cx, - right, - 0, - expr.span, - peel_hir_expr_refs(left).0.span, - Parens::Unneeded, - ); - }, - BinOpKind::Mul => { - check_op( - cx, - left, - 1, - expr.span, - peel_hir_expr_refs(right).0.span, - needs_parenthesis(cx, expr, right), - ); - check_op( - cx, - right, - 1, - expr.span, - peel_hir_expr_refs(left).0.span, - Parens::Unneeded, - ); - }, - BinOpKind::Div => check_op( + return; + } + + // we need to know whether a ref is coerced to a value + // if a ref is coerced, then the suggested lint must deref it + // e.g. `let _: i32 = x+0` with `x: &i32` should be replaced with `let _: i32 = *x`. + // we do this by checking the _kind_ of the type of the expression + // if it's a ref, we then check whether it is erased, and that's it. + let (peeled_left_span, left_is_coerced_to_value) = { + let expr = peel_hir_expr_refs(left).0; + let span = expr.span; + let is_coerced = expr_is_erased_ref(cx, expr); + (span, is_coerced) + }; + + let (peeled_right_span, right_is_coerced_to_value) = { + let expr = peel_hir_expr_refs(right).0; + let span = expr.span; + let is_coerced = expr_is_erased_ref(cx, expr); + (span, is_coerced) + }; + + match op { + BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { + check_op( + cx, + left, + 0, + expr.span, + peeled_right_span, + needs_parenthesis(cx, expr, right), + right_is_coerced_to_value, + ); + check_op( + cx, + right, + 0, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + }, + BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { + check_op( + cx, + right, + 0, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + }, + BinOpKind::Mul => { + check_op( + cx, + left, + 1, + expr.span, + peeled_right_span, + needs_parenthesis(cx, expr, right), + right_is_coerced_to_value, + ); + check_op( cx, right, 1, expr.span, - peel_hir_expr_refs(left).0.span, + peeled_left_span, Parens::Unneeded, - ), - BinOpKind::BitAnd => { - check_op( - cx, - left, - -1, - expr.span, - peel_hir_expr_refs(right).0.span, - needs_parenthesis(cx, expr, right), - ); - check_op( - cx, - right, - -1, - expr.span, - peel_hir_expr_refs(left).0.span, - Parens::Unneeded, - ); - }, - BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), - _ => (), - } + left_is_coerced_to_value, + ); + }, + BinOpKind::Div => check_op( + cx, + right, + 1, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ), + BinOpKind::BitAnd => { + check_op( + cx, + left, + -1, + expr.span, + peeled_right_span, + needs_parenthesis(cx, expr, right), + right_is_coerced_to_value, + ); + check_op( + cx, + right, + -1, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + }, + BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), + _ => (), + } +} + +fn expr_is_erased_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match cx.typeck_results().expr_ty(expr).kind() { + ty::Ref(r, ..) => r.is_erased(), + _ => false, } } @@ -144,11 +180,11 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) } fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool { - // This lint applies to integers - !cx.typeck_results().expr_ty(left).peel_refs().is_integral() - || !cx.typeck_results().expr_ty(right).peel_refs().is_integral() + // This lint applies to integers and their references + cx.typeck_results().expr_ty(left).peel_refs().is_integral() + && cx.typeck_results().expr_ty(right).peel_refs().is_integral() // `1 << 0` is a common pattern in bit manipulation code - || (cmp == BinOpKind::Shl + && !(cmp == BinOpKind::Shl && constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0)) && constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))) } @@ -161,11 +197,11 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv, _ => return, } { - span_ineffective_operation(cx, span, arg, Parens::Unneeded); + span_ineffective_operation(cx, span, arg, Parens::Unneeded, false); } } -fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) { +fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens, is_erased: bool) { if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), @@ -178,18 +214,28 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa 1 => v == 1, _ => unreachable!(), } { - span_ineffective_operation(cx, span, arg, parens); + span_ineffective_operation(cx, span, arg, parens, is_erased); } } } -fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span, parens: Parens) { +fn span_ineffective_operation( + cx: &LateContext<'_>, + span: Span, + arg: Span, + parens: Parens, + is_ref_coerced_to_val: bool, +) { let mut applicability = Applicability::MachineApplicable; let expr_snippet = snippet_with_applicability(cx, arg, "..", &mut applicability); - + let expr_snippet = if is_ref_coerced_to_val { + format!("*{expr_snippet}") + } else { + expr_snippet.into_owned() + }; let suggestion = match parens { Parens::Needed => format!("({expr_snippet})"), - Parens::Unneeded => expr_snippet.into_owned(), + Parens::Unneeded => expr_snippet, }; span_lint_and_sugg( diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index f3b4b1fffa0b5..660e9a21b181a 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -6,7 +6,9 @@ clippy::unnecessary_operation, clippy::op_ref, clippy::double_parens, - clippy::uninlined_format_args + clippy::uninlined_format_args, + clippy::borrow_deref_ref, + clippy::deref_addrof )] use std::fmt::Write as _; @@ -40,32 +42,45 @@ fn main() { let x = 0; x; + //~^ ERROR: this operation has no effect x; + //~^ ERROR: this operation has no effect x + 1; x; + //~^ ERROR: this operation has no effect 1 + x; x - ZERO; //no error, as we skip lookups (for now) x; + //~^ ERROR: this operation has no effect ((ZERO)) | x; //no error, as we skip lookups (for now) x; + //~^ ERROR: this operation has no effect x; + //~^ ERROR: this operation has no effect x / ONE; //no error, as we skip lookups (for now) x / 2; //no false positive x & NEG_ONE; //no error, as we skip lookups (for now) x; + //~^ ERROR: this operation has no effect let u: u8 = 0; u; + //~^ ERROR: this operation has no effect 1 << 0; // no error, this case is allowed, see issue 3430 42; + //~^ ERROR: this operation has no effect 1; + //~^ ERROR: this operation has no effect 42; + //~^ ERROR: this operation has no effect x; + //~^ ERROR: this operation has no effect x; + //~^ ERROR: this operation has no effect let mut a = A(String::new()); let b = a << 0; // no error: non-integer @@ -73,10 +88,15 @@ fn main() { 1 * Meter; // no error: non-integer 2; + //~^ ERROR: this operation has no effect -2; + //~^ ERROR: this operation has no effect 2 + x; + //~^ ERROR: this operation has no effect -2 + x; + //~^ ERROR: this operation has no effect x + 1; + //~^ ERROR: this operation has no effect (x + 1) % 3; // no error 4 % 3; // no error 4 % -3; // no error @@ -85,38 +105,110 @@ fn main() { let a = 0; let b = true; (if b { 1 } else { 2 }); + //~^ ERROR: this operation has no effect (if b { 1 } else { 2 }) + if b { 3 } else { 4 }; + //~^ ERROR: this operation has no effect (match a { 0 => 10, _ => 20 }); + //~^ ERROR: this operation has no effect (match a { 0 => 10, _ => 20 }) + match a { 0 => 30, _ => 40 }; + //~^ ERROR: this operation has no effect (if b { 1 } else { 2 }) + match a { 0 => 30, _ => 40 }; + //~^ ERROR: this operation has no effect (match a { 0 => 10, _ => 20 }) + if b { 3 } else { 4 }; + //~^ ERROR: this operation has no effect (if b { 1 } else { 2 }); + //~^ ERROR: this operation has no effect ({ a }) + 3; + //~^ ERROR: this operation has no effect ({ a } * 2); + //~^ ERROR: this operation has no effect (loop { let mut c = 0; if c == 10 { break c; } c += 1; }) + { a * 2 }; + //~^ ERROR: this operation has no effect fn f(_: i32) { todo!(); } + f(a + { 8 * 5 }); + //~^ ERROR: this operation has no effect f(if b { 1 } else { 2 } + 3); + //~^ ERROR: this operation has no effect + const _: i32 = { 2 * 4 } + 3; + //~^ ERROR: this operation has no effect const _: i32 = { 1 + 2 * 3 } + 3; + //~^ ERROR: this operation has no effect a as usize; + //~^ ERROR: this operation has no effect let _ = a as usize; + //~^ ERROR: this operation has no effect ({ a } as usize); + //~^ ERROR: this operation has no effect 2 * { a }; + //~^ ERROR: this operation has no effect (({ a } + 4)); + //~^ ERROR: this operation has no effect 1; + //~^ ERROR: this operation has no effect // Issue #9904 let x = 0i32; let _: i32 = x; + //~^ ERROR: this operation has no effect } pub fn decide(a: bool, b: bool) -> u32 { (if a { 1 } else { 2 }) + if b { 3 } else { 5 } } + +/// The following tests are from / for issue #12050 +/// In short, the lint didn't work for coerced references, +/// e.g. let x = &0; let y = x + 0; +/// because the suggested fix was `let y = x;` but +/// it should have been `let y = *x;` +fn issue_12050() { + { + let x = &0i32; + let _: i32 = *x; + //~^ ERROR: this operation has no effect + let _: i32 = *x; + //~^ ERROR: this operation has no effect + } + { + let x = &&0i32; + let _: i32 = **x; + //~^ ERROR: this operation has no effect + let x = &&0i32; + let _: i32 = **x; + //~^ ERROR: this operation has no effect + } + { + // this is just silly + let x = &&&0i32; + let _: i32 = ***x; + //~^ ERROR: this operation has no effect + let _: i32 = ***x; + //~^ ERROR: this operation has no effect + let x = 0i32; + let _: i32 = *&x; + //~^ ERROR: this operation has no effect + let _: i32 = **&&x; + //~^ ERROR: this operation has no effect + let _: i32 = *&*&x; + //~^ ERROR: this operation has no effect + let _: i32 = **&&*&x; + //~^ ERROR: this operation has no effect + } + { + // this is getting ridiculous, but we should still see the same + // error message so let's just keep going + let x = &0i32; + let _: i32 = ***&&*&x; + //~^ ERROR: this operation has no effect + let _: i32 = ***&&*&x; + //~^ ERROR: this operation has no effect + } +} diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index 631aa3b0215f2..bbef531e9dc05 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -6,7 +6,9 @@ clippy::unnecessary_operation, clippy::op_ref, clippy::double_parens, - clippy::uninlined_format_args + clippy::uninlined_format_args, + clippy::borrow_deref_ref, + clippy::deref_addrof )] use std::fmt::Write as _; @@ -40,32 +42,45 @@ fn main() { let x = 0; x + 0; + //~^ ERROR: this operation has no effect x + (1 - 1); + //~^ ERROR: this operation has no effect x + 1; 0 + x; + //~^ ERROR: this operation has no effect 1 + x; x - ZERO; //no error, as we skip lookups (for now) x | (0); + //~^ ERROR: this operation has no effect ((ZERO)) | x; //no error, as we skip lookups (for now) x * 1; + //~^ ERROR: this operation has no effect 1 * x; + //~^ ERROR: this operation has no effect x / ONE; //no error, as we skip lookups (for now) x / 2; //no false positive x & NEG_ONE; //no error, as we skip lookups (for now) -1 & x; + //~^ ERROR: this operation has no effect let u: u8 = 0; u & 255; + //~^ ERROR: this operation has no effect 1 << 0; // no error, this case is allowed, see issue 3430 42 << 0; + //~^ ERROR: this operation has no effect 1 >> 0; + //~^ ERROR: this operation has no effect 42 >> 0; + //~^ ERROR: this operation has no effect &x >> 0; + //~^ ERROR: this operation has no effect x >> &0; + //~^ ERROR: this operation has no effect let mut a = A(String::new()); let b = a << 0; // no error: non-integer @@ -73,10 +88,15 @@ fn main() { 1 * Meter; // no error: non-integer 2 % 3; + //~^ ERROR: this operation has no effect -2 % 3; + //~^ ERROR: this operation has no effect 2 % -3 + x; + //~^ ERROR: this operation has no effect -2 % -3 + x; + //~^ ERROR: this operation has no effect x + 1 % 3; + //~^ ERROR: this operation has no effect (x + 1) % 3; // no error 4 % 3; // no error 4 % -3; // no error @@ -85,38 +105,110 @@ fn main() { let a = 0; let b = true; 0 + if b { 1 } else { 2 }; + //~^ ERROR: this operation has no effect 0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }; + //~^ ERROR: this operation has no effect 0 + match a { 0 => 10, _ => 20 }; + //~^ ERROR: this operation has no effect 0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 }; + //~^ ERROR: this operation has no effect 0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 }; + //~^ ERROR: this operation has no effect 0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 }; + //~^ ERROR: this operation has no effect (if b { 1 } else { 2 }) + 0; + //~^ ERROR: this operation has no effect 0 + { a } + 3; + //~^ ERROR: this operation has no effect 0 + { a } * 2; + //~^ ERROR: this operation has no effect 0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 }; + //~^ ERROR: this operation has no effect fn f(_: i32) { todo!(); } + f(1 * a + { 8 * 5 }); + //~^ ERROR: this operation has no effect f(0 + if b { 1 } else { 2 } + 3); + //~^ ERROR: this operation has no effect + const _: i32 = { 2 * 4 } + 0 + 3; + //~^ ERROR: this operation has no effect const _: i32 = 0 + { 1 + 2 * 3 } + 3; + //~^ ERROR: this operation has no effect 0 + a as usize; + //~^ ERROR: this operation has no effect let _ = 0 + a as usize; + //~^ ERROR: this operation has no effect 0 + { a } as usize; + //~^ ERROR: this operation has no effect 2 * (0 + { a }); + //~^ ERROR: this operation has no effect 1 * ({ a } + 4); + //~^ ERROR: this operation has no effect 1 * 1; + //~^ ERROR: this operation has no effect // Issue #9904 let x = 0i32; let _: i32 = &x + 0; + //~^ ERROR: this operation has no effect } pub fn decide(a: bool, b: bool) -> u32 { 0 + if a { 1 } else { 2 } + if b { 3 } else { 5 } } + +/// The following tests are from / for issue #12050 +/// In short, the lint didn't work for coerced references, +/// e.g. let x = &0; let y = x + 0; +/// because the suggested fix was `let y = x;` but +/// it should have been `let y = *x;` +fn issue_12050() { + { + let x = &0i32; + let _: i32 = *x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = x + 0; + //~^ ERROR: this operation has no effect + } + { + let x = &&0i32; + let _: i32 = **x + 0; + //~^ ERROR: this operation has no effect + let x = &&0i32; + let _: i32 = *x + 0; + //~^ ERROR: this operation has no effect + } + { + // this is just silly + let x = &&&0i32; + let _: i32 = ***x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = **x + 0; + //~^ ERROR: this operation has no effect + let x = 0i32; + let _: i32 = *&x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = **&&x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = *&*&x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = **&&*&x + 0; + //~^ ERROR: this operation has no effect + } + { + // this is getting ridiculous, but we should still see the same + // error message so let's just keep going + let x = &0i32; + let _: i32 = **&&*&x + 0; + //~^ ERROR: this operation has no effect + let _: i32 = **&&*&x + 0; + //~^ ERROR: this operation has no effect + } +} diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 2a61327f15f2a..6bb980035c1a4 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -1,5 +1,5 @@ error: this operation has no effect - --> $DIR/identity_op.rs:42:5 + --> $DIR/identity_op.rs:44:5 | LL | x + 0; | ^^^^^ help: consider reducing it to: `x` @@ -8,238 +8,310 @@ LL | x + 0; = help: to override `-D warnings` add `#[allow(clippy::identity_op)]` error: this operation has no effect - --> $DIR/identity_op.rs:43:5 + --> $DIR/identity_op.rs:46:5 | LL | x + (1 - 1); | ^^^^^^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:45:5 + --> $DIR/identity_op.rs:49:5 | LL | 0 + x; | ^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:48:5 + --> $DIR/identity_op.rs:53:5 | LL | x | (0); | ^^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:51:5 + --> $DIR/identity_op.rs:57:5 | LL | x * 1; | ^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:52:5 + --> $DIR/identity_op.rs:59:5 | LL | 1 * x; | ^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:58:5 + --> $DIR/identity_op.rs:66:5 | LL | -1 & x; | ^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:61:5 + --> $DIR/identity_op.rs:70:5 | LL | u & 255; | ^^^^^^^ help: consider reducing it to: `u` error: this operation has no effect - --> $DIR/identity_op.rs:64:5 + --> $DIR/identity_op.rs:74:5 | LL | 42 << 0; | ^^^^^^^ help: consider reducing it to: `42` error: this operation has no effect - --> $DIR/identity_op.rs:65:5 + --> $DIR/identity_op.rs:76:5 | LL | 1 >> 0; | ^^^^^^ help: consider reducing it to: `1` error: this operation has no effect - --> $DIR/identity_op.rs:66:5 + --> $DIR/identity_op.rs:78:5 | LL | 42 >> 0; | ^^^^^^^ help: consider reducing it to: `42` error: this operation has no effect - --> $DIR/identity_op.rs:67:5 + --> $DIR/identity_op.rs:80:5 | LL | &x >> 0; | ^^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:68:5 + --> $DIR/identity_op.rs:82:5 | LL | x >> &0; | ^^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:75:5 + --> $DIR/identity_op.rs:90:5 | LL | 2 % 3; | ^^^^^ help: consider reducing it to: `2` error: this operation has no effect - --> $DIR/identity_op.rs:76:5 + --> $DIR/identity_op.rs:92:5 | LL | -2 % 3; | ^^^^^^ help: consider reducing it to: `-2` error: this operation has no effect - --> $DIR/identity_op.rs:77:5 + --> $DIR/identity_op.rs:94:5 | LL | 2 % -3 + x; | ^^^^^^ help: consider reducing it to: `2` error: this operation has no effect - --> $DIR/identity_op.rs:78:5 + --> $DIR/identity_op.rs:96:5 | LL | -2 % -3 + x; | ^^^^^^^ help: consider reducing it to: `-2` error: this operation has no effect - --> $DIR/identity_op.rs:79:9 + --> $DIR/identity_op.rs:98:9 | LL | x + 1 % 3; | ^^^^^ help: consider reducing it to: `1` error: this operation has no effect - --> $DIR/identity_op.rs:87:5 + --> $DIR/identity_op.rs:107:5 | LL | 0 + if b { 1 } else { 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })` error: this operation has no effect - --> $DIR/identity_op.rs:88:5 + --> $DIR/identity_op.rs:109:5 | LL | 0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })` error: this operation has no effect - --> $DIR/identity_op.rs:89:5 + --> $DIR/identity_op.rs:111:5 | LL | 0 + match a { 0 => 10, _ => 20 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })` error: this operation has no effect - --> $DIR/identity_op.rs:90:5 + --> $DIR/identity_op.rs:113:5 | LL | 0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })` error: this operation has no effect - --> $DIR/identity_op.rs:91:5 + --> $DIR/identity_op.rs:115:5 | LL | 0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })` error: this operation has no effect - --> $DIR/identity_op.rs:92:5 + --> $DIR/identity_op.rs:117:5 | LL | 0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })` error: this operation has no effect - --> $DIR/identity_op.rs:93:5 + --> $DIR/identity_op.rs:119:5 | LL | (if b { 1 } else { 2 }) + 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })` error: this operation has no effect - --> $DIR/identity_op.rs:95:5 + --> $DIR/identity_op.rs:122:5 | LL | 0 + { a } + 3; | ^^^^^^^^^ help: consider reducing it to: `({ a })` error: this operation has no effect - --> $DIR/identity_op.rs:96:5 + --> $DIR/identity_op.rs:124:5 | LL | 0 + { a } * 2; | ^^^^^^^^^^^^^ help: consider reducing it to: `({ a } * 2)` error: this operation has no effect - --> $DIR/identity_op.rs:97:5 + --> $DIR/identity_op.rs:126:5 | LL | 0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(loop { let mut c = 0; if c == 10 { break c; } c += 1; })` error: this operation has no effect - --> $DIR/identity_op.rs:102:7 + --> $DIR/identity_op.rs:133:7 | LL | f(1 * a + { 8 * 5 }); | ^^^^^ help: consider reducing it to: `a` error: this operation has no effect - --> $DIR/identity_op.rs:103:7 + --> $DIR/identity_op.rs:135:7 | LL | f(0 + if b { 1 } else { 2 } + 3); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `if b { 1 } else { 2 }` error: this operation has no effect - --> $DIR/identity_op.rs:104:20 + --> $DIR/identity_op.rs:138:20 | LL | const _: i32 = { 2 * 4 } + 0 + 3; | ^^^^^^^^^^^^^ help: consider reducing it to: `{ 2 * 4 }` error: this operation has no effect - --> $DIR/identity_op.rs:105:20 + --> $DIR/identity_op.rs:140:20 | LL | const _: i32 = 0 + { 1 + 2 * 3 } + 3; | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `{ 1 + 2 * 3 }` error: this operation has no effect - --> $DIR/identity_op.rs:107:5 + --> $DIR/identity_op.rs:143:5 | LL | 0 + a as usize; | ^^^^^^^^^^^^^^ help: consider reducing it to: `a as usize` error: this operation has no effect - --> $DIR/identity_op.rs:108:13 + --> $DIR/identity_op.rs:145:13 | LL | let _ = 0 + a as usize; | ^^^^^^^^^^^^^^ help: consider reducing it to: `a as usize` error: this operation has no effect - --> $DIR/identity_op.rs:109:5 + --> $DIR/identity_op.rs:147:5 | LL | 0 + { a } as usize; | ^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `({ a } as usize)` error: this operation has no effect - --> $DIR/identity_op.rs:111:9 + --> $DIR/identity_op.rs:150:9 | LL | 2 * (0 + { a }); | ^^^^^^^^^^^ help: consider reducing it to: `{ a }` error: this operation has no effect - --> $DIR/identity_op.rs:112:5 + --> $DIR/identity_op.rs:152:5 | LL | 1 * ({ a } + 4); | ^^^^^^^^^^^^^^^ help: consider reducing it to: `(({ a } + 4))` error: this operation has no effect - --> $DIR/identity_op.rs:113:5 + --> $DIR/identity_op.rs:154:5 | LL | 1 * 1; | ^^^^^ help: consider reducing it to: `1` error: this operation has no effect - --> $DIR/identity_op.rs:117:18 + --> $DIR/identity_op.rs:159:18 | LL | let _: i32 = &x + 0; | ^^^^^^ help: consider reducing it to: `x` error: this operation has no effect - --> $DIR/identity_op.rs:121:5 + --> $DIR/identity_op.rs:164:5 | LL | 0 + if a { 1 } else { 2 } + if b { 3 } else { 5 } | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if a { 1 } else { 2 })` -error: aborting due to 40 previous errors +error: this operation has no effect + --> $DIR/identity_op.rs:175:22 + | +LL | let _: i32 = *x + 0; + | ^^^^^^ help: consider reducing it to: `*x` + +error: this operation has no effect + --> $DIR/identity_op.rs:177:22 + | +LL | let _: i32 = x + 0; + | ^^^^^ help: consider reducing it to: `*x` + +error: this operation has no effect + --> $DIR/identity_op.rs:182:22 + | +LL | let _: i32 = **x + 0; + | ^^^^^^^ help: consider reducing it to: `**x` + +error: this operation has no effect + --> $DIR/identity_op.rs:185:22 + | +LL | let _: i32 = *x + 0; + | ^^^^^^ help: consider reducing it to: `**x` + +error: this operation has no effect + --> $DIR/identity_op.rs:191:22 + | +LL | let _: i32 = ***x + 0; + | ^^^^^^^^ help: consider reducing it to: `***x` + +error: this operation has no effect + --> $DIR/identity_op.rs:193:22 + | +LL | let _: i32 = **x + 0; + | ^^^^^^^ help: consider reducing it to: `***x` + +error: this operation has no effect + --> $DIR/identity_op.rs:196:22 + | +LL | let _: i32 = *&x + 0; + | ^^^^^^^ help: consider reducing it to: `*&x` + +error: this operation has no effect + --> $DIR/identity_op.rs:198:22 + | +LL | let _: i32 = **&&x + 0; + | ^^^^^^^^^ help: consider reducing it to: `**&&x` + +error: this operation has no effect + --> $DIR/identity_op.rs:200:22 + | +LL | let _: i32 = *&*&x + 0; + | ^^^^^^^^^ help: consider reducing it to: `*&*&x` + +error: this operation has no effect + --> $DIR/identity_op.rs:202:22 + | +LL | let _: i32 = **&&*&x + 0; + | ^^^^^^^^^^^ help: consider reducing it to: `**&&*&x` + +error: this operation has no effect + --> $DIR/identity_op.rs:209:22 + | +LL | let _: i32 = **&&*&x + 0; + | ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x` + +error: this operation has no effect + --> $DIR/identity_op.rs:211:22 + | +LL | let _: i32 = **&&*&x + 0; + | ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x` + +error: aborting due to 52 previous errors From 79e70ccf6f428c5819d6bd0ecf69eedfd99b0a5f Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 30 Dec 2023 23:03:47 +0800 Subject: [PATCH 23/73] add setuid, setgid and sticky bits on high 3-bits of 12 --- clippy_lints/src/non_octal_unix_permissions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 7143ac9cfc174..12fd63b50f22a 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -50,7 +50,7 @@ fn check_binary_unix_permissions(lit_kind: &LitKind, snip: &str) -> bool { let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); // check whether is binary format unix permissions - if group_sizes.len() != 3 { + if group_sizes.len() != 3 && group_sizes.len() != 4 { return false; } group_sizes.iter().all(|len| *len == 3) From ab2d1c28d5c03ba5b416614257f68fb5243c2a00 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:43:50 +0100 Subject: [PATCH 24/73] Extend `UNCONDITIONAL_RECURSION` to check for `ToString` implementations --- clippy_lints/src/unconditional_recursion.rs | 69 ++++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 570770523ebcb..4036ad78a567b 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{get_trait_def_id, path_res}; +use clippy_utils::{expr_or_init, get_trait_def_id, path_res}; use rustc_ast::BinOpKind; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -56,13 +56,8 @@ fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(path_res(cx, expr), Res::Local(_)) } -fn check_partial_eq( - cx: &LateContext<'_>, - body: &Body<'_>, - method_span: Span, - method_def_id: LocalDefId, - name: Ident, -) { +#[allow(clippy::unnecessary_def_path)] +fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) { let args = cx .tcx .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) @@ -126,8 +121,62 @@ fn check_partial_eq( } } +#[allow(clippy::unnecessary_def_path)] +fn check_to_string(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) { + let args = cx + .tcx + .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) + .inputs(); + // That has one argument. + if let [_self_arg] = args + && let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id) + && let Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(impl_), + owner_id, + .. + }), + )) = cx.tcx.hir().parent_iter(hir_id).next() + // We exclude `impl` blocks generated from rustc's proc macros. + && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + // It is a implementation of a trait. + && let Some(trait_) = impl_.of_trait + && let Some(trait_def_id) = trait_.trait_def_id() + // The trait is `ToString`. + && Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"]) + { + let expr = expr_or_init(cx, body.value.peel_blocks()); + let is_bad = match expr.kind { + ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { + if is_local(cx, receiver) + && is_local(cx, &arg) + && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && trait_id == trait_def_id + { + true + } else { + false + } + }, + _ => false, + }; + if is_bad { + span_lint_and_then( + cx, + UNCONDITIONAL_RECURSION, + method_span, + "function cannot return without recursing", + |diag| { + diag.span_note(expr.span, "recursive call site"); + }, + ); + } + } +} + impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { - #[allow(clippy::unnecessary_def_path)] fn check_fn( &mut self, cx: &LateContext<'tcx>, @@ -141,6 +190,8 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { if let FnKind::Method(name, _) = kind { if name.name == sym::eq || name.name == sym::ne { check_partial_eq(cx, body, method_span, method_def_id, name); + } else if name.name == sym::to_string { + check_to_string(cx, body, method_span, method_def_id, name); } } } From d161f3b559b27b682320c9867cd04a7677e56b1e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:44:07 +0100 Subject: [PATCH 25/73] Add ui test for `UNCONDITIONAL_RECURSION` lint on `ToString` impl --- tests/ui/unconditional_recursion.rs | 28 ++++++++++++++++++++ tests/ui/unconditional_recursion.stderr | 35 ++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/tests/ui/unconditional_recursion.rs b/tests/ui/unconditional_recursion.rs index 1169118de83b5..36fb7e08d6432 100644 --- a/tests/ui/unconditional_recursion.rs +++ b/tests/ui/unconditional_recursion.rs @@ -158,6 +158,34 @@ struct S5; impl_partial_eq!(S5); //~^ ERROR: function cannot return without recursing +struct S6; + +impl std::string::ToString for S6 { + fn to_string(&self) -> String { + //~^ ERROR: function cannot return without recursing + self.to_string() + } +} + +struct S7; + +impl std::string::ToString for S7 { + fn to_string(&self) -> String { + //~^ ERROR: function cannot return without recursing + let x = self; + x.to_string() + } +} + +struct S8; + +impl std::string::ToString for S8 { + fn to_string(&self) -> String { + //~^ ERROR: function cannot return without recursing + (self as &Self).to_string() + } +} + fn main() { // test code goes here } diff --git a/tests/ui/unconditional_recursion.stderr b/tests/ui/unconditional_recursion.stderr index 1fb01c00f1952..040cc4a85a40e 100644 --- a/tests/ui/unconditional_recursion.stderr +++ b/tests/ui/unconditional_recursion.stderr @@ -22,6 +22,39 @@ LL | self.eq(other) | = help: a `loop` may express intention better if this is on purpose +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:164:5 + | +LL | fn to_string(&self) -> String { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing +LL | +LL | self.to_string() + | ---------------- recursive call site + | + = help: a `loop` may express intention better if this is on purpose + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:173:5 + | +LL | fn to_string(&self) -> String { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing +... +LL | x.to_string() + | ------------- recursive call site + | + = help: a `loop` may express intention better if this is on purpose + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:183:5 + | +LL | fn to_string(&self) -> String { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing +LL | +LL | (self as &Self).to_string() + | --------------------------- recursive call site + | + = help: a `loop` may express intention better if this is on purpose + error: function cannot return without recursing --> $DIR/unconditional_recursion.rs:12:5 | @@ -247,5 +280,5 @@ LL | impl_partial_eq!(S5); | -------------------- in this macro invocation = note: this error originates in the macro `impl_partial_eq` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 19 previous errors +error: aborting due to 22 previous errors From 5f1718810f18879dfadd597c7ed45e7a8c932a24 Mon Sep 17 00:00:00 2001 From: Aneesh Kadiyala <143342960+ARandomDev99@users.noreply.github.com> Date: Sat, 30 Dec 2023 22:22:44 +0530 Subject: [PATCH 26/73] Change `empty_enum_variants_with_brackets` applicability to `MaybeIncorrect` --- clippy_lints/src/empty_with_brackets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 52a88324fc3ce..969df6d85b5a0 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -97,7 +97,7 @@ impl EarlyLintPass for EmptyWithBrackets { span_after_ident, "remove the brackets", "", - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ); }, ); From d3bde854c456419a18ca41938dc645dbb307bd6c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 30 Dec 2023 17:01:28 +0100 Subject: [PATCH 27/73] Fix false positive in `PartialEq` implementation --- clippy_lints/src/unconditional_recursion.rs | 60 +++++++++++++-------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 4036ad78a567b..5366b5513d36d 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -1,15 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{expr_or_init, get_trait_def_id, path_res}; +use clippy_utils::{expr_or_init, get_trait_def_id}; use rustc_ast::BinOpKind; -use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::intravisit::FnKind; +use rustc_hir::intravisit::{walk_body, FnKind}; use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; use rustc_span::symbol::Ident; use rustc_span::{sym, Span}; +use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor; declare_clippy_lint! { /// ### What it does @@ -52,12 +52,19 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option { } } -fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - matches!(path_res(cx, expr), Res::Local(_)) +fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool { + let mut visitor = ReturnsVisitor::default(); + + walk_body(&mut visitor, body); + match visitor.returns.as_slice() { + [] => false, + [return_expr] => return_expr.hir_id != expr.hir_id, + _ => true, + } } #[allow(clippy::unnecessary_def_path)] -fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) { +fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { let args = cx .tcx .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) @@ -90,13 +97,23 @@ fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, me } else { BinOpKind::Ne }; - let expr = body.value.peel_blocks(); let is_bad = match expr.kind { - ExprKind::Binary(op, left, right) if op.node == to_check_op => is_local(cx, left) && is_local(cx, right), - ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { - if is_local(cx, receiver) - && is_local(cx, &arg) - && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + ExprKind::Binary(op, left, right) if op.node == to_check_op => { + // Then we check if the left-hand element is of the same type as `self`. + if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left) + && let Some(left_id) = get_ty_def_id(left_ty) + && self_arg == left_id + && let Some(right_ty) = cx.typeck_results().expr_ty_opt(right) + && let Some(right_id) = get_ty_def_id(right_ty) + && other_arg == right_id + { + true + } else { + false + } + }, + ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => { + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) && trait_id == trait_def_id { @@ -122,7 +139,7 @@ fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, me } #[allow(clippy::unnecessary_def_path)] -fn check_to_string(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) { +fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { let args = cx .tcx .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) @@ -146,12 +163,9 @@ fn check_to_string(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, met // The trait is `ToString`. && Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"]) { - let expr = expr_or_init(cx, body.value.peel_blocks()); let is_bad = match expr.kind { - ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => { - if is_local(cx, receiver) - && is_local(cx, &arg) - && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => { + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) && trait_id == trait_def_id { @@ -187,11 +201,15 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { method_def_id: LocalDefId, ) { // If the function is a method... - if let FnKind::Method(name, _) = kind { + if let FnKind::Method(name, _) = kind + && let expr = expr_or_init(cx, body.value).peel_blocks() + // Doesn't have a conditional return. + && !has_conditional_return(body, expr) + { if name.name == sym::eq || name.name == sym::ne { - check_partial_eq(cx, body, method_span, method_def_id, name); + check_partial_eq(cx, method_span, method_def_id, name, expr); } else if name.name == sym::to_string { - check_to_string(cx, body, method_span, method_def_id, name); + check_to_string(cx, method_span, method_def_id, name, expr); } } } From 7107aa22b2219ff11b645178e32d3905ad99da7d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 31 Dec 2023 11:01:47 +0100 Subject: [PATCH 28/73] Add regression tests for `unconditional_recursion` --- tests/ui/unconditional_recursion.rs | 58 ++++++++++++++++++++++--- tests/ui/unconditional_recursion.stderr | 53 ++++++++++++++++++++-- 2 files changed, 101 insertions(+), 10 deletions(-) diff --git a/tests/ui/unconditional_recursion.rs b/tests/ui/unconditional_recursion.rs index 36fb7e08d6432..19cd553b3759b 100644 --- a/tests/ui/unconditional_recursion.rs +++ b/tests/ui/unconditional_recursion.rs @@ -158,18 +158,64 @@ struct S5; impl_partial_eq!(S5); //~^ ERROR: function cannot return without recursing -struct S6; +struct S6 { + field: String, +} + +impl PartialEq for S6 { + fn eq(&self, other: &Self) -> bool { + let mine = &self.field; + let theirs = &other.field; + mine == theirs // Should not warn! + } +} + +struct S7<'a> { + field: &'a S7<'a>, +} + +impl<'a> PartialEq for S7<'a> { + fn eq(&self, other: &Self) -> bool { + //~^ ERROR: function cannot return without recursing + let mine = &self.field; + let theirs = &other.field; + mine == theirs + } +} + +struct S8 { + num: i32, + field: Option>, +} + +impl PartialEq for S8 { + fn eq(&self, other: &Self) -> bool { + if self.num != other.num { + return false; + } + + let (this, other) = match (self.field.as_deref(), other.field.as_deref()) { + (Some(x1), Some(x2)) => (x1, x2), + (None, None) => return true, + _ => return false, + }; + + this == other + } +} + +struct S9; -impl std::string::ToString for S6 { +impl std::string::ToString for S9 { fn to_string(&self) -> String { //~^ ERROR: function cannot return without recursing self.to_string() } } -struct S7; +struct S10; -impl std::string::ToString for S7 { +impl std::string::ToString for S10 { fn to_string(&self) -> String { //~^ ERROR: function cannot return without recursing let x = self; @@ -177,9 +223,9 @@ impl std::string::ToString for S7 { } } -struct S8; +struct S11; -impl std::string::ToString for S8 { +impl std::string::ToString for S11 { fn to_string(&self) -> String { //~^ ERROR: function cannot return without recursing (self as &Self).to_string() diff --git a/tests/ui/unconditional_recursion.stderr b/tests/ui/unconditional_recursion.stderr index 040cc4a85a40e..364dd5728190a 100644 --- a/tests/ui/unconditional_recursion.stderr +++ b/tests/ui/unconditional_recursion.stderr @@ -23,7 +23,7 @@ LL | self.eq(other) = help: a `loop` may express intention better if this is on purpose error: function cannot return without recursing - --> $DIR/unconditional_recursion.rs:164:5 + --> $DIR/unconditional_recursion.rs:210:5 | LL | fn to_string(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing @@ -34,7 +34,7 @@ LL | self.to_string() = help: a `loop` may express intention better if this is on purpose error: function cannot return without recursing - --> $DIR/unconditional_recursion.rs:173:5 + --> $DIR/unconditional_recursion.rs:219:5 | LL | fn to_string(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing @@ -45,7 +45,7 @@ LL | x.to_string() = help: a `loop` may express intention better if this is on purpose error: function cannot return without recursing - --> $DIR/unconditional_recursion.rs:183:5 + --> $DIR/unconditional_recursion.rs:229:5 | LL | fn to_string(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing @@ -87,6 +87,34 @@ note: recursive call site LL | self == other | ^^^^^^^^^^^^^ +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:28:5 + | +LL | / fn ne(&self, other: &Self) -> bool { +LL | | self != &Foo2::B // no error here +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:29:9 + | +LL | self != &Foo2::B // no error here + | ^^^^^^^^^^^^^^^^ + +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:31:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | self == &Foo2::B // no error here +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:32:9 + | +LL | self == &Foo2::B // no error here + | ^^^^^^^^^^^^^^^^ + error: function cannot return without recursing --> $DIR/unconditional_recursion.rs:42:5 | @@ -280,5 +308,22 @@ LL | impl_partial_eq!(S5); | -------------------- in this macro invocation = note: this error originates in the macro `impl_partial_eq` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 22 previous errors +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:178:5 + | +LL | / fn eq(&self, other: &Self) -> bool { +LL | | +LL | | let mine = &self.field; +LL | | let theirs = &other.field; +LL | | mine == theirs +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:182:9 + | +LL | mine == theirs + | ^^^^^^^^^^^^^^ + +error: aborting due to 25 previous errors From fe35e08e9f96f9c829add20351b2fbf28faac324 Mon Sep 17 00:00:00 2001 From: Florian Brucker Date: Sun, 17 Dec 2023 18:46:49 +0100 Subject: [PATCH 29/73] 8733: Suggest `str.lines` when splitting at hard-coded newlines Adds a new `splitting_strings_at_newlines` lint that suggests to use `str.lines` instead of splitting a trimmed string at hard-coded newlines. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/mod.rs | 35 +++++++ clippy_lints/src/methods/str_split.rs | 38 +++++++ tests/ui/str_split.fixed | 145 ++++++++++++++++++++++++++ tests/ui/str_split.rs | 145 ++++++++++++++++++++++++++ tests/ui/str_split.stderr | 65 ++++++++++++ 7 files changed, 430 insertions(+) create mode 100644 clippy_lints/src/methods/str_split.rs create mode 100644 tests/ui/str_split.fixed create mode 100644 tests/ui/str_split.rs create mode 100644 tests/ui/str_split.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e068f19d433..b9e441029b256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5583,6 +5583,7 @@ Released 2018-09-13 [`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 +[`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline [`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string [`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add [`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 9ab38dab1e5fd..02543ae912afa 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -435,6 +435,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, crate::methods::STRING_LIT_CHARS_ANY_INFO, + crate::methods::STR_SPLIT_AT_NEWLINE_INFO, crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 0cde17ef5ad5e..711cad1dcf66a 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -94,6 +94,7 @@ mod single_char_pattern; mod single_char_push_string; mod skip_while_next; mod stable_sort_primitive; +mod str_split; mod str_splitn; mod string_extend_chars; mod string_lit_chars_any; @@ -3856,6 +3857,36 @@ declare_clippy_lint! { "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`. + /// + /// ### Why is this bad? + /// + /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead. + /// + /// ### Example + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".trim().split('\n'); + /// ``` + /// Use instead: + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".lines(); + /// ``` + /// + /// ### Known Problems + /// + /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or + /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is + /// valid. + /// ``` + #[clippy::version = "1.76.0"] + pub STR_SPLIT_AT_NEWLINE, + pedantic, + "splitting a trimmed string at hard-coded newlines" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4011,6 +4042,7 @@ impl_lint_pass!(Methods => [ ITER_FILTER_IS_SOME, ITER_FILTER_IS_OK, MANUAL_IS_VARIANT_AND, + STR_SPLIT_AT_NEWLINE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4597,6 +4629,9 @@ impl Methods { ("sort_unstable_by", [arg]) => { unnecessary_sort_by::check(cx, expr, recv, arg, true); }, + ("split", [arg]) => { + str_split::check(cx, expr, recv, arg); + }, ("splitn" | "rsplitn", [count_arg, pat_arg]) => { if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) { suspicious_splitn::check(cx, name, expr, recv, count); diff --git a/clippy_lints/src/methods/str_split.rs b/clippy_lints/src/methods/str_split.rs new file mode 100644 index 0000000000000..3586e11f56ab2 --- /dev/null +++ b/clippy_lints/src/methods/str_split.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_context; +use clippy_utils::visitors::is_const_evaluatable; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::STR_SPLIT_AT_NEWLINE; + +pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) { + // We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an + // expression returning `String`), and `B` is a `Pattern` that hard-codes a newline (either `"\n"` + // or `"\r\n"`). There are a lot of ways to specify a pattern, and this lint only checks the most + // basic ones: a `'\n'`, `"\n"`, and `"\r\n"`. + if let ExprKind::MethodCall(trim_method_name, trim_recv, [], _) = split_recv.kind + && trim_method_name.ident.as_str() == "trim" + && cx.typeck_results().expr_ty_adjusted(trim_recv).peel_refs().is_str() + && !is_const_evaluatable(cx, trim_recv) + && let ExprKind::Lit(split_lit) = split_arg.kind + && (matches!(split_lit.node, LitKind::Char('\n')) + || matches!(split_lit.node, LitKind::Str(sym, _) if (sym.as_str() == "\n" || sym.as_str() == "\r\n"))) + { + let mut app = Applicability::MaybeIncorrect; + span_lint_and_sugg( + cx, + STR_SPLIT_AT_NEWLINE, + expr.span, + "using `str.trim().split()` with hard-coded newlines", + "use `str.lines()` instead", + format!( + "{}.lines()", + snippet_with_context(cx, trim_recv.span, expr.span.ctxt(), "..", &mut app).0 + ), + app, + ); + } +} diff --git a/tests/ui/str_split.fixed b/tests/ui/str_split.fixed new file mode 100644 index 0000000000000..4f33241da7a22 --- /dev/null +++ b/tests/ui/str_split.fixed @@ -0,0 +1,145 @@ +#![warn(clippy::str_split_at_newline)] + +use core::str::Split; +use std::ops::Deref; + +struct NotStr<'a> { + s: &'a str, +} + +impl<'a> NotStr<'a> { + fn trim(&'a self) -> &'a str { + self.s + } +} + +struct DerefsIntoNotStr<'a> { + not_str: &'a NotStr<'a>, +} + +impl<'a> Deref for DerefsIntoNotStr<'a> { + type Target = NotStr<'a>; + + fn deref(&self) -> &Self::Target { + self.not_str + } +} + +struct DerefsIntoStr<'a> { + s: &'a str, +} + +impl<'a> Deref for DerefsIntoStr<'a> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.s + } +} + +macro_rules! trim_split { + ( $x:expr, $y:expr ) => { + $x.trim().split($y); + }; +} + +macro_rules! make_str { + ( $x: expr ) => { + format!("x={}", $x) + }; +} + +fn main() { + let s1 = "hello\nworld\n"; + let s2 = s1.to_owned(); + + // CASES THAT SHOULD EMIT A LINT + + // Splitting a `str` variable at "\n" or "\r\n" after trimming should warn + let _ = s1.lines(); + #[allow(clippy::single_char_pattern)] + let _ = s1.lines(); + let _ = s1.lines(); + + // Splitting a `String` variable at "\n" or "\r\n" after trimming should warn + let _ = s2.lines(); + #[allow(clippy::single_char_pattern)] + let _ = s2.lines(); + let _ = s2.lines(); + + // Splitting a variable that derefs into `str` at "\n" or "\r\n" after trimming should warn. + let s3 = DerefsIntoStr { s: s1 }; + let _ = s3.lines(); + #[allow(clippy::single_char_pattern)] + let _ = s3.lines(); + let _ = s3.lines(); + + // If the `&str` is generated by a macro then the macro should not be expanded in the suggested fix. + let _ = make_str!(s1).lines(); + + // CASES THAT SHOULD NOT EMIT A LINT + + // Splitting a `str` constant at "\n" or "\r\n" after trimming should not warn + let _ = "hello\nworld\n".trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = "hello\nworld\n".trim().split("\n"); + let _ = "hello\nworld\n".trim().split("\r\n"); + + // Splitting a `str` variable at "\n" or "\r\n" without trimming should not warn, since it is not + // equivalent + let _ = s1.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s1.split("\n"); + let _ = s1.split("\r\n"); + + // Splitting a `String` variable at "\n" or "\r\n" without trimming should not warn. + let _ = s2.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s2.split("\n"); + + // Splitting a variable that derefs into `str` at "\n" or "\r\n" without trimming should not warn. + let _ = s3.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s3.split("\n"); + let _ = s3.split("\r\n"); + let _ = s2.split("\r\n"); + + // Splitting a `str` variable at other separators should not warn + let _ = s1.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s1.trim().split("\r"); + let _ = s1.trim().split("\n\r"); + let _ = s1.trim().split("\r \n"); + + // Splitting a `String` variable at other separators should not warn + let _ = s2.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s2.trim().split("\r"); + let _ = s2.trim().split("\n\r"); + + // Splitting a variable that derefs into `str` at other separators should not warn + let _ = s3.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s3.trim().split("\r"); + let _ = s3.trim().split("\n\r"); + let _ = s3.trim().split("\r \n"); + let _ = s2.trim().split("\r \n"); + + // Using `trim` and `split` on other types should not warn + let not_str = NotStr { s: s1 }; + let _ = not_str.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = not_str.trim().split("\n"); + let _ = not_str.trim().split("\r\n"); + let derefs_into_not_str = DerefsIntoNotStr { not_str: ¬_str }; + let _ = derefs_into_not_str.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = derefs_into_not_str.trim().split("\n"); + let _ = derefs_into_not_str.trim().split("\r\n"); + + // Code generated by macros should not create a warning + trim_split!(s1, "\r\n"); + trim_split!("hello\nworld\n", "\r\n"); + trim_split!(s2, "\r\n"); + trim_split!(s3, "\r\n"); +} diff --git a/tests/ui/str_split.rs b/tests/ui/str_split.rs new file mode 100644 index 0000000000000..f24caa61c3053 --- /dev/null +++ b/tests/ui/str_split.rs @@ -0,0 +1,145 @@ +#![warn(clippy::str_split_at_newline)] + +use core::str::Split; +use std::ops::Deref; + +struct NotStr<'a> { + s: &'a str, +} + +impl<'a> NotStr<'a> { + fn trim(&'a self) -> &'a str { + self.s + } +} + +struct DerefsIntoNotStr<'a> { + not_str: &'a NotStr<'a>, +} + +impl<'a> Deref for DerefsIntoNotStr<'a> { + type Target = NotStr<'a>; + + fn deref(&self) -> &Self::Target { + self.not_str + } +} + +struct DerefsIntoStr<'a> { + s: &'a str, +} + +impl<'a> Deref for DerefsIntoStr<'a> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.s + } +} + +macro_rules! trim_split { + ( $x:expr, $y:expr ) => { + $x.trim().split($y); + }; +} + +macro_rules! make_str { + ( $x: expr ) => { + format!("x={}", $x) + }; +} + +fn main() { + let s1 = "hello\nworld\n"; + let s2 = s1.to_owned(); + + // CASES THAT SHOULD EMIT A LINT + + // Splitting a `str` variable at "\n" or "\r\n" after trimming should warn + let _ = s1.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s1.trim().split("\n"); + let _ = s1.trim().split("\r\n"); + + // Splitting a `String` variable at "\n" or "\r\n" after trimming should warn + let _ = s2.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s2.trim().split("\n"); + let _ = s2.trim().split("\r\n"); + + // Splitting a variable that derefs into `str` at "\n" or "\r\n" after trimming should warn. + let s3 = DerefsIntoStr { s: s1 }; + let _ = s3.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s3.trim().split("\n"); + let _ = s3.trim().split("\r\n"); + + // If the `&str` is generated by a macro then the macro should not be expanded in the suggested fix. + let _ = make_str!(s1).trim().split('\n'); + + // CASES THAT SHOULD NOT EMIT A LINT + + // Splitting a `str` constant at "\n" or "\r\n" after trimming should not warn + let _ = "hello\nworld\n".trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = "hello\nworld\n".trim().split("\n"); + let _ = "hello\nworld\n".trim().split("\r\n"); + + // Splitting a `str` variable at "\n" or "\r\n" without trimming should not warn, since it is not + // equivalent + let _ = s1.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s1.split("\n"); + let _ = s1.split("\r\n"); + + // Splitting a `String` variable at "\n" or "\r\n" without trimming should not warn. + let _ = s2.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s2.split("\n"); + + // Splitting a variable that derefs into `str` at "\n" or "\r\n" without trimming should not warn. + let _ = s3.split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = s3.split("\n"); + let _ = s3.split("\r\n"); + let _ = s2.split("\r\n"); + + // Splitting a `str` variable at other separators should not warn + let _ = s1.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s1.trim().split("\r"); + let _ = s1.trim().split("\n\r"); + let _ = s1.trim().split("\r \n"); + + // Splitting a `String` variable at other separators should not warn + let _ = s2.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s2.trim().split("\r"); + let _ = s2.trim().split("\n\r"); + + // Splitting a variable that derefs into `str` at other separators should not warn + let _ = s3.trim().split('\r'); + #[allow(clippy::single_char_pattern)] + let _ = s3.trim().split("\r"); + let _ = s3.trim().split("\n\r"); + let _ = s3.trim().split("\r \n"); + let _ = s2.trim().split("\r \n"); + + // Using `trim` and `split` on other types should not warn + let not_str = NotStr { s: s1 }; + let _ = not_str.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = not_str.trim().split("\n"); + let _ = not_str.trim().split("\r\n"); + let derefs_into_not_str = DerefsIntoNotStr { not_str: ¬_str }; + let _ = derefs_into_not_str.trim().split('\n'); + #[allow(clippy::single_char_pattern)] + let _ = derefs_into_not_str.trim().split("\n"); + let _ = derefs_into_not_str.trim().split("\r\n"); + + // Code generated by macros should not create a warning + trim_split!(s1, "\r\n"); + trim_split!("hello\nworld\n", "\r\n"); + trim_split!(s2, "\r\n"); + trim_split!(s3, "\r\n"); +} diff --git a/tests/ui/str_split.stderr b/tests/ui/str_split.stderr new file mode 100644 index 0000000000000..ee0a9653711a1 --- /dev/null +++ b/tests/ui/str_split.stderr @@ -0,0 +1,65 @@ +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:59:13 + | +LL | let _ = s1.trim().split('\n'); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s1.lines()` + | + = note: `-D clippy::str-split-at-newline` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::str_split_at_newline)]` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:61:13 + | +LL | let _ = s1.trim().split("\n"); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s1.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:62:13 + | +LL | let _ = s1.trim().split("\r\n"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s1.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:65:13 + | +LL | let _ = s2.trim().split('\n'); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s2.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:67:13 + | +LL | let _ = s2.trim().split("\n"); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s2.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:68:13 + | +LL | let _ = s2.trim().split("\r\n"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s2.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:72:13 + | +LL | let _ = s3.trim().split('\n'); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s3.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:74:13 + | +LL | let _ = s3.trim().split("\n"); + | ^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s3.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:75:13 + | +LL | let _ = s3.trim().split("\r\n"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `s3.lines()` + +error: using `str.trim().split()` with hard-coded newlines + --> $DIR/str_split.rs:78:13 + | +LL | let _ = make_str!(s1).trim().split('\n'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `str.lines()` instead: `make_str!(s1).lines()` + +error: aborting due to 10 previous errors + From 457ab585fc5a5174021bed21c4c0a55deef9676c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 31 Dec 2023 14:44:11 +0100 Subject: [PATCH 30/73] Add `.front()` to `get_first` lint description --- clippy_lints/src/methods/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 0cde17ef5ad5e..7f5c82a3e7a1b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2590,11 +2590,11 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for usage of `x.get(0)` instead of - /// `x.first()`. + /// `x.first()` or `x.front()`. /// /// ### Why is this bad? - /// Using `x.first()` is easier to read and has the same - /// result. + /// Using `x.first()` for `Vec`s and slices or `x.front()` + /// for `VecDeque`s is easier to read and has the same result. /// /// ### Example /// ```no_run @@ -2610,7 +2610,7 @@ declare_clippy_lint! { #[clippy::version = "1.63.0"] pub GET_FIRST, style, - "Using `x.get(0)` when `x.first()` is simpler" + "Using `x.get(0)` when `x.first()` or `x.front()` is simpler" } declare_clippy_lint! { From ef35e82ea3d40ba271e97764c5b9868f7297d318 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 1 Jan 2024 02:20:46 +0100 Subject: [PATCH 31/73] don't look for safety comments in codeblocks --- .../src/undocumented_unsafe_blocks.rs | 10 ++++++++- tests/ui/unnecessary_safety_comment.rs | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index e5bc3b5a25f8e..add4b3e563784 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -681,11 +681,19 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos .filter(|(_, text)| !text.is_empty()); let (line_start, line) = lines.next()?; + let mut in_codeblock = false; // Check for a sequence of line comments. if line.starts_with("//") { let (mut line, mut line_start) = (line, line_start); loop { - if line.to_ascii_uppercase().contains("SAFETY:") { + // Don't lint if the safety comment is part of a codeblock in a doc comment. + // It may or may not be required, and we can't very easily check it (and we shouldn't, since + // the safety comment isn't referring to the node we're currently checking) + if line.trim_start_matches("///").trim_start().starts_with("```") { + in_codeblock = !in_codeblock; + } + + if line.to_ascii_uppercase().contains("SAFETY:") && !in_codeblock { return Some(start_pos + BytePos(u32::try_from(line_start).unwrap())); } match lines.next() { diff --git a/tests/ui/unnecessary_safety_comment.rs b/tests/ui/unnecessary_safety_comment.rs index d9a7ad8e56c57..bdc6fa0f46bbe 100644 --- a/tests/ui/unnecessary_safety_comment.rs +++ b/tests/ui/unnecessary_safety_comment.rs @@ -73,4 +73,25 @@ mod issue_10084 { } } +mod issue_12048 { + pub const X: u8 = 0; + + /// Returns a pointer to five. + /// + /// # Examples + /// + /// ``` + /// use foo::point_to_five; + /// + /// let five_pointer = point_to_five(); + /// // Safety: this pointer always points to a valid five. + /// let five = unsafe { *five_pointer }; + /// assert_eq!(five, 5); + /// ``` + pub fn point_to_five() -> *const u8 { + static FIVE: u8 = 5; + &FIVE + } +} + fn main() {} From 70024e16c000edfb56324852414760cc82567217 Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Wed, 27 Dec 2023 16:25:59 +0200 Subject: [PATCH 32/73] New Lint: [`thread_local_initializer_can_be_made_const`] Adds a new lint to suggest using `const` on `thread_local!` initializers that can be evaluated at compile time. Impl details: The lint relies on the expansion of `thread_local!`. For non const-labelled initializers, `thread_local!` produces a function called `__init` that lazily initializes the value. We check the function and decide whether the body can be const. The body of the function is exactly the initializer. If so, we lint the body. changelog: new lint [`thread_local_initializer_can_be_made_const`] --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 4 + ...ead_local_initializer_can_be_made_const.rs | 102 ++++++++++++++++++ ..._local_initializer_can_be_made_const.fixed | 30 ++++++ ...ead_local_initializer_can_be_made_const.rs | 30 ++++++ ...local_initializer_can_be_made_const.stderr | 29 +++++ 7 files changed, 197 insertions(+) create mode 100644 clippy_lints/src/thread_local_initializer_can_be_made_const.rs create mode 100644 tests/ui/thread_local_initializer_can_be_made_const.fixed create mode 100644 tests/ui/thread_local_initializer_can_be_made_const.rs create mode 100644 tests/ui/thread_local_initializer_can_be_made_const.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index b9e441029b256..e6c081ca94f1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5616,6 +5616,7 @@ Released 2018-09-13 [`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr [`test_attr_in_doctest`]: https://rust-lang.github.io/rust-clippy/master/index.html#test_attr_in_doctest [`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module +[`thread_local_initializer_can_be_made_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const [`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some [`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 02543ae912afa..f6c9ffea9fcd5 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -652,6 +652,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, + crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index dc24243b57479..854c111f9f536 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -323,6 +323,7 @@ mod swap_ptr_to_ref; mod tabs_in_doc_comments; mod temporary_assignment; mod tests_outside_test_module; +mod thread_local_initializer_can_be_made_const; mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; @@ -1088,6 +1089,9 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { behavior: pub_underscore_fields_behavior, }) }); + store.register_late_pass(move |_| { + Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv())) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/thread_local_initializer_can_be_made_const.rs b/clippy_lints/src/thread_local_initializer_can_be_made_const.rs new file mode 100644 index 0000000000000..9fee4c062007f --- /dev/null +++ b/clippy_lints/src/thread_local_initializer_can_be_made_const.rs @@ -0,0 +1,102 @@ +use clippy_config::msrvs::Msrv; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::fn_has_unsatisfiable_preds; +use clippy_utils::qualify_min_const_fn::is_min_const_fn; +use clippy_utils::source::snippet; +use rustc_errors::Applicability; +use rustc_hir::{intravisit, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::impl_lint_pass; +use rustc_span::sym::thread_local_macro; + +declare_clippy_lint! { + /// ### What it does + /// Suggests to use `const` in `thread_local!` macro if possible. + /// ### Why is this bad? + /// + /// The `thread_local!` macro wraps static declarations and makes them thread-local. + /// It supports using a `const` keyword that may be used for declarations that can + /// be evaluated as a constant expression. This can enable a more efficient thread + /// local implementation that can avoid lazy initialization. For types that do not + /// need to be dropped, this can enable an even more efficient implementation that + /// does not need to track any additional state. + /// + /// https://doc.rust-lang.org/std/macro.thread_local.html + /// + /// ### Example + /// ```no_run + /// // example code where clippy issues a warning + /// thread_local! { + /// static BUF: String = String::new(); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// // example code which does not raise clippy warning + /// thread_local! { + /// static BUF: String = const { String::new() }; + /// } + /// ``` + #[clippy::version = "1.76.0"] + pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, + perf, + "suggest using `const` in `thread_local!` macro" +} + +pub struct ThreadLocalInitializerCanBeMadeConst { + msrv: Msrv, +} + +impl ThreadLocalInitializerCanBeMadeConst { + #[must_use] + pub fn new(msrv: Msrv) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]); + +impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: rustc_hir::intravisit::FnKind<'tcx>, + _: &'tcx rustc_hir::FnDecl<'tcx>, + body: &'tcx rustc_hir::Body<'tcx>, + span: rustc_span::Span, + defid: rustc_span::def_id::LocalDefId, + ) { + if in_external_macro(cx.sess(), span) + && let Some(callee) = span.source_callee() + && let Some(macro_def_id) = callee.macro_def_id + && cx.tcx.is_diagnostic_item(thread_local_macro, macro_def_id) + && let intravisit::FnKind::ItemFn(..) = fn_kind + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + && !fn_has_unsatisfiable_preds(cx, defid.to_def_id()) + && let mir = cx.tcx.optimized_mir(defid.to_def_id()) + && let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) + // this is the `__init` function emitted by the `thread_local!` macro + // when the `const` keyword is not used. We avoid checking the `__init` directly + // as that is not a public API. + // we know that the function is const-qualifiable, so now we need only to get the + // initializer expression to span-lint it. + && let ExprKind::Block(block, _) = body.value.kind + && let Some(ret_expr) = block.expr + && let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }") + && initializer_snippet != "thread_local! { ... }" + { + span_lint_and_sugg( + cx, + THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, + ret_expr.span, + "initializer for `thread_local` value can be made `const`", + "replace with", + format!("const {{ {initializer_snippet} }}"), + Applicability::MachineApplicable, + ); + } + } + + extract_msrv_attr!(LateContext); +} diff --git a/tests/ui/thread_local_initializer_can_be_made_const.fixed b/tests/ui/thread_local_initializer_can_be_made_const.fixed new file mode 100644 index 0000000000000..bbde25b0a88b5 --- /dev/null +++ b/tests/ui/thread_local_initializer_can_be_made_const.fixed @@ -0,0 +1,30 @@ +#![warn(clippy::thread_local_initializer_can_be_made_const)] + +use std::cell::RefCell; + +fn main() { + // lint and suggest const + thread_local! { + static BUF_1: RefCell = const { RefCell::new(String::new()) }; + } + //~^^ ERROR: initializer for `thread_local` value can be made `const` + + // don't lint + thread_local! { + static BUF_2: RefCell = const { RefCell::new(String::new()) }; + } + + thread_local! { + static SIMPLE:i32 = const { 1 }; + } + //~^^ ERROR: initializer for `thread_local` value can be made `const` + + // lint and suggest const for all non const items + thread_local! { + static BUF_3_CAN_BE_MADE_CONST: RefCell = const { RefCell::new(String::new()) }; + static CONST_MIXED_WITH:i32 = const { 1 }; + static BUF_4_CAN_BE_MADE_CONST: RefCell = const { RefCell::new(String::new()) }; + } + //~^^^^ ERROR: initializer for `thread_local` value can be made `const` + //~^^^ ERROR: initializer for `thread_local` value can be made `const` +} diff --git a/tests/ui/thread_local_initializer_can_be_made_const.rs b/tests/ui/thread_local_initializer_can_be_made_const.rs new file mode 100644 index 0000000000000..3d7aacf2f094f --- /dev/null +++ b/tests/ui/thread_local_initializer_can_be_made_const.rs @@ -0,0 +1,30 @@ +#![warn(clippy::thread_local_initializer_can_be_made_const)] + +use std::cell::RefCell; + +fn main() { + // lint and suggest const + thread_local! { + static BUF_1: RefCell = RefCell::new(String::new()); + } + //~^^ ERROR: initializer for `thread_local` value can be made `const` + + // don't lint + thread_local! { + static BUF_2: RefCell = const { RefCell::new(String::new()) }; + } + + thread_local! { + static SIMPLE:i32 = 1; + } + //~^^ ERROR: initializer for `thread_local` value can be made `const` + + // lint and suggest const for all non const items + thread_local! { + static BUF_3_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); + static CONST_MIXED_WITH:i32 = const { 1 }; + static BUF_4_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); + } + //~^^^^ ERROR: initializer for `thread_local` value can be made `const` + //~^^^ ERROR: initializer for `thread_local` value can be made `const` +} diff --git a/tests/ui/thread_local_initializer_can_be_made_const.stderr b/tests/ui/thread_local_initializer_can_be_made_const.stderr new file mode 100644 index 0000000000000..b35bd306b5230 --- /dev/null +++ b/tests/ui/thread_local_initializer_can_be_made_const.stderr @@ -0,0 +1,29 @@ +error: initializer for `thread_local` value can be made `const` + --> $DIR/thread_local_initializer_can_be_made_const.rs:8:41 + | +LL | static BUF_1: RefCell = RefCell::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` + | + = note: `-D clippy::thread-local-initializer-can-be-made-const` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::thread_local_initializer_can_be_made_const)]` + +error: initializer for `thread_local` value can be made `const` + --> $DIR/thread_local_initializer_can_be_made_const.rs:18:29 + | +LL | static SIMPLE:i32 = 1; + | ^ help: replace with: `const { 1 }` + +error: initializer for `thread_local` value can be made `const` + --> $DIR/thread_local_initializer_can_be_made_const.rs:24:59 + | +LL | static BUF_3_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` + +error: initializer for `thread_local` value can be made `const` + --> $DIR/thread_local_initializer_can_be_made_const.rs:26:59 + | +LL | static BUF_4_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` + +error: aborting due to 4 previous errors + From ff2919ac5d1f90c146df714b9fd0f716713e96cb Mon Sep 17 00:00:00 2001 From: Lieselotte <52315535+she3py@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:47:57 +0100 Subject: [PATCH 33/73] Remove deadlinks from `unchecked_duration_subtraction` --- clippy_lints/src/instant_subtraction.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 655f4b82aa4f1..17b6256f982b2 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -40,7 +40,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Lints subtraction between an [`Instant`] and a [`Duration`]. + /// Lints subtraction between an `Instant` and a `Duration`. /// /// ### Why is this bad? /// Unchecked subtraction could cause underflow on certain platforms, leading to @@ -57,9 +57,6 @@ declare_clippy_lint! { /// # use std::time::{Instant, Duration}; /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); /// ``` - /// - /// [`Duration`]: std::time::Duration - /// [`Instant::now()`]: std::time::Instant::now; #[clippy::version = "1.67.0"] pub UNCHECKED_DURATION_SUBTRACTION, pedantic, From de06ce886ea74cc60d7c883024009a5ee1c19eaa Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Wed, 29 Nov 2023 15:28:46 -0500 Subject: [PATCH 34/73] Address unused tuple struct fields in clippy --- .../src/methods/iter_overeager_cloned.rs | 8 ++++---- clippy_lints/src/methods/mod.rs | 8 ++++---- clippy_lints/src/question_mark.rs | 5 ++--- .../src/transmute/transmute_undefined_repr.rs | 16 ++++++++-------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index eac6df0545fff..b2fe129cd9510 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -22,7 +22,7 @@ pub(super) enum Op<'a> { // rm `.cloned()` // e.g. `map` `for_each` `all` `any` - NeedlessMove(&'a str, &'a Expr<'a>), + NeedlessMove(&'a Expr<'a>), // later `.cloned()` // and add `&` to the parameter of closure parameter @@ -59,7 +59,7 @@ pub(super) fn check<'tcx>( return; } - if let Op::NeedlessMove(_, expr) = op { + if let Op::NeedlessMove(expr) = op { let rustc_hir::ExprKind::Closure(closure) = expr.kind else { return; }; @@ -104,7 +104,7 @@ pub(super) fn check<'tcx>( } let (lint, msg, trailing_clone) = match op { - Op::RmCloned | Op::NeedlessMove(_, _) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), Op::LaterCloned | Op::FixClosure(_, _) => ( ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", @@ -133,7 +133,7 @@ pub(super) fn check<'tcx>( diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable); } }, - Op::NeedlessMove(_, _) => { + Op::NeedlessMove(_) => { let method_span = expr.span.with_lo(cloned_call.span.hi()); if let Some(snip) = snippet_opt(cx, method_span) { let replace_span = expr.span.with_lo(cloned_recv.span.hi()); diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 25b1ea526e2e3..c1e126137dfe3 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4186,7 +4186,7 @@ impl Methods { expr, recv, recv2, - iter_overeager_cloned::Op::NeedlessMove(name, arg), + iter_overeager_cloned::Op::NeedlessMove(arg), false, ); } @@ -4204,7 +4204,7 @@ impl Methods { expr, recv, recv2, - iter_overeager_cloned::Op::NeedlessMove(name, arg), + iter_overeager_cloned::Op::NeedlessMove(arg), false, ), Some(("chars", recv, _, _, _)) @@ -4379,7 +4379,7 @@ impl Methods { expr, recv, recv2, - iter_overeager_cloned::Op::NeedlessMove(name, arg), + iter_overeager_cloned::Op::NeedlessMove(arg), false, ), _ => {}, @@ -4433,7 +4433,7 @@ impl Methods { expr, recv, recv2, - iter_overeager_cloned::Op::NeedlessMove(name, m_arg), + iter_overeager_cloned::Op::NeedlessMove(m_arg), false, ), _ => {}, diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 509d9483e1d7a..9469888a4d4bb 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -80,7 +80,6 @@ enum IfBlockType<'hir> { Ty<'hir>, Symbol, &'hir Expr<'hir>, - Option<&'hir Expr<'hir>>, ), /// An `if let Xxx(a) = b { c } else { d }` expression. /// @@ -143,7 +142,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool { match *if_block { - IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => { + IfBlockType::IfIs(caller, caller_ty, call_sym, if_then) => { // If the block could be identified as `if x.is_none()/is_err()`, // we then only need to check the if_then return to see if it is none/err. is_type_diagnostic_item(cx, caller_ty, smbl) @@ -235,7 +234,7 @@ impl QuestionMark { && !is_else_clause(cx.tcx, expr) && let ExprKind::MethodCall(segment, caller, ..) = &cond.kind && let caller_ty = cx.typeck_results().expr_ty(caller) - && let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else) + && let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then) && (is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block)) { let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index a65bc0ce458b4..db378cfd75551 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -26,18 +26,18 @@ pub(super) fn check<'tcx>( // `Repr(C)` <-> unordered type. // If the first field of the `Repr(C)` type matches then the transmute is ok - (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty)) - | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) => { + (ReducedTy::OrderedFields(Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty)) + | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) => { from_ty = from_sub_ty; to_ty = to_sub_ty; continue; }, - (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => { + (ReducedTy::OrderedFields(Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => { from_ty = from_sub_ty; to_ty = to_sub_ty; continue; }, - (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) + (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) if reduced_tys.from_fat_ptr => { from_ty = from_sub_ty; @@ -235,8 +235,8 @@ enum ReducedTy<'tcx> { TypeErasure { raw_ptr_only: bool }, /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero /// sized fields with a defined order. - /// The second value is the first non-zero sized type. - OrderedFields(Ty<'tcx>, Option>), + /// The value is the first non-zero sized type. + OrderedFields(Option>), /// The type is a struct containing multiple non-zero sized fields with no defined order. UnorderedFields(Ty<'tcx>), /// Any other type. @@ -259,7 +259,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty::Tuple(args) => { let mut iter = args.iter(); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { - return ReducedTy::OrderedFields(ty, None); + return ReducedTy::OrderedFields(None); }; if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; @@ -281,7 +281,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> continue; } if def.repr().inhibit_struct_field_reordering_opt() { - ReducedTy::OrderedFields(ty, Some(sized_ty)) + ReducedTy::OrderedFields(Some(sized_ty)) } else { ReducedTy::UnorderedFields(ty) } From 04ebf3c8c9aef6c021a60d73f0f1a798a81b7171 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Wed, 29 Nov 2023 15:29:11 -0500 Subject: [PATCH 35/73] Remove #[allow(unused_tuple_struct_fields)] from Clippy tests --- .../auxiliary/helper.rs | 1 - tests/ui/format.fixed | 1 - tests/ui/format.rs | 1 - tests/ui/format.stderr | 30 +++++------ tests/ui/from_iter_instead_of_collect.fixed | 2 +- tests/ui/from_iter_instead_of_collect.rs | 2 +- tests/ui/must_use_candidates.fixed | 1 - tests/ui/must_use_candidates.rs | 1 - tests/ui/must_use_candidates.stderr | 10 ++-- tests/ui/numbered_fields.fixed | 1 - tests/ui/numbered_fields.rs | 1 - tests/ui/numbered_fields.stderr | 4 +- tests/ui/option_if_let_else.fixed | 1 - tests/ui/option_if_let_else.rs | 1 - tests/ui/option_if_let_else.stderr | 50 +++++++++---------- tests/ui/unreadable_literal.fixed | 1 - tests/ui/unreadable_literal.rs | 1 - tests/ui/unreadable_literal.stderr | 20 ++++---- 18 files changed, 59 insertions(+), 70 deletions(-) diff --git a/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs b/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs index b03c21262c3bd..96e037d4fcd76 100644 --- a/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs +++ b/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs @@ -2,7 +2,6 @@ // As the most common case is the `http` crate, it replicates `http::HeaderName`'s structure. #![allow(clippy::declare_interior_mutable_const)] -#![allow(unused_tuple_struct_fields)] use std::sync::atomic::AtomicUsize; diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed index 36679a9c883d3..2b32fdeae2b59 100644 --- a/tests/ui/format.fixed +++ b/tests/ui/format.fixed @@ -1,6 +1,5 @@ #![warn(clippy::useless_format)] #![allow( - unused_tuple_struct_fields, clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args, diff --git a/tests/ui/format.rs b/tests/ui/format.rs index b0920daf0886b..bad192067e933 100644 --- a/tests/ui/format.rs +++ b/tests/ui/format.rs @@ -1,6 +1,5 @@ #![warn(clippy::useless_format)] #![allow( - unused_tuple_struct_fields, clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args, diff --git a/tests/ui/format.stderr b/tests/ui/format.stderr index d4630a8f1dabb..e02fdb1e41519 100644 --- a/tests/ui/format.stderr +++ b/tests/ui/format.stderr @@ -1,5 +1,5 @@ error: useless use of `format!` - --> $DIR/format.rs:20:5 + --> $DIR/format.rs:19:5 | LL | format!("foo"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` @@ -8,19 +8,19 @@ LL | format!("foo"); = help: to override `-D warnings` add `#[allow(clippy::useless_format)]` error: useless use of `format!` - --> $DIR/format.rs:21:5 + --> $DIR/format.rs:20:5 | LL | format!("{{}}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:22:5 + --> $DIR/format.rs:21:5 | LL | format!("{{}} abc {{}}"); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:23:5 + --> $DIR/format.rs:22:5 | LL | / format!( LL | | r##"foo {{}} @@ -35,67 +35,67 @@ LL ~ " bar"##.to_string(); | error: useless use of `format!` - --> $DIR/format.rs:28:13 + --> $DIR/format.rs:27:13 | LL | let _ = format!(""); | ^^^^^^^^^^^ help: consider using `String::new()`: `String::new()` error: useless use of `format!` - --> $DIR/format.rs:30:5 + --> $DIR/format.rs:29:5 | LL | format!("{}", "foo"); | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:38:5 + --> $DIR/format.rs:37:5 | LL | format!("{}", arg); | ^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:68:5 + --> $DIR/format.rs:67:5 | LL | format!("{}", 42.to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string()` error: useless use of `format!` - --> $DIR/format.rs:70:5 + --> $DIR/format.rs:69:5 | LL | format!("{}", x.display().to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string()` error: useless use of `format!` - --> $DIR/format.rs:74:18 + --> $DIR/format.rs:73:18 | LL | let _ = Some(format!("{}", a + "bar")); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` error: useless use of `format!` - --> $DIR/format.rs:78:22 + --> $DIR/format.rs:77:22 | LL | let _s: String = format!("{}", &*v.join("\n")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `(&*v.join("\n")).to_string()` error: useless use of `format!` - --> $DIR/format.rs:84:13 + --> $DIR/format.rs:83:13 | LL | let _ = format!("{x}"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:86:13 + --> $DIR/format.rs:85:13 | LL | let _ = format!("{y}", y = x); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:90:13 + --> $DIR/format.rs:89:13 | LL | let _ = format!("{abc}"); | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `abc.to_string()` error: useless use of `format!` - --> $DIR/format.rs:92:13 + --> $DIR/format.rs:91:13 | LL | let _ = format!("{xx}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `xx.to_string()` diff --git a/tests/ui/from_iter_instead_of_collect.fixed b/tests/ui/from_iter_instead_of_collect.fixed index 82c8e1d8abdb4..c250162dfb8c0 100644 --- a/tests/ui/from_iter_instead_of_collect.fixed +++ b/tests/ui/from_iter_instead_of_collect.fixed @@ -1,5 +1,5 @@ #![warn(clippy::from_iter_instead_of_collect)] -#![allow(unused_imports, unused_tuple_struct_fields)] +#![allow(unused_imports)] #![allow(clippy::useless_vec)] use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; diff --git a/tests/ui/from_iter_instead_of_collect.rs b/tests/ui/from_iter_instead_of_collect.rs index 2aed6b14be14c..8adbb841c8ba2 100644 --- a/tests/ui/from_iter_instead_of_collect.rs +++ b/tests/ui/from_iter_instead_of_collect.rs @@ -1,5 +1,5 @@ #![warn(clippy::from_iter_instead_of_collect)] -#![allow(unused_imports, unused_tuple_struct_fields)] +#![allow(unused_imports)] #![allow(clippy::useless_vec)] use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; diff --git a/tests/ui/must_use_candidates.fixed b/tests/ui/must_use_candidates.fixed index 3ed705b29061e..db20ba29f3d98 100644 --- a/tests/ui/must_use_candidates.fixed +++ b/tests/ui/must_use_candidates.fixed @@ -1,7 +1,6 @@ #![feature(never_type)] #![allow( unused_mut, - unused_tuple_struct_fields, clippy::redundant_allocation, clippy::needless_pass_by_ref_mut )] diff --git a/tests/ui/must_use_candidates.rs b/tests/ui/must_use_candidates.rs index ab8efea0ac7e2..d7e5613024507 100644 --- a/tests/ui/must_use_candidates.rs +++ b/tests/ui/must_use_candidates.rs @@ -1,7 +1,6 @@ #![feature(never_type)] #![allow( unused_mut, - unused_tuple_struct_fields, clippy::redundant_allocation, clippy::needless_pass_by_ref_mut )] diff --git a/tests/ui/must_use_candidates.stderr b/tests/ui/must_use_candidates.stderr index 581399f3e4868..39446bf6cd922 100644 --- a/tests/ui/must_use_candidates.stderr +++ b/tests/ui/must_use_candidates.stderr @@ -1,5 +1,5 @@ error: this function could have a `#[must_use]` attribute - --> $DIR/must_use_candidates.rs:16:1 + --> $DIR/must_use_candidates.rs:15:1 | LL | pub fn pure(i: u8) -> u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn pure(i: u8) -> u8` @@ -8,25 +8,25 @@ LL | pub fn pure(i: u8) -> u8 { = help: to override `-D warnings` add `#[allow(clippy::must_use_candidate)]` error: this method could have a `#[must_use]` attribute - --> $DIR/must_use_candidates.rs:21:5 + --> $DIR/must_use_candidates.rs:20:5 | LL | pub fn inherent_pure(&self) -> u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn inherent_pure(&self) -> u8` error: this function could have a `#[must_use]` attribute - --> $DIR/must_use_candidates.rs:52:1 + --> $DIR/must_use_candidates.rs:51:1 | LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool` error: this function could have a `#[must_use]` attribute - --> $DIR/must_use_candidates.rs:64:1 + --> $DIR/must_use_candidates.rs:63:1 | LL | pub fn rcd(_x: Rc) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn rcd(_x: Rc) -> bool` error: this function could have a `#[must_use]` attribute - --> $DIR/must_use_candidates.rs:72:1 + --> $DIR/must_use_candidates.rs:71:1 | LL | pub fn arcd(_x: Arc) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn arcd(_x: Arc) -> bool` diff --git a/tests/ui/numbered_fields.fixed b/tests/ui/numbered_fields.fixed index 7f0a6f8e54477..dc88081ba0a71 100644 --- a/tests/ui/numbered_fields.fixed +++ b/tests/ui/numbered_fields.fixed @@ -1,5 +1,4 @@ #![warn(clippy::init_numbered_fields)] -#![allow(unused_tuple_struct_fields)] #[derive(Default)] struct TupleStruct(u32, u32, u8); diff --git a/tests/ui/numbered_fields.rs b/tests/ui/numbered_fields.rs index 38f3b36ec4d0e..e8fa652e3c1da 100644 --- a/tests/ui/numbered_fields.rs +++ b/tests/ui/numbered_fields.rs @@ -1,5 +1,4 @@ #![warn(clippy::init_numbered_fields)] -#![allow(unused_tuple_struct_fields)] #[derive(Default)] struct TupleStruct(u32, u32, u8); diff --git a/tests/ui/numbered_fields.stderr b/tests/ui/numbered_fields.stderr index d52a0cf15a83c..76f5e082f326c 100644 --- a/tests/ui/numbered_fields.stderr +++ b/tests/ui/numbered_fields.stderr @@ -1,5 +1,5 @@ error: used a field initializer for a tuple struct - --> $DIR/numbered_fields.rs:18:13 + --> $DIR/numbered_fields.rs:17:13 | LL | let _ = TupleStruct { | _____________^ @@ -13,7 +13,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::init_numbered_fields)]` error: used a field initializer for a tuple struct - --> $DIR/numbered_fields.rs:25:13 + --> $DIR/numbered_fields.rs:24:13 | LL | let _ = TupleStruct { | _____________^ diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 363520112ef26..d443334bb0592 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -1,6 +1,5 @@ #![warn(clippy::option_if_let_else)] #![allow( - unused_tuple_struct_fields, clippy::ref_option_ref, clippy::equatable_if_let, clippy::let_unit_value, diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index aaa87a0db5495..317c35bf8427b 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -1,6 +1,5 @@ #![warn(clippy::option_if_let_else)] #![allow( - unused_tuple_struct_fields, clippy::ref_option_ref, clippy::equatable_if_let, clippy::let_unit_value, diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 55a8360ffd0c8..e053d356ff24e 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -1,5 +1,5 @@ error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:11:5 + --> $DIR/option_if_let_else.rs:10:5 | LL | / if let Some(x) = string { LL | | (true, x) @@ -12,19 +12,19 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::option_if_let_else)]` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:29:13 + --> $DIR/option_if_let_else.rs:28:13 | LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:30:13 + --> $DIR/option_if_let_else.rs:29:13 | LL | let _ = if let Some(s) = &num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:31:13 + --> $DIR/option_if_let_else.rs:30:13 | LL | let _ = if let Some(s) = &mut num { | _____________^ @@ -44,13 +44,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:37:13 + --> $DIR/option_if_let_else.rs:36:13 | LL | let _ = if let Some(ref s) = num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:38:13 + --> $DIR/option_if_let_else.rs:37:13 | LL | let _ = if let Some(mut s) = num { | _____________^ @@ -70,7 +70,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:44:13 + --> $DIR/option_if_let_else.rs:43:13 | LL | let _ = if let Some(ref mut s) = num { | _____________^ @@ -90,7 +90,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:53:5 + --> $DIR/option_if_let_else.rs:52:5 | LL | / if let Some(x) = arg { LL | | let y = x * x; @@ -109,7 +109,7 @@ LL + }) | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:66:13 + --> $DIR/option_if_let_else.rs:65:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -121,7 +121,7 @@ LL | | }; | |_____^ help: try: `arg.map_or_else(side_effect, |x| x)` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:75:13 + --> $DIR/option_if_let_else.rs:74:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -144,7 +144,7 @@ LL ~ }, |x| x * x * x * x); | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:108:13 + --> $DIR/option_if_let_else.rs:107:13 | LL | / if let Some(idx) = s.find('.') { LL | | vec![s[..idx].to_string(), s[idx..].to_string()] @@ -154,7 +154,7 @@ LL | | } | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:119:5 + --> $DIR/option_if_let_else.rs:118:5 | LL | / if let Ok(binding) = variable { LL | | println!("Ok {binding}"); @@ -177,13 +177,13 @@ LL + }) | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:143:13 + --> $DIR/option_if_let_else.rs:142:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:153:13 + --> $DIR/option_if_let_else.rs:152:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -205,13 +205,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:181:13 + --> $DIR/option_if_let_else.rs:180:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:185:13 + --> $DIR/option_if_let_else.rs:184:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -231,7 +231,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:224:13 + --> $DIR/option_if_let_else.rs:223:13 | LL | let _ = match s { | _____________^ @@ -241,7 +241,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:228:13 + --> $DIR/option_if_let_else.rs:227:13 | LL | let _ = match Some(10) { | _____________^ @@ -251,7 +251,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:234:13 + --> $DIR/option_if_let_else.rs:233:13 | LL | let _ = match res { | _____________^ @@ -261,7 +261,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:238:13 + --> $DIR/option_if_let_else.rs:237:13 | LL | let _ = match res { | _____________^ @@ -271,13 +271,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:242:13 + --> $DIR/option_if_let_else.rs:241:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:259:17 + --> $DIR/option_if_let_else.rs:258:17 | LL | let _ = match initial { | _________________^ @@ -287,7 +287,7 @@ LL | | }; | |_________^ help: try: `initial.as_ref().map_or(42, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:266:17 + --> $DIR/option_if_let_else.rs:265:17 | LL | let _ = match initial { | _________________^ @@ -297,7 +297,7 @@ LL | | }; | |_________^ help: try: `initial.as_mut().map_or(42, |value| do_something2(value))` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:289:24 + --> $DIR/option_if_let_else.rs:288:24 | LL | let mut _hashmap = if let Some(hm) = &opt { | ________________________^ @@ -308,7 +308,7 @@ LL | | }; | |_____^ help: try: `opt.as_ref().map_or_else(HashMap::new, |hm| hm.clone())` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:295:19 + --> $DIR/option_if_let_else.rs:294:19 | LL | let mut _hm = if let Some(hm) = &opt { hm.clone() } else { new_map!() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.as_ref().map_or_else(|| new_map!(), |hm| hm.clone())` diff --git a/tests/ui/unreadable_literal.fixed b/tests/ui/unreadable_literal.fixed index 6d8c719ee072c..fb9c2672db829 100644 --- a/tests/ui/unreadable_literal.fixed +++ b/tests/ui/unreadable_literal.fixed @@ -1,5 +1,4 @@ #![warn(clippy::unreadable_literal)] -#![allow(unused_tuple_struct_fields)] struct Foo(u64); diff --git a/tests/ui/unreadable_literal.rs b/tests/ui/unreadable_literal.rs index 42ca773c3516e..0a24fa8525467 100644 --- a/tests/ui/unreadable_literal.rs +++ b/tests/ui/unreadable_literal.rs @@ -1,5 +1,4 @@ #![warn(clippy::unreadable_literal)] -#![allow(unused_tuple_struct_fields)] struct Foo(u64); diff --git a/tests/ui/unreadable_literal.stderr b/tests/ui/unreadable_literal.stderr index d7a3377ec37df..37f91acf82b94 100644 --- a/tests/ui/unreadable_literal.stderr +++ b/tests/ui/unreadable_literal.stderr @@ -1,5 +1,5 @@ error: long literal lacking separators - --> $DIR/unreadable_literal.rs:32:17 + --> $DIR/unreadable_literal.rs:31:17 | LL | let _bad = (0b110110_i64, 0x12345678_usize, 123456_f32, 1.234567_f32); | ^^^^^^^^^^^^ help: consider: `0b11_0110_i64` @@ -8,55 +8,55 @@ LL | let _bad = (0b110110_i64, 0x12345678_usize, 123456_f32, 1.234567_f32); = help: to override `-D warnings` add `#[allow(clippy::unreadable_literal)]` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:32:31 + --> $DIR/unreadable_literal.rs:31:31 | LL | let _bad = (0b110110_i64, 0x12345678_usize, 123456_f32, 1.234567_f32); | ^^^^^^^^^^^^^^^^ help: consider: `0x1234_5678_usize` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:32:49 + --> $DIR/unreadable_literal.rs:31:49 | LL | let _bad = (0b110110_i64, 0x12345678_usize, 123456_f32, 1.234567_f32); | ^^^^^^^^^^ help: consider: `123_456_f32` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:32:61 + --> $DIR/unreadable_literal.rs:31:61 | LL | let _bad = (0b110110_i64, 0x12345678_usize, 123456_f32, 1.234567_f32); | ^^^^^^^^^^^^ help: consider: `1.234_567_f32` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:34:20 + --> $DIR/unreadable_literal.rs:33:20 | LL | let _bad_sci = 1.123456e1; | ^^^^^^^^^^ help: consider: `1.123_456e1` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:36:18 + --> $DIR/unreadable_literal.rs:35:18 | LL | let _fail1 = 0xabcdef; | ^^^^^^^^ help: consider: `0x00ab_cdef` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:37:23 + --> $DIR/unreadable_literal.rs:36:23 | LL | let _fail2: u32 = 0xBAFEBAFE; | ^^^^^^^^^^ help: consider: `0xBAFE_BAFE` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:38:18 + --> $DIR/unreadable_literal.rs:37:18 | LL | let _fail3 = 0xabcdeff; | ^^^^^^^^^ help: consider: `0x0abc_deff` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:39:24 + --> $DIR/unreadable_literal.rs:38:24 | LL | let _fail4: i128 = 0xabcabcabcabcabcabc; | ^^^^^^^^^^^^^^^^^^^^ help: consider: `0x00ab_cabc_abca_bcab_cabc` error: long literal lacking separators - --> $DIR/unreadable_literal.rs:40:18 + --> $DIR/unreadable_literal.rs:39:18 | LL | let _fail5 = 1.100300400; | ^^^^^^^^^^^ help: consider: `1.100_300_400` From 88541d66370aba7ec48661617afd4d712f4e6711 Mon Sep 17 00:00:00 2001 From: Yuxiang Qiu Date: Tue, 2 Jan 2024 19:21:51 -0500 Subject: [PATCH 36/73] fix some typos --- book/src/development/macro_expansions.md | 2 +- clippy_lints/src/methods/mod.rs | 4 ++-- clippy_lints/src/missing_asserts_for_indexing.rs | 2 +- clippy_lints/src/no_effect.rs | 2 +- clippy_lints/src/serde_api.rs | 2 +- clippy_lints/src/wildcard_imports.rs | 2 +- clippy_utils/src/ty/type_certainty/mod.rs | 6 +++--- tests/ui/as_conversions.rs | 2 +- tests/ui/default_numeric_fallback_i32.fixed | 2 +- tests/ui/default_numeric_fallback_i32.rs | 2 +- tests/ui/redundant_as_str.fixed | 2 +- tests/ui/redundant_as_str.rs | 2 +- tests/ui/regex.rs | 2 +- tests/ui/struct_fields.rs | 2 +- tests/ui/useless_conversion.fixed | 4 ++-- tests/ui/useless_conversion.rs | 4 ++-- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/book/src/development/macro_expansions.md b/book/src/development/macro_expansions.md index c5eb000272d39..aecca9ef72e98 100644 --- a/book/src/development/macro_expansions.md +++ b/book/src/development/macro_expansions.md @@ -102,7 +102,7 @@ let x: Option = Some(42); m!(x, x.unwrap()); ``` -If the `m!(x, x.unwrapp());` line is expanded, we would get two expanded +If the `m!(x, x.unwrap());` line is expanded, we would get two expanded expressions: - `x.is_some()` (from the `$a.is_some()` line in the `m` macro) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 4c21f49638b66..fbca641cfa30e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3786,7 +3786,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// This pattern is often followed by manual unwrapping of the `Option`. The simplification - /// results in more readable and succint code without the need for manual unwrapping. + /// results in more readable and succinct code without the need for manual unwrapping. /// /// ### Example /// ```no_run @@ -3812,7 +3812,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// This pattern is often followed by manual unwrapping of `Result`. The simplification - /// results in more readable and succint code without the need for manual unwrapping. + /// results in more readable and succinct code without the need for manual unwrapping. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index 0f18e94345153..bbc4d0a0f9a54 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -331,7 +331,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap> slice, } if indexes.len() > 1 => { // if we have found an `assert!`, let's also check that it's actually right - // and if it convers the highest index and if not, suggest the correct length + // and if it covers the highest index and if not, suggest the correct length let sugg = match comparison { // `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks. // The user probably meant `v.len() > 5` diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 5978da8319932..6bbe427ea2988 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -165,7 +165,7 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { } fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - // It's very hard or impossable to check whether overridden operator have side-effect this lint. + // It's very hard or impossible to check whether overridden operator have side-effect this lint. // So, this function assume user-defined operator is overridden with an side-effect. // The definition of user-defined structure here is ADT-type, // Althrough this will weaken the ability of this lint, less error lint-fix happen. diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index 90834d784a57a..b4278d879e54e 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for mis-uses of the serde API. + /// Checks for misuses of the serde API. /// /// ### Why is this bad? /// Serde is very finnicky about how its API should be diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 9b0dac6af2500..b82bd1d7e7c89 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -142,7 +142,7 @@ impl LateLintPass<'_> for WildcardImports { } else { // In this case, the `use_path.span` ends right before the `::*`, so we need to // extend it up to the `*`. Since it is hard to find the `*` in weird - // formattings like `use _ :: *;`, we extend it up to, but not including the + // formatting like `use _ :: *;`, we extend it up to, but not including the // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we // can just use the end of the item span let mut span = use_path.span.with_hi(item.span.hi()); diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index da71fc3aaa87f..adca2ca1c3efa 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -38,7 +38,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { ExprKind::Call(callee, args) => { let lhs = expr_type_certainty(cx, callee); - let rhs = if type_is_inferrable_from_arguments(cx, expr) { + let rhs = if type_is_inferable_from_arguments(cx, expr) { meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) } else { Certainty::Uncertain @@ -57,7 +57,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id); }; let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); - let rhs = if type_is_inferrable_from_arguments(cx, expr) { + let rhs = if type_is_inferable_from_arguments(cx, expr) { meet( std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), ) @@ -279,7 +279,7 @@ fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: & } #[allow(clippy::cast_possible_truncation)] -fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { +fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let Some(callee_def_id) = (match expr.kind { ExprKind::Call(callee, _) => { let callee_ty = cx.typeck_results().expr_ty(callee); diff --git a/tests/ui/as_conversions.rs b/tests/ui/as_conversions.rs index 192eb51ea99fc..8499c0ac5a0f8 100644 --- a/tests/ui/as_conversions.rs +++ b/tests/ui/as_conversions.rs @@ -17,7 +17,7 @@ fn main() { with_span!( span - fn coverting() { + fn converting() { let x = 0u32 as u64; } ); diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index 9ef92d8f279a9..c364c6830578f 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -184,7 +184,7 @@ fn check_expect_suppression() { let x = 21; } -mod type_already_infered { +mod type_already_inferred { // Should NOT lint if bound to return type fn ret_i32() -> i32 { 1 diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index 4b54b8d84587e..ffa7b961d1ced 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -184,7 +184,7 @@ fn check_expect_suppression() { let x = 21; } -mod type_already_infered { +mod type_already_inferred { // Should NOT lint if bound to return type fn ret_i32() -> i32 { 1 diff --git a/tests/ui/redundant_as_str.fixed b/tests/ui/redundant_as_str.fixed index a38523a7c79e7..4185b402226c0 100644 --- a/tests/ui/redundant_as_str.fixed +++ b/tests/ui/redundant_as_str.fixed @@ -11,7 +11,7 @@ fn main() { let _no_as_str = string.as_bytes(); let _no_as_str = string.is_empty(); - // These methods are not redundant, and are equivelant to + // These methods are not redundant, and are equivalent to // doing dereferencing the string and applying the method let _not_redundant = string.as_str().escape_unicode(); let _not_redundant = string.as_str().trim(); diff --git a/tests/ui/redundant_as_str.rs b/tests/ui/redundant_as_str.rs index 33adb60999641..7a74d8a55ded7 100644 --- a/tests/ui/redundant_as_str.rs +++ b/tests/ui/redundant_as_str.rs @@ -11,7 +11,7 @@ fn main() { let _no_as_str = string.as_bytes(); let _no_as_str = string.is_empty(); - // These methods are not redundant, and are equivelant to + // These methods are not redundant, and are equivalent to // doing dereferencing the string and applying the method let _not_redundant = string.as_str().escape_unicode(); let _not_redundant = string.as_str().trim(); diff --git a/tests/ui/regex.rs b/tests/ui/regex.rs index 1ea0d65bf1e3b..4fb6c08bb4495 100644 --- a/tests/ui/regex.rs +++ b/tests/ui/regex.rs @@ -113,7 +113,7 @@ fn trivial_regex() { // #6005: unicode classes in bytes::Regex let a_byte_of_unicode = BRegex::new(r"\p{C}"); - // start and end word boundry, introduced in regex 0.10 + // start and end word boundary, introduced in regex 0.10 let _ = BRegex::new(r"\"); let _ = BRegex::new(r"\b{start}word\b{end}"); } diff --git a/tests/ui/struct_fields.rs b/tests/ui/struct_fields.rs index 8b1a1446e3c85..a8b12c6dfe382 100644 --- a/tests/ui/struct_fields.rs +++ b/tests/ui/struct_fields.rs @@ -65,7 +65,7 @@ struct NotSnakeCase2 { someData_a_b: bool, } -// no error, threshold is 3 fiels by default +// no error, threshold is 3 fields by default struct Fooo { foo: u8, bar: u8, diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index ed8387b7eb2c5..ce00fde2f9930 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -241,7 +241,7 @@ mod issue11300 { foo2::<(), _>([1, 2, 3].into_iter()); // This should lint. Removing the `.into_iter()` means that `I` gets substituted with `[i32; 3]`, - // and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unncessary. + // and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unnecessary. foo3([1, 2, 3]); } @@ -253,7 +253,7 @@ mod issue11300 { S1.foo([1, 2]); - // ICE that occured in itertools + // ICE that occurred in itertools trait Itertools { fn interleave_shortest(self, other: J) where diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index 991a7762fc64a..399796195868d 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -241,7 +241,7 @@ mod issue11300 { foo2::<(), _>([1, 2, 3].into_iter()); // This should lint. Removing the `.into_iter()` means that `I` gets substituted with `[i32; 3]`, - // and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unncessary. + // and `i32: Helper2<[i32, 3]>` is true, so this call is indeed unnecessary. foo3([1, 2, 3].into_iter()); } @@ -253,7 +253,7 @@ mod issue11300 { S1.foo([1, 2].into_iter()); - // ICE that occured in itertools + // ICE that occurred in itertools trait Itertools { fn interleave_shortest(self, other: J) where From 18e4632b2bc799dd767de90f1c231aa3f51c85a1 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 3 Jan 2024 19:36:44 +0100 Subject: [PATCH 37/73] Update copyright year for Clippy (2024 edition) --- COPYRIGHT | 2 +- LICENSE-APACHE | 2 +- LICENSE-MIT | 2 +- README.md | 2 +- rustc_tools_util/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index 82703b18fd7d2..219693d63d973 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ // REUSE-IgnoreStart -Copyright 2014-2022 The Rust Project Developers +Copyright 2014-2024 The Rust Project Developers Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 0d62c37278e58..506582c31d6d5 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2014-2022 The Rust Project Developers +Copyright 2014-2024 The Rust Project Developers Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index b724b24aa8309..6d8ee9afb6163 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2022 The Rust Project Developers +Copyright (c) 2014-2024 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index 5d490645d8979..fb3f7109d7e6d 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT -Copyright 2014-2023 The Rust Project Developers +Copyright 2014-2024 The Rust Project Developers Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/rustc_tools_util/README.md b/rustc_tools_util/README.md index e197ea048a0af..56f62b867a6c8 100644 --- a/rustc_tools_util/README.md +++ b/rustc_tools_util/README.md @@ -51,7 +51,7 @@ The changelog for `rustc_tools_util` is available under: -Copyright 2014-2022 The Rust Project Developers +Copyright 2014-2024 The Rust Project Developers Licensed under the Apache License, Version 2.0 or the MIT license From 5960107415caf34a84fc8bb2f2fd303b7e358041 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 30 Dec 2023 03:15:38 +0100 Subject: [PATCH 38/73] new lint: `option_as_ref_cloned` --- CHANGELOG.md | 1 + README.md | 2 +- book/src/README.md | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/mod.rs | 32 +++++++++++++++- .../src/methods/option_as_ref_cloned.rs | 24 ++++++++++++ tests/ui/option_as_ref_cloned.fixed | 21 +++++++++++ tests/ui/option_as_ref_cloned.rs | 21 +++++++++++ tests/ui/option_as_ref_cloned.stderr | 37 +++++++++++++++++++ 9 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 clippy_lints/src/methods/option_as_ref_cloned.rs create mode 100644 tests/ui/option_as_ref_cloned.fixed create mode 100644 tests/ui/option_as_ref_cloned.rs create mode 100644 tests/ui/option_as_ref_cloned.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c081ca94f1b..4d32bbec914a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5442,6 +5442,7 @@ Released 2018-09-13 [`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref [`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some +[`option_as_ref_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_cloned [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap [`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used diff --git a/README.md b/README.md index 5d490645d8979..0450b54121df4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. diff --git a/book/src/README.md b/book/src/README.md index 486ea3df70420..e7972b0db19ce 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index f6c9ffea9fcd5..20230106d5366 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -410,6 +410,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::NO_EFFECT_REPLACE_INFO, crate::methods::OBFUSCATED_IF_ELSE_INFO, crate::methods::OK_EXPECT_INFO, + crate::methods::OPTION_AS_REF_CLONED_INFO, crate::methods::OPTION_AS_REF_DEREF_INFO, crate::methods::OPTION_FILTER_MAP_INFO, crate::methods::OPTION_MAP_OR_ERR_OK_INFO, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index fbca641cfa30e..cf6f6ad1a0fc8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -71,6 +71,7 @@ mod no_effect_replace; mod obfuscated_if_else; mod ok_expect; mod open_options; +mod option_as_ref_cloned; mod option_as_ref_deref; mod option_map_or_err_ok; mod option_map_or_none; @@ -3887,6 +3888,31 @@ declare_clippy_lint! { "splitting a trimmed string at hard-coded newlines" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s + /// + /// ### Why is this bad? + /// This can be written more concisely by cloning the `Option` directly. + /// + /// ### Example + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.as_ref().cloned() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.clone() + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub OPTION_AS_REF_CLONED, + pedantic, + "cloning an `Option` via `as_ref().cloned()`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4043,6 +4069,7 @@ impl_lint_pass!(Methods => [ ITER_FILTER_IS_OK, MANUAL_IS_VARIANT_AND, STR_SPLIT_AT_NEWLINE, + OPTION_AS_REF_CLONED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4290,7 +4317,10 @@ impl Methods { ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), - ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv), + ("cloned", []) => { + cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv); + option_as_ref_cloned::check(cx, recv, span); + }, ("collect", []) if is_trait_method(cx, expr, sym::Iterator) => { needless_collect::check(cx, span, expr, recv, call_span); match method_call(recv) { diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints/src/methods/option_as_ref_cloned.rs new file mode 100644 index 0000000000000..d7fec360fa2ae --- /dev/null +++ b/clippy_lints/src/methods/option_as_ref_cloned.rs @@ -0,0 +1,24 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::{sym, Span}; + +use super::{method_call, OPTION_AS_REF_CLONED}; + +pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) { + if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option) + { + span_lint_and_sugg( + cx, + OPTION_AS_REF_CLONED, + as_ref_ident_span.to(cloned_ident_span), + &format!("cloning an `Option<_>` using `.{method}().cloned()`"), + "this can be written more concisely by cloning the `Option<_>` directly", + "clone".into(), + Applicability::MachineApplicable, + ); + } +} diff --git a/tests/ui/option_as_ref_cloned.fixed b/tests/ui/option_as_ref_cloned.fixed new file mode 100644 index 0000000000000..394dad219f777 --- /dev/null +++ b/tests/ui/option_as_ref_cloned.fixed @@ -0,0 +1,21 @@ +#![warn(clippy::option_as_ref_cloned)] +#![allow(clippy::clone_on_copy)] + +fn main() { + let mut x = Some(String::new()); + + let _: Option = x.clone(); + let _: Option = x.clone(); + + let y = x.as_ref(); + let _: Option<&String> = y.clone(); + + macro_rules! cloned_recv { + () => { + x.as_ref() + }; + } + + // Don't lint when part of the expression is from a macro + let _: Option = cloned_recv!().cloned(); +} diff --git a/tests/ui/option_as_ref_cloned.rs b/tests/ui/option_as_ref_cloned.rs new file mode 100644 index 0000000000000..7243957927b25 --- /dev/null +++ b/tests/ui/option_as_ref_cloned.rs @@ -0,0 +1,21 @@ +#![warn(clippy::option_as_ref_cloned)] +#![allow(clippy::clone_on_copy)] + +fn main() { + let mut x = Some(String::new()); + + let _: Option = x.as_ref().cloned(); + let _: Option = x.as_mut().cloned(); + + let y = x.as_ref(); + let _: Option<&String> = y.as_ref().cloned(); + + macro_rules! cloned_recv { + () => { + x.as_ref() + }; + } + + // Don't lint when part of the expression is from a macro + let _: Option = cloned_recv!().cloned(); +} diff --git a/tests/ui/option_as_ref_cloned.stderr b/tests/ui/option_as_ref_cloned.stderr new file mode 100644 index 0000000000000..ea03da3b69fab --- /dev/null +++ b/tests/ui/option_as_ref_cloned.stderr @@ -0,0 +1,37 @@ +error: cloning an `Option<_>` using `.as_ref().cloned()` + --> $DIR/option_as_ref_cloned.rs:7:31 + | +LL | let _: Option = x.as_ref().cloned(); + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-as-ref-cloned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::option_as_ref_cloned)]` +help: this can be written more concisely by cloning the `Option<_>` directly + | +LL | let _: Option = x.clone(); + | ~~~~~ + +error: cloning an `Option<_>` using `.as_mut().cloned()` + --> $DIR/option_as_ref_cloned.rs:8:31 + | +LL | let _: Option = x.as_mut().cloned(); + | ^^^^^^^^^^^^^^^ + | +help: this can be written more concisely by cloning the `Option<_>` directly + | +LL | let _: Option = x.clone(); + | ~~~~~ + +error: cloning an `Option<_>` using `.as_ref().cloned()` + --> $DIR/option_as_ref_cloned.rs:11:32 + | +LL | let _: Option<&String> = y.as_ref().cloned(); + | ^^^^^^^^^^^^^^^ + | +help: this can be written more concisely by cloning the `Option<_>` directly + | +LL | let _: Option<&String> = y.clone(); + | ~~~~~ + +error: aborting due to 3 previous errors + From ca83a98e66a1a6f71c09d92ab25fa385ca8353b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 20 Dec 2023 19:13:24 +0000 Subject: [PATCH 39/73] Track `HirId` instead of `Span` in `ObligationCauseCode::SizedArgumentType` This gets us more accurate suggestions. --- tests/ui/crashes/ice-6251.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/crashes/ice-6251.stderr b/tests/ui/crashes/ice-6251.stderr index 11081dc8087e2..0196c9923dbe6 100644 --- a/tests/ui/crashes/ice-6251.stderr +++ b/tests/ui/crashes/ice-6251.stderr @@ -6,7 +6,7 @@ LL | fn bug() -> impl Iterator { | = help: the trait `std::marker::Sized` is not implemented for `[u8]` = help: unsized fn params are gated as an unstable feature -help: function arguments must have a statically known size, borrowed types always have a known size +help: function arguments must have a statically known size, borrowed slices always have a known size | LL | fn bug() -> impl Iterator { | + From 89e4b7162c41f8e889369d1713557de38df1c7b9 Mon Sep 17 00:00:00 2001 From: Paul Houssel Date: Wed, 27 Dec 2023 15:07:03 +0100 Subject: [PATCH 40/73] Include GitLab in the CI section of the clippy doc book --- book/src/SUMMARY.md | 1 + book/src/continuous_integration/gitlab.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 book/src/continuous_integration/gitlab.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b02457307d743..a048fbbd8acf9 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Clippy's Lints](lints.md) - [Continuous Integration](continuous_integration/README.md) - [GitHub Actions](continuous_integration/github_actions.md) + - [GitLab CI](continuous_integration/gitlab.md) - [Travis CI](continuous_integration/travis.md) - [Development](development/README.md) - [Basics](development/basics.md) diff --git a/book/src/continuous_integration/gitlab.md b/book/src/continuous_integration/gitlab.md new file mode 100644 index 0000000000000..bb3ef246c2faa --- /dev/null +++ b/book/src/continuous_integration/gitlab.md @@ -0,0 +1,16 @@ +# GitLab CI + +You can add Clippy to GitLab CI by using the latest stable [rust docker image](https://hub.docker.com/_/rust), +as it is shown in the `.gitlab-ci.yml` CI configuration file below, + +```yml +# Make sure CI fails on all warnings, including Clippy lints +variables: + RUSTFLAGS: "-Dwarnings" + +clippy_check: + image: rust:latest + script: + - rustup component add clippy + - cargo clippy --all-targets --all-features +``` From 9dcf92649cc3bf4b5152545286ef3be74c196fa4 Mon Sep 17 00:00:00 2001 From: Yudai Fukushima <49578068+granddaifuku@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:49:33 +0000 Subject: [PATCH 41/73] fix: metadata-collector listing lints when config documents include comma --- clippy_config/src/conf.rs | 2 +- clippy_config/src/metadata.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index f88ab9fe440f4..5477d9b83a72c 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -550,7 +550,7 @@ define_Conf! { /// Lint: PUB_UNDERSCORE_FIELDS /// /// Lint "public" fields in a struct that are prefixed with an underscore based on their - /// exported visibility; or whether they are marked as "pub". + /// exported visibility, or whether they are marked as "pub". (pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PublicallyExported), } diff --git a/clippy_config/src/metadata.rs b/clippy_config/src/metadata.rs index 2451fbc91e898..3ba2796e18d3c 100644 --- a/clippy_config/src/metadata.rs +++ b/clippy_config/src/metadata.rs @@ -96,6 +96,9 @@ fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec, String)> { doc_comment.make_ascii_lowercase(); let lints: Vec = doc_comment .split_off(DOC_START.len()) + .lines() + .next() + .unwrap() .split(", ") .map(str::to_string) .collect(); From e75841397365ff207553551f14ab35122774f23c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 4 Jan 2024 17:13:18 +0100 Subject: [PATCH 42/73] Add .as_ref() to suggestion to remove .to_string() --- .../src/methods/unnecessary_to_owned.rs | 19 ++++++++++++--- tests/ui/unnecessary_to_owned_on_split.fixed | 18 +++++++++++++- tests/ui/unnecessary_to_owned_on_split.rs | 18 +++++++++++++- tests/ui/unnecessary_to_owned_on_split.stderr | 24 ++++++++++++------- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 5f69cf6cd7ada..12106d44e3656 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -3,13 +3,13 @@ use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs}; use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, Node}; +use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node}; use rustc_hir_typeck::{FnCtxt, Inherited}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; @@ -246,6 +246,19 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) && let Some(arg_snippet) = snippet_opt(cx, argument_expr.span) { + // We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x` + // implements `AsRef` but does not implement `Deref`. In this case, we have to + // add `.as_ref()` to the suggestion. + let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String) + && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref) + && cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target") + != Some(cx.tcx.types.str_) + { + ".as_ref()" + } else { + "" + }; + // The next suggestion may be incorrect because the removal of the `to_owned`-like // function could cause the iterator to hold a reference to a resource that is used // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148. @@ -255,7 +268,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb parent.span, &format!("unnecessary use of `{method_name}`"), "use", - format!("{receiver_snippet}.split({arg_snippet})"), + format!("{receiver_snippet}{as_ref}.split({arg_snippet})"), Applicability::MaybeIncorrect, ); return true; diff --git a/tests/ui/unnecessary_to_owned_on_split.fixed b/tests/ui/unnecessary_to_owned_on_split.fixed index 53dc3c43e2f86..f87c898f9b79a 100644 --- a/tests/ui/unnecessary_to_owned_on_split.fixed +++ b/tests/ui/unnecessary_to_owned_on_split.fixed @@ -1,4 +1,18 @@ -#[allow(clippy::single_char_pattern)] +#![allow(clippy::single_char_pattern)] + +struct Issue12068; + +impl AsRef for Issue12068 { + fn as_ref(&self) -> &str { + "" + } +} + +impl ToString for Issue12068 { + fn to_string(&self) -> String { + String::new() + } +} fn main() { let _ = "a".split('a').next().unwrap(); @@ -9,6 +23,8 @@ fn main() { //~^ ERROR: unnecessary use of `to_owned` let _ = "a".split("a").next().unwrap(); //~^ ERROR: unnecessary use of `to_owned` + let _ = Issue12068.as_ref().split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` let _ = [1].split(|x| *x == 2).next().unwrap(); //~^ ERROR: unnecessary use of `to_vec` diff --git a/tests/ui/unnecessary_to_owned_on_split.rs b/tests/ui/unnecessary_to_owned_on_split.rs index 62400e7eee17b..db5719e588095 100644 --- a/tests/ui/unnecessary_to_owned_on_split.rs +++ b/tests/ui/unnecessary_to_owned_on_split.rs @@ -1,4 +1,18 @@ -#[allow(clippy::single_char_pattern)] +#![allow(clippy::single_char_pattern)] + +struct Issue12068; + +impl AsRef for Issue12068 { + fn as_ref(&self) -> &str { + "" + } +} + +impl ToString for Issue12068 { + fn to_string(&self) -> String { + String::new() + } +} fn main() { let _ = "a".to_string().split('a').next().unwrap(); @@ -9,6 +23,8 @@ fn main() { //~^ ERROR: unnecessary use of `to_owned` let _ = "a".to_owned().split("a").next().unwrap(); //~^ ERROR: unnecessary use of `to_owned` + let _ = Issue12068.to_string().split('a').next().unwrap(); + //~^ ERROR: unnecessary use of `to_string` let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); //~^ ERROR: unnecessary use of `to_vec` diff --git a/tests/ui/unnecessary_to_owned_on_split.stderr b/tests/ui/unnecessary_to_owned_on_split.stderr index cfb3766d15ed6..4cfaeed3384ab 100644 --- a/tests/ui/unnecessary_to_owned_on_split.stderr +++ b/tests/ui/unnecessary_to_owned_on_split.stderr @@ -1,5 +1,5 @@ error: unnecessary use of `to_string` - --> $DIR/unnecessary_to_owned_on_split.rs:4:13 + --> $DIR/unnecessary_to_owned_on_split.rs:18:13 | LL | let _ = "a".to_string().split('a').next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split('a')` @@ -8,46 +8,52 @@ LL | let _ = "a".to_string().split('a').next().unwrap(); = help: to override `-D warnings` add `#[allow(clippy::unnecessary_to_owned)]` error: unnecessary use of `to_string` - --> $DIR/unnecessary_to_owned_on_split.rs:6:13 + --> $DIR/unnecessary_to_owned_on_split.rs:20:13 | LL | let _ = "a".to_string().split("a").next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split("a")` error: unnecessary use of `to_owned` - --> $DIR/unnecessary_to_owned_on_split.rs:8:13 + --> $DIR/unnecessary_to_owned_on_split.rs:22:13 | LL | let _ = "a".to_owned().split('a').next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split('a')` error: unnecessary use of `to_owned` - --> $DIR/unnecessary_to_owned_on_split.rs:10:13 + --> $DIR/unnecessary_to_owned_on_split.rs:24:13 | LL | let _ = "a".to_owned().split("a").next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"a".split("a")` +error: unnecessary use of `to_string` + --> $DIR/unnecessary_to_owned_on_split.rs:26:13 + | +LL | let _ = Issue12068.to_string().split('a').next().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Issue12068.as_ref().split('a')` + error: unnecessary use of `to_vec` - --> $DIR/unnecessary_to_owned_on_split.rs:13:13 + --> $DIR/unnecessary_to_owned_on_split.rs:29:13 | LL | let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` error: unnecessary use of `to_vec` - --> $DIR/unnecessary_to_owned_on_split.rs:15:13 + --> $DIR/unnecessary_to_owned_on_split.rs:31:13 | LL | let _ = [1].to_vec().split(|x| *x == 2).next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` error: unnecessary use of `to_owned` - --> $DIR/unnecessary_to_owned_on_split.rs:17:13 + --> $DIR/unnecessary_to_owned_on_split.rs:33:13 | LL | let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` error: unnecessary use of `to_owned` - --> $DIR/unnecessary_to_owned_on_split.rs:19:13 + --> $DIR/unnecessary_to_owned_on_split.rs:35:13 | LL | let _ = [1].to_owned().split(|x| *x == 2).next().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[1].split(|x| *x == 2)` -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors From 2666f39d3e5d4b354065056102ef4856aa23fdae Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 3 Jan 2024 22:14:36 +0100 Subject: [PATCH 43/73] Detect unconditional recursion between Default trait impl and static methods --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 271 +++++++++++++++++--- 2 files changed, 231 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 854c111f9f536..efdd392594977 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1083,7 +1083,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity)); store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)); - store.register_late_pass(|_| Box::new(unconditional_recursion::UnconditionalRecursion)); + store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| { Box::new(pub_underscore_fields::PubUnderscoreFields { behavior: pub_underscore_fields_behavior, diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 5366b5513d36d..e90306ded61c4 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -1,13 +1,19 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{expr_or_init, get_trait_def_id}; +use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id}; use rustc_ast::BinOpKind; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::intravisit::{walk_body, FnKind}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node}; +use rustc_hir::intravisit::{walk_body, walk_expr, FnKind, Visitor}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath, TyKind}; +use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty}; -use rustc_session::declare_lint_pass; -use rustc_span::symbol::Ident; +use rustc_middle::hir::map::Map; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt}; +use rustc_session::impl_lint_pass; +use rustc_span::symbol::{kw, Ident}; use rustc_span::{sym, Span}; use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor; @@ -42,7 +48,26 @@ declare_clippy_lint! { "detect unconditional recursion in some traits implementation" } -declare_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]); +#[derive(Default)] +pub struct UnconditionalRecursion { + /// The key is the `DefId` of the type implementing the `Default` trait and the value is the + /// `DefId` of the return call. + default_impl_for_type: FxHashMap, +} + +impl_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]); + +fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) { + span_lint_and_then( + cx, + UNCONDITIONAL_RECURSION, + method_span, + "function cannot return without recursing", + |diag| { + diag.span_note(expr.span, "recursive call site"); + }, + ); +} fn get_ty_def_id(ty: Ty<'_>) -> Option { match ty.peel_refs().kind() { @@ -52,17 +77,60 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option { } } -fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool { +fn get_hir_ty_def_id(tcx: TyCtxt<'_>, hir_ty: rustc_hir::Ty<'_>) -> Option { + let TyKind::Path(qpath) = hir_ty.kind else { return None }; + match qpath { + QPath::Resolved(_, path) => path.res.opt_def_id(), + QPath::TypeRelative(_, _) => { + let ty = hir_ty_to_ty(tcx, &hir_ty); + + match ty.kind() { + ty::Alias(ty::Projection, proj) => { + Res::::Def(DefKind::Trait, proj.trait_ref(tcx).def_id).opt_def_id() + }, + _ => None, + } + }, + QPath::LangItem(..) => None, + } +} + +fn get_return_calls_in_body<'tcx>(body: &'tcx Body<'tcx>) -> Vec<&'tcx Expr<'tcx>> { let mut visitor = ReturnsVisitor::default(); - walk_body(&mut visitor, body); - match visitor.returns.as_slice() { + visitor.visit_body(body); + visitor.returns +} + +fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool { + match get_return_calls_in_body(body).as_slice() { [] => false, [return_expr] => return_expr.hir_id != expr.hir_id, _ => true, } } +fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Option { + let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id); + if let Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(impl_), + owner_id, + .. + }), + )) = cx.tcx.hir().parent_iter(hir_id).next() + // We exclude `impl` blocks generated from rustc's proc macros. + && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + // It is a implementation of a trait. + && let Some(trait_) = impl_.of_trait + { + trait_.trait_def_id() + } else { + None + } +} + #[allow(clippy::unnecessary_def_path)] fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { let args = cx @@ -75,20 +143,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca && let Some(other_arg) = get_ty_def_id(*other_arg) // The two arguments are of the same type. && self_arg == other_arg - && let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id) - && let Some(( - _, - Node::Item(Item { - kind: ItemKind::Impl(impl_), - owner_id, - .. - }), - )) = cx.tcx.hir().parent_iter(hir_id).next() - // We exclude `impl` blocks generated from rustc's proc macros. - && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) - // It is a implementation of a trait. - && let Some(trait_) = impl_.of_trait - && let Some(trait_def_id) = trait_.trait_def_id() + && let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id) // The trait is `PartialEq`. && Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"]) { @@ -125,15 +180,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca _ => false, }; if is_bad { - span_lint_and_then( - cx, - UNCONDITIONAL_RECURSION, - method_span, - "function cannot return without recursing", - |diag| { - diag.span_note(expr.span, "recursive call site"); - }, - ); + span_error(cx, method_span, expr); } } } @@ -177,15 +224,156 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local _ => false, }; if is_bad { - span_lint_and_then( + span_error(cx, method_span, expr); + } + } +} + +fn is_default_method_on_current_ty(tcx: TyCtxt<'_>, qpath: QPath<'_>, implemented_ty_id: DefId) -> bool { + match qpath { + QPath::Resolved(_, path) => match path.segments { + [first, .., last] => last.ident.name == kw::Default && first.res.opt_def_id() == Some(implemented_ty_id), + _ => false, + }, + QPath::TypeRelative(ty, segment) => { + if segment.ident.name != kw::Default { + return false; + } + if matches!( + ty.kind, + TyKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::SelfTyAlias { .. }, + .. + }, + )) + ) { + return true; + } + get_hir_ty_def_id(tcx, *ty) == Some(implemented_ty_id) + }, + QPath::LangItem(..) => false, + } +} + +struct CheckCalls<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + map: Map<'tcx>, + implemented_ty_id: DefId, + found_default_call: bool, + method_span: Span, +} + +impl<'a, 'tcx> Visitor<'tcx> for CheckCalls<'a, 'tcx> +where + 'tcx: 'a, +{ + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.map + } + + #[allow(clippy::unnecessary_def_path)] + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if self.found_default_call { + return; + } + walk_expr(self, expr); + + if let ExprKind::Call(f, _) = expr.kind + && let ExprKind::Path(qpath) = f.kind + && is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id) + && let Some(method_def_id) = path_def_id(self.cx, f) + && let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id) + && Some(trait_def_id) == get_trait_def_id(self.cx, &["core", "default", "Default"]) + { + self.found_default_call = true; + span_error(self.cx, self.method_span, expr); + } + } +} + +impl UnconditionalRecursion { + #[allow(clippy::unnecessary_def_path)] + fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) { + if self.default_impl_for_type.is_empty() + && let Some(default_trait_id) = get_trait_def_id(cx, &["core", "default", "Default"]) + { + let impls = cx.tcx.trait_impls_of(default_trait_id); + for (ty, impl_def_ids) in impls.non_blanket_impls() { + let Some(self_def_id) = ty.def() else { continue }; + for impl_def_id in impl_def_ids { + if !cx.tcx.has_attr(*impl_def_id, sym::automatically_derived) && + let Some(assoc_item) = cx + .tcx + .associated_items(impl_def_id) + .in_definition_order() + // We're not interested in foreign implementations of the `Default` trait. + .find(|item| { + item.kind == AssocKind::Fn && item.def_id.is_local() && item.name == kw::Default + }) + && let Some(body_node) = cx.tcx.hir().get_if_local(assoc_item.def_id) + && let Some(body_id) = body_node.body_id() + && let body = cx.tcx.hir().body(body_id) + // We don't want to keep it if it has conditional return. + && let [return_expr] = get_return_calls_in_body(body).as_slice() + && let ExprKind::Call(call_expr, _) = return_expr.kind + // We need to use typeck here to infer the actual function being called. + && let body_def_id = cx.tcx.hir().enclosing_body_owner(call_expr.hir_id) + && let Some(body_owner) = cx.tcx.hir().maybe_body_owned_by(body_def_id) + && let typeck = cx.tcx.typeck_body(body_owner) + && let Some(call_def_id) = typeck.type_dependent_def_id(call_expr.hir_id) + { + self.default_impl_for_type.insert(self_def_id, call_def_id); + } + } + } + } + } + + fn check_default_new<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + decl: &FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + method_span: Span, + method_def_id: LocalDefId, + ) { + // We're only interested into static methods. + if decl.implicit_self.has_implicit_self() { + return; + } + // We don't check trait implementations. + if get_impl_trait_def_id(cx, method_def_id).is_some() { + return; + } + + let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id); + if let Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(impl_), + .. + }), + )) = cx.tcx.hir().parent_iter(hir_id).next() + && let Some(implemented_ty_id) = get_hir_ty_def_id(cx.tcx, *impl_.self_ty) + && { + self.init_default_impl_for_type_if_needed(cx); + true + } + && let Some(return_def_id) = self.default_impl_for_type.get(&implemented_ty_id) + && method_def_id.to_def_id() == *return_def_id + { + let mut c = CheckCalls { cx, - UNCONDITIONAL_RECURSION, + map: cx.tcx.hir(), + implemented_ty_id, + found_default_call: false, method_span, - "function cannot return without recursing", - |diag| { - diag.span_note(expr.span, "recursive call site"); - }, - ); + }; + walk_body(&mut c, body); } } } @@ -195,7 +383,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { &mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, - _decl: &'tcx FnDecl<'tcx>, + decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, method_span: Span, method_def_id: LocalDefId, @@ -211,6 +399,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion { } else if name.name == sym::to_string { check_to_string(cx, method_span, method_def_id, name, expr); } + self.check_default_new(cx, decl, body, method_span, method_def_id); } } } From bf5c0d8334adbaa246f2087f4d031da7e3d8bbb8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Jan 2024 17:12:54 +0100 Subject: [PATCH 44/73] Add tests for extension of `unconditional_recursion` lint on `Default` trait --- tests/ui/unconditional_recursion.rs | 34 ++++++++++++++++++++++++- tests/ui/unconditional_recursion.stderr | 17 ++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/ui/unconditional_recursion.rs b/tests/ui/unconditional_recursion.rs index 19cd553b3759b..e1a2d6a90b896 100644 --- a/tests/ui/unconditional_recursion.rs +++ b/tests/ui/unconditional_recursion.rs @@ -1,7 +1,7 @@ //@no-rustfix #![warn(clippy::unconditional_recursion)] -#![allow(clippy::partialeq_ne_impl)] +#![allow(clippy::partialeq_ne_impl, clippy::default_constructed_unit_structs)] enum Foo { A, @@ -232,6 +232,38 @@ impl std::string::ToString for S11 { } } +struct S12; + +impl std::default::Default for S12 { + fn default() -> Self { + Self::new() + } +} + +impl S12 { + fn new() -> Self { + //~^ ERROR: function cannot return without recursing + Self::default() + } + + fn bar() -> Self { + // Should not warn! + Self::default() + } +} + +#[derive(Default)] +struct S13 { + f: u32, +} + +impl S13 { + fn new() -> Self { + // Shoud not warn! + Self::default() + } +} + fn main() { // test code goes here } diff --git a/tests/ui/unconditional_recursion.stderr b/tests/ui/unconditional_recursion.stderr index 364dd5728190a..5d82e2a9f3194 100644 --- a/tests/ui/unconditional_recursion.stderr +++ b/tests/ui/unconditional_recursion.stderr @@ -325,5 +325,20 @@ note: recursive call site LL | mine == theirs | ^^^^^^^^^^^^^^ -error: aborting due to 25 previous errors +error: function cannot return without recursing + --> $DIR/unconditional_recursion.rs:244:5 + | +LL | / fn new() -> Self { +LL | | +LL | | Self::default() +LL | | } + | |_____^ + | +note: recursive call site + --> $DIR/unconditional_recursion.rs:246:9 + | +LL | Self::default() + | ^^^^^^^^^^^^^^^ + +error: aborting due to 26 previous errors From 0bac101c21d3b410b819fdac09d88adb71bfd8e5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 5 Jan 2024 10:02:40 +1100 Subject: [PATCH 45/73] Rename `EmitterWriter` as `HumanEmitter`. For consistency with other `Emitter` impls, such as `JsonEmitter`, `SilentEmitter`, `SharedEmitter`, etc. --- clippy_lints/src/doc/needless_doctest_main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index c639813a3f9ae..a744b69ecb478 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -5,7 +5,7 @@ use crate::doc::{NEEDLESS_DOCTEST_MAIN, TEST_ATTR_IN_DOCTEST}; use clippy_utils::diagnostics::span_lint; use rustc_ast::{CoroutineKind, Fn, FnRetTy, Item, ItemKind}; use rustc_data_structures::sync::Lrc; -use rustc_errors::emitter::EmitterWriter; +use rustc_errors::emitter::HumanEmitter; use rustc_errors::DiagCtxt; use rustc_lint::LateContext; use rustc_parse::maybe_new_parser_from_source_str; @@ -44,7 +44,7 @@ pub fn check( let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); - let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); + let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle); let dcx = DiagCtxt::with_emitter(Box::new(emitter)).disable_warnings(); #[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_dcx let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); From 890c0702e4838f4d547f2451fc32d04691c73275 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:45:59 +0100 Subject: [PATCH 46/73] don't change eagerness for struct literal syntax with significant drop --- clippy_utils/src/eager_or_lazy.rs | 13 ++- tests/ui/unnecessary_lazy_eval.fixed | 13 +++ tests/ui/unnecessary_lazy_eval.rs | 13 +++ tests/ui/unnecessary_lazy_eval.stderr | 126 +++++++++++++------------- 4 files changed, 100 insertions(+), 65 deletions(-) diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index 4e71c6483e6aa..6c40029a9de70 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -99,7 +99,10 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: } fn res_has_significant_drop(res: Res, cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - if let Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) = res { + if let Res::Def(DefKind::Ctor(..) | DefKind::Variant | DefKind::Enum | DefKind::Struct, _) + | Res::SelfCtor(_) + | Res::SelfTyAlias { .. } = res + { cx.typeck_results() .expr_ty(e) .has_significant_drop(cx.tcx, cx.param_env) @@ -173,6 +176,13 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS self.eagerness |= NoChange; return; }, + #[expect(clippy::match_same_arms)] // arm pattern can't be merged due to `ref`, see rust#105778 + ExprKind::Struct(path, ..) => { + if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) { + self.eagerness = ForceNoChange; + return; + } + }, ExprKind::Path(ref path) => { if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) { self.eagerness = ForceNoChange; @@ -291,7 +301,6 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS | ExprKind::Closure { .. } | ExprKind::Field(..) | ExprKind::AddrOf(..) - | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Block(Block { stmts: [], .. }, _) | ExprKind::OffsetOf(..) => (), diff --git a/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 66598f89208ba..d8031c484e5e1 100644 --- a/tests/ui/unnecessary_lazy_eval.fixed +++ b/tests/ui/unnecessary_lazy_eval.fixed @@ -47,6 +47,18 @@ impl Drop for Issue9427FollowUp { } } +struct Issue9427Followup2 { + ptr: *const (), +} +impl Issue9427Followup2 { + fn from_owned(ptr: *const ()) -> Option { + (!ptr.is_null()).then(|| Self { ptr }) + } +} +impl Drop for Issue9427Followup2 { + fn drop(&mut self) {} +} + struct Issue10437; impl Deref for Issue10437 { type Target = u32; @@ -128,6 +140,7 @@ fn main() { // Should not lint - bool let _ = (0 == 1).then(|| Issue9427(0)); // Issue9427 has a significant drop let _ = false.then(|| Issue9427FollowUp); // Issue9427FollowUp has a significant drop + let _ = false.then(|| Issue9427Followup2 { ptr: std::ptr::null() }); // should not lint, bind_instead_of_map takes priority let _ = Some(10).and_then(|idx| Some(ext_arr[idx])); diff --git a/tests/ui/unnecessary_lazy_eval.rs b/tests/ui/unnecessary_lazy_eval.rs index 5045fcd790eda..ea55b1d9a905f 100644 --- a/tests/ui/unnecessary_lazy_eval.rs +++ b/tests/ui/unnecessary_lazy_eval.rs @@ -47,6 +47,18 @@ impl Drop for Issue9427FollowUp { } } +struct Issue9427Followup2 { + ptr: *const (), +} +impl Issue9427Followup2 { + fn from_owned(ptr: *const ()) -> Option { + (!ptr.is_null()).then(|| Self { ptr }) + } +} +impl Drop for Issue9427Followup2 { + fn drop(&mut self) {} +} + struct Issue10437; impl Deref for Issue10437 { type Target = u32; @@ -128,6 +140,7 @@ fn main() { // Should not lint - bool let _ = (0 == 1).then(|| Issue9427(0)); // Issue9427 has a significant drop let _ = false.then(|| Issue9427FollowUp); // Issue9427FollowUp has a significant drop + let _ = false.then(|| Issue9427Followup2 { ptr: std::ptr::null() }); // should not lint, bind_instead_of_map takes priority let _ = Some(10).and_then(|idx| Some(ext_arr[idx])); diff --git a/tests/ui/unnecessary_lazy_eval.stderr b/tests/ui/unnecessary_lazy_eval.stderr index 466664aee6c79..6ff2691a46167 100644 --- a/tests/ui/unnecessary_lazy_eval.stderr +++ b/tests/ui/unnecessary_lazy_eval.stderr @@ -1,5 +1,5 @@ error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:71:13 + --> $DIR/unnecessary_lazy_eval.rs:83:13 | LL | let _ = opt.unwrap_or_else(|| 2); | ^^^^-------------------- @@ -10,7 +10,7 @@ LL | let _ = opt.unwrap_or_else(|| 2); = help: to override `-D warnings` add `#[allow(clippy::unnecessary_lazy_evaluations)]` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:72:13 + --> $DIR/unnecessary_lazy_eval.rs:84:13 | LL | let _ = opt.unwrap_or_else(|| astronomers_pi); | ^^^^--------------------------------- @@ -18,7 +18,7 @@ LL | let _ = opt.unwrap_or_else(|| astronomers_pi); | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:73:13 + --> $DIR/unnecessary_lazy_eval.rs:85:13 | LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); | ^^^^------------------------------------- @@ -26,7 +26,7 @@ LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:75:13 + --> $DIR/unnecessary_lazy_eval.rs:87:13 | LL | let _ = opt.and_then(|_| ext_opt); | ^^^^--------------------- @@ -34,7 +34,7 @@ LL | let _ = opt.and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:76:13 + --> $DIR/unnecessary_lazy_eval.rs:88:13 | LL | let _ = opt.or_else(|| ext_opt); | ^^^^------------------- @@ -42,7 +42,7 @@ LL | let _ = opt.or_else(|| ext_opt); | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:77:13 + --> $DIR/unnecessary_lazy_eval.rs:89:13 | LL | let _ = opt.or_else(|| None); | ^^^^---------------- @@ -50,7 +50,7 @@ LL | let _ = opt.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:78:13 + --> $DIR/unnecessary_lazy_eval.rs:90:13 | LL | let _ = opt.get_or_insert_with(|| 2); | ^^^^------------------------ @@ -58,7 +58,7 @@ LL | let _ = opt.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:79:13 + --> $DIR/unnecessary_lazy_eval.rs:91:13 | LL | let _ = opt.ok_or_else(|| 2); | ^^^^---------------- @@ -66,7 +66,7 @@ LL | let _ = opt.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:80:13 + --> $DIR/unnecessary_lazy_eval.rs:92:13 | LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); | ^^^^^^^^^^^^^^^^^------------------------------- @@ -74,7 +74,7 @@ LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:81:13 + --> $DIR/unnecessary_lazy_eval.rs:93:13 | LL | let _ = cond.then(|| astronomers_pi); | ^^^^^----------------------- @@ -82,7 +82,7 @@ LL | let _ = cond.then(|| astronomers_pi); | help: use `then_some(..)` instead: `then_some(astronomers_pi)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:82:13 + --> $DIR/unnecessary_lazy_eval.rs:94:13 | LL | let _ = true.then(|| -> _ {}); | ^^^^^---------------- @@ -90,7 +90,7 @@ LL | let _ = true.then(|| -> _ {}); | help: use `then_some(..)` instead: `then_some({})` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:83:13 + --> $DIR/unnecessary_lazy_eval.rs:95:13 | LL | let _ = true.then(|| {}); | ^^^^^----------- @@ -98,7 +98,7 @@ LL | let _ = true.then(|| {}); | help: use `then_some(..)` instead: `then_some({})` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:87:13 + --> $DIR/unnecessary_lazy_eval.rs:99:13 | LL | let _ = Some(1).unwrap_or_else(|| *r); | ^^^^^^^^--------------------- @@ -106,7 +106,7 @@ LL | let _ = Some(1).unwrap_or_else(|| *r); | help: use `unwrap_or(..)` instead: `unwrap_or(*r)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:89:13 + --> $DIR/unnecessary_lazy_eval.rs:101:13 | LL | let _ = Some(1).unwrap_or_else(|| *b); | ^^^^^^^^--------------------- @@ -114,7 +114,7 @@ LL | let _ = Some(1).unwrap_or_else(|| *b); | help: use `unwrap_or(..)` instead: `unwrap_or(*b)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:91:13 + --> $DIR/unnecessary_lazy_eval.rs:103:13 | LL | let _ = Some(1).as_ref().unwrap_or_else(|| &r); | ^^^^^^^^^^^^^^^^^--------------------- @@ -122,7 +122,7 @@ LL | let _ = Some(1).as_ref().unwrap_or_else(|| &r); | help: use `unwrap_or(..)` instead: `unwrap_or(&r)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:92:13 + --> $DIR/unnecessary_lazy_eval.rs:104:13 | LL | let _ = Some(1).as_ref().unwrap_or_else(|| &b); | ^^^^^^^^^^^^^^^^^--------------------- @@ -130,7 +130,7 @@ LL | let _ = Some(1).as_ref().unwrap_or_else(|| &b); | help: use `unwrap_or(..)` instead: `unwrap_or(&b)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:95:13 + --> $DIR/unnecessary_lazy_eval.rs:107:13 | LL | let _ = Some(10).unwrap_or_else(|| 2); | ^^^^^^^^^-------------------- @@ -138,7 +138,7 @@ LL | let _ = Some(10).unwrap_or_else(|| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:96:13 + --> $DIR/unnecessary_lazy_eval.rs:108:13 | LL | let _ = Some(10).and_then(|_| ext_opt); | ^^^^^^^^^--------------------- @@ -146,7 +146,7 @@ LL | let _ = Some(10).and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:97:28 + --> $DIR/unnecessary_lazy_eval.rs:109:28 | LL | let _: Option = None.or_else(|| ext_opt); | ^^^^^------------------- @@ -154,7 +154,7 @@ LL | let _: Option = None.or_else(|| ext_opt); | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:98:13 + --> $DIR/unnecessary_lazy_eval.rs:110:13 | LL | let _ = None.get_or_insert_with(|| 2); | ^^^^^------------------------ @@ -162,7 +162,7 @@ LL | let _ = None.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:99:35 + --> $DIR/unnecessary_lazy_eval.rs:111:35 | LL | let _: Result = None.ok_or_else(|| 2); | ^^^^^---------------- @@ -170,7 +170,7 @@ LL | let _: Result = None.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:100:28 + --> $DIR/unnecessary_lazy_eval.rs:112:28 | LL | let _: Option = None.or_else(|| None); | ^^^^^---------------- @@ -178,7 +178,7 @@ LL | let _: Option = None.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:103:13 + --> $DIR/unnecessary_lazy_eval.rs:115:13 | LL | let _ = deep.0.unwrap_or_else(|| 2); | ^^^^^^^-------------------- @@ -186,7 +186,7 @@ LL | let _ = deep.0.unwrap_or_else(|| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:104:13 + --> $DIR/unnecessary_lazy_eval.rs:116:13 | LL | let _ = deep.0.and_then(|_| ext_opt); | ^^^^^^^--------------------- @@ -194,7 +194,7 @@ LL | let _ = deep.0.and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:105:13 + --> $DIR/unnecessary_lazy_eval.rs:117:13 | LL | let _ = deep.0.or_else(|| None); | ^^^^^^^---------------- @@ -202,7 +202,7 @@ LL | let _ = deep.0.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:106:13 + --> $DIR/unnecessary_lazy_eval.rs:118:13 | LL | let _ = deep.0.get_or_insert_with(|| 2); | ^^^^^^^------------------------ @@ -210,7 +210,7 @@ LL | let _ = deep.0.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:107:13 + --> $DIR/unnecessary_lazy_eval.rs:119:13 | LL | let _ = deep.0.ok_or_else(|| 2); | ^^^^^^^---------------- @@ -218,7 +218,7 @@ LL | let _ = deep.0.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:137:28 + --> $DIR/unnecessary_lazy_eval.rs:150:28 | LL | let _: Option = None.or_else(|| Some(3)); | ^^^^^------------------- @@ -226,7 +226,7 @@ LL | let _: Option = None.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:138:13 + --> $DIR/unnecessary_lazy_eval.rs:151:13 | LL | let _ = deep.0.or_else(|| Some(3)); | ^^^^^^^------------------- @@ -234,7 +234,7 @@ LL | let _ = deep.0.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:139:13 + --> $DIR/unnecessary_lazy_eval.rs:152:13 | LL | let _ = opt.or_else(|| Some(3)); | ^^^^------------------- @@ -242,7 +242,7 @@ LL | let _ = opt.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:145:13 + --> $DIR/unnecessary_lazy_eval.rs:158:13 | LL | let _ = res2.unwrap_or_else(|_| 2); | ^^^^^--------------------- @@ -250,7 +250,7 @@ LL | let _ = res2.unwrap_or_else(|_| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:146:13 + --> $DIR/unnecessary_lazy_eval.rs:159:13 | LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); | ^^^^^---------------------------------- @@ -258,7 +258,7 @@ LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:147:13 + --> $DIR/unnecessary_lazy_eval.rs:160:13 | LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); | ^^^^^-------------------------------------- @@ -266,7 +266,7 @@ LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:169:35 + --> $DIR/unnecessary_lazy_eval.rs:182:35 | LL | let _: Result = res.and_then(|_| Err(2)); | ^^^^-------------------- @@ -274,7 +274,7 @@ LL | let _: Result = res.and_then(|_| Err(2)); | help: use `and(..)` instead: `and(Err(2))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:170:35 + --> $DIR/unnecessary_lazy_eval.rs:183:35 | LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); | ^^^^--------------------------------- @@ -282,7 +282,7 @@ LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); | help: use `and(..)` instead: `and(Err(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:171:35 + --> $DIR/unnecessary_lazy_eval.rs:184:35 | LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)); | ^^^^------------------------------------- @@ -290,7 +290,7 @@ LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)) | help: use `and(..)` instead: `and(Err(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:173:35 + --> $DIR/unnecessary_lazy_eval.rs:186:35 | LL | let _: Result = res.or_else(|_| Ok(2)); | ^^^^------------------ @@ -298,7 +298,7 @@ LL | let _: Result = res.or_else(|_| Ok(2)); | help: use `or(..)` instead: `or(Ok(2))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:174:35 + --> $DIR/unnecessary_lazy_eval.rs:187:35 | LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); | ^^^^------------------------------- @@ -306,7 +306,7 @@ LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); | help: use `or(..)` instead: `or(Ok(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:175:35 + --> $DIR/unnecessary_lazy_eval.rs:188:35 | LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); | ^^^^----------------------------------- @@ -314,7 +314,7 @@ LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:176:35 + --> $DIR/unnecessary_lazy_eval.rs:189:35 | LL | let _: Result = res. | ___________________________________^ @@ -329,7 +329,7 @@ LL | | or_else(|_| Ok(ext_str.some_field)); | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:206:14 + --> $DIR/unnecessary_lazy_eval.rs:219:14 | LL | let _x = false.then(|| i32::MAX + 1); | ^^^^^^--------------------- @@ -337,7 +337,7 @@ LL | let _x = false.then(|| i32::MAX + 1); | help: use `then_some(..)` instead: `then_some(i32::MAX + 1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:208:14 + --> $DIR/unnecessary_lazy_eval.rs:221:14 | LL | let _x = false.then(|| i32::MAX * 2); | ^^^^^^--------------------- @@ -345,7 +345,7 @@ LL | let _x = false.then(|| i32::MAX * 2); | help: use `then_some(..)` instead: `then_some(i32::MAX * 2)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:210:14 + --> $DIR/unnecessary_lazy_eval.rs:223:14 | LL | let _x = false.then(|| i32::MAX - 1); | ^^^^^^--------------------- @@ -353,7 +353,7 @@ LL | let _x = false.then(|| i32::MAX - 1); | help: use `then_some(..)` instead: `then_some(i32::MAX - 1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:212:14 + --> $DIR/unnecessary_lazy_eval.rs:225:14 | LL | let _x = false.then(|| i32::MIN - 1); | ^^^^^^--------------------- @@ -361,7 +361,7 @@ LL | let _x = false.then(|| i32::MIN - 1); | help: use `then_some(..)` instead: `then_some(i32::MIN - 1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:214:14 + --> $DIR/unnecessary_lazy_eval.rs:227:14 | LL | let _x = false.then(|| (1 + 2 * 3 - 2 / 3 + 9) << 2); | ^^^^^^------------------------------------- @@ -369,7 +369,7 @@ LL | let _x = false.then(|| (1 + 2 * 3 - 2 / 3 + 9) << 2); | help: use `then_some(..)` instead: `then_some((1 + 2 * 3 - 2 / 3 + 9) << 2)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:216:14 + --> $DIR/unnecessary_lazy_eval.rs:229:14 | LL | let _x = false.then(|| 255u8 << 7); | ^^^^^^------------------- @@ -377,7 +377,7 @@ LL | let _x = false.then(|| 255u8 << 7); | help: use `then_some(..)` instead: `then_some(255u8 << 7)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:218:14 + --> $DIR/unnecessary_lazy_eval.rs:231:14 | LL | let _x = false.then(|| 255u8 << 8); | ^^^^^^------------------- @@ -385,7 +385,7 @@ LL | let _x = false.then(|| 255u8 << 8); | help: use `then_some(..)` instead: `then_some(255u8 << 8)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:220:14 + --> $DIR/unnecessary_lazy_eval.rs:233:14 | LL | let _x = false.then(|| 255u8 >> 8); | ^^^^^^------------------- @@ -393,7 +393,7 @@ LL | let _x = false.then(|| 255u8 >> 8); | help: use `then_some(..)` instead: `then_some(255u8 >> 8)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:223:14 + --> $DIR/unnecessary_lazy_eval.rs:236:14 | LL | let _x = false.then(|| i32::MAX + -1); | ^^^^^^---------------------- @@ -401,7 +401,7 @@ LL | let _x = false.then(|| i32::MAX + -1); | help: use `then_some(..)` instead: `then_some(i32::MAX + -1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:225:14 + --> $DIR/unnecessary_lazy_eval.rs:238:14 | LL | let _x = false.then(|| -i32::MAX); | ^^^^^^------------------ @@ -409,7 +409,7 @@ LL | let _x = false.then(|| -i32::MAX); | help: use `then_some(..)` instead: `then_some(-i32::MAX)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:227:14 + --> $DIR/unnecessary_lazy_eval.rs:240:14 | LL | let _x = false.then(|| -i32::MIN); | ^^^^^^------------------ @@ -417,7 +417,7 @@ LL | let _x = false.then(|| -i32::MIN); | help: use `then_some(..)` instead: `then_some(-i32::MIN)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:230:14 + --> $DIR/unnecessary_lazy_eval.rs:243:14 | LL | let _x = false.then(|| 255 >> -7); | ^^^^^^------------------ @@ -425,7 +425,7 @@ LL | let _x = false.then(|| 255 >> -7); | help: use `then_some(..)` instead: `then_some(255 >> -7)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:232:14 + --> $DIR/unnecessary_lazy_eval.rs:245:14 | LL | let _x = false.then(|| 255 << -1); | ^^^^^^------------------ @@ -433,7 +433,7 @@ LL | let _x = false.then(|| 255 << -1); | help: use `then_some(..)` instead: `then_some(255 << -1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:234:14 + --> $DIR/unnecessary_lazy_eval.rs:247:14 | LL | let _x = false.then(|| 1 / 0); | ^^^^^^-------------- @@ -441,7 +441,7 @@ LL | let _x = false.then(|| 1 / 0); | help: use `then_some(..)` instead: `then_some(1 / 0)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:236:14 + --> $DIR/unnecessary_lazy_eval.rs:249:14 | LL | let _x = false.then(|| x << -1); | ^^^^^^---------------- @@ -449,7 +449,7 @@ LL | let _x = false.then(|| x << -1); | help: use `then_some(..)` instead: `then_some(x << -1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:238:14 + --> $DIR/unnecessary_lazy_eval.rs:251:14 | LL | let _x = false.then(|| x << 2); | ^^^^^^--------------- @@ -457,7 +457,7 @@ LL | let _x = false.then(|| x << 2); | help: use `then_some(..)` instead: `then_some(x << 2)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:248:14 + --> $DIR/unnecessary_lazy_eval.rs:261:14 | LL | let _x = false.then(|| x / 0); | ^^^^^^-------------- @@ -465,7 +465,7 @@ LL | let _x = false.then(|| x / 0); | help: use `then_some(..)` instead: `then_some(x / 0)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:250:14 + --> $DIR/unnecessary_lazy_eval.rs:263:14 | LL | let _x = false.then(|| x % 0); | ^^^^^^-------------- @@ -473,7 +473,7 @@ LL | let _x = false.then(|| x % 0); | help: use `then_some(..)` instead: `then_some(x % 0)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:253:14 + --> $DIR/unnecessary_lazy_eval.rs:266:14 | LL | let _x = false.then(|| 1 / -1); | ^^^^^^--------------- @@ -481,7 +481,7 @@ LL | let _x = false.then(|| 1 / -1); | help: use `then_some(..)` instead: `then_some(1 / -1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:255:14 + --> $DIR/unnecessary_lazy_eval.rs:268:14 | LL | let _x = false.then(|| i32::MIN / -1); | ^^^^^^---------------------- @@ -489,7 +489,7 @@ LL | let _x = false.then(|| i32::MIN / -1); | help: use `then_some(..)` instead: `then_some(i32::MIN / -1)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:258:14 + --> $DIR/unnecessary_lazy_eval.rs:271:14 | LL | let _x = false.then(|| i32::MIN / 0); | ^^^^^^--------------------- @@ -497,7 +497,7 @@ LL | let _x = false.then(|| i32::MIN / 0); | help: use `then_some(..)` instead: `then_some(i32::MIN / 0)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:260:14 + --> $DIR/unnecessary_lazy_eval.rs:273:14 | LL | let _x = false.then(|| 4 / 2); | ^^^^^^-------------- @@ -505,7 +505,7 @@ LL | let _x = false.then(|| 4 / 2); | help: use `then_some(..)` instead: `then_some(4 / 2)` error: unnecessary closure used with `bool::then` - --> $DIR/unnecessary_lazy_eval.rs:268:14 + --> $DIR/unnecessary_lazy_eval.rs:281:14 | LL | let _x = false.then(|| f1 + f2); | ^^^^^^---------------- From f73e37d00ce29fbf81f62103ce06d2d3787c3842 Mon Sep 17 00:00:00 2001 From: Matthew Jasper Date: Thu, 4 Jan 2024 10:29:47 +0000 Subject: [PATCH 47/73] Update clippy for hir::Guard removal --- clippy_lints/src/entry.rs | 4 +- clippy_lints/src/manual_clamp.rs | 4 +- clippy_lints/src/matches/collapsible_match.rs | 8 +-- .../src/matches/match_like_matches.rs | 23 ++------- clippy_lints/src/matches/needless_match.rs | 17 ++----- clippy_lints/src/matches/redundant_guards.rs | 50 ++++++++----------- .../src/matches/redundant_pattern_match.rs | 20 ++++---- .../src/mixed_read_write_in_expression.rs | 4 +- clippy_lints/src/utils/author.rs | 10 +--- clippy_utils/src/hir_utils.rs | 24 ++------- clippy_utils/src/lib.rs | 4 +- 11 files changed, 56 insertions(+), 112 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index ce0a1dfdc61f2..0b4bc375df0d9 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -8,7 +8,7 @@ use core::fmt::{self, Write}; use rustc_errors::Applicability; use rustc_hir::hir_id::HirIdSet; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, Guard, HirId, Let, Pat, Stmt, StmtKind, UnOp}; +use rustc_hir::{Block, Expr, ExprKind, HirId, Pat, Stmt, StmtKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::{Span, SyntaxContext, DUMMY_SP}; @@ -465,7 +465,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> { let mut is_map_used = self.is_map_used; for arm in arms { self.visit_pat(arm.pat); - if let Some(Guard::If(guard) | Guard::IfLet(&Let { init: guard, .. })) = arm.guard { + if let Some(guard) = arm.guard { self.visit_non_tail_expr(guard); } is_map_used |= self.visit_cond_arm(arm.body); diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 385fe387a314f..0da309f9531e0 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -11,7 +11,7 @@ use clippy_utils::{ use itertools::Itertools; use rustc_errors::{Applicability, Diagnostic}; use rustc_hir::def::Res; -use rustc_hir::{Arm, BinOpKind, Block, Expr, ExprKind, Guard, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind}; +use rustc_hir::{Arm, BinOpKind, Block, Expr, ExprKind, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; @@ -394,7 +394,7 @@ fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Opt // Find possible min/max branches let minmax_values = |a: &'tcx Arm<'tcx>| { if let PatKind::Binding(_, var_hir_id, _, None) = &a.pat.kind - && let Some(Guard::If(e)) = a.guard + && let Some(e) = a.guard { Some((e, var_hir_id, a.body)) } else { diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index 91e6ca7fa8bc1..5fef5930fab25 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -7,7 +7,7 @@ use clippy_utils::{ }; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind}; +use rustc_hir::{Arm, Expr, HirId, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; @@ -16,7 +16,7 @@ use super::COLLAPSIBLE_MATCH; pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { 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, arm.body, arm.guard.as_ref(), Some(els_arm.body)); + check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body)); } } } @@ -35,7 +35,7 @@ fn check_arm<'tcx>( outer_is_match: bool, outer_pat: &'tcx Pat<'tcx>, outer_then_body: &'tcx Expr<'tcx>, - outer_guard: Option<&'tcx Guard<'tcx>>, + outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, ) { let inner_expr = peel_blocks_with_stmt(outer_then_body); @@ -71,7 +71,7 @@ fn check_arm<'tcx>( // the binding must not be used in the if guard && outer_guard.map_or( true, - |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id) + |e| !is_local_used(cx, e, binding_id) ) // ...or anywhere in the inner expression && match inner { diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 56123326fe4a7..b062e81cefddd 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -4,7 +4,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment}; use rustc_ast::{Attribute, LitKind}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat, PatKind, QPath}; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; use rustc_span::source_map::Spanned; @@ -41,14 +41,8 @@ pub(super) fn check_match<'tcx>( find_matches_sugg( cx, scrutinee, - arms.iter().map(|arm| { - ( - cx.tcx.hir().attrs(arm.hir_id), - Some(arm.pat), - arm.body, - arm.guard.as_ref(), - ) - }), + arms.iter() + .map(|arm| (cx.tcx.hir().attrs(arm.hir_id), Some(arm.pat), arm.body, arm.guard)), e, false, ) @@ -67,14 +61,7 @@ where I: Clone + DoubleEndedIterator + ExactSizeIterator - + Iterator< - Item = ( - &'a [Attribute], - Option<&'a Pat<'b>>, - &'a Expr<'b>, - Option<&'a Guard<'b>>, - ), - >, + + Iterator>, &'a Expr<'b>, Option<&'a Expr<'b>>)>, { if !span_contains_comment(cx.sess().source_map(), expr.span) && iter.len() >= 2 @@ -115,7 +102,7 @@ where }) .join(" | ") }; - let pat_and_guard = if let Some(Guard::If(g)) = first_guard { + let pat_and_guard = if let Some(g) = first_guard { format!( "{pat} if {}", snippet_with_applicability(cx, g.span, "..", &mut applicability) diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index 44dc29c36a6ba..cc482f15a91df 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -8,7 +8,7 @@ use clippy_utils::{ }; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, Guard, ItemKind, Node, Pat, PatKind, Path, QPath}; +use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, ItemKind, Node, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::sym; @@ -66,18 +66,9 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) let arm_expr = peel_blocks_with_stmt(arm.body); if let Some(guard_expr) = &arm.guard { - match guard_expr { - // gives up if `pat if expr` can have side effects - Guard::If(if_cond) => { - if if_cond.can_have_side_effects() { - return false; - } - }, - // gives up `pat if let ...` arm - Guard::IfLet(_) => { - return false; - }, - }; + if guard_expr.can_have_side_effects() { + return false; + } } if let PatKind::Wild = arm.pat.kind { diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index f57b22374c8e8..dfaaeb14ca3cb 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -5,7 +5,7 @@ use clippy_utils::visitors::{for_each_expr, is_local_used}; use rustc_ast::{BorrowKind, LitKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind}; +use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol}; @@ -21,20 +21,19 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { }; // `Some(x) if matches!(x, y)` - if let Guard::If(if_expr) = guard - && let ExprKind::Match( - scrutinee, - [ - arm, - Arm { - pat: Pat { - kind: PatKind::Wild, .. - }, - .. + if let ExprKind::Match( + scrutinee, + [ + arm, + Arm { + pat: Pat { + kind: PatKind::Wild, .. }, - ], - MatchSource::Normal, - ) = if_expr.kind + .. + }, + ], + MatchSource::Normal, + ) = guard.kind && let Some(binding) = get_pat_binding(cx, scrutinee, outer_arm) { let pat_span = match (arm.pat.kind, binding.byref_ident) { @@ -45,14 +44,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { emit_redundant_guards( cx, outer_arm, - if_expr.span, + guard.span, snippet(cx, pat_span, ""), &binding, arm.guard, ); } // `Some(x) if let Some(2) = x` - else if let Guard::IfLet(let_expr) = guard + else if let ExprKind::Let(let_expr) = guard.kind && let Some(binding) = get_pat_binding(cx, let_expr.init, outer_arm) { let pat_span = match (let_expr.pat.kind, binding.byref_ident) { @@ -71,8 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { } // `Some(x) if x == Some(2)` // `Some(x) if Some(2) == x` - else if let Guard::If(if_expr) = guard - && let ExprKind::Binary(bin_op, local, pat) = if_expr.kind + else if let ExprKind::Binary(bin_op, local, pat) = guard.kind && matches!(bin_op.node, BinOpKind::Eq) // Ensure they have the same type. If they don't, we'd need deref coercion which isn't // possible (currently) in a pattern. In some cases, you can use something like @@ -96,16 +94,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { emit_redundant_guards( cx, outer_arm, - if_expr.span, + guard.span, snippet(cx, pat_span, ""), &binding, None, ); - } else if let Guard::If(if_expr) = guard - && let ExprKind::MethodCall(path, recv, args, ..) = if_expr.kind + } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind && let Some(binding) = get_pat_binding(cx, recv, outer_arm) { - check_method_calls(cx, outer_arm, path.ident.name, recv, args, if_expr, &binding); + check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding); } } } @@ -216,7 +213,7 @@ fn emit_redundant_guards<'tcx>( guard_span: Span, binding_replacement: Cow<'static, str>, pat_binding: &PatBindingInfo, - inner_guard: Option>, + inner_guard: Option<&Expr<'_>>, ) { span_lint_and_then( cx, @@ -242,12 +239,7 @@ fn emit_redundant_guards<'tcx>( ( guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()), inner_guard.map_or_else(String::new, |guard| { - let (prefix, span) = match guard { - Guard::If(e) => ("if", e.span), - Guard::IfLet(l) => ("if let", l.span), - }; - - format!(" {prefix} {}", snippet(cx, span, "")) + format!(" if {}", snippet(cx, guard.span, "")) }), ), ], diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index a4acdfb1db4e1..b5870d94d996a 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -9,7 +9,7 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; -use rustc_hir::{Arm, Expr, ExprKind, Guard, Node, Pat, PatKind, QPath, UnOp}; +use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_span::{sym, Span, Symbol}; @@ -277,8 +277,6 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_")); if let Some(guard) = maybe_guard { - let Guard::If(guard) = *guard else { return }; // `...is_none() && let ...` is a syntax error - // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, // counter to the intuition that it should be `Guard::IfLet`, so we need another check @@ -319,7 +317,7 @@ fn found_good_method<'tcx>( cx: &LateContext<'_>, arms: &'tcx [Arm<'tcx>], node: (&PatKind<'_>, &PatKind<'_>), -) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { +) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { match node { ( PatKind::TupleStruct(ref path_left, patterns_left, _), @@ -409,7 +407,7 @@ fn get_good_method<'tcx>( cx: &LateContext<'_>, arms: &'tcx [Arm<'tcx>], path_left: &QPath<'_>, -) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { +) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { if let Some(name) = get_ident(path_left) { let (expected_item_left, should_be_left, should_be_right) = match name.as_str() { "Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"), @@ -478,7 +476,7 @@ fn find_good_method_for_match<'a, 'tcx>( expected_item_right: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { +) -> Option<(&'a str, Option<&'tcx Expr<'tcx>>)> { let first_pat = arms[0].pat; let second_pat = arms[1].pat; @@ -496,8 +494,8 @@ fn find_good_method_for_match<'a, 'tcx>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), - (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard)), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard)), _ => None, }, _ => None, @@ -511,7 +509,7 @@ fn find_good_method_for_matches_macro<'a, 'tcx>( expected_item_left: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { +) -> Option<(&'a str, Option<&'tcx Expr<'tcx>>)> { let first_pat = arms[0].pat; let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) { @@ -522,8 +520,8 @@ fn find_good_method_for_matches_macro<'a, 'tcx>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), - (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard)), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard)), _ => None, }, _ => None, diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 3ff40081c4724..195ce17629a71 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id}; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Local, Node, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; @@ -119,7 +119,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> { ExprKind::Match(e, arms, _) => { self.visit_expr(e); for arm in arms { - if let Some(Guard::If(if_expr)) = arm.guard { + if let Some(if_expr) = arm.guard { self.visit_expr(if_expr); } // make sure top level arm expressions aren't linted diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 8817e46b3c8c0..8d38b87e1d794 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -318,17 +318,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.pat(field!(arm.pat)); match arm.value.guard { None => chain!(self, "{arm}.guard.is_none()"), - Some(hir::Guard::If(expr)) => { + Some(expr) => { bind!(self, expr); - chain!(self, "let Some(Guard::If({expr})) = {arm}.guard"); + chain!(self, "let Some({expr}) = {arm}.guard"); self.expr(expr); }, - Some(hir::Guard::IfLet(let_expr)) => { - bind!(self, let_expr); - chain!(self, "let Some(Guard::IfLet({let_expr}) = {arm}.guard"); - self.pat(field!(let_expr.pat)); - self.expr(field!(let_expr.init)); - }, } self.expr(field!(arm.body)); } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index e610ed930505b..a23105691bf3b 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -8,7 +8,7 @@ use rustc_hir::def::Res; use rustc_hir::MatchSource::TryDesugar; use rustc_hir::{ ArrayLen, BinOpKind, BindingAnnotation, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, - GenericArgs, Guard, HirId, HirIdMap, InlineAsmOperand, Let, Lifetime, LifetimeName, Pat, PatField, PatKind, Path, + GenericArgs, HirId, HirIdMap, InlineAsmOperand, Let, Lifetime, LifetimeName, Pat, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding, }; use rustc_lexer::{tokenize, TokenKind}; @@ -320,7 +320,7 @@ impl HirEqInterExpr<'_, '_, '_> { && self.eq_expr(le, re) && over(la, ra, |l, r| { self.eq_pat(l.pat, r.pat) - && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r)) + && both(&l.guard, &r.guard, |l, r| self.eq_expr(l, r)) && self.eq_expr(l.body, r.body) }) }, @@ -410,16 +410,6 @@ impl HirEqInterExpr<'_, '_, '_> { left.ident.name == right.ident.name && self.eq_expr(left.expr, right.expr) } - fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool { - match (left, right) { - (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r), - (Guard::IfLet(l), Guard::IfLet(r)) => { - self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init) - }, - _ => false, - } - } - fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool { match (left, right) { (GenericArg::Const(l), GenericArg::Const(r)) => self.eq_body(l.value.body, r.value.body), @@ -876,7 +866,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { for arm in arms { self.hash_pat(arm.pat); if let Some(ref e) = arm.guard { - self.hash_guard(e); + self.hash_expr(e); } self.hash_expr(arm.body); } @@ -1056,14 +1046,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } - pub fn hash_guard(&mut self, g: &Guard<'_>) { - match g { - Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => { - self.hash_expr(expr); - }, - } - } - pub fn hash_lifetime(&mut self, lifetime: &Lifetime) { lifetime.ident.name.hash(&mut self.s); std::mem::discriminant(&lifetime.res).hash(&mut self.s); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 70a3c6f82c1cd..cdf8528f48a27 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3164,7 +3164,7 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option< self.is_never = false; if let Some(guard) = arm.guard { let in_final_expr = mem::replace(&mut self.in_final_expr, false); - self.visit_expr(guard.body()); + self.visit_expr(guard); self.in_final_expr = in_final_expr; // The compiler doesn't consider diverging guards as causing the arm to diverge. self.is_never = false; @@ -3223,7 +3223,7 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option< fn visit_arm(&mut self, arm: &Arm<'tcx>) { if let Some(guard) = arm.guard { let in_final_expr = mem::replace(&mut self.in_final_expr, false); - self.visit_expr(guard.body()); + self.visit_expr(guard); self.in_final_expr = in_final_expr; } self.visit_expr(arm.body); From 9e4e8ef1023d02367a8919f4d13df913f2232cb2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Jan 2024 16:44:01 +0100 Subject: [PATCH 48/73] Don't emit `struct_field_names` lint if all fields are booleans and don't start with the type's name --- clippy_lints/src/item_name_repetitions.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index a9f1612ff05ed..276c1abb60cd5 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -1,6 +1,7 @@ //! lint on enum variants that are prefixed or suffixed by the same characters use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir}; +use clippy_utils::is_bool; use clippy_utils::macros::span_is_local; use clippy_utils::source::is_present_in_source; use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case}; @@ -231,6 +232,10 @@ fn check_fields(cx: &LateContext<'_>, threshold: u64, item: &Item<'_>, fields: & (false, _) => ("pre", prefix), (true, false) => ("post", postfix), }; + if fields.iter().all(|field| is_bool(field.ty)) { + // If all fields are booleans, we don't want to emit this lint. + return; + } span_lint_and_help( cx, STRUCT_FIELD_NAMES, From 7aa4624a8fe8ff2c4273e35457a7cee6100a8355 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Jan 2024 16:44:41 +0100 Subject: [PATCH 49/73] Extend `struct_field_names` lint ui test --- tests/ui/struct_fields.rs | 22 ++++++++++++++++++---- tests/ui/struct_fields.stderr | 28 +++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/tests/ui/struct_fields.rs b/tests/ui/struct_fields.rs index a8b12c6dfe382..7c9e9d8ed2692 100644 --- a/tests/ui/struct_fields.rs +++ b/tests/ui/struct_fields.rs @@ -39,14 +39,14 @@ struct DataStruct { struct DoublePrefix { //~^ ERROR: all fields have the same prefix: `some_data` some_data_a: bool, - some_data_b: bool, + some_data_b: i8, some_data_c: bool, } struct DoublePostfix { //~^ ERROR: all fields have the same postfix: `some_data` a_some_data: bool, - b_some_data: bool, + b_some_data: i8, c_some_data: bool, } @@ -54,14 +54,14 @@ struct DoublePostfix { struct NotSnakeCase { //~^ ERROR: all fields have the same postfix: `someData` a_someData: bool, - b_someData: bool, + b_someData: i8, c_someData: bool, } #[allow(non_snake_case)] struct NotSnakeCase2 { //~^ ERROR: all fields have the same prefix: `someData` someData_c: bool, - someData_b: bool, + someData_b: i8, someData_a_b: bool, } @@ -328,4 +328,18 @@ external! { } +// Should not warn +struct Config { + use_foo: bool, + use_bar: bool, + use_baz: bool, +} + +struct Use { + use_foo: bool, + //~^ ERROR: field name starts with the struct's name + use_bar: bool, + use_baz: bool, +} + fn main() {} diff --git a/tests/ui/struct_fields.stderr b/tests/ui/struct_fields.stderr index 4ca57715b1856..d2bdbd17d5cc5 100644 --- a/tests/ui/struct_fields.stderr +++ b/tests/ui/struct_fields.stderr @@ -45,7 +45,7 @@ error: all fields have the same prefix: `some_data` LL | / struct DoublePrefix { LL | | LL | | some_data_a: bool, -LL | | some_data_b: bool, +LL | | some_data_b: i8, LL | | some_data_c: bool, LL | | } | |_^ @@ -58,7 +58,7 @@ error: all fields have the same postfix: `some_data` LL | / struct DoublePostfix { LL | | LL | | a_some_data: bool, -LL | | b_some_data: bool, +LL | | b_some_data: i8, LL | | c_some_data: bool, LL | | } | |_^ @@ -71,7 +71,7 @@ error: all fields have the same postfix: `someData` LL | / struct NotSnakeCase { LL | | LL | | a_someData: bool, -LL | | b_someData: bool, +LL | | b_someData: i8, LL | | c_someData: bool, LL | | } | |_^ @@ -84,7 +84,7 @@ error: all fields have the same prefix: `someData` LL | / struct NotSnakeCase2 { LL | | LL | | someData_c: bool, -LL | | someData_b: bool, +LL | | someData_b: i8, LL | | someData_a_b: bool, LL | | } | |_^ @@ -261,5 +261,23 @@ LL | mk_struct_full_def!(PrefixData, some_data, some_meta, some_other); = help: remove the prefixes = note: this error originates in the macro `mk_struct_full_def` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 21 previous errors +error: field name starts with the struct's name + --> $DIR/struct_fields.rs:339:5 + | +LL | use_foo: bool, + | ^^^^^^^^^^^^^ + +error: field name starts with the struct's name + --> $DIR/struct_fields.rs:341:5 + | +LL | use_bar: bool, + | ^^^^^^^^^^^^^ + +error: field name starts with the struct's name + --> $DIR/struct_fields.rs:342:5 + | +LL | use_baz: bool, + | ^^^^^^^^^^^^^ + +error: aborting due to 24 previous errors From 2dba787c9086dc03f62c00b4dc97ba87694985ba Mon Sep 17 00:00:00 2001 From: Georgiy Komarov Date: Fri, 5 Jan 2024 12:54:53 -0300 Subject: [PATCH 50/73] Update URLs in `Type Checking` --- book/src/development/type_checking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/development/type_checking.md b/book/src/development/type_checking.md index a8c9660da4c85..dc29ab5d08dad 100644 --- a/book/src/development/type_checking.md +++ b/book/src/development/type_checking.md @@ -133,7 +133,7 @@ in this chapter: - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) -[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/sty/enum.TyKind.html#variant.Adt +[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/ty_kind/enum.TyKind.html#variant.Adt [AdtDef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/adt/struct.AdtDef.html [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty [node_type]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.node_type @@ -144,7 +144,7 @@ in this chapter: [LateLintPass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/typeck_results/struct.TypeckResults.html#method.pat_ty [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html -[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/sty/enum.TyKind.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/ty_kind/enum.TyKind.html [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html [middle_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/struct.Ty.html [hir_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/struct.Ty.html From fd9d330839c5ae8ddfa3438b0db422515b06ad36 Mon Sep 17 00:00:00 2001 From: Centri3 <114838443+Centri3@users.noreply.github.com> Date: Mon, 29 May 2023 22:05:07 -0500 Subject: [PATCH 51/73] Don't lint `let_unit_value` when `()` is explicit --- clippy_lints/src/unit_types/let_unit_value.rs | 21 ++++++- tests/ui/crashes/ice-8821.fixed | 10 ---- tests/ui/crashes/ice-8821.rs | 2 - tests/ui/crashes/ice-8821.stderr | 11 ---- tests/ui/let_unit.fixed | 50 ++++++++--------- tests/ui/let_unit.rs | 48 ++++++++-------- tests/ui/let_unit.stderr | 56 ++----------------- 7 files changed, 71 insertions(+), 127 deletions(-) delete mode 100644 tests/ui/crashes/ice-8821.fixed delete mode 100644 tests/ui/crashes/ice-8821.stderr diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index ef67f4b04b5a8..ed0958197f883 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -13,12 +13,31 @@ use rustc_middle::ty; use super::LET_UNIT_VALUE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + // skip `let () = { ... }` + if let PatKind::Tuple(fields, ..) = local.pat.kind + && fields.is_empty() + { + return; + } + if let Some(init) = local.init && !local.pat.span.from_expansion() && !in_external_macro(cx.sess(), local.span) && !is_from_async_await(local.span) && cx.typeck_results().pat_ty(local.pat).is_unit() { + // skip `let awa = ()` + if let ExprKind::Tup([]) = init.kind { + return; + } + + // skip `let _: () = { ... }` + if let Some(ty) = local.ty + && let TyKind::Tup([]) = ty.kind + { + return; + } + if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer)) || matches!(local.pat.kind, PatKind::Tuple([], ddpos) if ddpos.as_opt_usize().is_none())) && expr_needs_inferred_result(cx, init) @@ -34,7 +53,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { |diag| { diag.span_suggestion( local.pat.span, - "use a wild (`_`) binding", + "use a wildcard binding", "_", Applicability::MaybeIncorrect, // snippet ); diff --git a/tests/ui/crashes/ice-8821.fixed b/tests/ui/crashes/ice-8821.fixed deleted file mode 100644 index a25bb46f9ff7b..0000000000000 --- a/tests/ui/crashes/ice-8821.fixed +++ /dev/null @@ -1,10 +0,0 @@ -#![warn(clippy::let_unit_value)] - -fn f() {} -static FN: fn() = f; - -fn main() { - FN(); - //~^ ERROR: this let-binding has unit value - //~| NOTE: `-D clippy::let-unit-value` implied by `-D warnings` -} diff --git a/tests/ui/crashes/ice-8821.rs b/tests/ui/crashes/ice-8821.rs index 082f7c92646a6..fb87b79aeed87 100644 --- a/tests/ui/crashes/ice-8821.rs +++ b/tests/ui/crashes/ice-8821.rs @@ -5,6 +5,4 @@ static FN: fn() = f; fn main() { let _: () = FN(); - //~^ ERROR: this let-binding has unit value - //~| NOTE: `-D clippy::let-unit-value` implied by `-D warnings` } diff --git a/tests/ui/crashes/ice-8821.stderr b/tests/ui/crashes/ice-8821.stderr deleted file mode 100644 index 94ebb20918ed5..0000000000000 --- a/tests/ui/crashes/ice-8821.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: this let-binding has unit value - --> $DIR/ice-8821.rs:7:5 - | -LL | let _: () = FN(); - | ^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `FN();` - | - = note: `-D clippy::let-unit-value` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]` - -error: aborting due to 1 previous error - diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index f98ce9d50a994..4d41b5e5e5034 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -13,7 +13,14 @@ fn main() { let _y = 1; // this is fine let _z = ((), 1); // this as well if true { - (); + // do not lint this, since () is explicit + let _a = (); + let () = dummy(); + let () = (); + () = dummy(); + () = (); + let _a: () = (); + let _a: () = dummy(); } consume_units_with_for_loop(); // should be fine as well @@ -23,6 +30,8 @@ fn main() { let_and_return!(()) // should be fine } +fn dummy() {} + // Related to issue #1964 fn consume_units_with_for_loop() { // `for_let_unit` lint should not be triggered by consuming them using for loop. @@ -74,40 +83,29 @@ fn _returns_generic() { x.then(|| T::default()) } - let _: () = f(); // Ok - let _: () = f(); // Lint. + let _: () = f(); + let x: () = f(); - let _: () = f2(0i32); // Ok - let _: () = f2(0i32); // Lint. + let _: () = f2(0i32); + let x: () = f2(0i32); - f3(()); // Lint - f3(()); // Lint + let _: () = f3(()); + let x: () = f3(()); - // Should lint: - // fn f4(mut x: Vec) -> T { - // x.pop().unwrap() - // } - // let _: () = f4(vec![()]); - // let x: () = f4(vec![()]); + fn f4(mut x: Vec) -> T { + x.pop().unwrap() + } + let _: () = f4(vec![()]); + let x: () = f4(vec![()]); - // Ok let _: () = { let x = 5; f2(x) }; - let _: () = if true { f() } else { f2(0) }; // Ok - let _: () = if true { f() } else { f2(0) }; // Lint - - // Ok - let _: () = match Some(0) { - None => f2(1), - Some(0) => f(), - Some(1) => f2(3), - Some(_) => f2('x'), - }; + let _: () = if true { f() } else { f2(0) }; + let x: () = if true { f() } else { f2(0) }; - // Lint match Some(0) { None => f2(1), Some(0) => f(), @@ -155,7 +153,7 @@ fn _returns_generic() { { let _: () = x; let _: () = y; - z; + let _: () = z; let _: () = x1; let _: () = x2; let _: () = opt; diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 6d942ca8908c4..daa660be25e62 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -13,7 +13,14 @@ fn main() { let _y = 1; // this is fine let _z = ((), 1); // this as well if true { + // do not lint this, since () is explicit let _a = (); + let () = dummy(); + let () = (); + () = dummy(); + () = (); + let _a: () = (); + let _a: () = dummy(); } consume_units_with_for_loop(); // should be fine as well @@ -23,6 +30,8 @@ fn main() { let_and_return!(()) // should be fine } +fn dummy() {} + // Related to issue #1964 fn consume_units_with_for_loop() { // `for_let_unit` lint should not be triggered by consuming them using for loop. @@ -74,41 +83,30 @@ fn _returns_generic() { x.then(|| T::default()) } - let _: () = f(); // Ok - let x: () = f(); // Lint. + let _: () = f(); + let x: () = f(); - let _: () = f2(0i32); // Ok - let x: () = f2(0i32); // Lint. + let _: () = f2(0i32); + let x: () = f2(0i32); - let _: () = f3(()); // Lint - let x: () = f3(()); // Lint + let _: () = f3(()); + let x: () = f3(()); - // Should lint: - // fn f4(mut x: Vec) -> T { - // x.pop().unwrap() - // } - // let _: () = f4(vec![()]); - // let x: () = f4(vec![()]); + fn f4(mut x: Vec) -> T { + x.pop().unwrap() + } + let _: () = f4(vec![()]); + let x: () = f4(vec![()]); - // Ok let _: () = { let x = 5; f2(x) }; - let _: () = if true { f() } else { f2(0) }; // Ok - let x: () = if true { f() } else { f2(0) }; // Lint - - // Ok - let _: () = match Some(0) { - None => f2(1), - Some(0) => f(), - Some(1) => f2(3), - Some(_) => f2('x'), - }; + let _: () = if true { f() } else { f2(0) }; + let x: () = if true { f() } else { f2(0) }; - // Lint - let _: () = match Some(0) { + let x = match Some(0) { None => f2(1), Some(0) => f(), Some(1) => f2(3), diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index de106f50e0e7c..00a3c439ba06f 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -8,13 +8,7 @@ LL | let _x = println!("x"); = help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]` error: this let-binding has unit value - --> $DIR/let_unit.rs:16:9 - | -LL | let _a = (); - | ^^^^^^^^^^^^ help: omit the `let` binding: `();` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:51:5 + --> $DIR/let_unit.rs:60:5 | LL | / let _ = v LL | | .into_iter() @@ -37,45 +31,9 @@ LL + .unwrap(); | error: this let-binding has unit value - --> $DIR/let_unit.rs:78:5 - | -LL | let x: () = f(); // Lint. - | ^^^^-^^^^^^^^^^^ - | | - | help: use a wild (`_`) binding: `_` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:81:5 + --> $DIR/let_unit.rs:109:5 | -LL | let x: () = f2(0i32); // Lint. - | ^^^^-^^^^^^^^^^^^^^^^ - | | - | help: use a wild (`_`) binding: `_` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:83:5 - | -LL | let _: () = f3(()); // Lint - | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:84:5 - | -LL | let x: () = f3(()); // Lint - | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:100:5 - | -LL | let x: () = if true { f() } else { f2(0) }; // Lint - | ^^^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | help: use a wild (`_`) binding: `_` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:111:5 - | -LL | / let _: () = match Some(0) { +LL | / let x = match Some(0) { LL | | None => f2(1), LL | | Some(0) => f(), LL | | Some(1) => f2(3), @@ -93,11 +51,5 @@ LL + Some(_) => (), LL + }; | -error: this let-binding has unit value - --> $DIR/let_unit.rs:158:13 - | -LL | let _: () = z; - | ^^^^^^^^^^^^^^ help: omit the `let` binding: `z;` - -error: aborting due to 10 previous errors +error: aborting due to 3 previous errors From e10a05dff3f278f89dc4bce7fb9cbafc40767d49 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 2 Jan 2024 23:32:40 +0300 Subject: [PATCH 52/73] rustc_span: Optimize syntax context comparisons Including comparisons with root context --- clippy_lints/src/casts/unnecessary_cast.rs | 2 +- clippy_lints/src/implicit_hasher.rs | 2 +- clippy_lints/src/implicit_return.rs | 2 +- clippy_lints/src/methods/option_map_unwrap_or.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 849920bb76d51..3761ba81f5219 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -145,7 +145,7 @@ pub(super) fn check<'tcx>( if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) { if let Some(id) = path_to_local(cast_expr) && let Some(span) = cx.tcx.hir().opt_span(id) - && span.ctxt() != cast_expr.span.ctxt() + && !span.eq_ctxt(cast_expr.span) { // Binding context is different than the identifiers context. // Weird macro wizardry could be involved here. diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 43eb6a9b8386a..788fe82872784 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -118,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { vis.visit_ty(impl_.self_ty); for target in &vis.found { - if item.span.ctxt() != target.span().ctxt() { + if !item.span.eq_ctxt(target.span()) { return; } diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index d68c5c4bac68b..5288efd8df8cd 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -225,7 +225,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { _: LocalDefId, ) { if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_))) - || span.ctxt() != body.value.span.ctxt() + || !span.eq_ctxt(body.value.span) || in_external_macro(cx.sess(), span) { return; diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 63e64a5b35d05..47c9438c588f2 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -67,7 +67,7 @@ pub(super) fn check<'tcx>( } } - if unwrap_arg.span.ctxt() != map_span.ctxt() { + if !unwrap_arg.span.eq_ctxt(map_span) { return; } From 60c647b262d09ed104c6945ff29ab0ee35a2dee4 Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 6 Jan 2024 21:41:25 +0800 Subject: [PATCH 53/73] Fix bug: allow no- '_'-split binary format string, add test --- clippy_lints/src/non_octal_unix_permissions.rs | 11 ++++++++--- tests/ui/non_octal_unix_permissions.fixed | 4 ++++ tests/ui/non_octal_unix_permissions.rs | 4 ++++ tests/ui/non_octal_unix_permissions.stderr | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 12fd63b50f22a..e732a882cbe04 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -50,10 +50,15 @@ fn check_binary_unix_permissions(lit_kind: &LitKind, snip: &str) -> bool { let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); // check whether is binary format unix permissions - if group_sizes.len() != 3 && group_sizes.len() != 4 { - return false; + if group_sizes.len() == 1 && (num_lit.integer.len() == 9 || num_lit.integer.len() == 12) { + // 0bxxxxxxxxx or 0bxxxxxxxxxxxx + true + } else if group_sizes.len() == 3 || group_sizes.len() == 4 { + // 0bxxx_xxx_xxx or 0bxxx_xxx_xxx_xxx + group_sizes.iter().all(|len| *len == 3) + } else { + false } - group_sizes.iter().all(|len| *len == 3) } else { false } diff --git a/tests/ui/non_octal_unix_permissions.fixed b/tests/ui/non_octal_unix_permissions.fixed index 245d36cb734f6..237f5f5b97a4f 100644 --- a/tests/ui/non_octal_unix_permissions.fixed +++ b/tests/ui/non_octal_unix_permissions.fixed @@ -25,9 +25,13 @@ fn main() { permissions.set_mode(0o644); permissions.set_mode(0o704); + // no error + permissions.set_mode(0b111_000_100); // DirBuilderExt::mode let mut builder = DirBuilder::new(); builder.mode(0o755); builder.mode(0o406); + // no error + permissions.set_mode(0b111000100); } diff --git a/tests/ui/non_octal_unix_permissions.rs b/tests/ui/non_octal_unix_permissions.rs index d1559cba554bb..c8da5dbcec282 100644 --- a/tests/ui/non_octal_unix_permissions.rs +++ b/tests/ui/non_octal_unix_permissions.rs @@ -25,9 +25,13 @@ fn main() { permissions.set_mode(644); permissions.set_mode(0o704); + // no error + permissions.set_mode(0b111_000_100); // DirBuilderExt::mode let mut builder = DirBuilder::new(); builder.mode(755); builder.mode(0o406); + // no error + permissions.set_mode(0b111000100); } diff --git a/tests/ui/non_octal_unix_permissions.stderr b/tests/ui/non_octal_unix_permissions.stderr index 78c8f1a2fcf78..83688c1b4517c 100644 --- a/tests/ui/non_octal_unix_permissions.stderr +++ b/tests/ui/non_octal_unix_permissions.stderr @@ -20,7 +20,7 @@ LL | permissions.set_mode(644); | ^^^ help: consider using an octal literal instead: `0o644` error: using a non-octal value to set unix file permissions - --> $DIR/non_octal_unix_permissions.rs:31:18 + --> $DIR/non_octal_unix_permissions.rs:33:18 | LL | builder.mode(755); | ^^^ help: consider using an octal literal instead: `0o755` From 28c133b4bc2795d78c0bc15d99af5e5f44093a2d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 16:56:24 +0100 Subject: [PATCH 54/73] Extend `map_clone` lint to also work on non-explicit closures --- clippy_lints/src/methods/map_clone.rs | 98 +++++++++++++++++---------- 1 file changed, 64 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index cc6eeaa86e5a3..c8d5a358098b4 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -2,7 +2,7 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; -use clippy_utils::{is_diag_trait_item, peel_blocks}; +use clippy_utils::{is_diag_trait_item, match_def_path, paths, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -19,50 +19,63 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ && (cx.tcx.impl_of_method(method_id).map_or(false, |id| { is_type_diagnostic_item(cx, cx.tcx.type_of(id).instantiate_identity(), sym::Option) }) || is_diag_trait_item(cx, method_id, sym::Iterator)) - && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind { - let closure_body = cx.tcx.hir().body(body); - let closure_expr = peel_blocks(closure_body.value); - match closure_body.params[0].pat.kind { - hir::PatKind::Ref(inner, hir::Mutability::Not) => { - if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind { - if ident_eq(name, closure_expr) { - lint_explicit_closure(cx, e.span, recv.span, true, msrv); - } - } - }, - hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => { - match closure_expr.kind { - hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { - if ident_eq(name, inner) { - if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { + match arg.kind { + hir::ExprKind::Closure(&hir::Closure { body, .. }) => { + let closure_body = cx.tcx.hir().body(body); + let closure_expr = peel_blocks(closure_body.value); + match closure_body.params[0].pat.kind { + hir::PatKind::Ref(inner, hir::Mutability::Not) => { + if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind { + if ident_eq(name, closure_expr) { lint_explicit_closure(cx, e.span, recv.span, true, msrv); } } }, - hir::ExprKind::MethodCall(method, obj, [], _) => { - if ident_eq(name, obj) && method.ident.name == sym::clone - && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) - && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id) - // no autoderefs - && !cx.typeck_results().expr_adjustments(obj).iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))) - { - let obj_ty = cx.typeck_results().expr_ty(obj); - if let ty::Ref(_, ty, mutability) = obj_ty.kind() { - if matches!(mutability, Mutability::Not) { - let copy = is_copy(cx, *ty); - lint_explicit_closure(cx, e.span, recv.span, copy, msrv); + hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => { + match closure_expr.kind { + hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { + if ident_eq(name, inner) { + if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); + } } - } else { - lint_needless_cloning(cx, e.span, recv.span); - } + }, + hir::ExprKind::MethodCall(method, obj, [], _) => { + if ident_eq(name, obj) && method.ident.name == sym::clone + && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id) + // no autoderefs + && !cx.typeck_results().expr_adjustments(obj).iter() + .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))) + { + let obj_ty = cx.typeck_results().expr_ty(obj); + if let ty::Ref(_, ty, mutability) = obj_ty.kind() { + if matches!(mutability, Mutability::Not) { + let copy = is_copy(cx, *ty); + lint_explicit_closure(cx, e.span, recv.span, copy, msrv); + } + } else { + lint_needless_cloning(cx, e.span, recv.span); + } + } + }, + _ => {}, } }, _ => {}, } }, + hir::ExprKind::Path(qpath) => { + if let Some(path_def_id) = cx.qpath_res(&qpath, arg.hir_id).opt_def_id() + && match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD) + { + // FIXME: It would be better to infer the type to check if it's copyable or not + // to suggest to use `.copied()` instead of `.cloned()` where applicable. + lint_path(cx, e.span, recv.span); + } + }, _ => {}, } } @@ -88,6 +101,23 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { ); } +fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span) { + let mut applicability = Applicability::MachineApplicable; + + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + "you are explicitly cloning with `.map()`", + "consider calling the dedicated `cloned` method", + format!( + "{}.cloned()", + snippet_with_applicability(cx, root, "..", &mut applicability), + ), + applicability, + ); +} + fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) { let mut applicability = Applicability::MachineApplicable; From 6410606815c64946d93ad51d5de968a960364961 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 16:56:38 +0100 Subject: [PATCH 55/73] Update `map_clone` lint ui test --- tests/ui/useless_asref.fixed | 8 ++++++++ tests/ui/useless_asref.rs | 8 ++++++++ tests/ui/useless_asref.stderr | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/ui/useless_asref.fixed b/tests/ui/useless_asref.fixed index f6770558bd800..9083ae2d0b867 100644 --- a/tests/ui/useless_asref.fixed +++ b/tests/ui/useless_asref.fixed @@ -132,6 +132,14 @@ fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { foo_rt(mru.as_ref()); } +fn foo() { + let x = Some(String::new()); + let y = x.as_ref().cloned(); + //~^ ERROR: you are explicitly cloning with `.map()` + let y = x.as_ref().cloned(); + //~^ ERROR: you are explicitly cloning with `.map()` +} + fn main() { not_ok(); ok(); diff --git a/tests/ui/useless_asref.rs b/tests/ui/useless_asref.rs index 0996218076b85..65f361dc2aeb9 100644 --- a/tests/ui/useless_asref.rs +++ b/tests/ui/useless_asref.rs @@ -132,6 +132,14 @@ fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { foo_rt(mru.as_ref()); } +fn foo() { + let x = Some(String::new()); + let y = x.as_ref().map(Clone::clone); + //~^ ERROR: you are explicitly cloning with `.map()` + let y = x.as_ref().map(String::clone); + //~^ ERROR: you are explicitly cloning with `.map()` +} + fn main() { not_ok(); ok(); diff --git a/tests/ui/useless_asref.stderr b/tests/ui/useless_asref.stderr index 163eb7b143743..e91a9db4e3db6 100644 --- a/tests/ui/useless_asref.stderr +++ b/tests/ui/useless_asref.stderr @@ -70,5 +70,20 @@ error: this call to `as_ref` does nothing LL | foo_rt(mrt.as_ref()); | ^^^^^^^^^^^^ help: try: `mrt` -error: aborting due to 11 previous errors +error: you are explicitly cloning with `.map()` + --> $DIR/useless_asref.rs:137:13 + | +LL | let y = x.as_ref().map(Clone::clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.as_ref().cloned()` + | + = note: `-D clippy::map-clone` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_clone)]` + +error: you are explicitly cloning with `.map()` + --> $DIR/useless_asref.rs:139:13 + | +LL | let y = x.as_ref().map(String::clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.as_ref().cloned()` + +error: aborting due to 13 previous errors From af35d37749166b550e18a65325deb76cc05a343a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 17:16:34 +0100 Subject: [PATCH 56/73] Fix new dogfood issue --- clippy_utils/src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 46ce4ffdce5d4..c475e7b7c4356 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -420,7 +420,7 @@ pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) ast_format_args .get()? .get(&format_args_expr.span.with_parent(None)) - .map(Rc::clone) + .cloned() }) } From 5b7a0de3e776f3cd231722d66812db9f37c4d6a0 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 7 Jan 2024 10:42:00 +0100 Subject: [PATCH 57/73] Do not suggest `bool::then()` and `bool::then_some` in `const` contexts --- clippy_lints/src/if_then_some_else_none.rs | 7 ++++++- tests/ui/if_then_some_else_none.rs | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index cd6c46a71a8e1..8f48941c4a91e 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{contains_return, higher, in_constant, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; @@ -74,6 +74,11 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { return; } + // `bool::then()` and `bool::then_some()` are not const + if in_constant(cx, expr.hir_id) { + return; + } + let ctxt = expr.span.ctxt(); if let Some(higher::If { diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index abc92459148eb..ccde154bd56ca 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -130,3 +130,8 @@ fn issue11394(b: bool, v: Result<(), ()>) -> Result<(), ()> { Ok(()) } + +const fn issue12103(x: u32) -> Option { + // Should not issue an error in `const` context + if x > 42 { Some(150) } else { None } +} From 78aa2e29e273fed22c53065ffc319ae75af0266c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Jan 2024 13:13:29 +0100 Subject: [PATCH 58/73] Handle "calls" inside the closure as well in `map_clone` lint --- clippy_lints/src/methods/map_clone.rs | 31 +++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index c8d5a358098b4..652d2b8008191 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -61,26 +61,39 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ } } }, + hir::ExprKind::Call(call, [_]) => { + if let hir::ExprKind::Path(qpath) = call.kind { + handle_path(cx, call, &qpath, e, recv); + } + }, _ => {}, } }, _ => {}, } }, - hir::ExprKind::Path(qpath) => { - if let Some(path_def_id) = cx.qpath_res(&qpath, arg.hir_id).opt_def_id() - && match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD) - { - // FIXME: It would be better to infer the type to check if it's copyable or not - // to suggest to use `.copied()` instead of `.cloned()` where applicable. - lint_path(cx, e.span, recv.span); - } - }, + hir::ExprKind::Path(qpath) => handle_path(cx, arg, &qpath, e, recv), _ => {}, } } } +fn handle_path( + cx: &LateContext<'_>, + arg: &hir::Expr<'_>, + qpath: &hir::QPath<'_>, + e: &hir::Expr<'_>, + recv: &hir::Expr<'_>, +) { + if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() + && match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD) + { + // FIXME: It would be better to infer the type to check if it's copyable or not + // to suggest to use `.copied()` instead of `.cloned()` where applicable. + lint_path(cx, e.span, recv.span); + } +} + fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind { path.segments.len() == 1 && path.segments[0].ident == name From f66e940c88a50aa36fb2fa64a60fb3d3910e0ce9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Jan 2024 13:18:11 +0100 Subject: [PATCH 59/73] Update ui test for `map_clone` lint --- tests/ui/map_clone.fixed | 5 +++++ tests/ui/map_clone.rs | 5 +++++ tests/ui/map_clone.stderr | 20 +++++++++++++------- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/ui/map_clone.fixed b/tests/ui/map_clone.fixed index dd979013d3c1f..b7cf12c1a8411 100644 --- a/tests/ui/map_clone.fixed +++ b/tests/ui/map_clone.fixed @@ -4,6 +4,7 @@ clippy::iter_cloned_collect, clippy::many_single_char_names, clippy::redundant_clone, + clippy::redundant_closure, clippy::useless_vec )] @@ -60,4 +61,8 @@ fn main() { let _ = Some(RefCell::new(String::new()).borrow()).map(|s| s.clone()); } + + let x = Some(String::new()); + let y = x.as_ref().cloned(); + //~^ ERROR: you are explicitly cloning with `.map()` } diff --git a/tests/ui/map_clone.rs b/tests/ui/map_clone.rs index 96cba71965f0f..693914ec30a7d 100644 --- a/tests/ui/map_clone.rs +++ b/tests/ui/map_clone.rs @@ -4,6 +4,7 @@ clippy::iter_cloned_collect, clippy::many_single_char_names, clippy::redundant_clone, + clippy::redundant_closure, clippy::useless_vec )] @@ -60,4 +61,8 @@ fn main() { let _ = Some(RefCell::new(String::new()).borrow()).map(|s| s.clone()); } + + let x = Some(String::new()); + let y = x.as_ref().map(|x| String::clone(x)); + //~^ ERROR: you are explicitly cloning with `.map()` } diff --git a/tests/ui/map_clone.stderr b/tests/ui/map_clone.stderr index eb11f084887d6..e6192a487ab09 100644 --- a/tests/ui/map_clone.stderr +++ b/tests/ui/map_clone.stderr @@ -1,5 +1,5 @@ error: you are using an explicit closure for copying elements - --> $DIR/map_clone.rs:11:22 + --> $DIR/map_clone.rs:12:22 | LL | let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()` @@ -8,34 +8,40 @@ LL | let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); = help: to override `-D warnings` add `#[allow(clippy::map_clone)]` error: you are using an explicit closure for cloning elements - --> $DIR/map_clone.rs:12:26 + --> $DIR/map_clone.rs:13:26 | LL | let _: Vec = vec![String::new()].iter().map(|x| x.clone()).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()` error: you are using an explicit closure for copying elements - --> $DIR/map_clone.rs:13:23 + --> $DIR/map_clone.rs:14:23 | LL | let _: Vec = vec![42, 43].iter().map(|&x| x).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()` error: you are using an explicit closure for copying elements - --> $DIR/map_clone.rs:15:26 + --> $DIR/map_clone.rs:16:26 | LL | let _: Option = Some(&16).map(|b| *b); | ^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&16).copied()` error: you are using an explicit closure for copying elements - --> $DIR/map_clone.rs:16:25 + --> $DIR/map_clone.rs:17:25 | LL | let _: Option = Some(&1).map(|x| x.clone()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&1).copied()` error: you are needlessly cloning iterator elements - --> $DIR/map_clone.rs:27:29 + --> $DIR/map_clone.rs:28:29 | LL | let _ = std::env::args().map(|v| v.clone()); | ^^^^^^^^^^^^^^^^^^^ help: remove the `map` call -error: aborting due to 6 previous errors +error: you are explicitly cloning with `.map()` + --> $DIR/map_clone.rs:66:13 + | +LL | let y = x.as_ref().map(|x| String::clone(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.as_ref().cloned()` + +error: aborting due to 7 previous errors From a843996e81cd6993b9125708558c3ec5b674baf1 Mon Sep 17 00:00:00 2001 From: cocodery Date: Sun, 7 Jan 2024 22:35:21 +0800 Subject: [PATCH 60/73] Simplify check function, weirdly binary format literals happen not often --- .../src/non_octal_unix_permissions.rs | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index e732a882cbe04..2701d6bdca391 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -1,8 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::{match_def_path, paths}; -use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -41,29 +39,6 @@ declare_clippy_lint! { declare_lint_pass!(NonOctalUnixPermissions => [NON_OCTAL_UNIX_PERMISSIONS]); -fn check_binary_unix_permissions(lit_kind: &LitKind, snip: &str) -> bool { - // support binary unix permissions - if let Some(num_lit) = NumericLiteral::from_lit_kind(snip, lit_kind) { - if num_lit.radix != Radix::Binary { - return false; - } - - let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); - // check whether is binary format unix permissions - if group_sizes.len() == 1 && (num_lit.integer.len() == 9 || num_lit.integer.len() == 12) { - // 0bxxxxxxxxx or 0bxxxxxxxxxxxx - true - } else if group_sizes.len() == 3 || group_sizes.len() == 4 { - // 0bxxx_xxx_xxx or 0bxxx_xxx_xxx_xxx - group_sizes.iter().all(|len| *len == 3) - } else { - false - } - } else { - false - } -} - impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { match &expr.kind { @@ -76,10 +51,10 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { )) || (path.ident.name == sym!(set_mode) && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) - && let ExprKind::Lit(lit_kind) = param.kind + && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && let Some(snip) = snippet_opt(cx, param.span) - && !(snip.starts_with("0o") || check_binary_unix_permissions(&lit_kind.node, &snip)) + && !(snip.starts_with("0o") || snip.starts_with("0b")) { show_error(cx, param); } @@ -88,10 +63,10 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { if let ExprKind::Path(ref path) = func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE) - && let ExprKind::Lit(lit_kind) = param.kind + && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && let Some(snip) = snippet_opt(cx, param.span) - && !(snip.starts_with("0o") || check_binary_unix_permissions(&lit_kind.node, &snip)) + && !(snip.starts_with("0o") || snip.starts_with("0b")) { show_error(cx, param); } From bbadce9ec0a8855ab72852af1a6ed1f68c9ce0c2 Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Wed, 3 Jan 2024 04:03:00 +0200 Subject: [PATCH 61/73] Fixed ICE introduced in #12004 Issue: in #12004, we emit a lint for `filter(Option::is_some)`. If the parent expression is a `.map` we don't emit that lint as there exists a more specialized lint for that. The ICE introduced in #12004 is a consequence of the assumption that a parent expression after a filter would be a method call with the filter call being the receiver. However, it is entirely possible to have a closure of the form ``` || { vec![Some(1), None].into_iter().filter(Option::is_some) } ``` The previous implementation looked at the parent expression; namely the closure, and tried to check the parameters by indexing [0] on an empty list. This commit is an overhaul of the lint with significantly more FP tests and checks. Impl details: 1. We verify that the filter method we are in is a proper trait method to avoid FPs. 2. We check that the parent expression is not a map by checking whether it exists; if is a trait method; and then a method call. 3. We check that we don't have comments in the span. 4. We verify that we are in an Iterator of Option and Result. 5. We check the contents of the filter. 1. For closures we peel it. If it is not a single expression, we don't lint. 2. For paths, we do a typecheck to avoid FPs for types that impl functions with the same names. 3. For calls, we verify the type, via the path, and that the param of the closure is the single argument to the call. 4. For method calls we verify that the receiver is the parameter of the closure. Since we handle single, non-block exprs, the parameter can't be shadowed, so no FP. This commit also adds additional FP tests. --- clippy_lints/src/methods/filter_map.rs | 3 +- clippy_lints/src/methods/iter_filter.rs | 206 ++++++++++++++++----- tests/ui/iter_filter_is_ok.fixed | 193 +++++++++++++++++++- tests/ui/iter_filter_is_ok.rs | 193 +++++++++++++++++++- tests/ui/iter_filter_is_ok.stderr | 74 +++++++- tests/ui/iter_filter_is_some.fixed | 231 +++++++++++++++++++++++- tests/ui/iter_filter_is_some.rs | 231 +++++++++++++++++++++++- tests/ui/iter_filter_is_some.stderr | 62 ++++++- 8 files changed, 1092 insertions(+), 101 deletions(-) diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 7cfd3d346b638..e489899c19e34 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -26,13 +26,14 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(body.value); - let arg_id = body.params[0].pat.hir_id; match closure_expr.kind { hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => { if ident.name == method_name && let hir::ExprKind::Path(path) = &receiver.kind && let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id) + && !body.params.is_empty() { + let arg_id = body.params[0].pat.hir_id; return arg_id == *local; } false diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs index ade8e3155fae3..9f84321ced4ae 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints/src/methods/iter_filter.rs @@ -1,87 +1,197 @@ +use clippy_utils::ty::get_iterator_item_ty; +use hir::ExprKind; use rustc_lint::{LateContext, LintContext}; use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; -use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment}; +use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::def::Res; use rustc_hir::QPath; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::Span; use std::borrow::Cow; -fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { - match &expr.kind { - hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, - hir::ExprKind::Path(QPath::Resolved(_, segments)) => { - segments.segments.last().unwrap().ident.name == method_name +/// +/// Returns true if the expression is a method call to `method_name` +/// e.g. `a.method_name()` or `Option::method_name`. +/// +/// The type-checker verifies for us that the method accepts the right kind of items +/// (e.g. `Option::is_some` accepts `Option<_>`), so we don't need to check that. +/// +/// How to capture each case: +/// +/// `.filter(|a| { std::option::Option::is_some(a) })` +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a closure, getting unwrapped and +/// recursively checked. +/// `std::option::Option::is_some(a)` +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a call. It unwraps to a path with +/// `QPath::TypeRelative`. Since this is a type relative path, we need to check the method name, the +/// type, and that the parameter of the closure is passed in the call. This part is the dual of +/// `receiver.method_name()` below. +/// +/// `filter(std::option::Option::is_some);` +/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a type relative path, like above, we check the +/// type and the method name. +/// +/// `filter(|a| a.is_some());` +/// ^^^^^^^^^^^^^^^ <- this is a method call inside a closure, +/// we check that the parameter of the closure is the receiver of the method call and don't allow +/// any other parameters. +fn is_method( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + type_symbol: Symbol, + method_name: Symbol, + params: &[&hir::Pat<'_>], +) -> bool { + fn pat_is_recv(ident: Ident, param: &hir::Pat<'_>) -> bool { + match param.kind { + hir::PatKind::Binding(_, _, other, _) => ident == other, + hir::PatKind::Ref(pat, _) => pat_is_recv(ident, pat), + _ => false, + } + } + match expr.kind { + hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, recv, ..) => { + // compare the identifier of the receiver to the parameter + // we are in a filter => closure has a single parameter and a single, non-block + // expression, this means that the parameter shadows all outside variables with + // the same name => avoid FPs. If the parameter is not the receiver, then this hits + // outside variables => avoid FP + if ident.name == method_name + && let ExprKind::Path(QPath::Resolved(None, path)) = recv.kind + && let &[seg] = path.segments + && params.iter().any(|p| pat_is_recv(seg.ident, p)) + { + return true; + } + false + }, + // This is used to check for complete paths via `|a| std::option::Option::is_some(a)` + // this then unwraps to a path with `QPath::TypeRelative` + // we pass the params as they've been passed to the current call through the closure + hir::ExprKind::Call(expr, [param]) => { + // this will hit the `QPath::TypeRelative` case and check that the method name is correct + if is_method(cx, expr, type_symbol, method_name, params) + // we then check that this is indeed passing the parameter of the closure + && let ExprKind::Path(QPath::Resolved(None, path)) = param.kind + && let &[seg] = path.segments + && params.iter().any(|p| pat_is_recv(seg.ident, p)) + { + return true; + } + false + }, + hir::ExprKind::Path(QPath::TypeRelative(ty, mname)) => { + let ty = cx.typeck_results().node_type(ty.hir_id); + if let Some(did) = cx.tcx.get_diagnostic_item(type_symbol) + && ty.ty_adt_def() == cx.tcx.type_of(did).skip_binder().ty_adt_def() + { + return mname.ident.name == method_name; + } + false }, - hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(body.value); - let arg_id = body.params[0].pat.hir_id; - match closure_expr.kind { - hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => { - if ident.name == method_name - && let hir::ExprKind::Path(path) = &receiver.kind - && let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id) - { - return arg_id == *local; - } - false - }, - _ => false, - } + let params = body.params.iter().map(|param| param.pat).collect::>(); + is_method(cx, closure_expr, type_symbol, method_name, params.as_slice()) }, _ => false, } } fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) { - is_method(cx, parent_expr, rustc_span::sym::map) - } else { - false + if let Some(expr) = get_parent_expr(cx, expr) + && is_trait_method(cx, expr, sym::Iterator) + && let hir::ExprKind::MethodCall(path, _, _, _) = expr.kind + && path.ident.name == rustc_span::sym::map + { + return true; } + false } -#[allow(clippy::too_many_arguments)] -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) { - let is_iterator = is_trait_method(cx, expr, sym::Iterator); - let parent_is_not_map = !parent_is_map(cx, expr); +enum FilterType { + IsSome, + IsOk, +} - if is_iterator - && parent_is_not_map - && is_method(cx, filter_arg, sym!(is_some)) - && !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi())) +/// Returns the `FilterType` of the expression if it is a filter over an Iter