From 60c059114c3c1d2bcbe90c71f308b32f7cea1b07 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Wed, 13 Dec 2023 11:09:08 +0800 Subject: [`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 64a924a776a..712bc075650 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 9072d233563..e62dc8b255e 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 7ea2e3e6819..e81aafdb3f3 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 920cd9f8f77..115cd30a6b3 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 bdb7b5f47bc..a4abfc11320 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 b03b8b84c33..0da9b77f32e 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 -- cgit 1.4.1-3-g733a5 From eae2317977acb0d42a93740ae93122a1287d8090 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Thu, 14 Dec 2023 09:27:01 +0800 Subject: 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 bd12ee40628..1df5a25f674 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 1ca18170f8a..e9476c80ccb 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 bc74f7b728e..4e37af7f378 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 -- cgit 1.4.1-3-g733a5 From 4cea5a8f33715bfe38824c798a5d75218cbfec04 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 16 Dec 2023 14:02:50 +0100 Subject: 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 5e13c73f035..2c33c93412a 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 81b8bd7da77..b318fd42f7c 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 5aca9b2925c..08ad6efa37f 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]); +} -- cgit 1.4.1-3-g733a5 From 1c431f4da96fe701388f9ca7ecff1020b402664a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:36:12 +0100 Subject: 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 b1fa30aa068..570770523eb 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); } } } -- cgit 1.4.1-3-g733a5 From 4ab8cdd5b9f715938f5d4f1c4c6a87b4ea400ce5 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 19 Dec 2023 22:52:04 -0500 Subject: 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 f929bec9583..f6d0976091c 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; | -- cgit 1.4.1-3-g733a5 From a9baf344b8563995caecb8343fa7ac8a7cd26cc7 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 25 Dec 2023 09:39:32 +0800 Subject: 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 e62dc8b255e..00f0d764434 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 256b94f6c05..942cedac275 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 e81aafdb3f3..c95679c9eb8 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 115cd30a6b3..9ef92d8f279 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 a4abfc11320..4b54b8d8458 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 0da9b77f32e..7663977fb65 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` -- cgit 1.4.1-3-g733a5 From a578b9ba0b13e5dde95d2f09aab1b9d5ae699cb6 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 25 Dec 2023 14:28:01 +0800 Subject: 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 a23e12f7a18..4ae4fc9b096 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 198b95d8c94..3a51538b742 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 483e1ce15f6..91f73d30b53 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 -- cgit 1.4.1-3-g733a5 From 4c64d7cea2377a0ee00c021f65f5754db6ef63c0 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 26 Dec 2023 18:43:16 -0500 Subject: 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 99d80bec025..5ba960db66d 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 73c25550742..012797e5ca7 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 0f0e3f2db92..37f18a4c087 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 999ee7acfe7..94f494b65c4 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 30bd476332f..05e1b3b9202 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 339287a7dd9..b588c8f0f02 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 ``` -- cgit 1.4.1-3-g733a5 From c4a80f2e3e8375ff54826840e2e2a5c53a20e2ce Mon Sep 17 00:00:00 2001 From: Yuxiang Qiu Date: Fri, 24 Nov 2023 20:56:57 -0500 Subject: 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 +- clippy_lints/src/methods/manual_is_variant_and.rs | 59 ++++++++++++++++ clippy_lints/src/methods/mod.rs | 36 +++++++++- clippy_lints/src/methods/option_map_unwrap_or.rs | 2 +- clippy_lints/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 e01c09b295c..054d9390dd2 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 8dec477cfdd..773cce36df4 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 a00e2d3e044..1db94ad3045 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 8a0955147bb..29957e423b0 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 00000000000..d29acd4622a --- /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 25b1ea526e2..0cde17ef5ad 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 63e64a5b35d..35deabe49b6 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 094ead9f4ad..29f44ec2a4d 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 00000000000..8c34b51103c --- /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 00000000000..25b2489d942 --- /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 00000000000..c243de098dc --- /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 + -- cgit 1.4.1-3-g733a5 From e5c9fb68279322000536166a4a82a754904e52f7 Mon Sep 17 00:00:00 2001 From: Florian Brucker Date: Wed, 27 Dec 2023 22:15:17 +0100 Subject: 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 9ad4250a141..3a0305b4d55 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 79e7eda4070..9573fdbcfde 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 81962c0a6e9..8a04480dbc6 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 6e57ab3489f..781ab316d9d 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 -- cgit 1.4.1-3-g733a5 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: 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 01a23c515f5..7539b6af385 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 e06aa2cc9fd..58c6ee914c6 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 89ccdf583f3..2af311d6c51 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 5eb163c5fcb..65dd6815c3a 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 -- cgit 1.4.1-3-g733a5 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: 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 7539b6af385..c44f5150dd1 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 58c6ee914c6..bece09bba1a 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 2af311d6c51..a82bd578f76 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 65dd6815c3a..5f42eec544f 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 -- cgit 1.4.1-3-g733a5 From 99b4330f5cdcf258a8146950d33426c6bb442abc Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Dec 2023 01:52:10 +0000 Subject: 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 4cbfa97a8a3..26f120cb33f 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( -- cgit 1.4.1-3-g733a5 From b8aab4bd1dbb842ca79e9c6c1db663935a0b044e Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Thu, 28 Dec 2023 09:40:01 -0800 Subject: 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 c1f6c71a63e..c6c188854fd 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 -- cgit 1.4.1-3-g733a5 From 15b1edb2092efef167b375ac4a88429a90714210 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 28 Dec 2023 19:33:07 +0100 Subject: 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 +- clippy_lints/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 ++++++++- clippy_lints/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 +++++++++++ clippy_lints/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 +- tests/ui-internal/disallow_struct_span_lint.rs | 4 +- tests/ui-internal/disallow_struct_span_lint.stderr | 8 +- tests/ui-toml/suppress_lint_in_const/test.rs | 3 +- tests/ui-toml/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 +- tests/ui/branches_sharing_code/shared_at_bottom.rs | 8 +- .../branches_sharing_code/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 +- .../redundant_pattern_matching_if_let_true.fixed | 38 ++++ tests/ui/redundant_pattern_matching_if_let_true.rs | 38 ++++ .../redundant_pattern_matching_if_let_true.stderr | 47 ++++ tests/ui/redundant_pattern_matching_ipaddr.fixed | 6 + tests/ui/redundant_pattern_matching_ipaddr.rs | 6 + tests/ui/redundant_pattern_matching_ipaddr.stderr | 44 ++-- tests/ui/redundant_pattern_matching_poll.fixed | 6 + tests/ui/redundant_pattern_matching_poll.rs | 6 + tests/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 70aff7f60e0..f82421a687b 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 f6084a46272..eda20531e40 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 2bb89321cef..7c9a8eb1bfb 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 20f31320127..74b8e5eaa1c 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 3cf4377002a..a4f368397ce 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 13e61e5a032..471ad73e207 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 ad8b7ded46b..8cba35f3d87 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 a15ec199a28..1e327f7a6df 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 c965341d3fd..bb08ac7508b 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 1220eb89013..eae9dfac064 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 9ce5acfbcc2..71e1a25c2bc 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 3901dd984f9..2b08437d827 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 0f5a9ea5d84..fab8ffedb94 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 0ae03d101ab..391db0b0df7 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 955f90d4262..af5b1f95739 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 3a5756482a1..c9dc48668f2 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 8c032b17023..c09d3d1862b 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 7758d6a58e6..755a4ff525d 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 9b88dd76e5c..5e099f1e76f 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 a726b116956..36de9021f49 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 21b9efba54c..d070ee74985 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 9ba1d3afcbe..4cd5f3b81e5 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 252b3a83a18..73687fbbe54 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 92dc4d57ab1..fdf8fa4e277 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 48fc5746b3c..91e6ca7fa8b 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 0627e458dfe..152aba99ce9 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 4c7568f39b4..50494f4819f 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 2582f7edcf6..a4acdfb1db4 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 844ab40cab1..7cfd3d346b6 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 00000000000..ade8e3155fa --- /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 b4f60ffadd7..25b1ea526e2 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 c4775b6bd04..637368e9361 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 218ca5e80f3..bf27adb45af 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 89e4e3c740d..556c493d36c 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 fc5835408a9..509d9483e1d 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 b50141f048c..0ed957f1f2f 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 16c929edb92..cde08dfcc74 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 00000000000..01a23c515f5 --- /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 95a92afea66..06de7a11031 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 00000000000..b1fa30aa068 --- /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 7a6549a7c54..e5bc3b5a25f 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 d41576cadad..903593ecfd7 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 df715b12dea..8817e46b3c8 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 5d23326cec8..b8869eedf52 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 7fe76b946a4..adc35bd82ae 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 46c96fad884..db80e07ca1c 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 3135a033648..ba682813dad 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 d752fe7d97e..ebc38e531fe 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 af123e107d5..5aaafb41721 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 d575da6dece..5a2526fd267 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 3155c0235ff..c81d54918cb 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 76c487fb135..7d424124f2b 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 17c1b03d88c..3edb3a10b76 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 f8ace799593..84e7eff4557 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 10809a6d247..1309ae45d0a 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 780d1fe1c8a..099be4ed355 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 d6de21631e2..ff5b6100117 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 e3f2ca72d1c..02f1d09b833 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 d1bc20d6831..5ef696d855e 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 4560df6d4cd..6907dc0523f 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 d102efa7a58..549908b8770 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 d00717befc1..8223df0fe7b 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 fff6bfcc753..44b0b6e7391 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 70bfea231ae..563a273dcdd 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 e8a36bf48f1..16df3e433db 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 00000000000..5e7193b6826 --- /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 f77f691c192..fe8d640c470 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 ee69f3379b6..3efaf05827e 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 708ac666675..bff46e55722 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 040d6352c52..4e162a97dee 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 00000000000..e06aa2cc9fd --- /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 00000000000..89ccdf583f3 --- /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 00000000000..5eb163c5fcb --- /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 d5a4309db59..62beb195939 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 5a9ad204f70..1e09ff5c67e 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 384860ea116..700f3cfec4e 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 77abd663e0a..abc92459148 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 5c97b06da15..9b3d65cc803 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 f0da5dfc60b..1ac0bb11014 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 1c34875d2b8..6d64fa1e6cf 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 f58b3cebbc3..771fbfa44ee 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 00000000000..a5ca41528af --- /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 00000000000..e4e73f5ada1 --- /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 00000000000..f3acbe38d8a --- /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 00000000000..c3fa93f0ab2 --- /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 00000000000..b023776abe4 --- /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 00000000000..1f2b10036fe --- /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 b555186cc22..6b29ce75985 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 5852c7094a4..e92c4c1375e 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 bf0b1bbf0dd..dec6947697a 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 1086ae2c984..79e33a7218b 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 131cceaf712..2c135fb22bf 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 c3e83c0f1f5..9a911b4dbac 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 da7876e772e..4d48ef14d31 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 deae389dbef..fd1568d94e3 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 ee004c0e194..b1c3cfa48a4 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 eae2fa176a8..2550b9cd2b3 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 148f9d02f5e..6a0fc10822b 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 d492ea1be75..a1875c1c06e 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 dd96e141006..bb43403a043 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 00000000000..6d910678934 --- /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 00000000000..a82e673982a --- /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 00000000000..211a332d79a --- /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 70dd9fc250f..429d33118a5 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 6e2a2f7b6d2..e7136b72c20 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 d36129a2bee..54cefa5e82b 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 718c2f8ea3d..08d83f87e4c 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 daa4761aff5..7bc2b3be4d3 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 c010c3c4437..de64331b4e8 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 00000000000..e8943dbc72d --- /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 00000000000..bfe47ffcf38 --- /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 00000000000..4687794949d --- /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 00000000000..1169118de83 --- /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 00000000000..1fb01c00f19 --- /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 00000000000..53dc3c43e2f --- /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 00000000000..62400e7eee1 --- /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 00000000000..cfb3766d15e --- /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 99e211654d1..a5274eeb349 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -566,8 +566,74 @@ Otherwise, have a great day =^.^= - - Fork me on GitHub + + + -- cgit 1.4.1-3-g733a5 From 3fceca23bb64e304df56b3bd86d26790b5301bdf Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 29 Dec 2023 05:16:53 +0000 Subject: 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 637368e9361..5f69cf6cd7a 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() -- cgit 1.4.1-3-g733a5 From fa7dd1c4e0106ce0346236801653188689dbaa43 Mon Sep 17 00:00:00 2001 From: Parker Timmerman Date: Sun, 17 Dec 2023 15:55:06 -0500 Subject: 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 + .../pub_underscore_fields/exported/clippy.toml | 1 + .../pub_underscore_fields.all_pub_fields.stderr | 60 ++++++++++++++++ .../pub_underscore_fields.exported.stderr | 12 ++++ .../pub_underscore_fields/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 f82421a687b..0ac7297f8f1 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 7c9a8eb1bfb..3b62ae0524a 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 67ea0af07e6..77d7f1760c9 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 df48cc3f5e3..baee09629ac 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 eae9dfac064..9dd22135d95 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 755a4ff525d..712fe73f6a1 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 00000000000..00465ce4381 --- /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 00000000000..95835d101b1 --- /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 00000000000..94a0d3554bc --- /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 00000000000..c6bd499fd8c --- /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 00000000000..b76f6b3d388 --- /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 00000000000..1d8fee7daad --- /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 12828cf9dec..ed4fb84df1a 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 -- cgit 1.4.1-3-g733a5 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: 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 +- clippy_lints/src/empty_structs_with_brackets.rs | 102 --------------- clippy_lints/src/empty_with_brackets.rs | 152 ++++++++++++++++++++++ clippy_lints/src/lib.rs | 4 +- tests/ui/empty_enum_variants_with_brackets.fixed | 27 ++++ tests/ui/empty_enum_variants_with_brackets.rs | 27 ++++ tests/ui/empty_enum_variants_with_brackets.stderr | 36 +++++ 8 files changed, 247 insertions(+), 105 deletions(-) delete mode 100644 clippy_lints/src/empty_structs_with_brackets.rs create mode 100644 clippy_lints/src/empty_with_brackets.rs 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 0ac7297f8f1..931517e72f6 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 9dd22135d95..b606bb4aa33 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_structs_with_brackets.rs deleted file mode 100644 index 3cf67b3ecbf..00000000000 --- a/clippy_lints/src/empty_structs_with_brackets.rs +++ /dev/null @@ -1,102 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_opt; -use rustc_ast::ast::{Item, ItemKind, VariantData}; -use rustc_errors::Applicability; -use rustc_lexer::TokenKind; -use rustc_lint::{EarlyContext, EarlyLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; - -declare_clippy_lint! { - /// ### What it does - /// Finds structs without fields (a so-called "empty struct") that are declared with brackets. - /// - /// ### Why is this bad? - /// Empty brackets after a struct declaration can be omitted. - /// - /// ### Example - /// ```no_run - /// struct Cookie {} - /// ``` - /// Use instead: - /// ```no_run - /// struct Cookie; - /// ``` - #[clippy::version = "1.62.0"] - pub EMPTY_STRUCTS_WITH_BRACKETS, - restriction, - "finds struct declarations with empty brackets" -} -declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]); - -impl EarlyLintPass for EmptyStructsWithBrackets { - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - let span_after_ident = item.span.with_lo(item.ident.span.hi()); - - if let ItemKind::Struct(var_data, _) = &item.kind - && has_brackets(var_data) - && has_no_fields(cx, var_data, span_after_ident) - { - span_lint_and_then( - cx, - EMPTY_STRUCTS_WITH_BRACKETS, - span_after_ident, - "found empty brackets on struct declaration", - |diagnostic| { - diagnostic.span_suggestion_hidden( - span_after_ident, - "remove the brackets", - ";", - Applicability::Unspecified, - ); - }, - ); - } - } -} - -fn has_no_ident_token(braces_span_str: &str) -> bool { - !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident) -} - -fn has_brackets(var_data: &VariantData) -> bool { - !matches!(var_data, VariantData::Unit(_)) -} - -fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool { - if !var_data.fields().is_empty() { - return false; - } - - // there might still be field declarations hidden from the AST - // (conditionally compiled code using #[cfg(..)]) - - let Some(braces_span_str) = snippet_opt(cx, braces_span) else { - return false; - }; - - has_no_ident_token(braces_span_str.as_ref()) -} - -#[cfg(test)] -mod unit_test { - use super::*; - - #[test] - fn test_has_no_ident_token() { - let input = "{ field: u8 }"; - assert!(!has_no_ident_token(input)); - - let input = "(u8, String);"; - assert!(!has_no_ident_token(input)); - - let input = " { - // test = 5 - } - "; - assert!(has_no_ident_token(input)); - - let input = " ();"; - assert!(has_no_ident_token(input)); - } -} diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs new file mode 100644 index 00000000000..52a88324fc3 --- /dev/null +++ b/clippy_lints/src/empty_with_brackets.rs @@ -0,0 +1,152 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use rustc_ast::ast::{Item, ItemKind, Variant, VariantData}; +use rustc_errors::Applicability; +use rustc_lexer::TokenKind; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Finds structs without fields (a so-called "empty struct") that are declared with brackets. + /// + /// ### Why is this bad? + /// Empty brackets after a struct declaration can be omitted. + /// + /// ### Example + /// ```no_run + /// struct Cookie {} + /// ``` + /// Use instead: + /// ```no_run + /// struct Cookie; + /// ``` + #[clippy::version = "1.62.0"] + pub EMPTY_STRUCTS_WITH_BRACKETS, + restriction, + "finds struct declarations with empty brackets" +} + +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()); + + if let ItemKind::Struct(var_data, _) = &item.kind + && has_brackets(var_data) + && has_no_fields(cx, var_data, span_after_ident) + { + span_lint_and_then( + cx, + EMPTY_STRUCTS_WITH_BRACKETS, + span_after_ident, + "found empty brackets on struct declaration", + |diagnostic| { + diagnostic.span_suggestion_hidden( + span_after_ident, + "remove the brackets", + ";", + Applicability::Unspecified, + ); + }, + ); + } + } + + 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 { + !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident) +} + +fn has_brackets(var_data: &VariantData) -> bool { + !matches!(var_data, VariantData::Unit(_)) +} + +fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool { + if !var_data.fields().is_empty() { + return false; + } + + // there might still be field declarations hidden from the AST + // (conditionally compiled code using #[cfg(..)]) + + let Some(braces_span_str) = snippet_opt(cx, braces_span) else { + return false; + }; + + has_no_ident_token(braces_span_str.as_ref()) +} + +#[cfg(test)] +mod unit_test { + use super::*; + + #[test] + fn test_has_no_ident_token() { + let input = "{ field: u8 }"; + assert!(!has_no_ident_token(input)); + + let input = "(u8, String);"; + assert!(!has_no_ident_token(input)); + + let input = " { + // test = 5 + } + "; + assert!(has_no_ident_token(input)); + + let input = " ();"; + assert!(has_no_ident_token(input)); + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 712fe73f6a1..dc24243b574 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 00000000000..1a5e78dd47f --- /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 00000000000..ca20b969a24 --- /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 00000000000..6b2f286147f --- /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 + -- cgit 1.4.1-3-g733a5 From ca40b0c284a54676ee9e9b560f5ea43b51dfbfbc Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 30 Dec 2023 01:58:30 +0800 Subject: fix/issue#11243: allow 3-digit-grouped binary in non_octal_unix_permissions --- clippy_lints/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 49e9e2c00cc..7143ac9cfc1 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); } -- cgit 1.4.1-3-g733a5 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: 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 31a42734c13..5d9cde06cd8 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" ); } -- cgit 1.4.1-3-g733a5 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: 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 c9dc48668f2..903d3a2ab89 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 448d0114dff..c8b9076041a 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 70f3f82a936..a232c7cecc5 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 29f526b455c..3054d848efb 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 af5afd47bfc..4cf20e2aa56 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 -- cgit 1.4.1-3-g733a5 From c2b3f5c767e69a3788efb01633ae53f6adfcd6dd Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Sat, 30 Dec 2023 13:25:33 +0200 Subject: `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 8ecb038627f..f671517c134 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 f3b4b1fffa0..660e9a21b18 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 631aa3b0215..bbef531e9dc 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 2a61327f15f..6bb980035c1 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 -- cgit 1.4.1-3-g733a5 From 79e70ccf6f428c5819d6bd0ecf69eedfd99b0a5f Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 30 Dec 2023 23:03:47 +0800 Subject: 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 7143ac9cfc1..12fd63b50f2 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) -- cgit 1.4.1-3-g733a5 From ab2d1c28d5c03ba5b416614257f68fb5243c2a00 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:43:50 +0100 Subject: 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 570770523eb..4036ad78a56 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); } } } -- cgit 1.4.1-3-g733a5 From d161f3b559b27b682320c9867cd04a7677e56b1e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Dec 2023 16:44:07 +0100 Subject: 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 1169118de83..36fb7e08d64 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 1fb01c00f19..040cc4a85a4 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 -- cgit 1.4.1-3-g733a5 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: 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 52a88324fc3..969df6d85b5 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, ); }, ); -- cgit 1.4.1-3-g733a5 From d3bde854c456419a18ca41938dc645dbb307bd6c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 30 Dec 2023 17:01:28 +0100 Subject: 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 4036ad78a56..5366b5513d3 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); } } } -- cgit 1.4.1-3-g733a5 From 7107aa22b2219ff11b645178e32d3905ad99da7d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 31 Dec 2023 11:01:47 +0100 Subject: 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 36fb7e08d64..19cd553b375 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 040cc4a85a4..364dd572819 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 -- cgit 1.4.1-3-g733a5 From fe35e08e9f96f9c829add20351b2fbf28faac324 Mon Sep 17 00:00:00 2001 From: Florian Brucker Date: Sun, 17 Dec 2023 18:46:49 +0100 Subject: 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 e4e068f19d4..b9e441029b2 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 9ab38dab1e5..02543ae912a 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 0cde17ef5ad..711cad1dcf6 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 00000000000..3586e11f56a --- /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 00000000000..4f33241da7a --- /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 00000000000..f24caa61c30 --- /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 00000000000..ee0a9653711 --- /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 + -- cgit 1.4.1-3-g733a5 From 457ab585fc5a5174021bed21c4c0a55deef9676c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 31 Dec 2023 14:44:11 +0100 Subject: 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 0cde17ef5ad..7f5c82a3e7a 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! { -- cgit 1.4.1-3-g733a5 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: don't look for safety comments in codeblocks --- clippy_lints/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 e5bc3b5a25f..add4b3e5637 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 d9a7ad8e56c..bdc6fa0f46b 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() {} -- cgit 1.4.1-3-g733a5 From 70024e16c000edfb56324852414760cc82567217 Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Wed, 27 Dec 2023 16:25:59 +0200 Subject: 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 + .../thread_local_initializer_can_be_made_const.rs | 102 +++++++++++++++++++++ ...hread_local_initializer_can_be_made_const.fixed | 30 ++++++ .../thread_local_initializer_can_be_made_const.rs | 30 ++++++ ...read_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 b9e441029b2..e6c081ca94f 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 02543ae912a..f6c9ffea9fc 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 dc24243b574..854c111f9f5 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 00000000000..9fee4c06200 --- /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 00000000000..bbde25b0a88 --- /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 00000000000..3d7aacf2f09 --- /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 00000000000..b35bd306b52 --- /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 + -- cgit 1.4.1-3-g733a5 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: 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 655f4b82aa4..17b6256f982 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, -- cgit 1.4.1-3-g733a5 From de06ce886ea74cc60d7c883024009a5ee1c19eaa Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Wed, 29 Nov 2023 15:28:46 -0500 Subject: Address unused tuple struct fields in clippy --- clippy_lints/src/methods/iter_overeager_cloned.rs | 8 ++++---- clippy_lints/src/methods/mod.rs | 8 ++++---- clippy_lints/src/question_mark.rs | 5 ++--- clippy_lints/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 eac6df0545f..b2fe129cd95 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 25b1ea526e2..c1e126137df 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 509d9483e1d..9469888a4d4 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 a65bc0ce458..db378cfd755 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) } -- cgit 1.4.1-3-g733a5 From 04ebf3c8c9aef6c021a60d73f0f1a798a81b7171 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Wed, 29 Nov 2023 15:29:11 -0500 Subject: 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 b03c21262c3..96e037d4fcd 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 36679a9c883..2b32fdeae2b 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 b0920daf088..bad192067e9 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 d4630a8f1da..e02fdb1e415 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 82c8e1d8abd..c250162dfb8 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 2aed6b14be1..8adbb841c8b 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 3ed705b2906..db20ba29f3d 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 ab8efea0ac7..d7e56130245 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 581399f3e48..39446bf6cd9 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 7f0a6f8e544..dc88081ba0a 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 38f3b36ec4d..e8fa652e3c1 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 d52a0cf15a8..76f5e082f32 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 363520112ef..d443334bb05 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 aaa87a0db54..317c35bf842 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 55a8360ffd0..e053d356ff2 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 6d8c719ee07..fb9c2672db8 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 42ca773c351..0a24fa85254 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 d7a3377ec37..37f91acf82b 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` -- cgit 1.4.1-3-g733a5 From 88541d66370aba7ec48661617afd4d712f4e6711 Mon Sep 17 00:00:00 2001 From: Yuxiang Qiu Date: Tue, 2 Jan 2024 19:21:51 -0500 Subject: 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 c5eb000272d..aecca9ef72e 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 4c21f49638b..fbca641cfa3 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 0f18e943451..bbc4d0a0f9a 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 5978da83199..6bbe427ea29 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 90834d784a5..b4278d879e5 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 9b0dac6af25..b82bd1d7e7c 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 da71fc3aaa8..adca2ca1c3e 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 192eb51ea99..8499c0ac5a0 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 9ef92d8f279..c364c683057 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 4b54b8d8458..ffa7b961d1c 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 a38523a7c79..4185b402226 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 33adb609996..7a74d8a55de 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 1ea0d65bf1e..4fb6c08bb44 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 8b1a1446e3c..a8b12c6dfe3 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 ed8387b7eb2..ce00fde2f99 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 991a7762fc6..39979619586 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 -- cgit 1.4.1-3-g733a5 From 18e4632b2bc799dd767de90f1c231aa3f51c85a1 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 3 Jan 2024 19:36:44 +0100 Subject: 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 82703b18fd7..219693d63d9 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 0d62c37278e..506582c31d6 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 b724b24aa83..6d8ee9afb61 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 5d490645d89..fb3f7109d7e 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 e197ea048a0..56f62b867a6 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 -- cgit 1.4.1-3-g733a5 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: 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 +++++++++++++++++++- clippy_lints/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 e6c081ca94f..4d32bbec914 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 5d490645d89..0450b54121d 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 486ea3df704..e7972b0db19 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 f6c9ffea9fc..20230106d53 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 fbca641cfa3..cf6f6ad1a0f 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 00000000000..d7fec360fa2 --- /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 00000000000..394dad219f7 --- /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 00000000000..7243957927b --- /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 00000000000..ea03da3b69f --- /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 + -- cgit 1.4.1-3-g733a5 From ca83a98e66a1a6f71c09d92ab25fa385ca8353b4 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Wed, 20 Dec 2023 19:13:24 +0000 Subject: 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 11081dc8087..0196c9923db 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 { | + -- cgit 1.4.1-3-g733a5 From 89e4b7162c41f8e889369d1713557de38df1c7b9 Mon Sep 17 00:00:00 2001 From: Paul Houssel Date: Wed, 27 Dec 2023 15:07:03 +0100 Subject: 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 b02457307d7..a048fbbd8ac 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 00000000000..bb3ef246c2f --- /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 +``` -- cgit 1.4.1-3-g733a5 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: 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 f88ab9fe440..5477d9b83a7 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 2451fbc91e8..3ba2796e18d 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(); -- cgit 1.4.1-3-g733a5 From e75841397365ff207553551f14ab35122774f23c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 4 Jan 2024 17:13:18 +0100 Subject: Add .as_ref() to suggestion to remove .to_string() --- clippy_lints/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 5f69cf6cd7a..12106d44e36 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 53dc3c43e2f..f87c898f9b7 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 62400e7eee1..db5719e5880 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 cfb3766d15e..4cfaeed3384 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 -- cgit 1.4.1-3-g733a5 From 2666f39d3e5d4b354065056102ef4856aa23fdae Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 3 Jan 2024 22:14:36 +0100 Subject: 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 854c111f9f5..efdd3925949 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 5366b5513d3..e90306ded61 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); } } } -- cgit 1.4.1-3-g733a5 From bf5c0d8334adbaa246f2087f4d031da7e3d8bbb8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Jan 2024 17:12:54 +0100 Subject: 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 19cd553b375..e1a2d6a90b8 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 364dd572819..5d82e2a9f31 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 -- cgit 1.4.1-3-g733a5 From 0bac101c21d3b410b819fdac09d88adb71bfd8e5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 5 Jan 2024 10:02:40 +1100 Subject: 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 c639813a3f9..a744b69ecb4 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())); -- cgit 1.4.1-3-g733a5 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: 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 4e71c6483e6..6c40029a9de 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 66598f89208..d8031c484e5 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 5045fcd790e..ea55b1d9a90 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 466664aee6c..6ff2691a461 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); | ^^^^^^---------------- -- cgit 1.4.1-3-g733a5 From f73e37d00ce29fbf81f62103ce06d2d3787c3842 Mon Sep 17 00:00:00 2001 From: Matthew Jasper Date: Thu, 4 Jan 2024 10:29:47 +0000 Subject: 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 ++-- clippy_lints/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 ++++----- clippy_lints/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 ce0a1dfdc61..0b4bc375df0 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 385fe387a31..0da309f9531 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 91e6ca7fa8b..5fef5930fab 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 56123326fe4..b062e81cefd 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 44dc29c36a6..cc482f15a91 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 f57b22374c8..dfaaeb14ca3 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 a4acdfb1db4..b5870d94d99 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 3ff40081c47..195ce17629a 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 8817e46b3c8..8d38b87e1d7 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 e610ed93050..a23105691bf 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 70a3c6f82c1..cdf8528f48a 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); -- cgit 1.4.1-3-g733a5 From 9e4e8ef1023d02367a8919f4d13df913f2232cb2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Jan 2024 16:44:01 +0100 Subject: 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 a9f1612ff05..276c1abb60c 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, -- cgit 1.4.1-3-g733a5 From 7aa4624a8fe8ff2c4273e35457a7cee6100a8355 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Jan 2024 16:44:41 +0100 Subject: 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 a8b12c6dfe3..7c9e9d8ed26 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 4ca57715b18..d2bdbd17d5c 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 -- cgit 1.4.1-3-g733a5 From 2dba787c9086dc03f62c00b4dc97ba87694985ba Mon Sep 17 00:00:00 2001 From: Georgiy Komarov Date: Fri, 5 Jan 2024 12:54:53 -0300 Subject: 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 a8c9660da4c..dc29ab5d08d 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 -- cgit 1.4.1-3-g733a5 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: 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 ef67f4b04b5..ed0958197f8 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 a25bb46f9ff..00000000000 --- 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 082f7c92646..fb87b79aeed 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 94ebb20918e..00000000000 --- 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 f98ce9d50a9..4d41b5e5e50 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 6d942ca8908..daa660be25e 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 de106f50e0e..00a3c439ba0 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 -- cgit 1.4.1-3-g733a5 From e10a05dff3f278f89dc4bce7fb9cbafc40767d49 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 2 Jan 2024 23:32:40 +0300 Subject: 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 849920bb76d..3761ba81f52 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 43eb6a9b838..788fe828727 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 d68c5c4bac6..5288efd8df8 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 63e64a5b35d..47c9438c588 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; } -- cgit 1.4.1-3-g733a5 From 60c647b262d09ed104c6945ff29ab0ee35a2dee4 Mon Sep 17 00:00:00 2001 From: cocodery Date: Sat, 6 Jan 2024 21:41:25 +0800 Subject: 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 12fd63b50f2..e732a882cbe 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 245d36cb734..237f5f5b97a 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 d1559cba554..c8da5dbcec2 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 78c8f1a2fcf..83688c1b451 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` -- cgit 1.4.1-3-g733a5 From 28c133b4bc2795d78c0bc15d99af5e5f44093a2d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 16:56:24 +0100 Subject: 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 cc6eeaa86e5..c8d5a358098 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; -- cgit 1.4.1-3-g733a5 From 6410606815c64946d93ad51d5de968a960364961 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 16:56:38 +0100 Subject: 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 f6770558bd8..9083ae2d0b8 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 0996218076b..65f361dc2ae 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 163eb7b1437..e91a9db4e3d 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 -- cgit 1.4.1-3-g733a5 From af35d37749166b550e18a65325deb76cc05a343a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Jan 2024 17:16:34 +0100 Subject: 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 46ce4ffdce5..c475e7b7c43 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() }) } -- cgit 1.4.1-3-g733a5 From 5b7a0de3e776f3cd231722d66812db9f37c4d6a0 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 7 Jan 2024 10:42:00 +0100 Subject: 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 cd6c46a71a8..8f48941c4a9 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 abc92459148..ccde154bd56 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 } +} -- cgit 1.4.1-3-g733a5 From 78aa2e29e273fed22c53065ffc319ae75af0266c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Jan 2024 13:13:29 +0100 Subject: 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 c8d5a358098..652d2b80081 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 -- cgit 1.4.1-3-g733a5 From f66e940c88a50aa36fb2fa64a60fb3d3910e0ce9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Jan 2024 13:18:11 +0100 Subject: 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 dd979013d3c..b7cf12c1a84 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 96cba71965f..693914ec30a 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 eb11f084887..e6192a487ab 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 -- cgit 1.4.1-3-g733a5 From a843996e81cd6993b9125708558c3ec5b674baf1 Mon Sep 17 00:00:00 2001 From: cocodery Date: Sun, 7 Jan 2024 22:35:21 +0800 Subject: Simplify check function, weirdly binary format literals happen not often --- clippy_lints/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 e732a882cbe..2701d6bdca3 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); } -- cgit 1.4.1-3-g733a5 From bbadce9ec0a8855ab72852af1a6ed1f68c9ce0c2 Mon Sep 17 00:00:00 2001 From: Quinn Sinclair Date: Wed, 3 Jan 2024 04:03:00 +0200 Subject: 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 7cfd3d346b6..e489899c19e 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 ade8e3155fa..9f84321ced4 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