diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index d530eb6c73a3..3a99d65233d3 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -16,7 +16,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/.github/workflows/clippy_pr.yml b/.github/workflows/clippy_pr.yml index d91c638a8fb5..f9e882d9757c 100644 --- a/.github/workflows/clippy_pr.yml +++ b/.github/workflows/clippy_pr.yml @@ -24,7 +24,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 03641a9aa62f..d4dc80efe79d 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -20,9 +20,9 @@ jobs: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: - node-version: '20.x' + node-version: '24.x' - name: Install remark run: npm install remark-cli remark-lint remark-lint-maximum-line-length@^3.1.3 remark-preset-lint-recommended remark-gfm diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f666caf306f..91d793489be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6916,6 +6916,7 @@ Released 2018-09-13 [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push +[`same_length_and_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_length_and_capacity [`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method [`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some [`seek_from_current`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 10c08dba50b9..c2abbac37535 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -10,6 +10,7 @@ clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" opener = "0.7" +rustc-literal-escaper = "0.0.5" walkdir = "2.3" [package.metadata.rust-analyzer] diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index dcca08aee7e6..cd103908be03 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -22,7 +22,6 @@ extern crate rustc_arena; #[expect(unused_extern_crates, reason = "required to link to rustc crates")] extern crate rustc_driver; extern crate rustc_lexer; -extern crate rustc_literal_escaper; pub mod deprecate_lint; pub mod dogfood; diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 2586c89bc868..4aa55e53445c 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -3,6 +3,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node}; use clippy_utils::msrvs::Msrv; +use clippy_utils::visitors::is_const_evaluatable; use clippy_utils::{is_inside_always_const_context, msrvs}; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; @@ -50,6 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { _ => return, } && let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn) + && is_const_evaluatable(cx, condition) && let Some((Constant::Bool(assert_val), const_src)) = ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt()) && let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 902ba70577b9..a04a56d72bc0 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -433,7 +433,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio }, ExprKind::MethodCall(path, receiver, args, _) => { let type_of_receiver = cx.typeck_results().expr_ty(receiver); - if !type_of_receiver.is_diag_item(cx, sym::Option) && !type_of_receiver.is_diag_item(cx, sym::Result) { + if !matches!(type_of_receiver.opt_diag_name(cx), Some(sym::Option | sym::Result)) { return None; } METHODS_WITH_NEGATION diff --git a/clippy_lints/src/casts/cast_precision_loss.rs b/clippy_lints/src/casts/cast_precision_loss.rs index 712e38db499f..748ab3163496 100644 --- a/clippy_lints/src/casts/cast_precision_loss.rs +++ b/clippy_lints/src/casts/cast_precision_loss.rs @@ -23,15 +23,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca let cast_to_f64 = to_nbits == 64; let mantissa_nbits = if cast_to_f64 { 52 } else { 23 }; - let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64; - let arch_dependent_str = "on targets with 64-bit wide pointers "; - let from_nbits_str = if arch_dependent { - "64".to_owned() - } else if is_isize_or_usize(cast_from) { - // FIXME: handle 16 bits `usize` type - "32 or 64".to_owned() + + let has_width = if is_isize_or_usize(cast_from) { + "can be up to 64 bits wide depending on the target architecture".to_owned() } else { - from_nbits.to_string() + format!("is {from_nbits} bits wide") }; span_lint( @@ -39,13 +35,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca CAST_PRECISION_LOSS, expr.span, format!( - "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \ - but `{1}`'s mantissa is only {4} bits wide)", - cast_from, - if cast_to_f64 { "f64" } else { "f32" }, - if arch_dependent { arch_dependent_str } else { "" }, - from_nbits_str, - mantissa_nbits + "casting `{cast_from}` to `{cast_to}` may cause a loss of precision \ + (`{cast_from}` {has_width}, \ + but `{cast_to}`'s mantissa is only {mantissa_nbits} bits wide)", ), ); } diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 494d6180d3cb..7220a8a80066 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -836,7 +836,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.93.0"] pub NEEDLESS_TYPE_CAST, - pedantic, + nursery, "binding defined with one type but always cast to another" } diff --git a/clippy_lints/src/casts/needless_type_cast.rs b/clippy_lints/src/casts/needless_type_cast.rs index ca6aa0f87bbf..1d899a21c229 100644 --- a/clippy_lints/src/casts/needless_type_cast.rs +++ b/clippy_lints/src/casts/needless_type_cast.rs @@ -1,6 +1,8 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; use clippy_utils::visitors::{Descend, for_each_expr, for_each_expr_without_closures}; use core::ops::ControlFlow; +use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -14,6 +16,7 @@ use super::NEEDLESS_TYPE_CAST; struct BindingInfo<'a> { source_ty: Ty<'a>, ty_span: Span, + init: Option<&'a Expr<'a>>, } struct UsageInfo<'a> { @@ -73,6 +76,7 @@ fn collect_binding_from_let<'a>( BindingInfo { source_ty: ty, ty_span: ty_hir.span, + init: Some(let_expr.init), }, ); } @@ -103,6 +107,7 @@ fn collect_binding_from_local<'a>( BindingInfo { source_ty: ty, ty_span: ty_hir.span, + init: let_stmt.init, }, ); } @@ -182,12 +187,7 @@ fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool { .iter() .any(|p| p.kind.is_ty_or_const()) }; - match res { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id), - // Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT - Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))), - _ => false, - } + cx.tcx.res_generics_def_id(res).is_some_and(has_type_params) } fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool { @@ -234,6 +234,18 @@ fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> } } +fn can_coerce_to_target_type(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Lit(lit) => matches!( + lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) + ), + ExprKind::Unary(rustc_hir::UnOp::Neg, inner) => can_coerce_to_target_type(inner), + ExprKind::Binary(_, lhs, rhs) => can_coerce_to_target_type(lhs) && can_coerce_to_target_type(rhs), + _ => false, + } +} + fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) { let mut usages = Vec::new(); @@ -274,7 +286,19 @@ fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId return; }; - span_lint_and_sugg( + // Don't lint if there's exactly one use and the initializer cannot be coerced to the + // target type (i.e., would require an explicit cast). In such cases, the fix would add + // a cast to the initializer rather than eliminating one - the cast isn't truly "needless." + // See: https://github.com/rust-lang/rust-clippy/issues/16240 + if usages.len() == 1 + && binding_info + .init + .is_some_and(|init| !can_coerce_to_target_type(init) && !init.span.from_expansion()) + { + return; + } + + span_lint_and_then( cx, NEEDLESS_TYPE_CAST, binding_info.ty_span, @@ -282,8 +306,28 @@ fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId "this binding is defined as `{}` but is always cast to `{}`", binding_info.source_ty, first_target ), - "consider defining it as", - first_target.to_string(), - Applicability::MaybeIncorrect, + |diag| { + if let Some(init) = binding_info + .init + .filter(|i| !can_coerce_to_target_type(i) && !i.span.from_expansion()) + { + let sugg = Sugg::hir(cx, init, "..").as_ty(first_target); + diag.multipart_suggestion( + format!("consider defining it as `{first_target}` and casting the initializer"), + vec![ + (binding_info.ty_span, first_target.to_string()), + (init.span, sugg.to_string()), + ], + Applicability::MachineApplicable, + ); + } else { + diag.span_suggestion( + binding_info.ty_span, + "consider defining it as", + first_target.to_string(), + Applicability::MachineApplicable, + ); + } + }, ); } diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index 592c820a25e1..b3805c678174 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{ExprUseNode, expr_use_ctxt, std_or_core}; +use clippy_utils::{ExprUseNode, expr_use_ctxt, is_expr_temporary_value, std_or_core}; use rustc_errors::Applicability; -use rustc_hir::{Expr, Mutability, Ty, TyKind}; +use rustc_hir::{Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -23,10 +23,18 @@ pub(super) fn check<'tcx>( if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(_, to_mutbl) = cast_to.kind() && let use_cx = expr_use_ctxt(cx, expr) - // TODO: only block the lint if `cast_expr` is a temporary - && !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) && let Some(std_or_core) = std_or_core(cx) { + if let ExprKind::AddrOf(_, _, addr_inner) = cast_expr.kind + && is_expr_temporary_value(cx, addr_inner) + && matches!( + use_cx.use_node(cx), + ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_) + ) + { + return; + } + let fn_name = match to_mutbl { Mutability::Not => "from_ref", Mutability::Mut => "from_mut", diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index b13e307a3f9c..be07ce1272bd 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -76,7 +76,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.51.0"] pub COLLAPSIBLE_ELSE_IF, - style, + pedantic, "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" } @@ -267,6 +267,9 @@ impl LateLintPass<'_> for CollapsibleIf { && !expr.span.from_expansion() { if let Some(else_) = else_ + // Short circuit if both `if` branches contain only a single `if {..} else {}`, as + // collapsing such blocks can lead to less readable code (#4971) + && !(single_inner_if_else(then) && single_inner_if_else(else_)) && let ExprKind::Block(else_, None) = else_.kind { self.check_collapsible_else_if(cx, then.span, else_); @@ -280,6 +283,19 @@ impl LateLintPass<'_> for CollapsibleIf { } } +/// Returns true if `expr` is a block that contains only one `if {..} else {}` statement +fn single_inner_if_else(expr: &Expr<'_>) -> bool { + if let ExprKind::Block(block, None) = expr.kind + && let Some(inner_expr) = expr_block(block) + && let ExprKind::If(_, _, else_) = inner_expr.kind + && else_.is_some() + { + true + } else { + false + } +} + /// If `block` is a block with either one expression or a statement containing an expression, /// return the expression. We don't peel blocks recursively, as extra blocks might be intentional. fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 87d75234ebc0..6b68940c6423 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -667,6 +667,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::returns::LET_AND_RETURN_INFO, crate::returns::NEEDLESS_RETURN_INFO, crate::returns::NEEDLESS_RETURN_WITH_QUESTION_MARK_INFO, + crate::same_length_and_capacity::SAME_LENGTH_AND_CAPACITY_INFO, crate::same_name_method::SAME_NAME_METHOD_INFO, crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO, crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO, diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 8c067432cb4e..58403ad19235 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -88,6 +88,9 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.desugaring_kind().is_some() { + return; + } let (id, span) = match &expr.kind { ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span), ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index e7230ebf8cba..7e335d5c9809 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -1,16 +1,18 @@ use clippy_utils::attrs::span_contains_cfg; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; -use rustc_data_structures::fx::FxIndexMap; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::span_contains_non_whitespace; +use rustc_data_structures::fx::{FxIndexMap, IndexEntry}; use rustc_errors::Applicability; -use rustc_hir::def::CtorOf; use rustc_hir::def::DefKind::Ctor; use rustc_hir::def::Res::Def; +use rustc_hir::def::{CtorOf, DefKind}; use rustc_hir::def_id::LocalDefId; -use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData}; +use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Pat, PatKind, Path, QPath, Variant, VariantData}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -118,7 +120,6 @@ impl LateLintPass<'_> for EmptyWithBrackets { } fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) { - // FIXME: handle `$name {}` if !variant.span.from_expansion() && !variant.ident.span.from_expansion() && let span_after_ident = variant.span.with_lo(variant.ident.span.hi()) @@ -126,44 +127,14 @@ impl LateLintPass<'_> for EmptyWithBrackets { { match variant.data { VariantData::Struct { .. } => { - // Empty struct variants can be linted immediately - 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::MaybeIncorrect, - ); - }, - ); + self.add_enum_variant(variant.def_id); }, VariantData::Tuple(.., local_def_id) => { // Don't lint reachable tuple enums if cx.effective_visibilities.is_reachable(variant.def_id) { return; } - if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) { - // empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the - // definition was encountered. Now that there's a definition, convert it - // to Usage::Unused. - if let Usage::NoDefinition { redundant_use_sites } = entry { - *entry = Usage::Unused { - redundant_use_sites: redundant_use_sites.clone(), - }; - } - } else { - self.empty_tuple_enum_variants.insert( - local_def_id, - Usage::Unused { - redundant_use_sites: vec![], - }, - ); - } + self.add_enum_variant(local_def_id); }, VariantData::Unit(..) => {}, } @@ -171,56 +142,58 @@ impl LateLintPass<'_> for EmptyWithBrackets { } fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let Some(def_id) = check_expr_for_enum_as_function(expr) { - if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) { + if let Some((def_id, mut span)) = check_expr_for_enum_as_function(cx, expr) { + if span.is_empty() + && let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) + { + span = parentheses_span; + } + + if span.is_empty() { + // The parentheses are not redundant. + self.empty_tuple_enum_variants.insert(def_id, Usage::Used); + } else { // Do not count expressions from macro expansion as a redundant use site. if expr.span.from_expansion() { return; } - match self.empty_tuple_enum_variants.get_mut(&def_id) { - Some( - &mut (Usage::Unused { - ref mut redundant_use_sites, - } - | Usage::NoDefinition { - ref mut redundant_use_sites, - }), - ) => { - redundant_use_sites.push(parentheses_span); - }, - None => { - // The variant isn't in the IndexMap which means its definition wasn't encountered yet. - self.empty_tuple_enum_variants.insert( - def_id, - Usage::NoDefinition { - redundant_use_sites: vec![parentheses_span], - }, - ); - }, - _ => {}, - } - } else { - // The parentheses are not redundant. - self.empty_tuple_enum_variants.insert(def_id, Usage::Used); + self.update_enum_variant_usage(def_id, span); } } } + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { + if !pat.span.from_expansion() + && let Some((def_id, span)) = check_pat_for_enum_as_function(cx, pat) + { + self.update_enum_variant_usage(def_id, span); + } + } + fn check_crate_post(&mut self, cx: &LateContext<'_>) { - for (local_def_id, usage) in &self.empty_tuple_enum_variants { + for (&local_def_id, usage) in &self.empty_tuple_enum_variants { // Ignore all variants with Usage::Used or Usage::NoDefinition let Usage::Unused { redundant_use_sites } = usage else { continue; }; + // Attempt to fetch the Variant from LocalDefId. - let Node::Variant(variant) = cx.tcx.hir_node( - cx.tcx - .local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()), - ) else { + let variant = if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(local_def_id) { + variant + } else if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(cx.tcx.local_parent(local_def_id)) { + variant + } else { continue; }; + // Span of the parentheses in variant definition let span = variant.span.with_lo(variant.ident.span.hi()); + let span_inner = span + .with_lo(SpanRangeExt::trim_start(span, cx).start + BytePos(1)) + .with_hi(span.hi() - BytePos(1)); + if span_contains_non_whitespace(cx, span_inner, false) { + continue; + } span_lint_hir_and_then( cx, EMPTY_ENUM_VARIANTS_WITH_BRACKETS, @@ -252,6 +225,43 @@ impl LateLintPass<'_> for EmptyWithBrackets { } } +impl EmptyWithBrackets { + fn add_enum_variant(&mut self, local_def_id: LocalDefId) { + self.empty_tuple_enum_variants + .entry(local_def_id) + .and_modify(|entry| { + // empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before + // the definition was encountered. Now that there's a + // definition, convert it to Usage::Unused. + if let Usage::NoDefinition { redundant_use_sites } = entry { + *entry = Usage::Unused { + redundant_use_sites: redundant_use_sites.clone(), + }; + } + }) + .or_insert_with(|| Usage::Unused { + redundant_use_sites: vec![], + }); + } + + fn update_enum_variant_usage(&mut self, def_id: LocalDefId, parentheses_span: Span) { + match self.empty_tuple_enum_variants.entry(def_id) { + IndexEntry::Occupied(mut e) => { + if let Usage::Unused { redundant_use_sites } | Usage::NoDefinition { redundant_use_sites } = e.get_mut() + { + redundant_use_sites.push(parentheses_span); + } + }, + IndexEntry::Vacant(e) => { + // The variant isn't in the IndexMap which means its definition wasn't encountered yet. + e.insert(Usage::NoDefinition { + redundant_use_sites: vec![parentheses_span], + }); + }, + } + } +} + fn has_brackets(var_data: &VariantData<'_>) -> bool { !matches!(var_data, VariantData::Unit(..)) } @@ -277,17 +287,47 @@ fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option { } // Returns the LocalDefId of the variant being called as a function if it exists. -fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option { - if let ExprKind::Path(QPath::Resolved( - _, - Path { - res: Def(Ctor(CtorOf::Variant, _), def_id), - .. +fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(LocalDefId, Span)> { + match expr.kind { + ExprKind::Path(QPath::Resolved( + _, + Path { + res: Def(Ctor(CtorOf::Variant, _), def_id), + span, + .. + }, + )) => def_id.as_local().map(|id| (id, span.with_lo(expr.span.hi()))), + ExprKind::Struct(qpath, ..) + if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) => + { + let ty = cx.tcx.type_of(def_id).instantiate_identity(); + if let ty::FnDef(ctor_def_id, _) = ty.kind() { + def_id = *ctor_def_id; + } + + def_id.as_local().map(|id| (id, qpath.span().with_lo(expr.span.hi()))) }, - )) = expr.kind - { - def_id.as_local() - } else { - None + _ => None, + } +} + +fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(LocalDefId, Span)> { + match pat.kind { + PatKind::TupleStruct(qpath, ..) + if let Def(Ctor(CtorOf::Variant, _), def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) => + { + def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi()))) + }, + PatKind::Struct(qpath, ..) + if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) => + { + let ty = cx.tcx.type_of(def_id).instantiate_identity(); + if let ty::FnDef(ctor_def_id, _) = ty.kind() { + def_id = *ctor_def_id; + } + + def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi()))) + }, + _ => None, } } diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index bdfe2e49e66e..75ab890a8a7f 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -4,7 +4,7 @@ use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_expr; use clippy_utils::{ SpanlessEq, can_move_expr_to_closure_no_visit, desugar_await, higher, is_expr_final_block_expr, - is_expr_used_or_unified, paths, peel_hir_expr_while, + is_expr_used_or_unified, paths, peel_hir_expr_while, span_contains_non_whitespace, }; use core::fmt::{self, Write}; use rustc_errors::Applicability; @@ -167,7 +167,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { "if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}", map_ty.entry_path(), )) - } else if let Some(insertion) = then_search.as_single_insertion() { + } else if let Some(insertion) = then_search.as_single_insertion() + && let span_in_between = then_expr.span.shrink_to_lo().between(insertion.call.span) + && let span_in_between = span_in_between.split_at(1).1 + && !span_contains_non_whitespace(cx, span_in_between, true) + { let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0; if contains_expr.negated { if insertion.value.can_have_side_effects() { diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index c42998ffc3f5..bd2bd6628464 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -82,7 +82,7 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp // check for `unwrap` if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]) { let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs(); - if receiver_ty.is_diag_item(self.lcx, sym::Option) || receiver_ty.is_diag_item(self.lcx, sym::Result) { + if matches!(receiver_ty.opt_diag_name(self.lcx), Some(sym::Option | sym::Result)) { self.result.push(expr.span); } } diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index a23ba9ab837a..fea55f91bce7 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -1,10 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::higher; +use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node}; use clippy_utils::res::MaybeDef; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; +use clippy_utils::std_or_core; +use rustc_errors::Applicability; use rustc_hir::{AssignOpKind, Expr, ExprKind, LangItem, MatchSource}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::sym; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; +use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -38,7 +41,152 @@ declare_clippy_lint! { pedantic, "`format!(..)` appended to existing `String`" } -declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); +impl_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); + +pub(crate) struct FormatPushString { + format_args: FormatArgsStorage, +} + +enum FormatSearchResults { + /// The expression is itself a `format!()` invocation -- we can make a suggestion to replace it + Direct(Span), + /// The expression contains zero or more `format!()`s, e.g.: + /// ```ignore + /// if true { + /// format!("hello") + /// } else { + /// format!("world") + /// } + /// ``` + /// or + /// ```ignore + /// match true { + /// true => format!("hello"), + /// false => format!("world"), + /// } + Nested(Vec), +} + +impl FormatPushString { + pub(crate) fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } + + fn find_formats<'tcx>(&self, cx: &LateContext<'_>, e: &'tcx Expr<'tcx>) -> FormatSearchResults { + let expr_as_format = |e| { + if let Some(macro_call) = root_macro_call_first_node(cx, e) + && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) + && let Some(format_args) = self.format_args.get(cx, e, macro_call.expn) + { + Some(format_args_inputs_span(format_args)) + } else { + None + } + }; + + let e = e.peel_blocks().peel_borrows(); + if let Some(fmt) = expr_as_format(e) { + FormatSearchResults::Direct(fmt) + } else { + fn inner<'tcx>( + e: &'tcx Expr<'tcx>, + expr_as_format: &impl Fn(&'tcx Expr<'tcx>) -> Option, + out: &mut Vec, + ) { + let e = e.peel_blocks().peel_borrows(); + + match e.kind { + _ if expr_as_format(e).is_some() => out.push(e.span), + ExprKind::Match(_, arms, MatchSource::Normal) => { + for arm in arms { + inner(arm.body, expr_as_format, out); + } + }, + ExprKind::If(_, then, els) => { + inner(then, expr_as_format, out); + if let Some(els) = els { + inner(els, expr_as_format, out); + } + }, + _ => {}, + } + } + let mut spans = vec![]; + inner(e, &expr_as_format, &mut spans); + FormatSearchResults::Nested(spans) + } + } +} + +impl<'tcx> LateLintPass<'tcx> for FormatPushString { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let (recv, arg) = match expr.kind { + ExprKind::MethodCall(_, recv, [arg], _) => { + if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) + { + (recv, arg) + } else { + return; + } + }, + ExprKind::AssignOp(op, recv, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, recv) => { + (recv, arg) + }, + _ => return, + }; + let Some(std_or_core) = std_or_core(cx) else { + // not even `core` is available, so can't suggest `write!` + return; + }; + match self.find_formats(cx, arg) { + FormatSearchResults::Direct(format_args) => { + span_lint_and_then( + cx, + FORMAT_PUSH_STRING, + expr.span, + "`format!(..)` appended to existing `String`", + |diag| { + let mut app = Applicability::MaybeIncorrect; + let msg = "consider using `write!` to avoid the extra allocation"; + + let sugg = format!( + "let _ = write!({recv}, {format_args})", + recv = snippet_with_context(cx.sess(), recv.span, expr.span.ctxt(), "_", &mut app).0, + format_args = snippet_with_applicability(cx.sess(), format_args, "..", &mut app), + ); + diag.span_suggestion_verbose(expr.span, msg, sugg, app); + + // TODO: omit the note if the `Write` trait is imported at point + // Tip: `TyCtxt::in_scope_traits` isn't it -- it returns a non-empty list only when called on + // the `HirId` of a `ExprKind::MethodCall` that is a call of a _trait_ method. + diag.note(format!("you may need to import the `{std_or_core}::fmt::Write` trait")); + }, + ); + }, + FormatSearchResults::Nested(spans) => { + if !spans.is_empty() { + span_lint_and_then( + cx, + FORMAT_PUSH_STRING, + expr.span, + "`format!(..)` appended to existing `String`", + |diag| { + diag.help("consider using `write!` to avoid the extra allocation"); + diag.span_labels(spans, "`format!` used here"); + + // TODO: omit the note if the `Write` trait is imported at point + // Tip: `TyCtxt::in_scope_traits` isn't it -- it returns a non-empty list only when called + // on the `HirId` of a `ExprKind::MethodCall` that is a call of + // a _trait_ method. + diag.note(format!("you may need to import the `{std_or_core}::fmt::Write` trait")); + }, + ); + } + }, + } + } +} fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { cx.typeck_results() @@ -46,54 +194,3 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { .peel_refs() .is_lang_item(cx, LangItem::String) } -fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - let e = e.peel_blocks().peel_borrows(); - - if e.span.from_expansion() - && let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id - { - cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro) - } else if let Some(higher::If { then, r#else, .. }) = higher::If::hir(e) { - is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e)) - } else { - match higher::IfLetOrMatch::parse(cx, e) { - Some(higher::IfLetOrMatch::Match(_, arms, MatchSource::Normal)) => { - arms.iter().any(|arm| is_format(cx, arm.body)) - }, - Some(higher::IfLetOrMatch::IfLet(_, _, then, r#else, _)) => { - is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e)) - }, - _ => false, - } - } -} - -impl<'tcx> LateLintPass<'tcx> for FormatPushString { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let arg = match expr.kind { - ExprKind::MethodCall(_, _, [arg], _) => { - if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) - { - arg - } else { - return; - } - }, - ExprKind::AssignOp(op, left, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, left) => arg, - _ => return, - }; - if is_format(cx, arg) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - FORMAT_PUSH_STRING, - expr.span, - "`format!(..)` appended to existing `String`", - |diag| { - diag.help("consider using `write!` to avoid the extra allocation"); - }, - ); - } - } -} diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index bdc366f6878a..9a7427ea1447 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -596,4 +596,8 @@ impl<'tcx> LateLintPass<'tcx> for Functions { impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api); ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api); } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + result::check_expr(cx, expr, self.large_error_threshold, &self.large_error_ignored); + } } diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs index 04e15a1d8a0e..77fec9371425 100644 --- a/clippy_lints/src/functions/result.rs +++ b/clippy_lints/src/functions/result.rs @@ -50,7 +50,7 @@ pub(super) fn check_item<'tcx>( let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false); } } @@ -70,7 +70,7 @@ pub(super) fn check_impl_item<'tcx>( let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false); } } @@ -87,7 +87,7 @@ pub(super) fn check_trait_item<'tcx>( if cx.effective_visibilities.is_exported(item.owner_id.def_id) { check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false); } } } @@ -111,12 +111,15 @@ fn check_result_large_err<'tcx>( hir_ty_span: Span, large_err_threshold: u64, large_err_ignored: &DefIdSet, + is_closure: bool, ) { if let ty::Adt(adt, _) = err_ty.kind() && large_err_ignored.contains(&adt.did()) { return; } + + let subject = if is_closure { "closure" } else { "function" }; if let ty::Adt(adt, subst) = err_ty.kind() && let Some(local_def_id) = adt.did().as_local() && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id) @@ -130,7 +133,7 @@ fn check_result_large_err<'tcx>( cx, RESULT_LARGE_ERR, hir_ty_span, - "the `Err`-variant returned from this function is very large", + format!("the `Err`-variant returned from this {subject} is very large"), |diag| { diag.span_label( def.variants[first_variant.ind].span, @@ -161,7 +164,7 @@ fn check_result_large_err<'tcx>( cx, RESULT_LARGE_ERR, hir_ty_span, - "the `Err`-variant returned from this function is very large", + format!("the `Err`-variant returned from this {subject} is very large"), |diag: &mut Diag<'_, ()>| { diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes")); diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`")); @@ -170,3 +173,33 @@ fn check_result_large_err<'tcx>( } } } + +pub(super) fn check_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + large_err_threshold: u64, + large_err_ignored: &DefIdSet, +) { + if let hir::ExprKind::Closure(closure) = expr.kind + && let ty::Closure(_, args) = cx.typeck_results().expr_ty(expr).kind() + && let closure_sig = args.as_closure().sig() + && let Ok(err_binder) = closure_sig.output().try_map_bound(|output_ty| { + if let ty::Adt(adt, args) = output_ty.kind() + && let [_, err_arg] = args.as_slice() + && let Some(err_ty) = err_arg.as_type() + && adt.is_diag_item(cx, sym::Result) + { + return Ok(err_ty); + } + + Err(()) + }) + { + let err_ty = cx.tcx.instantiate_bound_regions_with_erased(err_binder); + let hir_ty_span = match closure.fn_decl.output { + hir::FnRetTy::Return(hir_ty) => hir_ty.span, + hir::FnRetTy::DefaultReturn(_) => expr.span, + }; + check_result_large_err(cx, err_ty, hir_ty_span, large_err_threshold, large_err_ignored, true); + } +} diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 54e9538fcb99..ff22ba4fcd0d 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::is_zero_integer_const; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::is_else_clause; -use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet}; +use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -78,6 +78,7 @@ impl LateLintPass<'_> for IfNotElse { // } // ``` if !e.span.from_expansion() && !is_else_clause(cx.tcx, e) { + let mut applicability = Applicability::MachineApplicable; match cond.kind { ExprKind::Unary(UnOp::Not, _) | ExprKind::Binary(_, _, _) => span_lint_and_sugg( cx, @@ -85,8 +86,16 @@ impl LateLintPass<'_> for IfNotElse { e.span, msg, "try", - make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)), - Applicability::MachineApplicable, + make_sugg( + cx, + e.span, + &cond.kind, + cond_inner.span, + els.span, + "..", + &mut applicability, + ), + applicability, ), _ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help), } @@ -97,28 +106,26 @@ impl LateLintPass<'_> for IfNotElse { fn make_sugg<'a>( sess: &impl HasSession, + expr_span: Span, cond_kind: &'a ExprKind<'a>, cond_inner: Span, els_span: Span, default: &'a str, - indent_relative_to: Option, + applicability: &mut Applicability, ) -> String { - let cond_inner_snip = snippet(sess, cond_inner, default); - let els_snip = snippet(sess, els_span, default); - let indent = indent_relative_to.and_then(|s| indent_of(sess, s)); + let (cond_inner_snip, _) = snippet_with_context(sess, cond_inner, expr_span.ctxt(), default, applicability); + let (els_snip, _) = snippet_with_context(sess, els_span, expr_span.ctxt(), default, applicability); + let indent = indent_of(sess, expr_span); let suggestion = match cond_kind { ExprKind::Unary(UnOp::Not, cond_rest) => { - format!( - "if {} {} else {}", - snippet(sess, cond_rest.span, default), - els_snip, - cond_inner_snip - ) + let (cond_rest_snip, _) = + snippet_with_context(sess, cond_rest.span, expr_span.ctxt(), default, applicability); + format!("if {cond_rest_snip} {els_snip} else {cond_inner_snip}") }, ExprKind::Binary(_, lhs, rhs) => { - let lhs_snip = snippet(sess, lhs.span, default); - let rhs_snip = snippet(sess, rhs.span, default); + let (lhs_snip, _) = snippet_with_context(sess, lhs.span, expr_span.ctxt(), default, applicability); + let (rhs_snip, _) = snippet_with_context(sess, rhs.span, expr_span.ctxt(), default, applicability); format!("if {lhs_snip} == {rhs_snip} {els_snip} else {cond_inner_snip}") }, diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 7f3ef58c93d1..9e5e4fa58d2f 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -4,10 +4,12 @@ use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; +use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{ - as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, - is_none_expr, peel_blocks, sym, + as_some_expr, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_none_expr, + peel_blocks, sym, }; +use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -76,8 +78,14 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) - && !contains_return(then_block.stmts) - && then_block.expr.is_none_or(|expr| !contains_return(expr)) + && for_each_expr_without_closures(then_block, |e| { + if matches!(e.kind, ExprKind::Ret(..) | ExprKind::Yield(..)) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_none() { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { sym::then_some @@ -101,13 +109,19 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { .maybe_paren() .to_string(); let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let method_body = if let Some(first_stmt) = then_block.stmts.first() - && let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt) + let method_body = if let Some(_) = then_block.stmts.first() + && let Some(then_span) = walk_span_to_context(then.span, ctxt) { - let block_snippet = - snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app); + let block_before_snippet = + snippet_with_applicability(cx, then_span.until(then_expr.span), "..", &mut app); + let block_after_snippet = snippet_with_applicability( + cx, + then_expr.span.shrink_to_hi().until(then_span.shrink_to_hi()), + "..", + &mut app, + ); let closure = if method_name == sym::then { "|| " } else { "" }; - format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end()) + format!("{closure}{block_before_snippet}{arg_snip}{block_after_snippet}") } else if method_name == sym::then { (std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned() } else { diff --git a/clippy_lints/src/ifs/branches_sharing_code.rs b/clippy_lints/src/ifs/branches_sharing_code.rs index b3f597cc8736..b6e8d047c5cd 100644 --- a/clippy_lints/src/ifs/branches_sharing_code.rs +++ b/clippy_lints/src/ifs/branches_sharing_code.rs @@ -9,7 +9,7 @@ use clippy_utils::{ use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; +use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, ItemKind, LetStmt, Node, Stmt, StmtKind, UseKind, intravisit}; use rustc_lint::LateContext; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; @@ -108,6 +108,7 @@ struct BlockEq { /// The name and id of every local which can be moved at the beginning and the end. moved_locals: Vec<(HirId, Symbol)>, } + impl BlockEq { fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { match &b.stmts[..self.start_end_eq] { @@ -129,20 +130,33 @@ impl BlockEq { } /// If the statement is a local, checks if the bound names match the expected list of names. -fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { - if let StmtKind::Let(l) = s.kind { - let mut i = 0usize; - let mut res = true; - l.pat.each_binding_or_first(&mut |_, _, _, name| { - if names.get(i).is_some_and(|&(_, n)| n == name.name) { - i += 1; - } else { - res = false; - } - }); - res && i == names.len() - } else { - false +fn eq_binding_names(cx: &LateContext<'_>, s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { + match s.kind { + StmtKind::Let(l) => { + let mut i = 0usize; + let mut res = true; + l.pat.each_binding_or_first(&mut |_, _, _, name| { + if names.get(i).is_some_and(|&(_, n)| n == name.name) { + i += 1; + } else { + res = false; + } + }); + res && i == names.len() + }, + StmtKind::Item(item_id) + if let [(_, name)] = names + && let item = cx.tcx.hir_item(item_id) + && let ItemKind::Static(_, ident, ..) + | ItemKind::Const(ident, ..) + | ItemKind::Fn { ident, .. } + | ItemKind::TyAlias(ident, ..) + | ItemKind::Use(_, UseKind::Single(ident)) + | ItemKind::Mod(ident, _) = item.kind => + { + *name == ident.name + }, + _ => false, } } @@ -164,6 +178,7 @@ fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: & /// Checks if the given statement should be considered equal to the statement in the same /// position for each block. fn eq_stmts( + cx: &LateContext<'_>, stmt: &Stmt<'_>, blocks: &[&Block<'_>], get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>, @@ -178,7 +193,7 @@ fn eq_stmts( let new_bindings = &moved_bindings[old_count..]; blocks .iter() - .all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(s, new_bindings))) + .all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(cx, s, new_bindings))) } else { true }) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt))) @@ -218,7 +233,7 @@ fn scan_block_for_eq<'tcx>( return true; } modifies_any_local(cx, stmt, &cond_locals) - || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) + || !eq_stmts(cx, stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) }) .map_or(block.stmts.len(), |(i, stmt)| { adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev()) @@ -279,6 +294,7 @@ fn scan_block_for_eq<'tcx>( })) .fold(end_search_start, |init, (stmt, offset)| { if eq_stmts( + cx, stmt, blocks, |b| b.stmts.get(b.stmts.len() - offset), @@ -290,11 +306,26 @@ fn scan_block_for_eq<'tcx>( // Clear out all locals seen at the end so far. None of them can be moved. let stmts = &blocks[0].stmts; for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { - if let StmtKind::Let(l) = stmt.kind { - l.pat.each_binding_or_first(&mut |_, id, _, _| { - // FIXME(rust/#120456) - is `swap_remove` correct? - eq.locals.swap_remove(&id); - }); + match stmt.kind { + StmtKind::Let(l) => { + l.pat.each_binding_or_first(&mut |_, id, _, _| { + // FIXME(rust/#120456) - is `swap_remove` correct? + eq.locals.swap_remove(&id); + }); + }, + StmtKind::Item(item_id) => { + let item = cx.tcx.hir_item(item_id); + if let ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::Fn { .. } + | ItemKind::TyAlias(..) + | ItemKind::Use(..) + | ItemKind::Mod(..) = item.kind + { + eq.local_items.swap_remove(&item.owner_id.to_def_id()); + } + }, + _ => {}, } } moved_locals.truncate(moved_locals_at_start); diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 638a08b096db..9dc74a157cbf 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -223,25 +223,20 @@ impl<'tcx> ImplicitHasherType<'tcx> { _ => None, }) .collect(); - let params_len = params.len(); let ty = lower_ty(cx.tcx, hir_ty); - if ty.is_diag_item(cx, sym::HashMap) && params_len == 2 { - Some(ImplicitHasherType::HashMap( + match (ty.opt_diag_name(cx), ¶ms[..]) { + (Some(sym::HashMap), [k, v]) => Some(ImplicitHasherType::HashMap( hir_ty.span, ty, - snippet(cx, params[0].span, "K"), - snippet(cx, params[1].span, "V"), - )) - } else if ty.is_diag_item(cx, sym::HashSet) && params_len == 1 { - Some(ImplicitHasherType::HashSet( - hir_ty.span, - ty, - snippet(cx, params[0].span, "T"), - )) - } else { - None + snippet(cx, k.span, "K"), + snippet(cx, v.span, "V"), + )), + (Some(sym::HashSet), [t]) => { + Some(ImplicitHasherType::HashSet(hir_ty.span, ty, snippet(cx, t.span, "T"))) + }, + _ => None, } } else { None diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 40487fe48f22..a957afdb1910 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -318,6 +318,7 @@ mod replace_box; mod reserve_after_initialization; mod return_self_not_must_use; mod returns; +mod same_length_and_capacity; mod same_name_method; mod self_named_constructors; mod semicolon_block; @@ -739,7 +740,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |_| Box::new(cargo::Cargo::new(conf))), Box::new(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())), Box::new(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)), - Box::new(|_| Box::new(format_push_string::FormatPushString)), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(format_push_string::FormatPushString::new(format_args.clone()))) + }, Box::new(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))), Box::new(|_| Box::new(strings::TrimSplitWhitespace)), Box::new(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)), @@ -852,6 +856,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|_| Box::new(volatile_composites::VolatileComposites)), Box::new(|_| Box::::default()), Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))), + Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index c6b650a1a88b..39b2391c98ec 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx _ => arg, }; - if ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap) { + if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) { span_lint_and_then( cx, FOR_KV_MAP, diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 21198c3c8bc2..ddc783069385 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -26,6 +26,7 @@ mod while_let_on_iterator; use clippy_config::Conf; use clippy_utils::msrvs::Msrv; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::{higher, sym}; use rustc_ast::Label; use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; @@ -881,13 +882,44 @@ impl<'tcx> LateLintPass<'tcx> for Loops { manual_while_let_some::check(cx, condition, body, span); } - if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind - && matches!( - path.ident.name, - sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map - ) - { - unused_enumerate_index::check_method(cx, expr, recv, arg); + if let ExprKind::MethodCall(path, recv, args, _) = expr.kind { + let name = path.ident.name; + + let is_iterator_method = || { + cx.ty_based_def(expr) + .assoc_fn_parent(cx) + .is_diag_item(cx, sym::Iterator) + }; + + // is_iterator_method is a bit expensive, so we call it last in each match arm + match (name, args) { + (sym::for_each | sym::all | sym::any, [arg]) => { + if let ExprKind::Closure(closure) = arg.kind + && is_iterator_method() + { + unused_enumerate_index::check_method(cx, recv, arg, closure); + never_loop::check_iterator_reduction(cx, expr, recv, closure); + } + }, + + (sym::filter_map | sym::find_map | sym::flat_map | sym::map, [arg]) => { + if let ExprKind::Closure(closure) = arg.kind + && is_iterator_method() + { + unused_enumerate_index::check_method(cx, recv, arg, closure); + } + }, + + (sym::try_for_each | sym::reduce, [arg]) | (sym::fold | sym::try_fold, [_, arg]) => { + if let ExprKind::Closure(closure) = arg.kind + && is_iterator_method() + { + never_loop::check_iterator_reduction(cx, expr, recv, closure); + } + }, + + _ => {}, + } } } } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 0d37be17689a..a037af3433c3 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -3,14 +3,16 @@ use super::utils::make_iterator_snippet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::sym; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use rustc_errors::Applicability; use rustc_hir::{ - Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, + Block, Closure, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, + StructTailExpr, }; use rustc_lint::LateContext; -use rustc_span::{BytePos, Span, sym}; +use rustc_span::{BytePos, Span}; use std::iter::once; use std::ops::ControlFlow; @@ -72,6 +74,31 @@ pub(super) fn check<'tcx>( } } +pub(super) fn check_iterator_reduction<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + closure: &'tcx Closure<'tcx>, +) { + let closure_body = cx.tcx.hir_body(closure.body).value; + let body_ty = cx.typeck_results().expr_ty(closure_body); + if body_ty.is_never() { + span_lint_and_then( + cx, + NEVER_LOOP, + expr.span, + "this iterator reduction never loops (closure always diverges)", + |diag| { + let mut app = Applicability::HasPlaceholders; + let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "", &mut app).0; + diag.note("if you only need one element, `if let Some(x) = iter.next()` is clearer"); + let sugg = format!("if let Some(x) = {recv_snip}.next() {{ ... }}"); + diag.span_suggestion_verbose(expr.span, "consider this pattern", sugg, app); + }, + ); + } +} + fn contains_any_break_or_continue(block: &Block<'_>) -> bool { for_each_expr_without_closures(block, |e| match e.kind { ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()), diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 82ded453616d..816273c7ba8b 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,10 +1,10 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; use clippy_utils::{expr_or_init, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; +use rustc_hir::{Closure, Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; use rustc_span::{Span, SyntaxContext, sym}; @@ -60,14 +60,12 @@ pub(super) fn check<'tcx>( pub(super) fn check_method<'tcx>( cx: &LateContext<'tcx>, - e: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, arg: &'tcx Expr<'tcx>, + closure: &'tcx Closure<'tcx>, ) { - if let ExprKind::Closure(closure) = arg.kind - && let body = cx.tcx.hir_body(closure.body) - && let [param] = body.params - && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) + let body = cx.tcx.hir_body(closure.body); + if let [param] = body.params && let [input] = closure.fn_decl.inputs && !arg.span.from_expansion() && !input.span.from_expansion() diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 0f3d8b336675..38ee4ce104a5 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -374,7 +374,7 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo } let ty = typeck_results.pat_ty(pat); // Option and Result are allowed, everything else isn't. - if !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) { + if !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) { has_disallowed = true; } }); diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 89411115f730..c26b2dbde7fc 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -3,7 +3,7 @@ use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::has_let_expr; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment}; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -44,6 +44,8 @@ pub(crate) fn check_if_let<'tcx>( { ex_new = ex_inner; } + + let (snippet, _) = snippet_with_context(cx, ex_new.span, expr.span.ctxt(), "..", &mut applicability); span_lint_and_then( cx, MATCH_LIKE_MATCHES_MACRO, @@ -53,11 +55,7 @@ pub(crate) fn check_if_let<'tcx>( diag.span_suggestion_verbose( expr.span, "use `matches!` directly", - format!( - "{}matches!({}, {pat})", - if b0 { "" } else { "!" }, - snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), - ), + format!("{}matches!({snippet}, {pat})", if b0 { "" } else { "!" }), applicability, ); }, @@ -178,6 +176,8 @@ pub(super) fn check_match<'tcx>( { ex_new = ex_inner; } + + let (snippet, _) = snippet_with_context(cx, ex_new.span, e.span.ctxt(), "..", &mut applicability); span_lint_and_then( cx, MATCH_LIKE_MATCHES_MACRO, @@ -187,11 +187,7 @@ pub(super) fn check_match<'tcx>( diag.span_suggestion_verbose( e.span, "use `matches!` directly", - format!( - "{}matches!({}, {pat_and_guard})", - if b0 { "" } else { "!" }, - snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), - ), + format!("{}matches!({snippet}, {pat_and_guard})", if b0 { "" } else { "!" },), applicability, ); }, diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index fa44a56af182..00bd1c2ca698 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -16,7 +16,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { let ty = cx.typeck_results().expr_ty(ex).peel_refs(); let adt_def = match ty.kind() { ty::Adt(adt_def, _) - if adt_def.is_enum() && !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) => + if adt_def.is_enum() && !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) => { adt_def }, diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 288f966991ac..e891b2ac6d64 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -26,12 +26,10 @@ pub(super) fn check<'tcx>( let arg_root = get_arg_root(cx, arg); if contains_call(cx, arg_root) && !contains_return(arg_root) { let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); - let closure_args = if receiver_type.is_diag_item(cx, sym::Option) { - "||" - } else if receiver_type.is_diag_item(cx, sym::Result) { - "|_|" - } else { - return; + let closure_args = match receiver_type.opt_diag_name(cx) { + Some(sym::Option) => "||", + Some(sym::Result) => "|_|", + _ => return, }; let span_replace_word = method_span.with_hi(expr.span.hi()); diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints/src/methods/iter_count.rs index ea2508cd7f38..8b303c0ca5b2 100644 --- a/clippy_lints/src/methods/iter_count.rs +++ b/clippy_lints/src/methods/iter_count.rs @@ -11,26 +11,17 @@ use super::ITER_COUNT; pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: Symbol) { let ty = cx.typeck_results().expr_ty(recv); - let caller_type = if derefs_to_slice(cx, recv, ty).is_some() { - "slice" - } else if ty.is_diag_item(cx, sym::Vec) { - "Vec" - } else if ty.is_diag_item(cx, sym::VecDeque) { - "VecDeque" - } else if ty.is_diag_item(cx, sym::HashSet) { - "HashSet" - } else if ty.is_diag_item(cx, sym::HashMap) { - "HashMap" - } else if ty.is_diag_item(cx, sym::BTreeMap) { - "BTreeMap" - } else if ty.is_diag_item(cx, sym::BTreeSet) { - "BTreeSet" - } else if ty.is_diag_item(cx, sym::LinkedList) { - "LinkedList" - } else if ty.is_diag_item(cx, sym::BinaryHeap) { - "BinaryHeap" - } else { - return; + let caller_type = match ty.opt_diag_name(cx) { + _ if derefs_to_slice(cx, recv, ty).is_some() => "slice", + Some(sym::Vec) => "Vec", + Some(sym::VecDeque) => "VecDeque", + Some(sym::HashSet) => "HashSet", + Some(sym::HashMap) => "HashMap", + Some(sym::BTreeMap) => "BTreeMap", + Some(sym::BTreeSet) => "BTreeSet", + Some(sym::LinkedList) => "LinkedList", + Some(sym::BinaryHeap) => "BinaryHeap", + _ => return, }; let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index 2d6bc36dc535..16db8663941e 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -38,7 +38,7 @@ pub(super) fn check<'tcx>( _ => return, } && let ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs() - && (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap)) + && matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) { let mut applicability = rustc_errors::Applicability::MachineApplicable; let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability); diff --git a/clippy_lints/src/methods/join_absolute_paths.rs b/clippy_lints/src/methods/join_absolute_paths.rs index e84b7452c758..905a58afa795 100644 --- a/clippy_lints/src/methods/join_absolute_paths.rs +++ b/clippy_lints/src/methods/join_absolute_paths.rs @@ -13,7 +13,7 @@ use super::JOIN_ABSOLUTE_PATHS; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if (ty.is_diag_item(cx, sym::Path) || ty.is_diag_item(cx, sym::PathBuf)) + if matches!(ty.opt_diag_name(cx), Some(sym::Path | sym::PathBuf)) && let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind && let LitKind::Str(symbol, _) = spanned.node && let sym_str = symbol.as_str() diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index a1aac96ccf86..8a1cc664ac60 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -25,7 +25,7 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_parent_id: De } // We check if it's an `Option` or a `Result`. if let Some(ty) = method_parent_id.opt_impl_ty(cx) { - if !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result) { + if !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) { return false; } } else { diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index 62bdc4a3e411..8eb26fb50747 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet; @@ -51,11 +51,8 @@ pub(super) fn check<'tcx>( // get snippets for args to map() and unwrap_or_else() let map_snippet = snippet(cx, map_arg.span, ".."); let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); - // lint, with note if neither arg is > 1 line and both map() and - // unwrap_or_else() have the same span - let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; - let same_span = map_arg.span.eq_ctxt(unwrap_arg.span); - if same_span && !multiline { + // lint, with note if both map() and unwrap_or_else() have the same span + if map_arg.span.eq_ctxt(unwrap_arg.span) { let var_snippet = snippet(cx, recv.span, ".."); span_lint_and_sugg( cx, @@ -67,9 +64,6 @@ pub(super) fn check<'tcx>( Applicability::MachineApplicable, ); return true; - } else if same_span && multiline { - span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); - return true; } } diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 055fdcabdd21..0e2012319147 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -81,7 +81,9 @@ pub(super) fn check<'tcx>( }, _ => return, }; - } else if let ExprKind::Index(_, index, _) = parent.kind { + } else if let ExprKind::Index(_, index, _) = parent.kind + && cx.typeck_results().expr_ty(index).is_usize() + { app = Applicability::MaybeIncorrect; let snip = snippet_with_applicability(cx, index.span, "_", &mut app); sugg = format!("nth({snip}).unwrap()"); diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints/src/methods/obfuscated_if_else.rs index b2466bbd982d..69d851e81600 100644 --- a/clippy_lints/src/methods/obfuscated_if_else.rs +++ b/clippy_lints/src/methods/obfuscated_if_else.rs @@ -1,7 +1,7 @@ use super::OBFUSCATED_IF_ELSE; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_eager_eval; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, sym}; use rustc_errors::Applicability; @@ -33,20 +33,22 @@ pub(super) fn check<'tcx>( let if_then = match then_method_name { sym::then if let ExprKind::Closure(closure) = then_arg.kind => { let body = cx.tcx.hir_body(closure.body); - snippet_with_applicability(cx, body.value.span, "..", &mut applicability) + snippet_with_context(cx, body.value.span, expr.span.ctxt(), "..", &mut applicability).0 }, - sym::then_some => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability), + sym::then_some => snippet_with_context(cx, then_arg.span, expr.span.ctxt(), "..", &mut applicability).0, _ => return, }; let els = match unwrap { - Unwrap::Or(arg) => snippet_with_applicability(cx, arg.span, "..", &mut applicability), + Unwrap::Or(arg) => snippet_with_context(cx, arg.span, expr.span.ctxt(), "..", &mut applicability).0, Unwrap::OrElse(arg) => match arg.kind { ExprKind::Closure(closure) => { let body = cx.tcx.hir_body(closure.body); - snippet_with_applicability(cx, body.value.span, "..", &mut applicability) + snippet_with_context(cx, body.value.span, expr.span.ctxt(), "..", &mut applicability).0 + }, + ExprKind::Path(_) => { + snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut applicability).0 + "()" }, - ExprKind::Path(_) => snippet_with_applicability(cx, arg.span, "_", &mut applicability) + "()", _ => return, }, Unwrap::OrDefault => "Default::default()".into(), @@ -54,7 +56,7 @@ pub(super) fn check<'tcx>( let sugg = format!( "if {} {{ {} }} else {{ {} }}", - Sugg::hir_with_applicability(cx, then_recv, "..", &mut applicability), + Sugg::hir_with_context(cx, then_recv, expr.span.ctxt(), "..", &mut applicability), if_then, els ); diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 07199b84f39e..448ab621a7ce 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -20,24 +20,28 @@ pub(super) fn check<'tcx>( let title; let or_arg_content: Span; - if ty.is_diag_item(cx, sym::Option) { - title = "found `.or(Some(…)).unwrap()`"; - if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) { - or_arg_content = content; - } else { + match ty.opt_diag_name(cx) { + Some(sym::Option) => { + title = "found `.or(Some(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) { + or_arg_content = content; + } else { + return; + } + }, + Some(sym::Result) => { + title = "found `.or(Ok(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) { + or_arg_content = content; + } else { + return; + } + }, + _ => { + // Someone has implemented a struct with .or(...).unwrap() chaining, + // but it's not an Option or a Result, so bail return; - } - } else if ty.is_diag_item(cx, sym::Result) { - title = "found `.or(Ok(…)).unwrap()`"; - if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) { - or_arg_content = content; - } else { - return; - } - } else { - // Someone has implemented a struct with .or(...).unwrap() chaining, - // but it's not an Option or a Result, so bail - return; + }, } let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index bd471e0b18e3..9dae6fbb48dd 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,44 +1,52 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{peel_blocks, strip_pat_refs}; +use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::PatKind; +use rustc_hir::def::{DefKind, Res}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::{Span, sym}; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{Span, Symbol, sym}; use super::UNNECESSARY_FOLD; /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be /// inferred. This checks for some common cases where it can be safely omitted -fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - let parent = cx.tcx.parent_hir_node(expr.hir_id); - - // some common cases where turbofish isn't needed: - // - assigned to a local variable with a type annotation - if let hir::Node::LetStmt(local) = parent - && local.ty.is_some() +fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool { + let use_cx = expr_use_ctxt(cx, expr); + if use_cx.same_ctxt + && let use_node = use_cx.use_node(cx) + && let Some(ty) = use_node.defined_ty(cx) { - return false; - } + // some common cases where turbofish isn't needed: + match (use_node, ty) { + // - assigned to a local variable with a type annotation + (ExprUseNode::LetStmt(_), _) => return false, - // - part of a function call argument, can be inferred from the function signature (provided that - // the parameter is not a generic type parameter) - if let hir::Node::Expr(parent_expr) = parent - && let hir::ExprKind::Call(recv, args) = parent_expr.kind - && let hir::ExprKind::Path(ref qpath) = recv.kind - && let Some(fn_def_id) = cx.qpath_res(qpath, recv.hir_id).opt_def_id() - && let fn_sig = cx.tcx.fn_sig(fn_def_id).skip_binder().skip_binder() - && let Some(arg_pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id) - && let Some(ty) = fn_sig.inputs().get(arg_pos) - && !matches!(ty.kind(), ty::Param(_)) - { - return false; + // - part of a function call argument, can be inferred from the function signature (provided that the + // parameter is not a generic type parameter) + (ExprUseNode::FnArg(..), DefinedTy::Mir { ty: arg_ty, .. }) + if !matches!(arg_ty.skip_binder().kind(), ty::Param(_)) => + { + return false; + }, + + // - the final expression in the body of a function with a simple return type + (ExprUseNode::Return(_), DefinedTy::Mir { ty: fn_return_ty, .. }) + if !fn_return_ty + .skip_binder() + .walk() + .any(|generic| generic.as_type().is_some_and(Ty::is_impl_trait)) => + { + return false; + }, + _ => {}, + } } // if it's neither of those, stay on the safe side and suggest turbofish, @@ -60,7 +68,7 @@ fn check_fold_with_op( fold_span: Span, op: hir::BinOpKind, replacement: Replacement, -) { +) -> bool { if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind // Extract the body of the closure passed to fold && let closure_body = cx.tcx.hir_body(body) @@ -93,7 +101,7 @@ fn check_fold_with_op( r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), ) } else { - format!("{method}{turbofish}()", method = replacement.method_name,) + format!("{method}{turbofish}()", method = replacement.method_name) }; span_lint_and_sugg( @@ -105,12 +113,47 @@ fn check_fold_with_op( sugg, applicability, ); + return true; + } + false +} + +fn check_fold_with_method( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + acc: &hir::Expr<'_>, + fold_span: Span, + method: Symbol, + replacement: Replacement, +) { + // Extract the name of the function passed to `fold` + if let Res::Def(DefKind::AssocFn, fn_did) = acc.res_if_named(cx, method) + // Check if the function belongs to the operator + && cx.tcx.is_diagnostic_item(method, fn_did) + { + let applicability = Applicability::MachineApplicable; + + let turbofish = if replacement.has_generic_return { + format!("::<{}>", cx.typeck_results().expr_ty(expr)) + } else { + String::new() + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_FOLD, + fold_span.with_hi(expr.span.hi()), + "this `.fold` can be written more succinctly using another method", + "try", + format!("{method}{turbofish}()", method = replacement.method_name), + applicability, + ); } } -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'tcx>, init: &hir::Expr<'_>, acc: &hir::Expr<'_>, fold_span: Span, @@ -124,60 +167,40 @@ pub(super) fn check( if let hir::ExprKind::Lit(lit) = init.kind { match lit.node { ast::LitKind::Bool(false) => { - check_fold_with_op( - cx, - expr, - acc, - fold_span, - hir::BinOpKind::Or, - Replacement { - method_name: "any", - has_args: true, - has_generic_return: false, - }, - ); + let replacement = Replacement { + method_name: "any", + has_args: true, + has_generic_return: false, + }; + check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, replacement); }, ast::LitKind::Bool(true) => { - check_fold_with_op( - cx, - expr, - acc, - fold_span, - hir::BinOpKind::And, - Replacement { - method_name: "all", - has_args: true, - has_generic_return: false, - }, - ); + let replacement = Replacement { + method_name: "all", + has_args: true, + has_generic_return: false, + }; + check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, replacement); }, ast::LitKind::Int(Pu128(0), _) => { - check_fold_with_op( - cx, - expr, - acc, - fold_span, - hir::BinOpKind::Add, - Replacement { - method_name: "sum", - has_args: false, - has_generic_return: needs_turbofish(cx, expr), - }, - ); + let replacement = Replacement { + method_name: "sum", + has_args: false, + has_generic_return: needs_turbofish(cx, expr), + }; + if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, replacement) { + check_fold_with_method(cx, expr, acc, fold_span, sym::add, replacement); + } }, ast::LitKind::Int(Pu128(1), _) => { - check_fold_with_op( - cx, - expr, - acc, - fold_span, - hir::BinOpKind::Mul, - Replacement { - method_name: "product", - has_args: false, - has_generic_return: needs_turbofish(cx, expr), - }, - ); + let replacement = Replacement { + method_name: "product", + has_args: false, + has_generic_return: needs_turbofish(cx, expr), + }; + if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, replacement) { + check_fold_with_method(cx, expr, acc, fold_span, sym::mul, replacement); + } }, _ => (), } diff --git a/clippy_lints/src/methods/unnecessary_get_then_check.rs b/clippy_lints/src/methods/unnecessary_get_then_check.rs index 10ea0c0c3e23..3207c4207fc0 100644 --- a/clippy_lints/src/methods/unnecessary_get_then_check.rs +++ b/clippy_lints/src/methods/unnecessary_get_then_check.rs @@ -11,11 +11,11 @@ use rustc_span::{Span, sym}; use super::UNNECESSARY_GET_THEN_CHECK; fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - ty.is_diag_item(cx, sym::HashSet) || ty.is_diag_item(cx, sym::BTreeSet) + matches!(ty.opt_diag_name(cx), Some(sym::HashSet | sym::BTreeSet)) } fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap) + matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) } pub(super) fn check( diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs index 73a407be4f21..30db2a75df57 100644 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -46,19 +46,19 @@ pub(super) fn check( ) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let (kind, none_value, none_prefix) = if ty.is_diag_item(cx, sym::Option) && !is_err { - ("an `Option`", "None", "") - } else if ty.is_diag_item(cx, sym::Result) - && let ty::Adt(_, substs) = ty.kind() - && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() - { - if is_never_like(t_or_e_ty) { - return; - } + let (kind, none_value, none_prefix) = match ty.opt_diag_name(cx) { + Some(sym::Option) if !is_err => ("an `Option`", "None", ""), + Some(sym::Result) + if let ty::Adt(_, substs) = ty.kind() + && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() => + { + if is_never_like(t_or_e_ty) { + return; + } - ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") - } else { - return; + ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") + }, + _ => return, }; let method_suffix = if is_err { "_err" } else { "" }; diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index 15b773c2c64f..a26f24d15247 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -112,10 +112,14 @@ fn should_lint<'tcx>( if let ExprKind::MethodCall(path, recv, ..) = &expr.kind { let recv_ty = typeck_results.expr_ty(recv).peel_refs(); - if path.ident.name == sym::debug_struct && recv_ty.is_diag_item(cx, sym::Formatter) { - has_debug_struct = true; - } else if path.ident.name == sym::finish_non_exhaustive && recv_ty.is_diag_item(cx, sym::DebugStruct) { - has_finish_non_exhaustive = true; + match (path.ident.name, recv_ty.opt_diag_name(cx)) { + (sym::debug_struct, Some(sym::Formatter)) => { + has_debug_struct = true; + }, + (sym::finish_non_exhaustive, Some(sym::DebugStruct)) => { + has_finish_non_exhaustive = true; + }, + _ => {}, } } ControlFlow::::Continue(()) diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 80cf081992cc..42dc9f2f1fa8 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -3,13 +3,14 @@ use clippy_utils::diagnostics::span_lint_and_then; use hir::def::{DefKind, Res}; use hir::{BlockCheckMode, ExprKind, QPath, UnOp}; use rustc_ast::{BorrowKind, Mutability}; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::intravisit::{Visitor, walk_body, walk_expr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, TyCtxt, TypeckResults}; use rustc_session::declare_lint_pass; -use rustc_span::{DesugaringKind, Span}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -56,12 +57,16 @@ declare_clippy_lint! { /// } /// ``` /// - /// ### Note + /// ### Notes /// - /// Taking a raw pointer to a union field is always safe and will - /// not be considered unsafe by this lint, even when linting code written - /// with a specified Rust version of 1.91 or earlier (which required - /// using an `unsafe` block). + /// - Unsafe operations only count towards the total for the innermost + /// enclosing `unsafe` block. + /// - Each call to a macro expanding to unsafe operations count for one + /// unsafe operation. + /// - Taking a raw pointer to a union field is always safe and will + /// not be considered unsafe by this lint, even when linting code written + /// with a specified Rust version of 1.91 or earlier (which required + /// using an `unsafe` block). #[clippy::version = "1.69.0"] pub MULTIPLE_UNSAFE_OPS_PER_BLOCK, restriction, @@ -71,10 +76,7 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]) impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { - if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) - || block.span.in_external_macro(cx.tcx.sess.source_map()) - || block.span.is_desugaring(DesugaringKind::Await) - { + if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || block.span.from_expansion() { return; } let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block); @@ -100,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { struct UnsafeExprCollector<'tcx> { tcx: TyCtxt<'tcx>, typeck_results: &'tcx TypeckResults<'tcx>, - unsafe_ops: Vec<(&'static str, Span)>, + unsafe_ops: FxHashMap, } impl<'tcx> UnsafeExprCollector<'tcx> { @@ -108,10 +110,33 @@ impl<'tcx> UnsafeExprCollector<'tcx> { let mut collector = Self { tcx: cx.tcx, typeck_results: cx.typeck_results(), - unsafe_ops: vec![], + unsafe_ops: FxHashMap::default(), }; collector.visit_block(block); - collector.unsafe_ops + #[allow( + rustc::potential_query_instability, + reason = "span ordering only needed inside the one expression being walked" + )] + let mut unsafe_ops = collector + .unsafe_ops + .into_iter() + .map(|(span, msg)| (msg, span)) + .collect::>(); + unsafe_ops.sort_unstable(); + unsafe_ops + } +} + +impl UnsafeExprCollector<'_> { + fn insert_span(&mut self, span: Span, message: &'static str) { + if span.from_expansion() { + self.unsafe_ops.insert( + span.source_callsite(), + "this macro call expands into one or more unsafe operations", + ); + } else { + self.unsafe_ops.insert(span, message); + } } } @@ -126,7 +151,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { return self.visit_expr(e); }, - ExprKind::InlineAsm(_) => self.unsafe_ops.push(("inline assembly used here", expr.span)), + // Do not recurse inside an inner `unsafe` block, it will be checked on its own + ExprKind::Block(block, _) if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) => return, + + ExprKind::InlineAsm(_) => self.insert_span(expr.span, "inline assembly used here"), ExprKind::AddrOf(BorrowKind::Raw, _, mut inner) => { while let ExprKind::Field(prefix, _) = inner.kind @@ -139,7 +167,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { ExprKind::Field(e, _) => { if self.typeck_results.expr_ty(e).is_union() { - self.unsafe_ops.push(("union field access occurs here", expr.span)); + self.insert_span(expr.span, "union field access occurs here"); } }, @@ -157,12 +185,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { .. }, )) => { - self.unsafe_ops - .push(("access of a mutable static occurs here", expr.span)); + self.insert_span(expr.span, "access of a mutable static occurs here"); }, ExprKind::Unary(UnOp::Deref, e) if self.typeck_results.expr_ty(e).is_raw_ptr() => { - self.unsafe_ops.push(("raw pointer dereference occurs here", expr.span)); + self.insert_span(expr.span, "raw pointer dereference occurs here"); }, ExprKind::Call(path_expr, _) => { @@ -172,7 +199,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { _ => None, }; if opt_sig.is_some_and(|sig| sig.safety().is_unsafe()) { - self.unsafe_ops.push(("unsafe function call occurs here", expr.span)); + self.insert_span(expr.span, "unsafe function call occurs here"); } }, @@ -182,7 +209,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { .type_dependent_def_id(expr.hir_id) .map(|def_id| self.tcx.fn_sig(def_id)); if opt_sig.is_some_and(|sig| sig.skip_binder().safety().is_unsafe()) { - self.unsafe_ops.push(("unsafe method call occurs here", expr.span)); + self.insert_span(expr.span, "unsafe method call occurs here"); } }, @@ -203,8 +230,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> { } )) ) { - self.unsafe_ops - .push(("modification of a mutable static occurs here", expr.span)); + self.insert_span(expr.span, "modification of a mutable static occurs here"); return self.visit_expr(rhs); } }, diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 3d2285efbe18..f3e42b1c58f8 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -1,7 +1,7 @@ use super::needless_pass_by_value::requires_exact_signature; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::snippet; +use clippy_utils::source::HasSession as _; use clippy_utils::visitors::for_each_expr; use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self}; use core::ops::ControlFlow; @@ -18,9 +18,9 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{self, Ty, TyCtxt, UpvarId, UpvarPath}; use rustc_session::impl_lint_pass; -use rustc_span::Span; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -269,18 +269,27 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { // If the argument is never used mutably, we emit the warning. let sp = input.span; if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind { + let Some(after_mut_span) = cx.sess().source_map().span_extend_to_prev_str( + inner_ty.ty.span.shrink_to_lo(), + "mut", + true, + true, + ) else { + return; + }; + let mut_span = after_mut_span.with_lo(after_mut_span.lo() - BytePos(3)); let is_cfged = is_cfged.get_or_insert_with(|| inherits_cfg(cx.tcx, *fn_def_id)); span_lint_hir_and_then( cx, NEEDLESS_PASS_BY_REF_MUT, cx.tcx.local_def_id_to_hir_id(*fn_def_id), sp, - "this argument is a mutable reference, but not used mutably", + "this parameter is a mutable reference but is not used mutably", |diag| { diag.span_suggestion( - sp, - "consider changing to".to_string(), - format!("&{}", snippet(cx, cx.tcx.hir_span(inner_ty.ty.hir_id), "_"),), + mut_span, + "consider removing this `mut`", + "", Applicability::Unspecified, ); if cx.effective_visibilities.is_exported(*fn_def_id) { diff --git a/clippy_lints/src/ptr/cmp_null.rs b/clippy_lints/src/ptr/cmp_null.rs index 905b48e6d1d4..f2d1c855eddd 100644 --- a/clippy_lints/src/ptr/cmp_null.rs +++ b/clippy_lints/src/ptr/cmp_null.rs @@ -14,13 +14,14 @@ pub(super) fn check<'tcx>( l: &Expr<'_>, r: &Expr<'_>, ) -> bool { + let mut applicability = Applicability::MachineApplicable; let non_null_path_snippet = match ( is_lint_allowed(cx, CMP_NULL, expr.hir_id), is_null_path(cx, l), is_null_path(cx, r), ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), + (false, true, false) => Sugg::hir_with_context(cx, r, expr.span.ctxt(), "..", &mut applicability).maybe_paren(), + (false, false, true) => Sugg::hir_with_context(cx, l, expr.span.ctxt(), "..", &mut applicability).maybe_paren(), _ => return false, }; let invert = if op == BinOpKind::Eq { "" } else { "!" }; @@ -32,7 +33,7 @@ pub(super) fn check<'tcx>( "comparing with null is better expressed by the `.is_null()` method", "try", format!("{invert}{non_null_path_snippet}.is_null()",), - Applicability::MachineApplicable, + applicability, ); true } diff --git a/clippy_lints/src/ptr/mod.rs b/clippy_lints/src/ptr/mod.rs index 6b2647e7b0a2..c4f40a7ffcaa 100644 --- a/clippy_lints/src/ptr/mod.rs +++ b/clippy_lints/src/ptr/mod.rs @@ -45,7 +45,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// This lint checks for equality comparisons with `ptr::null` + /// This lint checks for equality comparisons with `ptr::null` or `ptr::null_mut` /// /// ### Why is this bad? /// It's easier and more readable to use the inherent @@ -56,7 +56,7 @@ declare_clippy_lint! { /// ```rust,ignore /// use std::ptr; /// - /// if x == ptr::null { + /// if x == ptr::null() { /// // .. /// } /// ``` diff --git a/clippy_lints/src/same_length_and_capacity.rs b/clippy_lints/src/same_length_and_capacity.rs new file mode 100644 index 000000000000..042dec35f7c9 --- /dev/null +++ b/clippy_lints/src/same_length_and_capacity.rs @@ -0,0 +1,105 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::res::MaybeDef; +use clippy_utils::{eq_expr_value, sym}; +use rustc_hir::{Expr, ExprKind, LangItem, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::symbol::sym as rustc_sym; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usages of `Vec::from_raw_parts` and `String::from_raw_parts` + /// where the same expression is used for the length and the capacity. + /// + /// ### Why is this bad? + /// + /// If the same expression is being passed for the length and + /// capacity, it is most likely a semantic error. In the case of a + /// Vec, for example, the only way to end up with one that has + /// the same length and capacity is by going through a boxed slice, + /// e.g. `Box::from(some_vec)`, which shrinks the capacity to match + /// the length. + /// + /// ### Example + /// + /// ```no_run + /// #![feature(vec_into_raw_parts)] + /// let mut original: Vec:: = Vec::with_capacity(20); + /// original.extend([1, 2, 3, 4, 5]); + /// + /// let (ptr, mut len, cap) = original.into_raw_parts(); + /// + /// // I will add three more integers: + /// unsafe { + /// let ptr = ptr as *mut i32; + /// + /// for i in 6..9 { + /// *ptr.add(i - 1) = i as i32; + /// len += 1; + /// } + /// } + /// + /// // But I forgot the capacity was separate from the length: + /// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, len) }; + /// ``` + /// + /// Use instead: + /// ```no_run + /// #![feature(vec_into_raw_parts)] + /// let mut original: Vec:: = Vec::with_capacity(20); + /// original.extend([1, 2, 3, 4, 5]); + /// + /// let (ptr, mut len, cap) = original.into_raw_parts(); + /// + /// // I will add three more integers: + /// unsafe { + /// let ptr = ptr as *mut i32; + /// + /// for i in 6..9 { + /// *ptr.add(i - 1) = i as i32; + /// len += 1; + /// } + /// } + /// + /// // This time, leverage the previously saved capacity: + /// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, cap) }; + /// ``` + #[clippy::version = "1.93.0"] + pub SAME_LENGTH_AND_CAPACITY, + pedantic, + "`from_raw_parts` with same length and capacity" +} +declare_lint_pass!(SameLengthAndCapacity => [SAME_LENGTH_AND_CAPACITY]); + +impl<'tcx> LateLintPass<'tcx> for SameLengthAndCapacity { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Call(path_expr, args) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind + && fn_path.ident.name == sym::from_raw_parts + && args.len() >= 3 + && eq_expr_value(cx, &args[1], &args[2]) + { + let middle_ty = cx.typeck_results().node_type(ty.hir_id); + if middle_ty.is_diag_item(cx, rustc_sym::Vec) { + span_lint_and_help( + cx, + SAME_LENGTH_AND_CAPACITY, + expr.span, + "usage of `Vec::from_raw_parts` with the same expression for length and capacity", + None, + "try `Box::from(slice::from_raw_parts(...)).into::>()`", + ); + } else if middle_ty.is_lang_item(cx, LangItem::String) { + span_lint_and_help( + cx, + SAME_LENGTH_AND_CAPACITY, + expr.span, + "usage of `String::from_raw_parts` with the same expression for length and capacity", + None, + "try `String::from(str::from_utf8_unchecked(slice::from_raw_parts(...)))`", + ); + } + } + } +} diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index 688da33a1777..7482bac4c7b4 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -112,6 +112,16 @@ fn try_parse_op_call<'tcx>( None } +fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool { + // Guard on type to avoid useless potentially expansive `SpanlessEq` checks + cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr() + && matches!( + cx.typeck_results().expr_ty(expr).peel_refs().opt_diag_name(cx), + Some(sym::HashSet | sym::BTreeSet) + ) + && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, expr.peel_borrows()) +} + fn find_insert_calls<'tcx>( cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, @@ -122,9 +132,14 @@ fn find_insert_calls<'tcx>( && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver) && SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value) { - ControlFlow::Break(insert_expr) - } else { - ControlFlow::Continue(()) + return ControlFlow::Break(Some(insert_expr)); } + + if is_set_mutated(cx, contains_expr, e) { + return ControlFlow::Break(None); + } + + ControlFlow::Continue(()) }) + .flatten() } diff --git a/clippy_lints/src/time_subtraction.rs b/clippy_lints/src/time_subtraction.rs index e0fdca97dbee..3ba59aefea06 100644 --- a/clippy_lints/src/time_subtraction.rs +++ b/clippy_lints/src/time_subtraction.rs @@ -8,7 +8,6 @@ use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; -use rustc_span::source_map::Spanned; use rustc_span::sym; declare_clippy_lint! { @@ -84,43 +83,38 @@ impl_lint_pass!(UncheckedTimeSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_T impl LateLintPass<'_> for UncheckedTimeSubtraction { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Sub, .. + let (lhs, rhs) = match expr.kind { + ExprKind::Binary(op, lhs, rhs) if matches!(op.node, BinOpKind::Sub,) => (lhs, rhs), + ExprKind::MethodCall(fn_name, lhs, [rhs], _) if cx.ty_based_def(expr).is_diag_item(cx, sym::sub) => { + (lhs, rhs) }, - lhs, - rhs, - ) = expr.kind - { - let typeck = cx.typeck_results(); - let lhs_ty = typeck.expr_ty(lhs); - let rhs_ty = typeck.expr_ty(rhs); + _ => return, + }; + let typeck = cx.typeck_results(); + let lhs_name = typeck.expr_ty(lhs).opt_diag_name(cx); + let rhs_name = typeck.expr_ty(rhs).opt_diag_name(cx); - if lhs_ty.is_diag_item(cx, sym::Instant) { - // Instant::now() - instant - if is_instant_now_call(cx, lhs) - && rhs_ty.is_diag_item(cx, sym::Instant) - && let Some(sugg) = Sugg::hir_opt(cx, rhs) - { - print_manual_instant_elapsed_sugg(cx, expr, sugg); - } - // instant - duration - else if rhs_ty.is_diag_item(cx, sym::Duration) - && !expr.span.from_expansion() - && self.msrv.meets(cx, msrvs::TRY_FROM) - { - print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); - } + if lhs_name == Some(sym::Instant) { + // Instant::now() - instant + if is_instant_now_call(cx, lhs) && rhs_name == Some(sym::Instant) { + print_manual_instant_elapsed_sugg(cx, expr, rhs); } - // duration - duration - else if lhs_ty.is_diag_item(cx, sym::Duration) - && rhs_ty.is_diag_item(cx, sym::Duration) + // instant - duration + else if rhs_name == Some(sym::Duration) && !expr.span.from_expansion() && self.msrv.meets(cx, msrvs::TRY_FROM) { print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); } } + // duration - duration + else if lhs_name == Some(sym::Duration) + && rhs_name == Some(sym::Duration) + && !expr.span.from_expansion() + && self.msrv.meets(cx, msrvs::TRY_FROM) + { + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + } } } @@ -150,10 +144,12 @@ fn is_chained_time_subtraction(cx: &LateContext<'_>, lhs: &Expr<'_>) -> bool { /// Returns true if the type is Duration or Instant fn is_time_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - ty.is_diag_item(cx, sym::Duration) || ty.is_diag_item(cx, sym::Instant) + matches!(ty.opt_diag_name(cx), Some(sym::Duration | sym::Instant)) } -fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { +fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, rhs: &Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, rhs, expr.span.ctxt(), "", &mut applicability); span_lint_and_sugg( cx, MANUAL_INSTANT_ELAPSED, @@ -161,7 +157,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg "manual implementation of `Instant::elapsed`", "try", format!("{}.elapsed()", sugg.maybe_paren()), - Applicability::MachineApplicable, + applicability, ); } @@ -181,8 +177,9 @@ fn print_unchecked_duration_subtraction_sugg( // avoid suggestions if !is_chained_time_subtraction(cx, left_expr) { let mut applicability = Applicability::MachineApplicable; - let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); - let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); + let left_sugg = Sugg::hir_with_context(cx, left_expr, expr.span.ctxt(), "", &mut applicability); + let right_sugg = + Sugg::hir_with_context(cx, right_expr, expr.span.ctxt(), "", &mut applicability); diag.span_suggestion( expr.span, diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs index 1a6262f2ff76..31e770f421e1 100644 --- a/clippy_lints/src/transmute/transmuting_null.rs +++ b/clippy_lints/src/transmute/transmuting_null.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_integer_literal; +use clippy_utils::is_integer_const; use clippy_utils::res::{MaybeDef, MaybeResPath}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; @@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t // Catching: // `std::mem::transmute(0 as *const i32)` if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind - && is_integer_literal(inner_expr, 0) + && is_integer_const(cx, inner_expr, 0) { span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); return true; @@ -42,10 +42,5 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t return true; } - // FIXME: - // Also catch transmutations of variables which are known nulls. - // To do this, MIR const propagation seems to be the better tool. - // Whenever MIR const prop routines are more developed, this will - // become available. As of this writing (25/03/19) it is not yet. false } diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index eba60501ae21..38ce9dc3f916 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -10,13 +10,14 @@ use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; use rustc_hir::{ self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParamKind, HirId, Impl, - ImplItemImplKind, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, + ImplItemImplKind, ImplItemKind, Item, ItemKind, Node, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty as MiddleTy; use rustc_session::impl_lint_pass; use rustc_span::Span; use std::iter; +use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does @@ -213,6 +214,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { path.res, Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::Def(DefKind::TyParam, _) ) + && !ty_is_in_generic_args(cx, hir_ty) && !types_to_skip.contains(&hir_ty.hir_id) && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) && let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity() @@ -312,6 +314,38 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) { } } +fn ty_is_in_generic_args<'tcx>(cx: &LateContext<'tcx>, hir_ty: &Ty<'tcx, AmbigArg>) -> bool { + cx.tcx.hir_parent_iter(hir_ty.hir_id).any(|(_, parent)| { + matches!(parent, Node::ImplItem(impl_item) if impl_item.generics.params.iter().any(|param| { + let GenericParamKind::Const { ty: const_ty, .. } = ¶m.kind else { + return false; + }; + ty_contains_ty(const_ty, hir_ty) + })) + }) +} + +fn ty_contains_ty<'tcx>(outer: &Ty<'tcx>, inner: &Ty<'tcx, AmbigArg>) -> bool { + struct ContainsVisitor<'tcx> { + inner: &'tcx Ty<'tcx, AmbigArg>, + } + + impl<'tcx> Visitor<'tcx> for ContainsVisitor<'tcx> { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, t: &'tcx Ty<'tcx, AmbigArg>) -> Self::Result { + if t.hir_id == self.inner.hir_id { + return ControlFlow::Break(()); + } + + walk_ty(self, t) + } + } + + let mut visitor = ContainsVisitor { inner }; + visitor.visit_ty_unambig(outer).is_break() +} + /// Checks whether types `a` and `b` have the same lifetime parameters. /// /// This function does not check that types `a` and `b` are the same types. diff --git a/clippy_lints/src/write/empty_string.rs b/clippy_lints/src/write/empty_string.rs index e7eb99eb34ec..1291f2489a21 100644 --- a/clippy_lints/src/write/empty_string.rs +++ b/clippy_lints/src/write/empty_string.rs @@ -1,37 +1,43 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::MacroCall; use clippy_utils::source::expand_past_previous_comma; -use clippy_utils::sym; +use clippy_utils::{span_extract_comments, sym}; use rustc_ast::{FormatArgs, FormatArgsPiece}; use rustc_errors::Applicability; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING}; pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { - let mut span = format_args.span; - - let lint = if name == "writeln" { - span = expand_past_previous_comma(cx, span); - - WRITELN_EMPTY_STRING - } else { - PRINTLN_EMPTY_STRING - }; + let is_writeln = name == "writeln"; span_lint_and_then( cx, - lint, + if is_writeln { + WRITELN_EMPTY_STRING + } else { + PRINTLN_EMPTY_STRING + }, macro_call.span, format!("empty string literal in `{name}!`"), |diag| { - diag.span_suggestion( - span, - "remove the empty string", - String::new(), - Applicability::MachineApplicable, - ); + if span_extract_comments(cx.sess().source_map(), macro_call.span).is_empty() { + let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before( + macro_call.span.shrink_to_hi(), + ')', + false, + ); + let mut span = format_args.span.with_hi(closing_paren.lo()); + if is_writeln { + span = expand_past_previous_comma(cx, span); + } + + diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable); + } else { + // If there is a comment in the span of macro call, we don't provide an auto-fix suggestion. + diag.span_note(format_args.span, "remove the empty string"); + } }, ); } diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index bf133d26ed9d..94c2fb20d5f5 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -49,7 +49,7 @@ impl LateLintPass<'_> for ZeroSizedMapValues { && !in_trait_impl(cx, hir_ty.hir_id) // We don't care about infer vars && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) - && (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap)) + && matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) && let ty::Adt(_, args) = ty.kind() && let ty = args.type_at(1) // Ensure that no type information is missing, to avoid a delayed bug in the compiler if this is not the case. diff --git a/clippy_lints_internal/src/almost_standard_lint_formulation.rs b/clippy_lints_internal/src/almost_standard_lint_formulation.rs index 7eeec84720f7..b5a12606fb3e 100644 --- a/clippy_lints_internal/src/almost_standard_lint_formulation.rs +++ b/clippy_lints_internal/src/almost_standard_lint_formulation.rs @@ -1,9 +1,11 @@ use crate::lint_without_lint_pass::is_lint_ref_type; use clippy_utils::diagnostics::span_lint_and_help; use regex::Regex; +use rustc_ast::token::DocFragmentKind; use rustc_hir::{Attribute, Item, ItemKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{Span, Symbol}; declare_tool_lint! { /// ### What it does @@ -46,28 +48,22 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { let mut check_next = false; if let ItemKind::Static(Mutability::Not, _, ty, _) = item.kind { - let lines = cx - .tcx - .hir_attrs(item.hir_id()) - .iter() - .filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr))); + let lines = cx.tcx.hir_attrs(item.hir_id()).iter().filter_map(doc_attr); if is_lint_ref_type(cx, ty) { - for (line, attr) in lines { + for (line, span) in lines { let cur_line = line.as_str().trim(); if check_next && !cur_line.is_empty() { for formulation in &self.standard_formulations { let starts_with_correct_formulation = cur_line.starts_with(formulation.correction); if !starts_with_correct_formulation && formulation.wrong_pattern.is_match(cur_line) { - if let Some(ident) = attr.ident() { - span_lint_and_help( - cx, - ALMOST_STANDARD_LINT_FORMULATION, - ident.span, - "non-standard lint formulation", - None, - format!("consider using `{}`", formulation.correction), - ); - } + span_lint_and_help( + cx, + ALMOST_STANDARD_LINT_FORMULATION, + span, + "non-standard lint formulation", + None, + format!("consider using `{}`", formulation.correction), + ); return; } } @@ -84,3 +80,10 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation { } } } + +fn doc_attr(attr: &Attribute) -> Option<(Symbol, Span)> { + match Attribute::doc_str_and_fragment_kind(attr) { + Some((symbol, DocFragmentKind::Raw(span))) => Some((symbol, span)), + _ => None, + } +} diff --git a/clippy_lints_internal/src/internal_paths.rs b/clippy_lints_internal/src/internal_paths.rs index 95bdf27b019c..14d4139a0065 100644 --- a/clippy_lints_internal/src/internal_paths.rs +++ b/clippy_lints_internal/src/internal_paths.rs @@ -17,6 +17,7 @@ pub static TY_CTXT: PathLookup = type_path!(rustc_middle::ty::TyCtxt); // Paths in clippy itself pub static CLIPPY_SYM_MODULE: PathLookup = type_path!(clippy_utils::sym); +pub static MAYBE_DEF: PathLookup = type_path!(clippy_utils::res::MaybeDef); pub static MSRV_STACK: PathLookup = type_path!(clippy_utils::msrvs::MsrvStack); pub static PATH_LOOKUP_NEW: PathLookup = value_path!(clippy_utils::paths::PathLookup::new); pub static SPAN_LINT_AND_THEN: PathLookup = value_path!(clippy_utils::diagnostics::span_lint_and_then); diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index d686ba73387c..cca5608fa6be 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -38,6 +38,7 @@ mod lint_without_lint_pass; mod msrv_attr_impl; mod outer_expn_data_pass; mod produce_ice; +mod repeated_is_diagnostic_item; mod symbols; mod unnecessary_def_path; mod unsorted_clippy_utils_paths; @@ -77,4 +78,5 @@ pub fn register_lints(store: &mut LintStore) { store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl)); store.register_late_pass(|_| Box::new(almost_standard_lint_formulation::AlmostStandardFormulation::new())); store.register_late_pass(|_| Box::new(unusual_names::UnusualNames)); + store.register_late_pass(|_| Box::new(repeated_is_diagnostic_item::RepeatedIsDiagnosticItem)); } diff --git a/clippy_lints_internal/src/repeated_is_diagnostic_item.rs b/clippy_lints_internal/src/repeated_is_diagnostic_item.rs new file mode 100644 index 000000000000..55fb78b1e296 --- /dev/null +++ b/clippy_lints_internal/src/repeated_is_diagnostic_item.rs @@ -0,0 +1,561 @@ +use std::iter; +use std::ops::ControlFlow; + +use crate::internal_paths::MAYBE_DEF; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{snippet_indent, snippet_with_applicability}; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{eq_expr_value, if_sequence, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Node, StmtKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::print::with_forced_trimmed_paths; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_tool_lint! { + /// ### What it does + /// Checks for repeated use of `MaybeDef::is_diag_item`/`TyCtxt::is_diagnostic_item`; + /// suggests to first call `MaybDef::opt_diag_name`/`TyCtxt::get_diagnostic_name` and then + /// compare the output with all the `Symbol`s. + /// + /// ### Why is this bad? + /// Each of such calls ultimately invokes the `diagnostic_items` query. + /// While the query is cached, it's still better to avoid calling it multiple times if possible. + /// + /// ### Example + /// ```no_run + /// ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result) + /// cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did) + /// + /// if ty.is_diag_item(cx, sym::Option) { + /// .. + /// } else if ty.is_diag_item(cx, sym::Result) { + /// .. + /// } else { + /// .. + /// } + /// + /// if cx.tcx.is_diagnostic_item(sym::Option, did) { + /// .. + /// } else if cx.tcx.is_diagnostic_item(sym::Result, did) { + /// .. + /// } else { + /// .. + /// } + /// + /// { + /// if ty.is_diag_item(cx, sym::Option) { + /// .. + /// } + /// if ty.is_diag_item(cx, sym::Result) { + /// .. + /// } + /// } + /// + /// { + /// if cx.tcx.is_diagnostic_item(sym::Option, did) { + /// .. + /// } + /// if cx.tcx.is_diagnostic_item(sym::Result, did) { + /// .. + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) + /// matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result)) + /// + /// match ty.opt_diag_name(cx) { + /// Some(sym::Option) => { + /// .. + /// } + /// Some(sym::Result) => { + /// .. + /// } + /// _ => { + /// .. + /// } + /// } + /// + /// match cx.tcx.get_diagnostic_name(did) { + /// Some(sym::Option) => { + /// .. + /// } + /// Some(sym::Result) => { + /// .. + /// } + /// _ => { + /// .. + /// } + /// } + /// + /// { + /// let name = ty.opt_diag_name(cx); + /// if name == Some(sym::Option) { + /// .. + /// } + /// if name == Some(sym::Result) { + /// .. + /// } + /// } + /// + /// { + /// let name = cx.tcx.get_diagnostic_name(did); + /// if name == Some(sym::Option) { + /// .. + /// } + /// if name == Some(sym::Result) { + /// .. + /// } + /// } + /// ``` + pub clippy::REPEATED_IS_DIAGNOSTIC_ITEM, + Warn, + "repeated use of `MaybeDef::is_diag_item`/`TyCtxt::is_diagnostic_item`" +} +declare_lint_pass!(RepeatedIsDiagnosticItem => [REPEATED_IS_DIAGNOSTIC_ITEM]); + +const NOTE: &str = "each call performs the same compiler query -- it's faster to query once, and reuse the results"; + +impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem { + #[expect(clippy::too_many_lines)] + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + for [(cond1, stmt1_span), (cond2, stmt2_span)] in block + .stmts + .windows(2) + .filter_map(|pair| { + if let [if1, if2] = pair + && let StmtKind::Expr(e1) | StmtKind::Semi(e1) = if1.kind + && let ExprKind::If(cond1, ..) = e1.kind + && let StmtKind::Expr(e2) | StmtKind::Semi(e2) = if2.kind + && let ExprKind::If(cond2, ..) = e2.kind + { + Some([(cond1, if1.span), (cond2, if2.span)]) + } else { + None + } + }) + .chain( + if let Some(if1) = block.stmts.last() + && let StmtKind::Expr(e1) | StmtKind::Semi(e1) = if1.kind + && let ExprKind::If(cond1, ..) = e1.kind + && let Some(e2) = block.expr + && let ExprKind::If(cond2, ..) = e2.kind + { + Some([(cond1, if1.span), (cond2, e2.span)]) + } else { + None + }, + ) + { + let lint_span = stmt1_span.to(stmt2_span); + + // if recv1.is_diag_item(cx, sym1) && .. { + // .. + // } + // if recv2.is_diag_item(cx, sym2) && .. { + // .. + // } + if let Some(first @ (span1, (cx1, recv1, _))) = extract_nested_is_diag_item(cx, cond1) + && let Some(second @ (span2, (cx2, recv2, _))) = extract_nested_is_diag_item(cx, cond2) + && eq_expr_value(cx, cx1, cx2) + && eq_expr_value(cx, recv1, recv2) + { + let recv_ty = + with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); + let recv_ty = recv_ty.trim_end_matches("<'_>"); + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + lint_span, + format!("repeated calls to `{recv_ty}::is_diag_item`"), + |diag| { + diag.span_labels([span1, span2], "called here"); + diag.note(NOTE); + + let mut app = Applicability::HasPlaceholders; + let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app); + let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app); + let indent = snippet_indent(cx, stmt1_span).unwrap_or_default(); + let sugg: Vec<_> = iter::once(( + stmt1_span.shrink_to_lo(), + format!("let /* name */ = {recv}.opt_diag_name({cx_str});\n{indent}"), + )) // call `opt_diag_name` once + .chain([first, second].into_iter().map(|(expr_span, (_, _, sym))| { + let sym = snippet_with_applicability(cx, sym.span, "_", &mut app); + (expr_span, format!("/* name */ == Some({sym})")) + })) + .collect(); + + diag.multipart_suggestion_verbose( + format!("call `{recv_ty}::opt_diag_name`, and reuse the results"), + sugg, + app, + ); + }, + ); + return; + } + + // if cx.tcx.is_diagnostic_item(sym1, did) && .. { + // .. + // } + // if cx.tcx.is_diagnostic_item(sym2, did) && .. { + // .. + // } + if let Some(first @ (span1, (tcx1, did1, _))) = extract_nested_is_diagnostic_item(cx, cond1) + && let Some(second @ (span2, (tcx2, did2, _))) = extract_nested_is_diagnostic_item(cx, cond2) + && eq_expr_value(cx, tcx1, tcx2) + && eq_expr_value(cx, did1, did2) + { + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + lint_span, + "repeated calls to `TyCtxt::is_diagnostic_item`", + |diag| { + diag.span_labels([span1, span2], "called here"); + diag.note(NOTE); + + let mut app = Applicability::HasPlaceholders; + let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app); + let did = snippet_with_applicability(cx, did1.span, "_", &mut app); + let indent = snippet_indent(cx, stmt1_span).unwrap_or_default(); + let sugg: Vec<_> = iter::once(( + stmt1_span.shrink_to_lo(), + format!("let /* name */ = {tcx}.get_diagnostic_name({did});\n{indent}"), + )) // call `get_diagnostic_name` once + .chain([first, second].into_iter().map(|(expr_span, (_, _, sym))| { + let sym = snippet_with_applicability(cx, sym.span, "_", &mut app); + (expr_span, format!("/* name */ == Some({sym})")) + })) + .collect(); + + diag.multipart_suggestion_verbose( + "call `TyCtxt::get_diagnostic_name`, and reuse the results", + sugg, + app, + ); + }, + ); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, left, right) = expr.kind { + if op.node == BinOpKind::Or { + check_ors(cx, expr.span, left, right); + } else if op.node == BinOpKind::And + && let ExprKind::Unary(UnOp::Not, left) = left.kind + && let ExprKind::Unary(UnOp::Not, right) = right.kind + { + check_ands(cx, expr.span, left, right); + } + } else if let (conds, _) = if_sequence(expr) + && !conds.is_empty() + { + check_if_chains(cx, expr, conds); + } + } +} + +fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) { + // recv1.is_diag_item(cx, sym1) || recv2.is_diag_item(cx, sym2) + if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left) + && let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right) + && eq_expr_value(cx, cx1, cx2) + && eq_expr_value(cx, recv1, recv2) + { + let recv_ty = + with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); + let recv_ty = recv_ty.trim_end_matches("<'_>"); + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + span, + format!("repeated calls to `{recv_ty}::is_diag_item`"), + |diag| { + diag.note(NOTE); + + let mut app = Applicability::MachineApplicable; + let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app); + let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app); + let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app); + let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app); + diag.span_suggestion_verbose( + span, + format!("call `{recv_ty}::opt_diag_name`, and reuse the results"), + format!("matches!({recv}.opt_diag_name({cx_str}), Some({sym1} | {sym2}))"), + app, + ); + }, + ); + return; + } + + // cx.tcx.is_diagnostic_item(sym1, did) || cx.tcx.is_diagnostic_item(sym2, did) + if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left) + && let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right) + && eq_expr_value(cx, tcx1, tcx2) + && eq_expr_value(cx, did1, did2) + { + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + span, + "repeated calls to `TyCtxt::is_diagnostic_item`", + |diag| { + diag.note(NOTE); + + let mut app = Applicability::MachineApplicable; + let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app); + let did = snippet_with_applicability(cx, did1.span, "_", &mut app); + let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app); + let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app); + diag.span_suggestion_verbose( + span, + "call `TyCtxt::get_diagnostic_name`, and reuse the results", + format!("matches!({tcx}.get_diagnostic_name({did}), Some({sym1} | {sym2}))"), + app, + ); + }, + ); + } +} + +fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) { + // !recv1.is_diag_item(cx, sym1) && !recv2.is_diag_item(cx, sym2) + if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left) + && let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right) + && eq_expr_value(cx, cx1, cx2) + && eq_expr_value(cx, recv1, recv2) + { + let recv_ty = + with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); + let recv_ty = recv_ty.trim_end_matches("<'_>"); + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + span, + format!("repeated calls to `{recv_ty}::is_diag_item`"), + |diag| { + diag.note(NOTE); + + let mut app = Applicability::MachineApplicable; + let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app); + let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app); + let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app); + let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app); + diag.span_suggestion_verbose( + span, + format!("call `{recv_ty}::opt_diag_name`, and reuse the results"), + format!("!matches!({recv}.opt_diag_name({cx_str}), Some({sym1} | {sym2}))"), + app, + ); + }, + ); + return; + } + + // !cx.tcx.is_diagnostic_item(sym1, did) && !cx.tcx.is_diagnostic_item(sym2, did) + if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left) + && let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right) + && eq_expr_value(cx, tcx1, tcx2) + && eq_expr_value(cx, did1, did2) + { + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + span, + "repeated calls to `TyCtxt::is_diagnostic_item`", + |diag| { + diag.note(NOTE); + + let mut app = Applicability::MachineApplicable; + let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app); + let did = snippet_with_applicability(cx, did1.span, "_", &mut app); + let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app); + let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app); + diag.span_suggestion_verbose( + span, + "call `TyCtxt::get_diagnostic_name`, and reuse the results", + format!("!matches!({tcx}.get_diagnostic_name({did}), Some({sym1} | {sym2}))"), + app, + ); + }, + ); + } +} + +fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'tcx Expr<'_>>) { + // if ty.is_diag_item(cx, sym1) { + // .. + // } else if ty.is_diag_item(cx, sym2) { + // .. + // } else { + // .. + // } + let mut found = conds.iter().filter_map(|cond| extract_nested_is_diag_item(cx, cond)); + if let Some(first @ (_, (cx_1, recv1, _))) = found.next() + && let other = + found.filter(|(_, (cx_, recv, _))| eq_expr_value(cx, cx_, cx_1) && eq_expr_value(cx, recv, recv1)) + && let results = iter::once(first).chain(other).collect::>() + && results.len() > 1 + { + let recv_ty = + with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs())); + let recv_ty = recv_ty.trim_end_matches("<'_>"); + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + expr.span, + format!("repeated calls to `{recv_ty}::is_diag_item`"), + |diag| { + diag.span_labels(results.iter().map(|(span, _)| *span), "called here"); + diag.note(NOTE); + + let mut app = Applicability::HasPlaceholders; + let cx_str = snippet_with_applicability(cx, cx_1.span, "_", &mut app); + let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app); + let span_before = if let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(expr.hir_id) { + let_stmt.span + } else { + expr.span + }; + let indent = snippet_indent(cx, span_before).unwrap_or_default(); + let sugg: Vec<_> = iter::once(( + span_before.shrink_to_lo(), + format!("let /* name */ = {recv}.opt_diag_name({cx_str});\n{indent}"), + )) // call `opt_diag_name` once + .chain(results.into_iter().map(|(expr_span, (_, _, sym))| { + let sym = snippet_with_applicability(cx, sym.span, "_", &mut app); + (expr_span, format!("/* name */ == Some({sym})")) + })) + .collect(); + + diag.multipart_suggestion_verbose( + format!("call `{recv_ty}::opt_diag_name`, and reuse the results"), + sugg, + app, + ); + }, + ); + } + + // if cx.tcx.is_diagnostic_item(sym1, did) { + // .. + // } else if cx.tcx.is_diagnostic_item(sym2, did) { + // .. + // } else { + // .. + // } + let mut found = conds + .into_iter() + .filter_map(|cond| extract_nested_is_diagnostic_item(cx, cond)); + if let Some(first @ (_, (tcx1, did1, _))) = found.next() + && let other = found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, tcx, tcx1) && eq_expr_value(cx, did, did1)) + && let results = iter::once(first).chain(other).collect::>() + && results.len() > 1 + { + span_lint_and_then( + cx, + REPEATED_IS_DIAGNOSTIC_ITEM, + expr.span, + "repeated calls to `TyCtxt::is_diagnostic_item`", + |diag| { + diag.span_labels(results.iter().map(|(span, _)| *span), "called here"); + diag.note(NOTE); + + let mut app = Applicability::HasPlaceholders; + let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app); + let recv = snippet_with_applicability(cx, did1.span, "_", &mut app); + let span_before = if let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(expr.hir_id) { + let_stmt.span + } else { + expr.span + }; + let indent = snippet_indent(cx, span_before).unwrap_or_default(); + let sugg: Vec<_> = iter::once(( + span_before.shrink_to_lo(), + format!("let /* name */ = {tcx}.get_diagnostic_name({recv});\n{indent}"), + )) // call `get_diagnostic_name` once + .chain(results.into_iter().map(|(expr_span, (_, _, sym))| { + let sym = snippet_with_applicability(cx, sym.span, "_", &mut app); + (expr_span, format!("/* name */ == Some({sym})")) + })) + .collect(); + + diag.multipart_suggestion_verbose( + "call `TyCtxt::get_diagnostic_name`, and reuse the results", + sugg, + app, + ); + }, + ); + } +} + +fn extract_is_diag_item<'tcx>( + cx: &LateContext<'_>, + expr: &'tcx Expr<'tcx>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if let ExprKind::MethodCall(is_diag_item, recv, [cx_, sym], _) = expr.kind + && is_diag_item.ident.name == sym::is_diag_item + // Whether this a method from the `MaybeDef` trait + && let Some(did) = cx.ty_based_def(expr).opt_parent(cx).opt_def_id() + && MAYBE_DEF.matches(cx, did) + { + Some((cx_, recv, sym)) + } else { + None + } +} + +fn extract_is_diagnostic_item<'tcx>( + cx: &LateContext<'_>, + expr: &'tcx Expr<'tcx>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if let ExprKind::MethodCall(is_diag_item, tcx, [sym, did], _) = expr.kind + && is_diag_item.ident.name == sym::is_diagnostic_item + // Whether this is an inherent method on `TyCtxt` + && cx + .ty_based_def(expr) + .opt_parent(cx) + .opt_impl_ty(cx) + .is_diag_item(cx, sym::TyCtxt) + { + Some((tcx, did, sym)) + } else { + None + } +} + +fn extract_nested_is_diag_item<'tcx>( + cx: &LateContext<'tcx>, + cond: &'tcx Expr<'_>, +) -> Option<(Span, (&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>))> { + for_each_expr(cx, cond, |cond_part| { + if let Some(res) = extract_is_diag_item(cx, cond_part) { + ControlFlow::Break((cond_part.span, res)) + } else { + ControlFlow::Continue(()) + } + }) +} + +fn extract_nested_is_diagnostic_item<'tcx>( + cx: &LateContext<'tcx>, + cond: &'tcx Expr<'_>, +) -> Option<(Span, (&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>))> { + for_each_expr(cx, cond, |cond_part| { + if let Some(res) = extract_is_diagnostic_item(cx, cond_part) { + ControlFlow::Break((cond_part.span, res)) + } else { + ControlFlow::Continue(()) + } + }) +} diff --git a/clippy_utils/README.md b/clippy_utils/README.md index dc8695fef9f5..01257c1a3059 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-12-11 +nightly-2025-12-25 ``` diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 2fd773b06781..8820801853e0 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -86,7 +86,7 @@ pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { /// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { - attrs.iter().any(|attr| attr.is_doc_hidden()) + attrs.iter().any(AttributeExt::is_doc_hidden) } /// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index e43b0b95d9f7..9574e6fa40b0 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -809,10 +809,12 @@ impl<'tcx> ConstEvalCtxt<'tcx> { | sym::i128_legacy_const_max ) ) || self.tcx.opt_parent(did).is_some_and(|parent| { - parent.is_diag_item(&self.tcx, sym::f16_consts_mod) - || parent.is_diag_item(&self.tcx, sym::f32_consts_mod) - || parent.is_diag_item(&self.tcx, sym::f64_consts_mod) - || parent.is_diag_item(&self.tcx, sym::f128_consts_mod) + matches!( + parent.opt_diag_name(&self.tcx), + Some( + sym::f16_consts_mod | sym::f32_consts_mod | sym::f64_consts_mod | sym::f128_consts_mod + ) + ) })) => { did @@ -1139,7 +1141,9 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Struct(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, + ConstArgKind::Struct(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => { + None + }, }, } } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 5cadf5fbb869..055f6d03eff0 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -4,14 +4,17 @@ use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context}; use crate::tokenize_with_text; use rustc_ast::ast; use rustc_ast::ast::InlineAsmTemplatePiece; -use rustc_data_structures::fx::FxHasher; +use rustc_data_structures::fx::{FxHasher, FxIndexMap}; use rustc_hir::MatchSource::TryDesugar; use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; use rustc_hir::{ - AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, ByRef, Closure, ConstArg, ConstArgKind, Expr, - ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, - LifetimeKind, Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, - StructTailExpr, TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind, + AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, ByRef, Closure, ConstArg, ConstArgKind, ConstItemRhs, + Expr, ExprField, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericArgs, GenericBound, GenericBounds, + GenericParam, GenericParamKind, GenericParamSource, Generics, HirId, HirIdMap, InlineAsmOperand, ItemId, ItemKind, + LetExpr, Lifetime, LifetimeKind, LifetimeParamKind, Node, ParamName, Pat, PatExpr, PatExprKind, PatField, PatKind, + Path, PathSegment, PreciseCapturingArgKind, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, TraitBoundModifiers, Ty, + TyKind, TyPat, TyPatKind, UseKind, WherePredicate, WherePredicateKind, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::LateContext; @@ -106,6 +109,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { left_ctxt: SyntaxContext::root(), right_ctxt: SyntaxContext::root(), locals: HirIdMap::default(), + local_items: FxIndexMap::default(), } } @@ -144,6 +148,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> { // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`, // these blocks are considered equal since `x` is mapped to `y`. pub locals: HirIdMap, + pub local_items: FxIndexMap, } impl HirEqInterExpr<'_, '_, '_> { @@ -168,6 +173,189 @@ impl HirEqInterExpr<'_, '_, '_> { && self.eq_pat(l.pat, r.pat) }, (StmtKind::Expr(l), StmtKind::Expr(r)) | (StmtKind::Semi(l), StmtKind::Semi(r)) => self.eq_expr(l, r), + (StmtKind::Item(l), StmtKind::Item(r)) => self.eq_item(*l, *r), + _ => false, + } + } + + pub fn eq_item(&mut self, l: ItemId, r: ItemId) -> bool { + let left = self.inner.cx.tcx.hir_item(l); + let right = self.inner.cx.tcx.hir_item(r); + let eq = match (left.kind, right.kind) { + ( + ItemKind::Const(l_ident, l_generics, l_ty, ConstItemRhs::Body(l_body)), + ItemKind::Const(r_ident, r_generics, r_ty, ConstItemRhs::Body(r_body)), + ) => { + l_ident.name == r_ident.name + && self.eq_generics(l_generics, r_generics) + && self.eq_ty(l_ty, r_ty) + && self.eq_body(l_body, r_body) + }, + (ItemKind::Static(l_mut, l_ident, l_ty, l_body), ItemKind::Static(r_mut, r_ident, r_ty, r_body)) => { + l_mut == r_mut && l_ident.name == r_ident.name && self.eq_ty(l_ty, r_ty) && self.eq_body(l_body, r_body) + }, + ( + ItemKind::Fn { + sig: l_sig, + ident: l_ident, + generics: l_generics, + body: l_body, + has_body: l_has_body, + }, + ItemKind::Fn { + sig: r_sig, + ident: r_ident, + generics: r_generics, + body: r_body, + has_body: r_has_body, + }, + ) => { + l_ident.name == r_ident.name + && (l_has_body == r_has_body) + && self.eq_fn_sig(&l_sig, &r_sig) + && self.eq_generics(l_generics, r_generics) + && self.eq_body(l_body, r_body) + }, + (ItemKind::TyAlias(l_ident, l_generics, l_ty), ItemKind::TyAlias(r_ident, r_generics, r_ty)) => { + l_ident.name == r_ident.name && self.eq_generics(l_generics, r_generics) && self.eq_ty(l_ty, r_ty) + }, + (ItemKind::Use(l_path, l_kind), ItemKind::Use(r_path, r_kind)) => { + self.eq_path_segments(l_path.segments, r_path.segments) + && match (l_kind, r_kind) { + (UseKind::Single(l_ident), UseKind::Single(r_ident)) => l_ident.name == r_ident.name, + (UseKind::Glob, UseKind::Glob) | (UseKind::ListStem, UseKind::ListStem) => true, + _ => false, + } + }, + (ItemKind::Mod(l_ident, l_mod), ItemKind::Mod(r_ident, r_mod)) => { + l_ident.name == r_ident.name && over(l_mod.item_ids, r_mod.item_ids, |l, r| self.eq_item(*l, *r)) + }, + _ => false, + }; + if eq { + self.local_items.insert(l.owner_id.to_def_id(), r.owner_id.to_def_id()); + } + eq + } + + fn eq_fn_sig(&mut self, left: &FnSig<'_>, right: &FnSig<'_>) -> bool { + left.header.safety == right.header.safety + && left.header.constness == right.header.constness + && left.header.asyncness == right.header.asyncness + && left.header.abi == right.header.abi + && self.eq_fn_decl(left.decl, right.decl) + } + + fn eq_fn_decl(&mut self, left: &FnDecl<'_>, right: &FnDecl<'_>) -> bool { + over(left.inputs, right.inputs, |l, r| self.eq_ty(l, r)) + && (match (left.output, right.output) { + (FnRetTy::DefaultReturn(_), FnRetTy::DefaultReturn(_)) => true, + (FnRetTy::Return(l_ty), FnRetTy::Return(r_ty)) => self.eq_ty(l_ty, r_ty), + _ => false, + }) + && left.c_variadic == right.c_variadic + && left.implicit_self == right.implicit_self + && left.lifetime_elision_allowed == right.lifetime_elision_allowed + } + + fn eq_generics(&mut self, left: &Generics<'_>, right: &Generics<'_>) -> bool { + self.eq_generics_param(left.params, right.params) + && self.eq_generics_predicate(left.predicates, right.predicates) + } + + fn eq_generics_predicate(&mut self, left: &[WherePredicate<'_>], right: &[WherePredicate<'_>]) -> bool { + over(left, right, |l, r| match (l.kind, r.kind) { + (WherePredicateKind::BoundPredicate(l_bound), WherePredicateKind::BoundPredicate(r_bound)) => { + l_bound.origin == r_bound.origin + && self.eq_ty(l_bound.bounded_ty, r_bound.bounded_ty) + && self.eq_generics_param(l_bound.bound_generic_params, r_bound.bound_generic_params) + && self.eq_generics_bound(l_bound.bounds, r_bound.bounds) + }, + (WherePredicateKind::RegionPredicate(l_region), WherePredicateKind::RegionPredicate(r_region)) => { + Self::eq_lifetime(l_region.lifetime, r_region.lifetime) + && self.eq_generics_bound(l_region.bounds, r_region.bounds) + }, + (WherePredicateKind::EqPredicate(l_eq), WherePredicateKind::EqPredicate(r_eq)) => { + self.eq_ty(l_eq.lhs_ty, r_eq.lhs_ty) + }, + _ => false, + }) + } + + fn eq_generics_bound(&mut self, left: GenericBounds<'_>, right: GenericBounds<'_>) -> bool { + over(left, right, |l, r| match (l, r) { + (GenericBound::Trait(l_trait), GenericBound::Trait(r_trait)) => { + l_trait.modifiers == r_trait.modifiers + && self.eq_path(l_trait.trait_ref.path, r_trait.trait_ref.path) + && self.eq_generics_param(l_trait.bound_generic_params, r_trait.bound_generic_params) + }, + (GenericBound::Outlives(l_lifetime), GenericBound::Outlives(r_lifetime)) => { + Self::eq_lifetime(l_lifetime, r_lifetime) + }, + (GenericBound::Use(l_capture, _), GenericBound::Use(r_capture, _)) => { + over(l_capture, r_capture, |l, r| match (l, r) { + (PreciseCapturingArgKind::Lifetime(l_lifetime), PreciseCapturingArgKind::Lifetime(r_lifetime)) => { + Self::eq_lifetime(l_lifetime, r_lifetime) + }, + (PreciseCapturingArgKind::Param(l_param), PreciseCapturingArgKind::Param(r_param)) => { + l_param.ident == r_param.ident && l_param.res == r_param.res + }, + _ => false, + }) + }, + _ => false, + }) + } + + fn eq_generics_param(&mut self, left: &[GenericParam<'_>], right: &[GenericParam<'_>]) -> bool { + over(left, right, |l, r| { + (match (l.name, r.name) { + (ParamName::Plain(l_ident), ParamName::Plain(r_ident)) + | (ParamName::Error(l_ident), ParamName::Error(r_ident)) => l_ident.name == r_ident.name, + (ParamName::Fresh, ParamName::Fresh) => true, + _ => false, + }) && l.pure_wrt_drop == r.pure_wrt_drop + && self.eq_generics_param_kind(&l.kind, &r.kind) + && (matches!( + (l.source, r.source), + (GenericParamSource::Generics, GenericParamSource::Generics) + | (GenericParamSource::Binder, GenericParamSource::Binder) + )) + }) + } + + fn eq_generics_param_kind(&mut self, left: &GenericParamKind<'_>, right: &GenericParamKind<'_>) -> bool { + match (left, right) { + (GenericParamKind::Lifetime { kind: l_kind }, GenericParamKind::Lifetime { kind: r_kind }) => { + match (l_kind, r_kind) { + (LifetimeParamKind::Explicit, LifetimeParamKind::Explicit) + | (LifetimeParamKind::Error, LifetimeParamKind::Error) => true, + (LifetimeParamKind::Elided(l_lifetime_kind), LifetimeParamKind::Elided(r_lifetime_kind)) => { + l_lifetime_kind == r_lifetime_kind + }, + _ => false, + } + }, + ( + GenericParamKind::Type { + default: l_default, + synthetic: l_synthetic, + }, + GenericParamKind::Type { + default: r_default, + synthetic: r_synthetic, + }, + ) => both(*l_default, *r_default, |l, r| self.eq_ty(l, r)) && l_synthetic == r_synthetic, + ( + GenericParamKind::Const { + ty: l_ty, + default: l_default, + }, + GenericParamKind::Const { + ty: r_ty, + default: r_default, + }, + ) => self.eq_ty(l_ty, r_ty) && both(*l_default, *r_default, |l, r| self.eq_const_arg(l, r)), _ => false, } } @@ -479,16 +667,20 @@ impl HirEqInterExpr<'_, '_, '_> { (ConstArgKind::Infer(..), ConstArgKind::Infer(..)) => true, (ConstArgKind::Struct(path_a, inits_a), ConstArgKind::Struct(path_b, inits_b)) => { self.eq_qpath(path_a, path_b) - && inits_a.iter().zip(*inits_b).all(|(init_a, init_b)| { - self.eq_const_arg(init_a.expr, init_b.expr) - }) - } + && inits_a + .iter() + .zip(*inits_b) + .all(|(init_a, init_b)| self.eq_const_arg(init_a.expr, init_b.expr)) + }, // Use explicit match for now since ConstArg is undergoing flux. - (ConstArgKind::Path(..), _) - | (ConstArgKind::Anon(..), _) - | (ConstArgKind::Infer(..), _) - | (ConstArgKind::Struct(..), _) - | (ConstArgKind::Error(..), _) => false, + ( + ConstArgKind::Path(..) + | ConstArgKind::Anon(..) + | ConstArgKind::Infer(..) + | ConstArgKind::Struct(..) + | ConstArgKind::Error(..), + _, + ) => false, } } @@ -570,6 +762,17 @@ impl HirEqInterExpr<'_, '_, '_> { match (left.res, right.res) { (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r), (Res::Local(_), _) | (_, Res::Local(_)) => false, + (Res::Def(l_kind, l), Res::Def(r_kind, r)) + if l_kind == r_kind + && let DefKind::Const + | DefKind::Static { .. } + | DefKind::Fn + | DefKind::TyAlias + | DefKind::Use + | DefKind::Mod = l_kind => + { + (l == r || self.local_items.get(&l) == Some(&r)) && self.eq_path_segments(left.segments, right.segments) + }, _ => self.eq_path_segments(left.segments, right.segments), } } @@ -1344,7 +1547,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { for init in *inits { self.hash_const_arg(init.expr); } - } + }, ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 409f13013489..954c32687af6 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2490,7 +2490,7 @@ pub enum DefinedTy<'tcx> { /// in the context of its definition site. We also track the `def_id` of its /// definition site. /// - /// WARNING: As the `ty` in in the scope of the definition, not of the function + /// WARNING: As the `ty` is in the scope of the definition, not of the function /// using it, you must be very careful with how you use it. Using it in the wrong /// scope easily results in ICEs. Mir { @@ -2719,7 +2719,6 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtx moved_before_use, same_ctxt, }, - Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow"), None => ExprUseCtxt { node: Node::Crate(cx.tcx.hir_root_module()), child_id: HirId::INVALID, diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index b48d17863aa3..f30f26f3a70b 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -13,8 +13,8 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::source_map::{SourceMap, original_sp}; use rustc_span::{ - BytePos, DUMMY_SP, DesugaringKind, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, - Span, SpanData, SyntaxContext, hygiene, + BytePos, DUMMY_SP, DesugaringKind, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData, + SyntaxContext, hygiene, }; use std::borrow::Cow; use std::fmt; diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 00f4a9c7e586..a0d2e8673fe6 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -58,6 +58,7 @@ generate! { LowerHex, MAX, MIN, + MaybeDef, MsrvStack, Octal, OpenOptions, @@ -167,6 +168,7 @@ generate! { from_ne_bytes, from_ptr, from_raw, + from_raw_parts, from_str_radix, fs, fuse, @@ -192,6 +194,8 @@ generate! { io, is_ascii, is_char_boundary, + is_diag_item, + is_diagnostic_item, is_digit, is_empty, is_err, @@ -275,6 +279,7 @@ generate! { read_to_string, read_unaligned, read_volatile, + reduce, redundant_imports, redundant_pub_crate, regex, @@ -282,6 +287,7 @@ generate! { repeat, replace, replacen, + res, reserve, resize, restriction, @@ -364,6 +370,7 @@ generate! { trim_start, trim_start_matches, truncate, + try_fold, try_for_each, unreachable_pub, unsafe_removed_from_name, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1384f4078ebe..dbec79e111fb 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-12-11" +channel = "nightly-2025-12-25" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui-internal/repeated_is_diagnostic_item.fixed b/tests/ui-internal/repeated_is_diagnostic_item.fixed new file mode 100644 index 000000000000..fcacf504804c --- /dev/null +++ b/tests/ui-internal/repeated_is_diagnostic_item.fixed @@ -0,0 +1,77 @@ +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_span; + +use clippy_utils::res::MaybeDef; +use clippy_utils::sym; +use rustc_hir::def_id::DefId; +use rustc_lint::LateContext; +use rustc_middle::ty::{AdtDef, Ty, TyCtxt}; +use rustc_span::Symbol; + +fn binops(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) { + let did = ty.opt_def_id().unwrap(); + + let _ = matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + let _ = !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + let _ = matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + let _ = !matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + let _ = matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + let _ = !matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result)); + //~^ repeated_is_diagnostic_item + + // Don't lint: `is_diagnostic_item` is called not on `TyCtxt` + struct FakeTyCtxt; + impl FakeTyCtxt { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool { + unimplemented!() + } + } + let f = FakeTyCtxt; + let _ = f.is_diagnostic_item(sym::Option, did) || f.is_diagnostic_item(sym::Result, did); + + // Don't lint: `is_diagnostic_item` on `TyCtxt` comes from a(n unrelated) trait + trait IsDiagnosticItem { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool; + } + impl IsDiagnosticItem for TyCtxt<'_> { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool { + unimplemented!() + } + } + let _ = IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Option, did) + || IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Result, did); + + // Don't lint: `is_diag_item` is an inherent method + struct DoesntImplMaybeDef; + impl DoesntImplMaybeDef { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool { + unimplemented!() + } + } + let d = DoesntImplMaybeDef; + let _ = d.is_diag_item(cx, sym::Option) || d.is_diag_item(cx, sym::Result); + + // Don't lint: `is_diag_item` comes from a trait other than `MaybeDef` + trait FakeMaybeDef { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool; + } + struct Bar; + impl FakeMaybeDef for Bar { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool { + unimplemented!() + } + } + let b = Bar; + let _ = b.is_diag_item(cx, sym::Option) || b.is_diag_item(cx, sym::Result); +} + +fn main() {} diff --git a/tests/ui-internal/repeated_is_diagnostic_item.rs b/tests/ui-internal/repeated_is_diagnostic_item.rs new file mode 100644 index 000000000000..7ccbbfd94029 --- /dev/null +++ b/tests/ui-internal/repeated_is_diagnostic_item.rs @@ -0,0 +1,77 @@ +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_span; + +use clippy_utils::res::MaybeDef; +use clippy_utils::sym; +use rustc_hir::def_id::DefId; +use rustc_lint::LateContext; +use rustc_middle::ty::{AdtDef, Ty, TyCtxt}; +use rustc_span::Symbol; + +fn binops(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) { + let did = ty.opt_def_id().unwrap(); + + let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result); + //~^ repeated_is_diagnostic_item + let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result); + //~^ repeated_is_diagnostic_item + let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result); + //~^ repeated_is_diagnostic_item + let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result); + //~^ repeated_is_diagnostic_item + let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did); + //~^ repeated_is_diagnostic_item + let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did); + //~^ repeated_is_diagnostic_item + + // Don't lint: `is_diagnostic_item` is called not on `TyCtxt` + struct FakeTyCtxt; + impl FakeTyCtxt { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool { + unimplemented!() + } + } + let f = FakeTyCtxt; + let _ = f.is_diagnostic_item(sym::Option, did) || f.is_diagnostic_item(sym::Result, did); + + // Don't lint: `is_diagnostic_item` on `TyCtxt` comes from a(n unrelated) trait + trait IsDiagnosticItem { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool; + } + impl IsDiagnosticItem for TyCtxt<'_> { + fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool { + unimplemented!() + } + } + let _ = IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Option, did) + || IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Result, did); + + // Don't lint: `is_diag_item` is an inherent method + struct DoesntImplMaybeDef; + impl DoesntImplMaybeDef { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool { + unimplemented!() + } + } + let d = DoesntImplMaybeDef; + let _ = d.is_diag_item(cx, sym::Option) || d.is_diag_item(cx, sym::Result); + + // Don't lint: `is_diag_item` comes from a trait other than `MaybeDef` + trait FakeMaybeDef { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool; + } + struct Bar; + impl FakeMaybeDef for Bar { + fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool { + unimplemented!() + } + } + let b = Bar; + let _ = b.is_diag_item(cx, sym::Option) || b.is_diag_item(cx, sym::Result); +} + +fn main() {} diff --git a/tests/ui-internal/repeated_is_diagnostic_item.stderr b/tests/ui-internal/repeated_is_diagnostic_item.stderr new file mode 100644 index 000000000000..8c52ba561d79 --- /dev/null +++ b/tests/ui-internal/repeated_is_diagnostic_item.stderr @@ -0,0 +1,82 @@ +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:18:13 + | +LL | let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results + = note: `-D clippy::repeated-is-diagnostic-item` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::repeated_is_diagnostic_item)]` +help: call `Ty::opt_diag_name`, and reuse the results + | +LL - let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result); +LL + let _ = matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)); + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:20:13 + | +LL | let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL - let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result); +LL + let _ = !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)); + | + +error: repeated calls to `AdtDef::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:22:13 + | +LL | let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `AdtDef::opt_diag_name`, and reuse the results + | +LL - let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result); +LL + let _ = matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result)); + | + +error: repeated calls to `AdtDef::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:24:13 + | +LL | let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `AdtDef::opt_diag_name`, and reuse the results + | +LL - let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result); +LL + let _ = !matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result)); + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:26:13 + | +LL | let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL - let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did); +LL + let _ = matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result)); + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item.rs:28:13 + | +LL | let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL - let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did); +LL + let _ = !matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result)); + | + +error: aborting due to 6 previous errors + diff --git a/tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs b/tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs new file mode 100644 index 000000000000..807da07ce8aa --- /dev/null +++ b/tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs @@ -0,0 +1,213 @@ +//@no-rustfix +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_span; + +use clippy_utils::res::MaybeDef; +use clippy_utils::sym; +use rustc_hir::def_id::DefId; +use rustc_lint::LateContext; +use rustc_middle::ty::{AdtDef, Ty, TyCtxt}; +use rustc_span::Symbol; + +fn main() {} + +// if-chains with repeated calls on the same `ty` +fn if_chains(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) { + let did = ty.opt_def_id().unwrap(); + + let _ = if ty.is_diag_item(cx, sym::Option) { + //~^ repeated_is_diagnostic_item + "Option" + } else if ty.is_diag_item(cx, sym::Result) { + "Result" + } else { + return; + }; + // should ideally suggest the following: + // let _ = match ty.opt_diag_name() { + // Some(sym::Option) => { + // "Option" + // } + // Some(sym::Result) => { + // "Result" + // } + // _ => { + // return; + // } + // }; + + // same but in a stmt + if ty.is_diag_item(cx, sym::Option) { + //~^ repeated_is_diagnostic_item + eprintln!("Option"); + } else if ty.is_diag_item(cx, sym::Result) { + eprintln!("Result"); + } + // should ideally suggest the following: + // match ty.opt_diag_name() { + // Some(sym::Option) => { + // "Option" + // } + // Some(sym::Result) => { + // "Result" + // } + // _ => {} + // }; + + // nested conditions + let _ = if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + //~^ repeated_is_diagnostic_item + "Option" + } else if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + "Result" + } else { + return; + }; + + let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) { + //~^ repeated_is_diagnostic_item + "Option" + } else if cx.tcx.is_diagnostic_item(sym::Result, did) { + "Result" + } else { + return; + }; + // should ideally suggest the following: + // let _ = match cx.get_diagnostic_name(did) { + // Some(sym::Option) => { + // "Option" + // } + // Some(sym::Result) => { + // "Result" + // } + // _ => { + // return; + // } + // }; + + // same but in a stmt + if cx.tcx.is_diagnostic_item(sym::Option, did) { + //~^ repeated_is_diagnostic_item + eprintln!("Option"); + } else if cx.tcx.is_diagnostic_item(sym::Result, did) { + eprintln!("Result"); + } + // should ideally suggest the following: + // match cx.tcx.get_diagnostic_name(did) { + // Some(sym::Option) => { + // "Option" + // } + // Some(sym::Result) => { + // "Result" + // } + // _ => {} + // }; + + // nested conditions + let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + //~^ repeated_is_diagnostic_item + "Option" + } else if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + "Result" + } else { + return; + }; +} + +// if-chains with repeated calls on the same `ty` +fn consecutive_ifs(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) { + let did = ty.opt_def_id().unwrap(); + + { + if ty.is_diag_item(cx, sym::Option) { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if ty.is_diag_item(cx, sym::Result) { + println!("Result"); + } + println!("done!") + } + + // nested conditions + { + if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + println!("Result"); + } + println!("done!") + } + + { + if cx.tcx.is_diagnostic_item(sym::Option, did) { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if cx.tcx.is_diagnostic_item(sym::Result, did) { + println!("Result"); + } + println!("done!") + } + + // nested conditions + { + if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + println!("Result"); + } + println!("done!") + } + + // All the same, but the second if is the final expression + { + if ty.is_diag_item(cx, sym::Option) { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if ty.is_diag_item(cx, sym::Result) { + println!("Result"); + } + } + + // nested conditions + { + if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + println!("Result"); + } + } + + { + if cx.tcx.is_diagnostic_item(sym::Option, did) { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if cx.tcx.is_diagnostic_item(sym::Result, did) { + println!("Result"); + } + } + + // nested conditions + { + if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + //~^ repeated_is_diagnostic_item + println!("Option"); + } + if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + println!("Result"); + } + } +} diff --git a/tests/ui-internal/repeated_is_diagnostic_item_unfixable.stderr b/tests/ui-internal/repeated_is_diagnostic_item_unfixable.stderr new file mode 100644 index 000000000000..890817da5235 --- /dev/null +++ b/tests/ui-internal/repeated_is_diagnostic_item_unfixable.stderr @@ -0,0 +1,374 @@ +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:22:13 + | +LL | let _ = if ty.is_diag_item(cx, sym::Option) { + | ^ -------------------------------- called here + | _____________| + | | +LL | | +LL | | "Option" +LL | | } else if ty.is_diag_item(cx, sym::Result) { + | | -------------------------------- called here +... | +LL | | return; +LL | | }; + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results + = note: `-D clippy::repeated-is-diagnostic-item` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::repeated_is_diagnostic_item)]` +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ let _ = if /* name */ == Some(sym::Option) { +LL | +LL | "Option" +LL ~ } else if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:44:5 + | +LL | if ty.is_diag_item(cx, sym::Option) { + | ^ -------------------------------- called here + | _____| + | | +LL | | +LL | | eprintln!("Option"); +LL | | } else if ty.is_diag_item(cx, sym::Result) { + | | -------------------------------- called here +LL | | eprintln!("Result"); +LL | | } + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | eprintln!("Option"); +LL ~ } else if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:62:13 + | +LL | let _ = if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + | ^ -------------------------------- called here + | _____________| + | | +LL | | +LL | | "Option" +LL | | } else if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + | | -------------------------------- called here +... | +LL | | return; +LL | | }; + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ let _ = if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | "Option" +LL ~ } else if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:71:13 + | +LL | let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) { + | ^ ------------------------------------------- called here + | _____________| + | | +LL | | +LL | | "Option" +LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) { + | | ------------------------------------------- called here +... | +LL | | return; +LL | | }; + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ let _ = if /* name */ == Some(sym::Option) { +LL | +LL | "Option" +LL ~ } else if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:93:5 + | +LL | if cx.tcx.is_diagnostic_item(sym::Option, did) { + | ^ ------------------------------------------- called here + | _____| + | | +LL | | +LL | | eprintln!("Option"); +LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) { + | | ------------------------------------------- called here +LL | | eprintln!("Result"); +LL | | } + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | eprintln!("Option"); +LL ~ } else if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:111:13 + | +LL | let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + | ^ ------------------------------------------- called here + | _____________| + | | +LL | | +LL | | "Option" +LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + | | ------------------------------------------- called here +... | +LL | | return; +LL | | }; + | |_____^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ let _ = if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | "Option" +LL ~ } else if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:126:9 + | +LL | if ty.is_diag_item(cx, sym::Option) { + | ^ -------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if ty.is_diag_item(cx, sym::Result) { + | | -------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:138:9 + | +LL | if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + | ^ -------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + | | -------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:149:9 + | +LL | if cx.tcx.is_diagnostic_item(sym::Option, did) { + | ^ ------------------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) { + | | ------------------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:161:9 + | +LL | if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + | ^ ------------------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + | | ------------------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:173:9 + | +LL | if ty.is_diag_item(cx, sym::Option) { + | ^ -------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if ty.is_diag_item(cx, sym::Result) { + | | -------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `Ty::is_diag_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:184:9 + | +LL | if ty.is_diag_item(cx, sym::Option) && 4 == 5 { + | ^ -------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if ty.is_diag_item(cx, sym::Result) && 4 == 5 { + | | -------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `Ty::opt_diag_name`, and reuse the results + | +LL ~ let /* name */ = ty.opt_diag_name(cx); +LL ~ if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:194:9 + | +LL | if cx.tcx.is_diagnostic_item(sym::Option, did) { + | ^ ------------------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) { + | | ------------------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ if /* name */ == Some(sym::Option) { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) { + | + +error: repeated calls to `TyCtxt::is_diagnostic_item` + --> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:205:9 + | +LL | if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 { + | ^ ------------------------------------------- called here + | _________| + | | +LL | | +LL | | println!("Option"); +LL | | } +LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 { + | | ------------------------------------------- called here +LL | | println!("Result"); +LL | | } + | |_________^ + | + = note: each call performs the same compiler query -- it's faster to query once, and reuse the results +help: call `TyCtxt::get_diagnostic_name`, and reuse the results + | +LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did); +LL ~ if /* name */ == Some(sym::Option) && 4 == 5 { +LL | +LL | println!("Option"); +LL | } +LL ~ if /* name */ == Some(sym::Result) && 4 == 5 { + | + +error: aborting due to 14 previous errors + diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed index ec45dfd2033a..213b56b786b1 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed @@ -1,5 +1,5 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] -#![warn(clippy::collapsible_if)] +#![warn(clippy::collapsible_else_if)] #[rustfmt::skip] fn main() { diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.rs b/tests/ui-toml/collapsible_if/collapsible_else_if.rs index 54315a3c32bf..2d4c2c54031e 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.rs +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.rs @@ -1,5 +1,5 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] -#![warn(clippy::collapsible_if)] +#![warn(clippy::collapsible_else_if)] #[rustfmt::skip] fn main() { diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed index 40556ca5410f..962a4e00d86e 100644 --- a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed @@ -3,7 +3,7 @@ // Should warn pub fn pub_foo(s: &Vec, b: &u32, x: &mut u32) { - //~^ ERROR: this argument is a mutable reference, but not used mutably + //~^ needless_pass_by_ref_mut *x += *b + s.len() as u32; } diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs index bbc63ceb15a3..5f584c6704f2 100644 --- a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs @@ -3,7 +3,7 @@ // Should warn pub fn pub_foo(s: &mut Vec, b: &u32, x: &mut u32) { - //~^ ERROR: this argument is a mutable reference, but not used mutably + //~^ needless_pass_by_ref_mut *x += *b + s.len() as u32; } diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr index c10607bf4bab..57137ab08d1e 100644 --- a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr @@ -1,8 +1,10 @@ -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs:5:19 | LL | pub fn pub_foo(s: &mut Vec, b: &u32, x: &mut u32) { - | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` + | ^----^^^^^^^^ + | | + | help: consider removing this `mut` | = warning: changing this function will impact semver compatibility = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` diff --git a/tests/ui-toml/toml_disallowed_methods/clippy.toml b/tests/ui-toml/toml_disallowed_methods/clippy.toml index c7a326f28295..2c54b73d72d7 100644 --- a/tests/ui-toml/toml_disallowed_methods/clippy.toml +++ b/tests/ui-toml/toml_disallowed_methods/clippy.toml @@ -17,4 +17,6 @@ disallowed-methods = [ # re-exports "conf_disallowed_methods::identity", "conf_disallowed_methods::renamed", + # also used in desugaring + "std::future::Future::poll", ] diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs index 2dac01649a0f..621317246d6d 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs @@ -80,3 +80,19 @@ fn main() { renamed(1); //~^ disallowed_methods } + +mod issue16185 { + use std::pin::Pin; + use std::task::Context; + + async fn test(f: impl Future) { + // Should not lint even though desugaring uses + // disallowed method `std::future::Future::poll()`. + f.await + } + + fn explicit>(f: Pin<&mut F>, cx: &mut Context<'_>) { + f.poll(cx); + //~^ disallowed_methods + } +} diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr index 20474ad6e927..8e7e112a93f3 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr @@ -99,5 +99,11 @@ error: use of a disallowed method `conf_disallowed_methods::renamed` LL | renamed(1); | ^^^^^^^ -error: aborting due to 16 previous errors +error: use of a disallowed method `std::future::Future::poll` + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:95:11 + | +LL | f.poll(cx); + | ^^^^ + +error: aborting due to 17 previous errors diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index f467d4966aef..1c49b6e6b7b1 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -96,3 +96,8 @@ fn _f4() { assert!(C); //~^ assertions_on_constants } + +fn issue_16242(var: bool) { + // should not lint + assert!(cfg!(feature = "hey") && var); +} diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.rs b/tests/ui/branches_sharing_code/shared_at_bottom.rs index fa322dc28a78..a7f950b44caf 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_bottom.rs @@ -300,3 +300,65 @@ fn issue15004() { //~^ branches_sharing_code }; } + +pub fn issue15347() -> isize { + if false { + static A: isize = 4; + return A; + } else { + static A: isize = 5; + return A; + } + + if false { + //~^ branches_sharing_code + type ISize = isize; + return ISize::MAX; + } else { + type ISize = isize; + return ISize::MAX; + } + + if false { + //~^ branches_sharing_code + fn foo() -> isize { + 4 + } + return foo(); + } else { + fn foo() -> isize { + 4 + } + return foo(); + } + + if false { + //~^ branches_sharing_code + use std::num::NonZeroIsize; + return NonZeroIsize::new(4).unwrap().get(); + } else { + use std::num::NonZeroIsize; + return NonZeroIsize::new(4).unwrap().get(); + } + + if false { + //~^ branches_sharing_code + const B: isize = 5; + return B; + } else { + const B: isize = 5; + return B; + } + + // Should not lint! + const A: isize = 1; + if false { + const B: isize = A; + return B; + } else { + const C: isize = A; + return C; + } + + todo!() +} diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_bottom.stderr index 1c470fb0da5e..4ff3990232a5 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -202,5 +202,73 @@ LL ~ } LL ~ 1; | -error: aborting due to 12 previous errors +error: all if blocks contain the same code at the start + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:313:5 + | +LL | / if false { +LL | | +LL | | type ISize = isize; +LL | | return ISize::MAX; + | |__________________________^ + | +help: consider moving these statements before the if + | +LL ~ type ISize = isize; +LL + return ISize::MAX; +LL + if false { + | + +error: all if blocks contain the same code at the start + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:322:5 + | +LL | / if false { +LL | | +LL | | fn foo() -> isize { +LL | | 4 +LL | | } +LL | | return foo(); + | |_____________________^ + | +help: consider moving these statements before the if + | +LL ~ fn foo() -> isize { +LL + 4 +LL + } +LL + return foo(); +LL + if false { + | + +error: all if blocks contain the same code at the start + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:335:5 + | +LL | / if false { +LL | | +LL | | use std::num::NonZeroIsize; +LL | | return NonZeroIsize::new(4).unwrap().get(); + | |___________________________________________________^ + | +help: consider moving these statements before the if + | +LL ~ use std::num::NonZeroIsize; +LL + return NonZeroIsize::new(4).unwrap().get(); +LL + if false { + | + +error: all if blocks contain the same code at the start + --> tests/ui/branches_sharing_code/shared_at_bottom.rs:344:5 + | +LL | / if false { +LL | | +LL | | const B: isize = 5; +LL | | return B; + | |_________________^ + | +help: consider moving these statements before the if + | +LL ~ const B: isize = 5; +LL + return B; +LL + if false { + | + +error: aborting due to 16 previous errors diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 0ff1dc11c3ac..14b84b1ff1ef 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -1,4 +1,4 @@ -error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast.rs:23:5 | LL | x0 as f32; @@ -7,31 +7,31 @@ LL | x0 as f32; = note: `-D clippy::cast-precision-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` -error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `i64` to `f32` may cause a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast.rs:27:5 | LL | x1 as f32; | ^^^^^^^^^ -error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `i64` to `f64` may cause a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast.rs:30:5 | LL | x1 as f64; | ^^^^^^^^^ -error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `u32` to `f32` may cause a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast.rs:34:5 | LL | x2 as f32; | ^^^^^^^^^ -error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `u64` to `f32` may cause a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast.rs:38:5 | LL | x3 as f32; | ^^^^^^^^^ -error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `u64` to `f64` may cause a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast.rs:41:5 | LL | x3 as f64; diff --git a/tests/ui/cast_size.r32bit.stderr b/tests/ui/cast_size.r32bit.stderr index 5811cb3607ba..2f7eeda385e5 100644 --- a/tests/ui/cast_size.r32bit.stderr +++ b/tests/ui/cast_size.r32bit.stderr @@ -13,7 +13,7 @@ LL - 1isize as i8; LL + i8::try_from(1isize); | -error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `isize` to `f32` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:24:5 | LL | x0 as f32; @@ -22,19 +22,19 @@ LL | x0 as f32; = note: `-D clippy::cast-precision-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` -error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `usize` to `f32` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:26:5 | LL | x1 as f32; | ^^^^^^^^^ -error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `isize` to `f64` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:28:5 | LL | x0 as f64; | ^^^^^^^^^ -error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:30:5 | LL | x1 as f64; @@ -165,13 +165,13 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit LL | 1u32 as isize; | ^^^^^^^^^^^^^ -error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:61:5 | LL | 999_999_999 as f32; | ^^^^^^^^^^^^^^^^^^ -error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:63:5 | LL | 9_999_999_999_999_999usize as f64; diff --git a/tests/ui/cast_size.r64bit.stderr b/tests/ui/cast_size.r64bit.stderr index ba1419583aeb..8e5f38137602 100644 --- a/tests/ui/cast_size.r64bit.stderr +++ b/tests/ui/cast_size.r64bit.stderr @@ -13,7 +13,7 @@ LL - 1isize as i8; LL + i8::try_from(1isize); | -error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `isize` to `f32` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:24:5 | LL | x0 as f32; @@ -22,19 +22,19 @@ LL | x0 as f32; = note: `-D clippy::cast-precision-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` -error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `usize` to `f32` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:26:5 | LL | x1 as f32; | ^^^^^^^^^ -error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `isize` to `f64` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:28:5 | LL | x0 as f64; | ^^^^^^^^^ -error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:30:5 | LL | x1 as f64; @@ -165,13 +165,13 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit LL | 1u32 as isize; | ^^^^^^^^^^^^^ -error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) +error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) --> tests/ui/cast_size.rs:61:5 | LL | 999_999_999 as f32; | ^^^^^^^^^^^^^^^^^^ -error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) +error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide) --> tests/ui/cast_size.rs:63:5 | LL | 9_999_999_999_999_999usize as f64; diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index c12279cf12e6..4a0ee439e94a 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -38,3 +38,23 @@ fn issue15010() { debug_assert!(!f.is_null()); //~^ cmp_null } + +fn issue16281() { + use std::ptr; + + struct Container { + value: *const i32, + } + let x = Container { value: ptr::null() }; + + macro_rules! dot_value { + ($obj:expr) => { + $obj.value + }; + } + + if dot_value!(x).is_null() { + //~^ cmp_null + todo!() + } +} diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 2771a16e00c5..26ea8960e5fb 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -38,3 +38,23 @@ fn issue15010() { debug_assert!(f != std::ptr::null_mut()); //~^ cmp_null } + +fn issue16281() { + use std::ptr; + + struct Container { + value: *const i32, + } + let x = Container { value: ptr::null() }; + + macro_rules! dot_value { + ($obj:expr) => { + $obj.value + }; + } + + if dot_value!(x) == ptr::null() { + //~^ cmp_null + todo!() + } +} diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 381747cb3c65..51b98d2a2320 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -37,5 +37,11 @@ error: comparing with null is better expressed by the `.is_null()` method LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` -error: aborting due to 6 previous errors +error: comparing with null is better expressed by the `.is_null()` method + --> tests/ui/cmp_null.rs:56:8 + | +LL | if dot_value!(x) == ptr::null() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dot_value!(x).is_null()` + +error: aborting due to 7 previous errors diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index e7439beef186..cd2d9be9f433 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -1,5 +1,5 @@ #![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] -#![warn(clippy::collapsible_if, clippy::collapsible_else_if)] +#![warn(clippy::collapsible_else_if)] #[rustfmt::skip] fn main() { @@ -70,6 +70,17 @@ fn main() { } //~^^^^^^^^ collapsible_else_if + if x == "hello" { + if y == "world" { + print!("Hello "); + } else { + println!("world"); + } + } else if let Some(42) = Some(42) { + println!("42"); + } + //~^^^^^ collapsible_else_if + if x == "hello" { print!("Hello "); } else { @@ -78,6 +89,21 @@ fn main() { println!("world!") } } + + if x == "hello" { + if y == "world" { + print!("Hello "); + } else { + println!("world"); + } + } else { + if let Some(42) = Some(42) { + println!("42"); + } else { + println!("!"); + } + } + } #[rustfmt::skip] @@ -88,30 +114,12 @@ fn issue_7318() { } fn issue_13365() { - // all the `expect`s that we should fulfill + // ensure we fulfill `#[expect]` if true { } else { #[expect(clippy::collapsible_else_if)] if false {} } - - if true { - } else { - #[expect(clippy::style)] - if false {} - } - - if true { - } else { - #[expect(clippy::all)] - if false {} - } - - if true { - } else { - #[expect(warnings)] - if false {} - } } fn issue14799() { diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index 434ba3654f98..75f204328538 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -1,5 +1,5 @@ #![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] -#![warn(clippy::collapsible_if, clippy::collapsible_else_if)] +#![warn(clippy::collapsible_else_if)] #[rustfmt::skip] fn main() { @@ -84,6 +84,19 @@ fn main() { } //~^^^^^^^^ collapsible_else_if + if x == "hello" { + if y == "world" { + print!("Hello "); + } else { + println!("world"); + } + } else { + if let Some(42) = Some(42) { + println!("42"); + } + } + //~^^^^^ collapsible_else_if + if x == "hello" { print!("Hello "); } else { @@ -92,6 +105,21 @@ fn main() { println!("world!") } } + + if x == "hello" { + if y == "world" { + print!("Hello "); + } else { + println!("world"); + } + } else { + if let Some(42) = Some(42) { + println!("42"); + } else { + println!("!"); + } + } + } #[rustfmt::skip] @@ -104,30 +132,12 @@ fn issue_7318() { } fn issue_13365() { - // all the `expect`s that we should fulfill + // ensure we fulfill `#[expect]` if true { } else { #[expect(clippy::collapsible_else_if)] if false {} } - - if true { - } else { - #[expect(clippy::style)] - if false {} - } - - if true { - } else { - #[expect(clippy::all)] - if false {} - } - - if true { - } else { - #[expect(warnings)] - if false {} - } } fn issue14799() { diff --git a/tests/ui/collapsible_else_if.stderr b/tests/ui/collapsible_else_if.stderr index ce1da593a8e9..ebd78d2b1ffe 100644 --- a/tests/ui/collapsible_else_if.stderr +++ b/tests/ui/collapsible_else_if.stderr @@ -142,7 +142,25 @@ LL + } | error: this `else { if .. }` block can be collapsed - --> tests/ui/collapsible_else_if.rs:100:10 + --> tests/ui/collapsible_else_if.rs:93:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("42"); +LL | | } +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ } else if let Some(42) = Some(42) { +LL + println!("42"); +LL + } + | + +error: this `else { if .. }` block can be collapsed + --> tests/ui/collapsible_else_if.rs:128:10 | LL | }else{ | __________^ @@ -151,7 +169,7 @@ LL | | } | |_____^ help: collapse nested if block: `if false {}` error: this `else { if .. }` block can be collapsed - --> tests/ui/collapsible_else_if.rs:157:12 + --> tests/ui/collapsible_else_if.rs:167:12 | LL | } else { | ____________^ @@ -159,5 +177,5 @@ LL | | (if y == "world" { println!("world") } else { println!("!") }) LL | | } | |_____^ help: collapse nested if block: `if y == "world" { println!("world") } else { println!("!") }` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/crashes/ice-10148.stderr b/tests/ui/crashes/ice-10148.stderr index 639cf2dd442b..e91fb3778a31 100644 --- a/tests/ui/crashes/ice-10148.stderr +++ b/tests/ui/crashes/ice-10148.stderr @@ -2,7 +2,7 @@ error: empty string literal in `println!` --> tests/ui/crashes/ice-10148.rs:8:5 | LL | println!(with_span!(""something "")); - | ^^^^^^^^^^^^^^^^^^^^-----------^^^^^ + | ^^^^^^^^^^^^^^^^^^^^---------------^ | | | help: remove the empty string | diff --git a/tests/ui/crashes/ice-7410.rs b/tests/ui/crashes/ice-7410.rs index 71f00fb9aede..7b39f7ffc01a 100644 --- a/tests/ui/crashes/ice-7410.rs +++ b/tests/ui/crashes/ice-7410.rs @@ -1,6 +1,6 @@ //@ check-pass //@compile-flags: -Clink-arg=-nostartfiles -//@ignore-target: apple windows +//@ignore-target: windows #![crate_type = "lib"] #![no_std] diff --git a/tests/ui/def_id_nocore.rs b/tests/ui/def_id_nocore.rs index 5c13d8622767..6aa023a8d450 100644 --- a/tests/ui/def_id_nocore.rs +++ b/tests/ui/def_id_nocore.rs @@ -1,5 +1,3 @@ -//@ignore-target: apple - #![feature(no_core, lang_items)] #![no_core] #![allow(clippy::missing_safety_doc)] diff --git a/tests/ui/def_id_nocore.stderr b/tests/ui/def_id_nocore.stderr index 175dd0754081..bf022fb56a36 100644 --- a/tests/ui/def_id_nocore.stderr +++ b/tests/ui/def_id_nocore.stderr @@ -1,5 +1,5 @@ error: methods called `as_*` usually take `self` by reference or `self` by mutable reference - --> tests/ui/def_id_nocore.rs:33:19 + --> tests/ui/def_id_nocore.rs:31:19 | LL | pub fn as_ref(self) -> &'static str { | ^^^^ diff --git a/tests/ui/empty_enum_variants_with_brackets.fixed b/tests/ui/empty_enum_variants_with_brackets.fixed index abdf6ca5cb61..caf34eaefab9 100644 --- a/tests/ui/empty_enum_variants_with_brackets.fixed +++ b/tests/ui/empty_enum_variants_with_brackets.fixed @@ -1,5 +1,6 @@ #![warn(clippy::empty_enum_variants_with_brackets)] #![allow(dead_code)] +#![feature(more_qualified_paths)] pub enum PublicTestEnum { NonEmptyBraces { x: i32, y: i32 }, // No error @@ -102,4 +103,62 @@ pub enum PubFoo { Variant3(), } +fn issue16157() { + enum E { + V, + //~^ empty_enum_variants_with_brackets + } + + let E::V = E::V; + + ::V = E::V; + ::V = E::V; +} + +fn variant_with_braces() { + enum E { + V, + //~^ empty_enum_variants_with_brackets + } + E::V = E::V; + E::V = E::V; + ::V = ::V; + + enum F { + U, + //~^ empty_enum_variants_with_brackets + } + F::U = F::U; + ::U = F::U; +} + +fn variant_with_comments_and_cfg() { + enum E { + V( + // This is a comment + ), + } + E::V() = E::V(); + + enum F { + U { + // This is a comment + }, + } + F::U {} = F::U {}; + + enum G { + V(#[cfg(target_os = "cuda")] String), + } + G::V() = G::V(); + + enum H { + U { + #[cfg(target_os = "cuda")] + value: String, + }, + } + H::U {} = H::U {}; +} + fn main() {} diff --git a/tests/ui/empty_enum_variants_with_brackets.rs b/tests/ui/empty_enum_variants_with_brackets.rs index 63a5a8e9143e..f7ab062edd1e 100644 --- a/tests/ui/empty_enum_variants_with_brackets.rs +++ b/tests/ui/empty_enum_variants_with_brackets.rs @@ -1,5 +1,6 @@ #![warn(clippy::empty_enum_variants_with_brackets)] #![allow(dead_code)] +#![feature(more_qualified_paths)] pub enum PublicTestEnum { NonEmptyBraces { x: i32, y: i32 }, // No error @@ -102,4 +103,62 @@ pub enum PubFoo { Variant3(), } +fn issue16157() { + enum E { + V(), + //~^ empty_enum_variants_with_brackets + } + + let E::V() = E::V(); + + ::V() = E::V(); + ::V {} = E::V(); +} + +fn variant_with_braces() { + enum E { + V(), + //~^ empty_enum_variants_with_brackets + } + E::V() = E::V(); + E::V() = E::V {}; + ::V {} = ::V {}; + + enum F { + U {}, + //~^ empty_enum_variants_with_brackets + } + F::U {} = F::U {}; + ::U {} = F::U {}; +} + +fn variant_with_comments_and_cfg() { + enum E { + V( + // This is a comment + ), + } + E::V() = E::V(); + + enum F { + U { + // This is a comment + }, + } + F::U {} = F::U {}; + + enum G { + V(#[cfg(target_os = "cuda")] String), + } + G::V() = G::V(); + + enum H { + U { + #[cfg(target_os = "cuda")] + value: String, + }, + } + H::U {} = H::U {}; +} + fn main() {} diff --git a/tests/ui/empty_enum_variants_with_brackets.stderr b/tests/ui/empty_enum_variants_with_brackets.stderr index 7fe85e829a35..d50b07036a94 100644 --- a/tests/ui/empty_enum_variants_with_brackets.stderr +++ b/tests/ui/empty_enum_variants_with_brackets.stderr @@ -1,5 +1,5 @@ error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:7:16 + --> tests/ui/empty_enum_variants_with_brackets.rs:8:16 | LL | EmptyBraces {}, | ^^^ @@ -9,7 +9,7 @@ LL | EmptyBraces {}, = help: remove the brackets error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:15:16 + --> tests/ui/empty_enum_variants_with_brackets.rs:16:16 | LL | EmptyBraces {}, | ^^^ @@ -17,7 +17,7 @@ LL | EmptyBraces {}, = help: remove the brackets error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:17:21 + --> tests/ui/empty_enum_variants_with_brackets.rs:18:21 | LL | EmptyParentheses(), | ^^ @@ -25,7 +25,7 @@ LL | EmptyParentheses(), = help: remove the brackets error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:28:16 + --> tests/ui/empty_enum_variants_with_brackets.rs:29:16 | LL | Unknown(), | ^^ @@ -33,7 +33,7 @@ LL | Unknown(), = help: remove the brackets error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:47:16 + --> tests/ui/empty_enum_variants_with_brackets.rs:48:16 | LL | Unknown(), | ^^ @@ -41,7 +41,7 @@ LL | Unknown(), = help: remove the brackets error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:53:20 + --> tests/ui/empty_enum_variants_with_brackets.rs:54:20 | LL | Parentheses(), | ^^ @@ -56,7 +56,7 @@ LL ~ RedundantParenthesesFunctionCall::Parentheses; | error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:76:20 + --> tests/ui/empty_enum_variants_with_brackets.rs:77:20 | LL | Parentheses(), | ^^ @@ -71,12 +71,61 @@ LL ~ Parentheses, | error: enum variant has empty brackets - --> tests/ui/empty_enum_variants_with_brackets.rs:95:13 + --> tests/ui/empty_enum_variants_with_brackets.rs:96:13 | LL | Variant3(), | ^^ | = help: remove the brackets -error: aborting due to 8 previous errors +error: enum variant has empty brackets + --> tests/ui/empty_enum_variants_with_brackets.rs:108:10 + | +LL | V(), + | ^^ + | +help: remove the brackets + | +LL ~ V, +LL | +LL | } +LL | +LL ~ let E::V = E::V; +LL | +LL ~ ::V = E::V; +LL ~ ::V = E::V; + | + +error: enum variant has empty brackets + --> tests/ui/empty_enum_variants_with_brackets.rs:120:10 + | +LL | V(), + | ^^ + | +help: remove the brackets + | +LL ~ V, +LL | +LL | } +LL ~ E::V = E::V; +LL ~ E::V = E::V; +LL ~ ::V = ::V; + | + +error: enum variant has empty brackets + --> tests/ui/empty_enum_variants_with_brackets.rs:128:10 + | +LL | U {}, + | ^^^ + | +help: remove the brackets + | +LL ~ U, +LL | +LL | } +LL ~ F::U = F::U; +LL ~ ::U = F::U; + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/empty_loop_no_std.rs b/tests/ui/empty_loop_no_std.rs index 6407bd678f9c..1ea96abbcd8b 100644 --- a/tests/ui/empty_loop_no_std.rs +++ b/tests/ui/empty_loop_no_std.rs @@ -1,6 +1,4 @@ //@compile-flags: -Clink-arg=-nostartfiles -//@ignore-target: apple - #![warn(clippy::empty_loop)] #![crate_type = "lib"] #![no_std] diff --git a/tests/ui/empty_loop_no_std.stderr b/tests/ui/empty_loop_no_std.stderr index f36fb9d9e3f2..e34b50ed1aef 100644 --- a/tests/ui/empty_loop_no_std.stderr +++ b/tests/ui/empty_loop_no_std.stderr @@ -1,5 +1,5 @@ error: empty `loop {}` wastes CPU cycles - --> tests/ui/empty_loop_no_std.rs:10:5 + --> tests/ui/empty_loop_no_std.rs:8:5 | LL | loop {} | ^^^^^^^ diff --git a/tests/ui/entry.fixed b/tests/ui/entry.fixed index 1e36ca4f1f09..75e173b9a84d 100644 --- a/tests/ui/entry.fixed +++ b/tests/ui/entry.fixed @@ -273,3 +273,13 @@ mod issue_16173 { } fn main() {} + +fn issue15781(m: &mut std::collections::HashMap, k: i32, v: i32) { + fn very_important_fn() {} + m.entry(k).or_insert_with(|| { + //~^ map_entry + #[cfg(test)] + very_important_fn(); + v + }); +} diff --git a/tests/ui/entry.rs b/tests/ui/entry.rs index b3da0ef3ffd6..7e3308c87356 100644 --- a/tests/ui/entry.rs +++ b/tests/ui/entry.rs @@ -279,3 +279,13 @@ mod issue_16173 { } fn main() {} + +fn issue15781(m: &mut std::collections::HashMap, k: i32, v: i32) { + fn very_important_fn() {} + if !m.contains_key(&k) { + //~^ map_entry + #[cfg(test)] + very_important_fn(); + m.insert(k, v); + } +} diff --git a/tests/ui/entry.stderr b/tests/ui/entry.stderr index 009b78d29073..4a29b3860e89 100644 --- a/tests/ui/entry.stderr +++ b/tests/ui/entry.stderr @@ -246,5 +246,26 @@ LL + e.insert(42); LL + } | -error: aborting due to 11 previous errors +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> tests/ui/entry.rs:285:5 + | +LL | / if !m.contains_key(&k) { +LL | | +LL | | #[cfg(test)] +LL | | very_important_fn(); +LL | | m.insert(k, v); +LL | | } + | |_____^ + | +help: try + | +LL ~ m.entry(k).or_insert_with(|| { +LL + +LL + #[cfg(test)] +LL + very_important_fn(); +LL + v +LL + }); + | + +error: aborting due to 12 previous errors diff --git a/tests/ui/format_push_string.fixed b/tests/ui/format_push_string.fixed new file mode 100644 index 000000000000..f6396d9982a3 --- /dev/null +++ b/tests/ui/format_push_string.fixed @@ -0,0 +1,132 @@ +#![warn(clippy::format_push_string)] + +fn main() { + use std::fmt::Write; + + let mut string = String::new(); + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + + let _ = write!(string, "{:?}", 5678); + //~^ format_push_string + + macro_rules! string { + () => { + String::new() + }; + } + let _ = write!(string!(), "{:?}", 5678); + //~^ format_push_string +} + +// TODO: recognize the already imported `fmt::Write`, and don't add a note suggesting to import it +// again +mod import_write { + mod push_str { + mod imported_anonymously { + fn main(string: &mut String) { + use std::fmt::Write as _; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported { + fn main(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_anonymously_in_module { + use std::fmt::Write as _; + + fn main(string: &mut String) { + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_in_module { + use std::fmt::Write; + + fn main(string: &mut String) { + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_and_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + } + + mod add_assign { + mod imported_anonymously { + fn main(string: &mut String) { + use std::fmt::Write as _; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported { + fn main(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_anonymously_in_module { + use std::fmt::Write as _; + + fn main(string: &mut String) { + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_in_module { + use std::fmt::Write; + + fn main(string: &mut String) { + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + + mod imported_and_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string + } + } + } +} diff --git a/tests/ui/format_push_string.rs b/tests/ui/format_push_string.rs index 056ef59ff0e2..1ed0f5b3ac59 100644 --- a/tests/ui/format_push_string.rs +++ b/tests/ui/format_push_string.rs @@ -1,44 +1,132 @@ #![warn(clippy::format_push_string)] fn main() { + use std::fmt::Write; + let mut string = String::new(); string += &format!("{:?}", 1234); //~^ format_push_string string.push_str(&format!("{:?}", 5678)); //~^ format_push_string + + macro_rules! string { + () => { + String::new() + }; + } + string!().push_str(&format!("{:?}", 5678)); + //~^ format_push_string } -mod issue9493 { - pub fn u8vec_to_hex(vector: &Vec, upper: bool) -> String { - let mut hex = String::with_capacity(vector.len() * 2); - for byte in vector { - hex += &(if upper { +// TODO: recognize the already imported `fmt::Write`, and don't add a note suggesting to import it +// again +mod import_write { + mod push_str { + mod imported_anonymously { + fn main(string: &mut String) { + use std::fmt::Write as _; + + string.push_str(&format!("{:?}", 1234)); //~^ format_push_string - - format!("{byte:02X}") - } else { - format!("{byte:02x}") - }); + } + } + + mod imported { + fn main(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_anonymously_in_module { + use std::fmt::Write as _; + + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_in_module { + use std::fmt::Write; + + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_and_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } } - hex } - pub fn other_cases() { - let mut s = String::new(); - // if let - s += &(if let Some(_a) = Some(1234) { - //~^ format_push_string + mod add_assign { + mod imported_anonymously { + fn main(string: &mut String) { + use std::fmt::Write as _; - format!("{}", 1234) - } else { - format!("{}", 1234) - }); - // match - s += &(match Some(1234) { - //~^ format_push_string - Some(_) => format!("{}", 1234), - None => format!("{}", 1234), - }); + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported { + fn main(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_anonymously_in_module { + use std::fmt::Write as _; + + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_in_module { + use std::fmt::Write; + + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + mod imported_and_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } } } diff --git a/tests/ui/format_push_string.stderr b/tests/ui/format_push_string.stderr index bba2a8947c43..05e26fcbfc2b 100644 --- a/tests/ui/format_push_string.stderr +++ b/tests/ui/format_push_string.stderr @@ -1,60 +1,199 @@ error: `format!(..)` appended to existing `String` - --> tests/ui/format_push_string.rs:5:5 + --> tests/ui/format_push_string.rs:7:5 | LL | string += &format!("{:?}", 1234); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider using `write!` to avoid the extra allocation + = note: you may need to import the `std::fmt::Write` trait = note: `-D clippy::format-push-string` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::format_push_string)]` +help: consider using `write!` to avoid the extra allocation + | +LL - string += &format!("{:?}", 1234); +LL + let _ = write!(string, "{:?}", 1234); + | error: `format!(..)` appended to existing `String` - --> tests/ui/format_push_string.rs:8:5 + --> tests/ui/format_push_string.rs:10:5 | LL | string.push_str(&format!("{:?}", 5678)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider using `write!` to avoid the extra allocation + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 5678)); +LL + let _ = write!(string, "{:?}", 5678); + | error: `format!(..)` appended to existing `String` - --> tests/ui/format_push_string.rs:16:13 + --> tests/ui/format_push_string.rs:18:5 | -LL | / hex += &(if upper { -LL | | -LL | | -LL | | format!("{byte:02X}") -LL | | } else { -LL | | format!("{byte:02x}") -LL | | }); - | |______________^ +LL | string!().push_str(&format!("{:?}", 5678)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string!().push_str(&format!("{:?}", 5678)); +LL + let _ = write!(string!(), "{:?}", 5678); | - = help: consider using `write!` to avoid the extra allocation error: `format!(..)` appended to existing `String` - --> tests/ui/format_push_string.rs:30:9 + --> tests/ui/format_push_string.rs:30:17 | -LL | / s += &(if let Some(_a) = Some(1234) { -LL | | -LL | | -LL | | format!("{}", 1234) -LL | | } else { -LL | | format!("{}", 1234) -LL | | }); - | |__________^ +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); | - = help: consider using `write!` to avoid the extra allocation error: `format!(..)` appended to existing `String` - --> tests/ui/format_push_string.rs:38:9 + --> tests/ui/format_push_string.rs:39:17 | -LL | / s += &(match Some(1234) { -LL | | -LL | | Some(_) => format!("{}", 1234), -LL | | None => format!("{}", 1234), -LL | | }); - | |__________^ +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); | - = help: consider using `write!` to avoid the extra allocation -error: aborting due to 5 previous errors +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:48:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:57:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:66:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:73:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:84:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:93:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:102:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:111:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:120:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string.rs:127:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: aborting due to 15 previous errors diff --git a/tests/ui/format_push_string_no_core.rs b/tests/ui/format_push_string_no_core.rs new file mode 100644 index 000000000000..4bc45906fa78 --- /dev/null +++ b/tests/ui/format_push_string_no_core.rs @@ -0,0 +1,15 @@ +//@check-pass +#![warn(clippy::format_push_string)] +#![no_std] +#![feature(no_core)] +#![no_core] + +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +fn foo(string: &mut String) { + // can't suggest even `core::fmt::Write` because of `#![no_core]` + string.push_str(&format!("{:?}", 1234)); +} diff --git a/tests/ui/format_push_string_no_std.fixed b/tests/ui/format_push_string_no_std.fixed new file mode 100644 index 000000000000..32d8659dcbd5 --- /dev/null +++ b/tests/ui/format_push_string_no_std.fixed @@ -0,0 +1,15 @@ +#![warn(clippy::format_push_string)] +#![no_std] + +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +fn foo(string: &mut String) { + use core::fmt::Write; + + // TODO: recognize the already imported `fmt::Write`, and don't suggest importing it again + let _ = write!(string, "{:?}", 1234); + //~^ format_push_string +} diff --git a/tests/ui/format_push_string_no_std.rs b/tests/ui/format_push_string_no_std.rs new file mode 100644 index 000000000000..a74189abe528 --- /dev/null +++ b/tests/ui/format_push_string_no_std.rs @@ -0,0 +1,15 @@ +#![warn(clippy::format_push_string)] +#![no_std] + +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +fn foo(string: &mut String) { + use core::fmt::Write; + + // TODO: recognize the already imported `fmt::Write`, and don't suggest importing it again + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string +} diff --git a/tests/ui/format_push_string_no_std.stderr b/tests/ui/format_push_string_no_std.stderr new file mode 100644 index 000000000000..30fd42ac71b2 --- /dev/null +++ b/tests/ui/format_push_string_no_std.stderr @@ -0,0 +1,17 @@ +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_no_std.rs:13:5 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `core::fmt::Write` trait + = note: `-D clippy::format-push-string` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::format_push_string)]` +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/format_push_string_no_std_unfixable.rs b/tests/ui/format_push_string_no_std_unfixable.rs new file mode 100644 index 000000000000..f5ed5e435b5a --- /dev/null +++ b/tests/ui/format_push_string_no_std_unfixable.rs @@ -0,0 +1,13 @@ +//@no-rustfix +#![warn(clippy::format_push_string)] +#![no_std] + +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +fn foo(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string +} diff --git a/tests/ui/format_push_string_no_std_unfixable.stderr b/tests/ui/format_push_string_no_std_unfixable.stderr new file mode 100644 index 000000000000..cc716c84efe2 --- /dev/null +++ b/tests/ui/format_push_string_no_std_unfixable.stderr @@ -0,0 +1,17 @@ +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_no_std_unfixable.rs:11:5 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `core::fmt::Write` trait + = note: `-D clippy::format-push-string` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::format_push_string)]` +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/format_push_string_unfixable.rs b/tests/ui/format_push_string_unfixable.rs new file mode 100644 index 000000000000..ff6c13fe4a49 --- /dev/null +++ b/tests/ui/format_push_string_unfixable.rs @@ -0,0 +1,144 @@ +//@no-rustfix +#![warn(clippy::format_push_string)] + +mod issue9493 { + pub fn u8vec_to_hex(vector: &Vec, upper: bool) -> String { + let mut hex = String::with_capacity(vector.len() * 2); + for byte in vector { + hex += &(if upper { + format!("{byte:02X}") + //~^ format_push_string + } else { + format!("{byte:02x}") + }); + } + hex + } + + pub fn other_cases() { + let mut s = String::new(); + // if let + s += &(if let Some(_a) = Some(1234) { + format!("{}", 1234) + //~^ format_push_string + } else { + format!("{}", 1234) + }); + // match + s += &(match Some(1234) { + Some(_) => format!("{}", 1234), + //~^ format_push_string + None => format!("{}", 1234), + }); + } +} + +mod import_write { + mod push_str { + // TODO: suggest importing `std::fmt::Write`; + mod not_imported { + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing the first time, but not again + mod not_imported_and_not_imported { + fn foo(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing the first time, but not again + mod not_imported_and_imported { + fn foo(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing, but only for `bar` + mod imported_and_not_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + } + + mod add_assign { + // TODO: suggest importing `std::fmt::Write`; + mod not_imported { + fn main(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing the first time, but not again + mod not_imported_and_not_imported { + fn foo(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing the first time, but not again + mod not_imported_and_imported { + fn foo(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + + // TODO: suggest importing, but only for `bar` + mod imported_and_not_imported { + fn foo(string: &mut String) { + use std::fmt::Write; + + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + + fn bar(string: &mut String) { + string.push_str(&format!("{:?}", 1234)); + //~^ format_push_string + } + } + } +} + +fn main() {} diff --git a/tests/ui/format_push_string_unfixable.stderr b/tests/ui/format_push_string_unfixable.stderr new file mode 100644 index 000000000000..145e7fcc440d --- /dev/null +++ b/tests/ui/format_push_string_unfixable.stderr @@ -0,0 +1,233 @@ +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:8:13 + | +LL | / hex += &(if upper { +LL | | format!("{byte:02X}") + | | --------------------- `format!` used here +LL | | +LL | | } else { +LL | | format!("{byte:02x}") + | | --------------------- `format!` used here +LL | | }); + | |______________^ + | + = help: consider using `write!` to avoid the extra allocation + = note: you may need to import the `std::fmt::Write` trait + = note: `-D clippy::format-push-string` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::format_push_string)]` + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:21:9 + | +LL | / s += &(if let Some(_a) = Some(1234) { +LL | | format!("{}", 1234) + | | ------------------- `format!` used here +LL | | +LL | | } else { +LL | | format!("{}", 1234) + | | ------------------- `format!` used here +LL | | }); + | |__________^ + | + = help: consider using `write!` to avoid the extra allocation + = note: you may need to import the `std::fmt::Write` trait + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:28:9 + | +LL | / s += &(match Some(1234) { +LL | | Some(_) => format!("{}", 1234), + | | ------------------- `format!` used here +LL | | +LL | | None => format!("{}", 1234), + | | ------------------- `format!` used here +LL | | }); + | |__________^ + | + = help: consider using `write!` to avoid the extra allocation + = note: you may need to import the `std::fmt::Write` trait + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:41:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:49:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:54:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:62:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:69:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:79:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:84:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:94:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:102:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:107:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:115:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:122:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:132:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: `format!(..)` appended to existing `String` + --> tests/ui/format_push_string_unfixable.rs:137:17 + | +LL | string.push_str(&format!("{:?}", 1234)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: you may need to import the `std::fmt::Write` trait +help: consider using `write!` to avoid the extra allocation + | +LL - string.push_str(&format!("{:?}", 1234)); +LL + let _ = write!(string, "{:?}", 1234); + | + +error: aborting due to 17 previous errors + diff --git a/tests/ui/if_not_else.fixed b/tests/ui/if_not_else.fixed index 4e6f43e5671e..a29847f0cf97 100644 --- a/tests/ui/if_not_else.fixed +++ b/tests/ui/if_not_else.fixed @@ -76,3 +76,20 @@ fn with_annotations() { println!("foo is false"); } } + +fn issue15924() { + let x = 0; + if matches!(x, 0..10) { + println!(":("); + } else { + //~^ if_not_else + println!(":)"); + } + + if dbg!(x) == 1 { + println!(":("); + } else { + //~^ if_not_else + println!(":)"); + } +} diff --git a/tests/ui/if_not_else.rs b/tests/ui/if_not_else.rs index 6cd2e3bd63fe..4ae11d6ad90e 100644 --- a/tests/ui/if_not_else.rs +++ b/tests/ui/if_not_else.rs @@ -76,3 +76,20 @@ fn with_annotations() { println!("foo"); /* foo */ } } + +fn issue15924() { + let x = 0; + if !matches!(x, 0..10) { + //~^ if_not_else + println!(":)"); + } else { + println!(":("); + } + + if dbg!(x) != 1 { + //~^ if_not_else + println!(":)"); + } else { + println!(":("); + } +} diff --git a/tests/ui/if_not_else.stderr b/tests/ui/if_not_else.stderr index 824837bd52bb..0682bf80da55 100644 --- a/tests/ui/if_not_else.stderr +++ b/tests/ui/if_not_else.stderr @@ -147,5 +147,47 @@ LL + println!("foo is false"); LL + } | -error: aborting due to 6 previous errors +error: unnecessary boolean `not` operation + --> tests/ui/if_not_else.rs:82:5 + | +LL | / if !matches!(x, 0..10) { +LL | | +LL | | println!(":)"); +LL | | } else { +LL | | println!(":("); +LL | | } + | |_____^ + | +help: try + | +LL ~ if matches!(x, 0..10) { +LL + println!(":("); +LL + } else { +LL + +LL + println!(":)"); +LL + } + | + +error: unnecessary `!=` operation + --> tests/ui/if_not_else.rs:89:5 + | +LL | / if dbg!(x) != 1 { +LL | | +LL | | println!(":)"); +LL | | } else { +LL | | println!(":("); +LL | | } + | |_____^ + | +help: try + | +LL ~ if dbg!(x) == 1 { +LL + println!(":("); +LL + } else { +LL + +LL + println!(":)"); +LL + } + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index 7da9401a308f..ce122ac69b12 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -3,10 +3,20 @@ fn main() { // Should issue an error. - let _ = foo().then(|| { println!("true!"); "foo" }); + let _ = foo().then(|| { + //~^ if_then_some_else_none + + println!("true!"); + "foo" + }); // Should issue an error when macros are used. - let _ = matches!(true, true).then(|| { println!("true!"); matches!(true, false) }); + let _ = matches!(true, true).then(|| { + //~^ if_then_some_else_none + + println!("true!"); + matches!(true, false) + }); // Should issue an error. Binary expression `o < 32` should be parenthesized. let x = Some(5); @@ -71,7 +81,12 @@ fn _msrv_1_49() { #[clippy::msrv = "1.50"] fn _msrv_1_50() { - let _ = foo().then(|| { println!("true!"); 150 }); + let _ = foo().then(|| { + //~^ if_then_some_else_none + + println!("true!"); + 150 + }); } fn foo() -> bool { @@ -182,7 +197,10 @@ fn issue15005() { fn next(&mut self) -> Option { //~v if_then_some_else_none - (self.count < 5).then(|| { self.count += 1; self.count }) + (self.count < 5).then(|| { + self.count += 1; + self.count + }) } } } @@ -195,7 +213,10 @@ fn statements_from_macro() { }; } //~v if_then_some_else_none - let _ = true.then(|| { mac!(); 42 }); + let _ = true.then(|| { + mac!(); + 42 + }); } fn dont_lint_inside_macros() { @@ -218,3 +239,24 @@ mod issue15770 { Ok(()) } } + +mod issue16176 { + pub async fn foo() -> u32 { + todo!() + } + + pub async fn bar(cond: bool) -> Option { + if cond { Some(foo().await) } else { None } // OK + } +} + +fn issue16269() -> Option { + use std::cell::UnsafeCell; + + //~v if_then_some_else_none + (1 <= 3).then(|| { + let a = UnsafeCell::new(1); + // SAFETY: `bytes` bytes starting at `new_end` were just reserved. + unsafe { *a.get() } + }) +} diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 02962f83ce8a..1d6c86d94492 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -274,3 +274,26 @@ mod issue15770 { Ok(()) } } + +mod issue16176 { + pub async fn foo() -> u32 { + todo!() + } + + pub async fn bar(cond: bool) -> Option { + if cond { Some(foo().await) } else { None } // OK + } +} + +fn issue16269() -> Option { + use std::cell::UnsafeCell; + + //~v if_then_some_else_none + if 1 <= 3 { + let a = UnsafeCell::new(1); + // SAFETY: `bytes` bytes starting at `new_end` were just reserved. + Some(unsafe { *a.get() }) + } else { + None + } +} diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index 58651a055942..eff5f8c82dcb 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -9,10 +9,19 @@ LL | | println!("true!"); ... | LL | | None LL | | }; - | |_____^ help: try: `foo().then(|| { println!("true!"); "foo" })` + | |_____^ | = note: `-D clippy::if-then-some-else-none` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::if_then_some_else_none)]` +help: try + | +LL ~ let _ = foo().then(|| { +LL + +LL + +LL + println!("true!"); +LL + "foo" +LL ~ }); + | error: this could be simplified with `bool::then` --> tests/ui/if_then_some_else_none.rs:16:13 @@ -25,7 +34,17 @@ LL | | println!("true!"); ... | LL | | None LL | | }; - | |_____^ help: try: `matches!(true, true).then(|| { println!("true!"); matches!(true, false) })` + | |_____^ + | +help: try + | +LL ~ let _ = matches!(true, true).then(|| { +LL + +LL + +LL + println!("true!"); +LL + matches!(true, false) +LL ~ }); + | error: this could be simplified with `bool::then_some` --> tests/ui/if_then_some_else_none.rs:27:28 @@ -50,7 +69,17 @@ LL | | println!("true!"); ... | LL | | None LL | | }; - | |_____^ help: try: `foo().then(|| { println!("true!"); 150 })` + | |_____^ + | +help: try + | +LL ~ let _ = foo().then(|| { +LL + +LL + +LL + println!("true!"); +LL + 150 +LL ~ }); + | error: this could be simplified with `bool::then` --> tests/ui/if_then_some_else_none.rs:138:5 @@ -125,7 +154,15 @@ LL | | Some(self.count) LL | | } else { LL | | None LL | | } - | |_____________^ help: try: `(self.count < 5).then(|| { self.count += 1; self.count })` + | |_____________^ + | +help: try + | +LL ~ (self.count < 5).then(|| { +LL + self.count += 1; +LL + self.count +LL + }) + | error: this could be simplified with `bool::then` --> tests/ui/if_then_some_else_none.rs:249:13 @@ -137,7 +174,36 @@ LL | | Some(42) LL | | } else { LL | | None LL | | }; - | |_____^ help: try: `true.then(|| { mac!(); 42 })` + | |_____^ + | +help: try + | +LL ~ let _ = true.then(|| { +LL + mac!(); +LL + 42 +LL ~ }); + | -error: aborting due to 13 previous errors +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:292:5 + | +LL | / if 1 <= 3 { +LL | | let a = UnsafeCell::new(1); +LL | | // SAFETY: `bytes` bytes starting at `new_end` were just reserved. +LL | | Some(unsafe { *a.get() }) +LL | | } else { +LL | | None +LL | | } + | |_____^ + | +help: try + | +LL ~ (1 <= 3).then(|| { +LL + let a = UnsafeCell::new(1); +LL + // SAFETY: `bytes` bytes starting at `new_end` were just reserved. +LL + unsafe { *a.get() } +LL + }) + | + +error: aborting due to 14 previous errors diff --git a/tests/ui/manual_instant_elapsed.fixed b/tests/ui/manual_instant_elapsed.fixed index a04c601e08c1..2fa5702f8a04 100644 --- a/tests/ui/manual_instant_elapsed.fixed +++ b/tests/ui/manual_instant_elapsed.fixed @@ -28,3 +28,19 @@ fn main() { // //~^^ manual_instant_elapsed } + +fn issue16236() { + use std::ops::Sub as _; + macro_rules! deref { + ($e:expr) => { + *$e + }; + } + + let start = &Instant::now(); + let _ = deref!(start).elapsed(); + //~^ manual_instant_elapsed + + deref!(start).elapsed(); + //~^ manual_instant_elapsed +} diff --git a/tests/ui/manual_instant_elapsed.rs b/tests/ui/manual_instant_elapsed.rs index 7c67f6acf85d..e7a0e6499e74 100644 --- a/tests/ui/manual_instant_elapsed.rs +++ b/tests/ui/manual_instant_elapsed.rs @@ -28,3 +28,19 @@ fn main() { // //~^^ manual_instant_elapsed } + +fn issue16236() { + use std::ops::Sub as _; + macro_rules! deref { + ($e:expr) => { + *$e + }; + } + + let start = &Instant::now(); + let _ = Instant::now().sub(deref!(start)); + //~^ manual_instant_elapsed + + Instant::now() - deref!(start); + //~^ manual_instant_elapsed +} diff --git a/tests/ui/manual_instant_elapsed.stderr b/tests/ui/manual_instant_elapsed.stderr index e84f3126f707..e42ac5739a29 100644 --- a/tests/ui/manual_instant_elapsed.stderr +++ b/tests/ui/manual_instant_elapsed.stderr @@ -13,5 +13,17 @@ error: manual implementation of `Instant::elapsed` LL | Instant::now() - *ref_to_instant; // to ensure parens are added correctly | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*ref_to_instant).elapsed()` -error: aborting due to 2 previous errors +error: manual implementation of `Instant::elapsed` + --> tests/ui/manual_instant_elapsed.rs:41:13 + | +LL | let _ = Instant::now().sub(deref!(start)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `deref!(start).elapsed()` + +error: manual implementation of `Instant::elapsed` + --> tests/ui/manual_instant_elapsed.rs:44:5 + | +LL | Instant::now() - deref!(start); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `deref!(start).elapsed()` + +error: aborting due to 4 previous errors diff --git a/tests/ui/map_unwrap_or.stderr b/tests/ui/map_unwrap_or.stderr index b0b02f3f8d6b..df0207c420e6 100644 --- a/tests/ui/map_unwrap_or.stderr +++ b/tests/ui/map_unwrap_or.stderr @@ -127,6 +127,14 @@ LL | | x + 1 LL | | } LL | | ).unwrap_or_else(|| 0); | |__________________________^ + | +help: try + | +LL ~ let _ = opt.map_or_else(|| 0, |x| { +LL + +LL + x + 1 +LL ~ }); + | error: called `map().unwrap_or_else()` on an `Option` value --> tests/ui/map_unwrap_or.rs:63:13 @@ -138,6 +146,12 @@ LL | | .unwrap_or_else(|| LL | | 0 LL | | ); | |_________^ + | +help: try + | +LL ~ let _ = opt.map_or_else(|| +LL ~ 0, |x| x + 1); + | error: called `map().unwrap_or(false)` on an `Option` value --> tests/ui/map_unwrap_or.rs:70:13 @@ -161,6 +175,14 @@ LL | | x + 1 LL | | } LL | | ).unwrap_or_else(|_e| 0); | |____________________________^ + | +help: try + | +LL ~ let _ = res.map_or_else(|_e| 0, |x| { +LL + +LL + x + 1 +LL ~ }); + | error: called `map().unwrap_or_else()` on a `Result` value --> tests/ui/map_unwrap_or.rs:86:13 @@ -172,6 +194,13 @@ LL | | .unwrap_or_else(|_e| { LL | | 0 LL | | }); | |__________^ + | +help: try + | +LL ~ let _ = res.map_or_else(|_e| { +LL + 0 +LL ~ }, |x| x + 1); + | error: called `map().unwrap_or_else()` on a `Result` value --> tests/ui/map_unwrap_or.rs:111:13 diff --git a/tests/ui/match_like_matches_macro.fixed b/tests/ui/match_like_matches_macro.fixed index dad59c1ce6e4..045ee32bd8bb 100644 --- a/tests/ui/match_like_matches_macro.fixed +++ b/tests/ui/match_like_matches_macro.fixed @@ -1,6 +1,7 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, + irrefutable_let_patterns, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -230,3 +231,24 @@ fn issue15841(opt: Option>>, value: i32) { let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() })); //~^^^^ match_like_matches_macro } + +fn issue16015() -> bool { + use std::any::{TypeId, type_name}; + pub struct GetTypeId(T); + + impl GetTypeId { + pub const VALUE: TypeId = TypeId::of::(); + } + + macro_rules! typeid { + ($t:ty) => { + GetTypeId::<$t>::VALUE + }; + } + + matches!(typeid!(T), _); + //~^^^^ match_like_matches_macro + + matches!(typeid!(U), _) + //~^ match_like_matches_macro +} diff --git a/tests/ui/match_like_matches_macro.rs b/tests/ui/match_like_matches_macro.rs index 94bc6433e5cb..231e1ae98f86 100644 --- a/tests/ui/match_like_matches_macro.rs +++ b/tests/ui/match_like_matches_macro.rs @@ -1,6 +1,7 @@ #![warn(clippy::match_like_matches_macro)] #![allow( unreachable_patterns, + irrefutable_let_patterns, clippy::equatable_if_let, clippy::needless_borrowed_reference, clippy::redundant_guards @@ -277,3 +278,27 @@ fn issue15841(opt: Option>>, value: i32) { }; //~^^^^ match_like_matches_macro } + +fn issue16015() -> bool { + use std::any::{TypeId, type_name}; + pub struct GetTypeId(T); + + impl GetTypeId { + pub const VALUE: TypeId = TypeId::of::(); + } + + macro_rules! typeid { + ($t:ty) => { + GetTypeId::<$t>::VALUE + }; + } + + match typeid!(T) { + _ => true, + _ => false, + }; + //~^^^^ match_like_matches_macro + + if let _ = typeid!(U) { true } else { false } + //~^ match_like_matches_macro +} diff --git a/tests/ui/match_like_matches_macro.stderr b/tests/ui/match_like_matches_macro.stderr index a8e352461dbb..bc3e3584938e 100644 --- a/tests/ui/match_like_matches_macro.stderr +++ b/tests/ui/match_like_matches_macro.stderr @@ -1,5 +1,5 @@ error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:13:14 + --> tests/ui/match_like_matches_macro.rs:14:14 | LL | let _y = match x { | ______________^ @@ -20,7 +20,7 @@ LL + let _y = matches!(x, Some(0)); | error: redundant pattern matching, consider using `is_some()` - --> tests/ui/match_like_matches_macro.rs:20:14 + --> tests/ui/match_like_matches_macro.rs:21:14 | LL | let _w = match x { | ______________^ @@ -33,7 +33,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]` error: redundant pattern matching, consider using `is_none()` - --> tests/ui/match_like_matches_macro.rs:27:14 + --> tests/ui/match_like_matches_macro.rs:28:14 | LL | let _z = match x { | ______________^ @@ -43,7 +43,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:34:15 + --> tests/ui/match_like_matches_macro.rs:35:15 | LL | let _zz = match x { | _______________^ @@ -62,7 +62,7 @@ LL + let _zz = !matches!(x, Some(r) if r == 0); | error: `if let .. else` expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:41:16 + --> tests/ui/match_like_matches_macro.rs:42:16 | LL | let _zzz = if let Some(5) = x { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -74,7 +74,7 @@ LL + let _zzz = matches!(x, Some(5)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:66:20 + --> tests/ui/match_like_matches_macro.rs:67:20 | LL | let _ans = match x { | ____________________^ @@ -95,7 +95,7 @@ LL + let _ans = matches!(x, E::A(_) | E::B(_)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:77:20 + --> tests/ui/match_like_matches_macro.rs:78:20 | LL | let _ans = match x { | ____________________^ @@ -119,7 +119,7 @@ LL + let _ans = matches!(x, E::A(_) | E::B(_)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:88:20 + --> tests/ui/match_like_matches_macro.rs:89:20 | LL | let _ans = match x { | ____________________^ @@ -140,7 +140,7 @@ LL + let _ans = !matches!(x, E::B(_) | E::C); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:149:18 + --> tests/ui/match_like_matches_macro.rs:150:18 | LL | let _z = match &z { | __________________^ @@ -159,7 +159,7 @@ LL + let _z = matches!(z, Some(3)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:159:18 + --> tests/ui/match_like_matches_macro.rs:160:18 | LL | let _z = match &z { | __________________^ @@ -178,7 +178,7 @@ LL + let _z = matches!(&z, Some(3)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:177:21 + --> tests/ui/match_like_matches_macro.rs:178:21 | LL | let _ = match &z { | _____________________^ @@ -197,7 +197,7 @@ LL + let _ = matches!(&z, AnEnum::X); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:192:20 + --> tests/ui/match_like_matches_macro.rs:193:20 | LL | let _res = match &val { | ____________________^ @@ -216,7 +216,7 @@ LL + let _res = matches!(&val, &Some(ref _a)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:205:20 + --> tests/ui/match_like_matches_macro.rs:206:20 | LL | let _res = match &val { | ____________________^ @@ -235,7 +235,7 @@ LL + let _res = matches!(&val, &Some(ref _a)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:264:14 + --> tests/ui/match_like_matches_macro.rs:265:14 | LL | let _y = match Some(5) { | ______________^ @@ -254,7 +254,7 @@ LL + let _y = matches!(Some(5), Some(0)); | error: match expression looks like `matches!` macro - --> tests/ui/match_like_matches_macro.rs:274:13 + --> tests/ui/match_like_matches_macro.rs:275:13 | LL | let _ = match opt { | _____________^ @@ -272,5 +272,35 @@ LL - }; LL + let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() })); | -error: aborting due to 15 previous errors +error: match expression looks like `matches!` macro + --> tests/ui/match_like_matches_macro.rs:296:5 + | +LL | / match typeid!(T) { +LL | | _ => true, +LL | | _ => false, +LL | | }; + | |_____^ + | +help: use `matches!` directly + | +LL - match typeid!(T) { +LL - _ => true, +LL - _ => false, +LL - }; +LL + matches!(typeid!(T), _); + | + +error: `if let .. else` expression looks like `matches!` macro + --> tests/ui/match_like_matches_macro.rs:302:5 + | +LL | if let _ = typeid!(U) { true } else { false } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!` directly + | +LL - if let _ = typeid!(U) { true } else { false } +LL + matches!(typeid!(U), _) + | + +error: aborting due to 17 previous errors diff --git a/tests/ui/multiple_unsafe_ops_per_block.rs b/tests/ui/multiple_unsafe_ops_per_block.rs index c1512ba3e269..0ff881472cbb 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.rs +++ b/tests/ui/multiple_unsafe_ops_per_block.rs @@ -312,4 +312,137 @@ fn check_closures() { } } +fn issue16116() { + unsafe fn foo() -> u32 { + 0 + } + + // Do not lint even though `format!` expansion + // contains unsafe calls. + unsafe { + let _ = format!("{}", foo()); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + let _ = format!("{}", foo()); + let _ = format!("{}", foo()); + } + + // Do not lint: only one `assert!()` argument is unsafe + unsafe { + assert_eq!(foo(), 0, "{}", 1 + 2); + } + + // Each argument of a macro call may count as an unsafe operation. + unsafe { + //~^ multiple_unsafe_ops_per_block + assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation + } + + macro_rules! twice { + ($e:expr) => {{ + $e; + $e; + }}; + } + + // Do not lint, a repeated argument used twice by a macro counts + // as at most one unsafe operation. + unsafe { + twice!(foo()); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + twice!(foo()); + twice!(foo()); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + assert_eq!(foo(), 0, "{}", 1 + 2); + assert_eq!(foo(), 0, "{}", 1 + 2); + } + + macro_rules! unsafe_twice { + ($e:expr) => { + unsafe { + $e; + $e; + } + }; + }; + + // A macro whose expansion contains unsafe blocks will not + // check inside the blocks. + unsafe { + unsafe_twice!(foo()); + } + + macro_rules! double_non_arg_unsafe { + () => {{ + _ = str::from_utf8_unchecked(&[]); + _ = str::from_utf8_unchecked(&[]); + }}; + } + + // Do not lint: each unsafe expression contained in the + // macro expansion will count towards the macro call. + unsafe { + double_non_arg_unsafe!(); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + double_non_arg_unsafe!(); + double_non_arg_unsafe!(); + } + + // Do not lint: the inner macro call counts as one unsafe op. + unsafe { + assert_eq!(double_non_arg_unsafe!(), ()); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + assert_eq!(double_non_arg_unsafe!(), ()); + assert_eq!(double_non_arg_unsafe!(), ()); + } + + unsafe { + //~^ multiple_unsafe_ops_per_block + assert_eq!((double_non_arg_unsafe!(), double_non_arg_unsafe!()), ((), ())); + } + + macro_rules! unsafe_with_arg { + ($e:expr) => {{ + _ = str::from_utf8_unchecked(&[]); + $e; + }}; + } + + // A confusing situation: the macro call counts towards two unsafe calls, + // one coming from inside the macro itself, and one coming from its argument. + // The error message may seem a bit strange as both the macro call and its + // argument will be marked as counting as unsafe ops, but a short investigation + // in those rare situations should sort it out easily. + unsafe { + //~^ multiple_unsafe_ops_per_block + unsafe_with_arg!(foo()); + } + + macro_rules! ignore { + ($e: expr) => {}; + } + + // Another surprising case is when the macro argument is not + // used in the expansion, but in this case we won't see the + // unsafe operation at all. + unsafe { + ignore!(foo()); + ignore!(foo()); + } +} + fn main() {} diff --git a/tests/ui/multiple_unsafe_ops_per_block.stderr b/tests/ui/multiple_unsafe_ops_per_block.stderr index 63f7742b734b..185225bd28c8 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.stderr +++ b/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -31,16 +31,16 @@ LL | | *raw_ptr(); LL | | } | |_____^ | -note: union field access occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:50:14 - | -LL | drop(u.u); - | ^^^ note: raw pointer dereference occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:51:9 | LL | *raw_ptr(); | ^^^^^^^^^^ +note: union field access occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:50:14 + | +LL | drop(u.u); + | ^^^ error: this `unsafe` block contains 3 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:56:5 @@ -58,16 +58,16 @@ note: inline assembly used here | LL | asm!("nop"); | ^^^^^^^^^^^ -note: unsafe method call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:59:9 - | -LL | sample.not_very_safe(); - | ^^^^^^^^^^^^^^^^^^^^^^ note: modification of a mutable static occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:60:9 | LL | STATIC = 0; | ^^^^^^^^^^ +note: unsafe method call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:59:9 + | +LL | sample.not_very_safe(); + | ^^^^^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 6 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:66:5 @@ -81,36 +81,36 @@ LL | | asm!("nop"); LL | | } | |_____^ | -note: union field access occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:68:14 - | -LL | drop(u.u); - | ^^^ note: access of a mutable static occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:69:14 | LL | drop(STATIC); | ^^^^^^ -note: unsafe method call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:70:9 - | -LL | sample.not_very_safe(); - | ^^^^^^^^^^^^^^^^^^^^^^ -note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:71:9 - | -LL | not_very_safe(); - | ^^^^^^^^^^^^^^^ -note: raw pointer dereference occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:72:9 - | -LL | *raw_ptr(); - | ^^^^^^^^^^ note: inline assembly used here --> tests/ui/multiple_unsafe_ops_per_block.rs:73:9 | LL | asm!("nop"); | ^^^^^^^^^^^ +note: raw pointer dereference occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:72:9 + | +LL | *raw_ptr(); + | ^^^^^^^^^^ +note: union field access occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:68:14 + | +LL | drop(u.u); + | ^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:71:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: unsafe method call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:70:9 + | +LL | sample.not_very_safe(); + | ^^^^^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:109:5 @@ -139,16 +139,16 @@ error: this `unsafe` block contains 2 unsafe operations, expected only one LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:118:18 - | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: raw pointer dereference occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:118:43 | LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } | ^^^^^^^^^^^^^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:118:18 + | +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:139:9 @@ -224,16 +224,16 @@ LL | | foo().await; LL | | } | |_____^ | -note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:194:9 - | -LL | not_very_safe(); - | ^^^^^^^^^^^^^^^ note: modification of a mutable static occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:195:9 | LL | STATIC += 1; | ^^^^^^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:194:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:207:5 @@ -265,16 +265,16 @@ LL | | Some(foo_unchecked()).unwrap_unchecked().await; LL | | } | |_____^ | -note: unsafe method call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:216:9 - | -LL | Some(foo_unchecked()).unwrap_unchecked().await; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: unsafe function call occurs here --> tests/ui/multiple_unsafe_ops_per_block.rs:216:14 | LL | Some(foo_unchecked()).unwrap_unchecked().await; | ^^^^^^^^^^^^^^^ +note: unsafe method call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:216:9 + | +LL | Some(foo_unchecked()).unwrap_unchecked().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one --> tests/ui/multiple_unsafe_ops_per_block.rs:236:5 @@ -359,5 +359,170 @@ note: unsafe function call occurs here LL | apply(|| f(0)); | ^^^^ -error: aborting due to 16 previous errors +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:326:5 + | +LL | / unsafe { +LL | | +LL | | let _ = format!("{}", foo()); +LL | | let _ = format!("{}", foo()); +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:328:31 + | +LL | let _ = format!("{}", foo()); + | ^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:329:31 + | +LL | let _ = format!("{}", foo()); + | ^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:338:5 + | +LL | / unsafe { +LL | | +LL | | assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:340:20 + | +LL | assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation + | ^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:340:36 + | +LL | assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation + | ^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:356:5 + | +LL | / unsafe { +LL | | +LL | | twice!(foo()); +LL | | twice!(foo()); +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:358:16 + | +LL | twice!(foo()); + | ^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:359:16 + | +LL | twice!(foo()); + | ^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:362:5 + | +LL | / unsafe { +LL | | +LL | | assert_eq!(foo(), 0, "{}", 1 + 2); +LL | | assert_eq!(foo(), 0, "{}", 1 + 2); +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:364:20 + | +LL | assert_eq!(foo(), 0, "{}", 1 + 2); + | ^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:365:20 + | +LL | assert_eq!(foo(), 0, "{}", 1 + 2); + | ^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:396:5 + | +LL | / unsafe { +LL | | +LL | | double_non_arg_unsafe!(); +LL | | double_non_arg_unsafe!(); +LL | | } + | |_____^ + | +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:398:9 + | +LL | double_non_arg_unsafe!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:399:9 + | +LL | double_non_arg_unsafe!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:407:5 + | +LL | / unsafe { +LL | | +LL | | assert_eq!(double_non_arg_unsafe!(), ()); +LL | | assert_eq!(double_non_arg_unsafe!(), ()); +LL | | } + | |_____^ + | +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:409:20 + | +LL | assert_eq!(double_non_arg_unsafe!(), ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:410:20 + | +LL | assert_eq!(double_non_arg_unsafe!(), ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:413:5 + | +LL | / unsafe { +LL | | +LL | | assert_eq!((double_non_arg_unsafe!(), double_non_arg_unsafe!()), ((), ())); +LL | | } + | |_____^ + | +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:415:21 + | +LL | assert_eq!((double_non_arg_unsafe!(), double_non_arg_unsafe!()), ((), ())); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:415:47 + | +LL | assert_eq!((double_non_arg_unsafe!(), double_non_arg_unsafe!()), ((), ())); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:430:5 + | +LL | / unsafe { +LL | | +LL | | unsafe_with_arg!(foo()); +LL | | } + | |_____^ + | +note: this macro call expands into one or more unsafe operations + --> tests/ui/multiple_unsafe_ops_per_block.rs:432:9 + | +LL | unsafe_with_arg!(foo()); + | ^^^^^^^^^^^^^^^^^^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:432:26 + | +LL | unsafe_with_arg!(foo()); + | ^^^^^ + +error: aborting due to 24 previous errors diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index ba1451bf9704..99027e79b664 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -214,3 +214,8 @@ mod issue8055_regression { .len(); } } + +fn issue16270() { + // Do not lint, `..` implements `Index` but is not `usize` + _ = &(1..3).collect::>()[..]; +} diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index e054cd01e6f5..683cc49c9af3 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -214,3 +214,8 @@ mod issue8055_regression { .len(); } } + +fn issue16270() { + // Do not lint, `..` implements `Index` but is not `usize` + _ = &(1..3).collect::>()[..]; +} diff --git a/tests/ui/needless_pass_by_ref_mut.stderr b/tests/ui/needless_pass_by_ref_mut.stderr index 94d98f0e9b12..c427f4c3e42c 100644 --- a/tests/ui/needless_pass_by_ref_mut.stderr +++ b/tests/ui/needless_pass_by_ref_mut.stderr @@ -1,213 +1,281 @@ -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:12:11 | LL | fn foo(s: &mut Vec, b: &u32, x: &mut u32) { - | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` + | ^----^^^^^^^^ + | | + | help: consider removing this `mut` | = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_pass_by_ref_mut)]` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:38:12 | LL | fn foo6(s: &mut Vec) { - | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` + | ^----^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:49:12 | LL | fn bar(&mut self) {} - | ^^^^^^^^^ help: consider changing to: `&self` + | ^----^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:52:29 | LL | fn mushroom(&self, vec: &mut Vec) -> usize { - | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` + | ^----^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:130:16 | LL | async fn a1(x: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:135:16 | LL | async fn a2(x: &mut i32, y: String) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:140:16 | LL | async fn a3(x: &mut i32, y: String, z: String) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:145:16 | LL | async fn a4(x: &mut i32, y: i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:150:24 | LL | async fn a5(x: i32, y: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:155:24 | LL | async fn a6(x: i32, y: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:160:32 | LL | async fn a7(x: i32, y: i32, z: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:165:24 | LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:165:45 | LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:201:16 | LL | fn cfg_warn(s: &mut u32) {} - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` | = note: this is cfg-gated and may require further changes -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:206:20 | LL | fn cfg_warn(s: &mut u32) {} - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` | = note: this is cfg-gated and may require further changes -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:219:39 | LL | async fn inner_async2(x: &mut i32, y: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:228:26 | LL | async fn inner_async3(x: &mut i32, y: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:248:30 | LL | async fn call_in_closure1(n: &mut str) { - | ^^^^^^^^ help: consider changing to: `&str` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:268:16 | LL | fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize { - | ^^^^^^^^^^ help: consider changing to: `&usize` + | ^----^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:280:22 | LL | async fn closure4(n: &mut usize) { - | ^^^^^^^^^^ help: consider changing to: `&usize` + | ^----^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:335:12 | LL | fn bar(&mut self) {} - | ^^^^^^^^^ help: consider changing to: `&self` + | ^----^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:338:18 | LL | async fn foo(&mut self, u: &mut i32, v: &mut u32) { - | ^^^^^^^^^ help: consider changing to: `&self` + | ^----^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:338:45 | LL | async fn foo(&mut self, u: &mut i32, v: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:347:46 | LL | async fn foo2(&mut self, u: &mut i32, v: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:364:18 | LL | fn _empty_tup(x: &mut (())) {} - | ^^^^^^^^^ help: consider changing to: `&()` + | ^^----^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:366:19 | LL | fn _single_tup(x: &mut ((i32,))) {} - | ^^^^^^^^^^^^^ help: consider changing to: `&(i32,)` + | ^^----^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:368:18 | LL | fn _multi_tup(x: &mut ((i32, u32))) {} - | ^^^^^^^^^^^^^^^^^ help: consider changing to: `&(i32, u32)` + | ^^----^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:370:11 | LL | fn _fn(x: &mut (fn())) {} - | ^^^^^^^^^^^ help: consider changing to: `&fn()` + | ^^----^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:373:23 | LL | fn _extern_rust_fn(x: &mut extern "Rust" fn()) {} - | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&extern "Rust" fn()` + | ^----^^^^^^^^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:375:20 | LL | fn _extern_c_fn(x: &mut extern "C" fn()) {} - | ^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&extern "C" fn()` + | ^----^^^^^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:377:18 | LL | fn _unsafe_fn(x: &mut unsafe fn()) {} - | ^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe fn()` + | ^----^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:379:25 | LL | fn _unsafe_extern_fn(x: &mut unsafe extern "C" fn()) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn()` + | ^----^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:381:20 | LL | fn _fn_with_arg(x: &mut unsafe extern "C" fn(i32)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn(i32)` + | ^----^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider removing this `mut` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut.rs:383:20 | LL | fn _fn_with_ret(x: &mut unsafe extern "C" fn() -> (i32)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn() -> (i32)` + | ^----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: consider removing this `mut` error: aborting due to 34 previous errors diff --git a/tests/ui/needless_pass_by_ref_mut2.fixed b/tests/ui/needless_pass_by_ref_mut2.fixed index 0e2ac0202364..c462f1cc8d87 100644 --- a/tests/ui/needless_pass_by_ref_mut2.fixed +++ b/tests/ui/needless_pass_by_ref_mut2.fixed @@ -24,3 +24,9 @@ async fn inner_async4(u: &mut i32, v: &u32) { } fn main() {} + +//~v needless_pass_by_ref_mut +fn issue16267<'a>(msg: &str, slice: &'a [i32]) -> &'a [i32] { + println!("{msg}"); + &slice[0..5] +} diff --git a/tests/ui/needless_pass_by_ref_mut2.rs b/tests/ui/needless_pass_by_ref_mut2.rs index 9201d9a27298..b00f294c57f0 100644 --- a/tests/ui/needless_pass_by_ref_mut2.rs +++ b/tests/ui/needless_pass_by_ref_mut2.rs @@ -24,3 +24,9 @@ async fn inner_async4(u: &mut i32, v: &mut u32) { } fn main() {} + +//~v needless_pass_by_ref_mut +fn issue16267<'a>(msg: &str, slice: &'a mut [i32]) -> &'a [i32] { + println!("{msg}"); + &slice[0..5] +} diff --git a/tests/ui/needless_pass_by_ref_mut2.stderr b/tests/ui/needless_pass_by_ref_mut2.stderr index 9876a6b50718..aa5c412adb4d 100644 --- a/tests/ui/needless_pass_by_ref_mut2.stderr +++ b/tests/ui/needless_pass_by_ref_mut2.stderr @@ -1,17 +1,29 @@ -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut2.rs:8:26 | LL | async fn inner_async3(x: &mut i32, y: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&i32` + | ^----^^^ + | | + | help: consider removing this `mut` | = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_pass_by_ref_mut)]` -error: this argument is a mutable reference, but not used mutably +error: this parameter is a mutable reference but is not used mutably --> tests/ui/needless_pass_by_ref_mut2.rs:17:39 | LL | async fn inner_async4(u: &mut i32, v: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&u32` + | ^----^^^ + | | + | help: consider removing this `mut` -error: aborting due to 2 previous errors +error: this parameter is a mutable reference but is not used mutably + --> tests/ui/needless_pass_by_ref_mut2.rs:29:37 + | +LL | fn issue16267<'a>(msg: &str, slice: &'a mut [i32]) -> &'a [i32] { + | ^^^^----^^^^^ + | | + | help: consider removing this `mut` + +error: aborting due to 3 previous errors diff --git a/tests/ui/needless_type_cast.fixed b/tests/ui/needless_type_cast.fixed index 32c348d3ca3a..72eed32c4d73 100644 --- a/tests/ui/needless_type_cast.fixed +++ b/tests/ui/needless_type_cast.fixed @@ -9,6 +9,10 @@ fn generic(x: T) -> T { x } +fn returns_u8() -> u8 { + 10 +} + fn main() { let a: i32 = 10; //~^ needless_type_cast @@ -180,3 +184,86 @@ fn test_loop_with_generic() { }; let _ = x as i32; } + +fn test_size_of_cast() { + use std::mem::size_of; + // Should lint: suggest casting the initializer + let size: u64 = size_of::() as u64; + //~^ needless_type_cast + let _ = size as u64; + let _ = size as u64; +} + +fn test_suffixed_literal_cast() { + // Should lint: suggest casting the initializer + let a: i32 = 10u8 as i32; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_negative_literal() { + // Negative literal - should just change type, not add cast + let a: i32 = -10; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_suffixed_negative_literal() { + // Suffixed negative - needs cast + let a: i32 = -10i8 as i32; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_binary_op() { + // Binary op needs parens in cast + let a: i32 = 10 + 5; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_fn_return_as_init() { + let a: i32 = returns_u8() as i32; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_method_as_init() { + let a: i32 = 2u8.saturating_add(3) as i32; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_const_as_init() { + const X: u8 = 10; + let a: i32 = X as i32; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_single_use_fn_call() { + // Should not lint: only one use, and fixing would just move the cast + // to the initializer rather than eliminating it + let a: u8 = returns_u8(); + let _ = a as i32; +} + +fn test_single_use_suffixed_literal() { + // Should not lint: only one use with a suffixed literal + let a: u8 = 10u8; + let _ = a as i32; +} + +fn test_single_use_binary_op() { + // Should lint: binary op of unsuffixed literals can be coerced + let a: i32 = 10 + 5; + //~^ needless_type_cast + let _ = a as i32; +} diff --git a/tests/ui/needless_type_cast.rs b/tests/ui/needless_type_cast.rs index e28f620e035f..31337575fcc3 100644 --- a/tests/ui/needless_type_cast.rs +++ b/tests/ui/needless_type_cast.rs @@ -9,6 +9,10 @@ fn generic(x: T) -> T { x } +fn returns_u8() -> u8 { + 10 +} + fn main() { let a: u8 = 10; //~^ needless_type_cast @@ -180,3 +184,86 @@ fn test_loop_with_generic() { }; let _ = x as i32; } + +fn test_size_of_cast() { + use std::mem::size_of; + // Should lint: suggest casting the initializer + let size: usize = size_of::(); + //~^ needless_type_cast + let _ = size as u64; + let _ = size as u64; +} + +fn test_suffixed_literal_cast() { + // Should lint: suggest casting the initializer + let a: u8 = 10u8; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_negative_literal() { + // Negative literal - should just change type, not add cast + let a: i8 = -10; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_suffixed_negative_literal() { + // Suffixed negative - needs cast + let a: i8 = -10i8; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_binary_op() { + // Binary op needs parens in cast + let a: u8 = 10 + 5; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_fn_return_as_init() { + let a: u8 = returns_u8(); + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_method_as_init() { + let a: u8 = 2u8.saturating_add(3); + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_const_as_init() { + const X: u8 = 10; + let a: u8 = X; + //~^ needless_type_cast + let _ = a as i32; + let _ = a as i32; +} + +fn test_single_use_fn_call() { + // Should not lint: only one use, and fixing would just move the cast + // to the initializer rather than eliminating it + let a: u8 = returns_u8(); + let _ = a as i32; +} + +fn test_single_use_suffixed_literal() { + // Should not lint: only one use with a suffixed literal + let a: u8 = 10u8; + let _ = a as i32; +} + +fn test_single_use_binary_op() { + // Should lint: binary op of unsuffixed literals can be coerced + let a: u8 = 10 + 5; + //~^ needless_type_cast + let _ = a as i32; +} diff --git a/tests/ui/needless_type_cast.stderr b/tests/ui/needless_type_cast.stderr index 3ee9df1043e7..56d9e978d05c 100644 --- a/tests/ui/needless_type_cast.stderr +++ b/tests/ui/needless_type_cast.stderr @@ -1,5 +1,5 @@ error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:13:12 + --> tests/ui/needless_type_cast.rs:17:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` @@ -8,64 +8,154 @@ LL | let a: u8 = 10; = help: to override `-D warnings` add `#[allow(clippy::needless_type_cast)]` error: this binding is defined as `u8` but is always cast to `usize` - --> tests/ui/needless_type_cast.rs:33:12 + --> tests/ui/needless_type_cast.rs:37:12 | LL | let f: u8 = 1; | ^^ help: consider defining it as: `usize` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:39:12 + --> tests/ui/needless_type_cast.rs:43:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:52:12 + --> tests/ui/needless_type_cast.rs:56:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:59:12 + --> tests/ui/needless_type_cast.rs:63:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:66:12 + --> tests/ui/needless_type_cast.rs:70:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:77:12 + --> tests/ui/needless_type_cast.rs:81:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:99:16 + --> tests/ui/needless_type_cast.rs:103:16 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:107:12 + --> tests/ui/needless_type_cast.rs:111:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:116:12 + --> tests/ui/needless_type_cast.rs:120:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` error: this binding is defined as `u8` but is always cast to `i32` - --> tests/ui/needless_type_cast.rs:122:12 + --> tests/ui/needless_type_cast.rs:126:12 | LL | let a: u8 = 10; | ^^ help: consider defining it as: `i32` -error: aborting due to 11 previous errors +error: this binding is defined as `usize` but is always cast to `u64` + --> tests/ui/needless_type_cast.rs:191:15 + | +LL | let size: usize = size_of::(); + | ^^^^^ + | +help: consider defining it as `u64` and casting the initializer + | +LL - let size: usize = size_of::(); +LL + let size: u64 = size_of::() as u64; + | + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:199:12 + | +LL | let a: u8 = 10u8; + | ^^ + | +help: consider defining it as `i32` and casting the initializer + | +LL - let a: u8 = 10u8; +LL + let a: i32 = 10u8 as i32; + | + +error: this binding is defined as `i8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:207:12 + | +LL | let a: i8 = -10; + | ^^ help: consider defining it as: `i32` + +error: this binding is defined as `i8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:215:12 + | +LL | let a: i8 = -10i8; + | ^^ + | +help: consider defining it as `i32` and casting the initializer + | +LL - let a: i8 = -10i8; +LL + let a: i32 = -10i8 as i32; + | + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:223:12 + | +LL | let a: u8 = 10 + 5; + | ^^ help: consider defining it as: `i32` + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:230:12 + | +LL | let a: u8 = returns_u8(); + | ^^ + | +help: consider defining it as `i32` and casting the initializer + | +LL - let a: u8 = returns_u8(); +LL + let a: i32 = returns_u8() as i32; + | + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:237:12 + | +LL | let a: u8 = 2u8.saturating_add(3); + | ^^ + | +help: consider defining it as `i32` and casting the initializer + | +LL - let a: u8 = 2u8.saturating_add(3); +LL + let a: i32 = 2u8.saturating_add(3) as i32; + | + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:245:12 + | +LL | let a: u8 = X; + | ^^ + | +help: consider defining it as `i32` and casting the initializer + | +LL - let a: u8 = X; +LL + let a: i32 = X as i32; + | + +error: this binding is defined as `u8` but is always cast to `i32` + --> tests/ui/needless_type_cast.rs:266:12 + | +LL | let a: u8 = 10 + 5; + | ^^ help: consider defining it as: `i32` + +error: aborting due to 20 previous errors diff --git a/tests/ui/needless_type_cast_unfixable.rs b/tests/ui/needless_type_cast_unfixable.rs new file mode 100644 index 000000000000..bbea9bd24296 --- /dev/null +++ b/tests/ui/needless_type_cast_unfixable.rs @@ -0,0 +1,20 @@ +//@no-rustfix +#![warn(clippy::needless_type_cast)] + +struct Foo(*mut core::ffi::c_void); + +enum Bar { + Variant(*mut core::ffi::c_void), +} + +// Suggestions will not compile directly, as `123` is a literal which +// is not compatible with the suggested `*mut core::ffi::c_void` type +fn issue_16243() { + let underlying: isize = 123; + //~^ needless_type_cast + let handle: Foo = Foo(underlying as _); + + let underlying: isize = 123; + //~^ needless_type_cast + let handle: Bar = Bar::Variant(underlying as _); +} diff --git a/tests/ui/needless_type_cast_unfixable.stderr b/tests/ui/needless_type_cast_unfixable.stderr new file mode 100644 index 000000000000..b71f8a09c40f --- /dev/null +++ b/tests/ui/needless_type_cast_unfixable.stderr @@ -0,0 +1,17 @@ +error: this binding is defined as `isize` but is always cast to `*mut std::ffi::c_void` + --> tests/ui/needless_type_cast_unfixable.rs:13:21 + | +LL | let underlying: isize = 123; + | ^^^^^ help: consider defining it as: `*mut std::ffi::c_void` + | + = note: `-D clippy::needless-type-cast` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_type_cast)]` + +error: this binding is defined as `isize` but is always cast to `*mut std::ffi::c_void` + --> tests/ui/needless_type_cast_unfixable.rs:17:21 + | +LL | let underlying: isize = 123; + | ^^^^^ help: consider defining it as: `*mut std::ffi::c_void` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/never_loop_iterator_reduction.rs b/tests/ui/never_loop_iterator_reduction.rs new file mode 100644 index 000000000000..6b07b91db29a --- /dev/null +++ b/tests/ui/never_loop_iterator_reduction.rs @@ -0,0 +1,17 @@ +//@no-rustfix +#![warn(clippy::never_loop)] + +fn main() { + // diverging closure: should trigger + [0, 1].into_iter().for_each(|x| { + //~^ never_loop + + let _ = x; + panic!("boom"); + }); + + // benign closure: should NOT trigger + [0, 1].into_iter().for_each(|x| { + let _ = x + 1; + }); +} diff --git a/tests/ui/never_loop_iterator_reduction.stderr b/tests/ui/never_loop_iterator_reduction.stderr new file mode 100644 index 000000000000..b76ee283146c --- /dev/null +++ b/tests/ui/never_loop_iterator_reduction.stderr @@ -0,0 +1,27 @@ +error: this iterator reduction never loops (closure always diverges) + --> tests/ui/never_loop_iterator_reduction.rs:6:5 + | +LL | / [0, 1].into_iter().for_each(|x| { +LL | | +LL | | +LL | | let _ = x; +LL | | panic!("boom"); +LL | | }); + | |______^ + | + = note: if you only need one element, `if let Some(x) = iter.next()` is clearer + = note: `-D clippy::never-loop` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::never_loop)]` +help: consider this pattern + | +LL - [0, 1].into_iter().for_each(|x| { +LL - +LL - +LL - let _ = x; +LL - panic!("boom"); +LL - }); +LL + if let Some(x) = [0, 1].into_iter().next() { ... }; + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/obfuscated_if_else.fixed b/tests/ui/obfuscated_if_else.fixed index 70ae090626b9..6bdb170a4aa9 100644 --- a/tests/ui/obfuscated_if_else.fixed +++ b/tests/ui/obfuscated_if_else.fixed @@ -87,3 +87,9 @@ fn issue11141() { let _ = *if true { &42 } else { &17 } as u8; //~^ obfuscated_if_else } + +#[allow(clippy::useless_format)] +fn issue16288() { + if true { format!("this is a test") } else { Default::default() }; + //~^ obfuscated_if_else +} diff --git a/tests/ui/obfuscated_if_else.rs b/tests/ui/obfuscated_if_else.rs index 8e1f57ca2c02..f7b5ea8c0135 100644 --- a/tests/ui/obfuscated_if_else.rs +++ b/tests/ui/obfuscated_if_else.rs @@ -87,3 +87,9 @@ fn issue11141() { let _ = *true.then_some(&42).unwrap_or(&17) as u8; //~^ obfuscated_if_else } + +#[allow(clippy::useless_format)] +fn issue16288() { + true.then(|| format!("this is a test")).unwrap_or_default(); + //~^ obfuscated_if_else +} diff --git a/tests/ui/obfuscated_if_else.stderr b/tests/ui/obfuscated_if_else.stderr index 0de7259d8bb8..865dca56b97a 100644 --- a/tests/ui/obfuscated_if_else.stderr +++ b/tests/ui/obfuscated_if_else.stderr @@ -139,5 +139,11 @@ error: this method chain can be written more clearly with `if .. else ..` LL | let _ = *true.then_some(&42).unwrap_or(&17) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { &42 } else { &17 }` -error: aborting due to 23 previous errors +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:93:5 + | +LL | true.then(|| format!("this is a test")).unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { format!("this is a test") } else { Default::default() }` + +error: aborting due to 24 previous errors diff --git a/tests/ui/println_empty_string.fixed b/tests/ui/println_empty_string.fixed index 05e262ec7786..6b1039ee8020 100644 --- a/tests/ui/println_empty_string.fixed +++ b/tests/ui/println_empty_string.fixed @@ -19,3 +19,23 @@ fn main() { //~^ println_empty_string } } + +#[rustfmt::skip] +fn issue_16167() { + //~v println_empty_string + println!( + ); + + match "a" { + _ => println!(), // there is a space between "" and comma + //~^ println_empty_string + } + + eprintln!(); // there is a tab between "" and comma + //~^ println_empty_string + + match "a" { + _ => eprintln!(), // tab and space between "" and comma + //~^ println_empty_string + } +} diff --git a/tests/ui/println_empty_string.rs b/tests/ui/println_empty_string.rs index 028ddb60dbce..db3b8e1a0eac 100644 --- a/tests/ui/println_empty_string.rs +++ b/tests/ui/println_empty_string.rs @@ -19,3 +19,27 @@ fn main() { //~^ println_empty_string } } + +#[rustfmt::skip] +fn issue_16167() { + //~v println_empty_string + println!( + "\ + \ + " + , + ); + + match "a" { + _ => println!("" ,), // there is a space between "" and comma + //~^ println_empty_string + } + + eprintln!("" ,); // there is a tab between "" and comma + //~^ println_empty_string + + match "a" { + _ => eprintln!("" ,), // tab and space between "" and comma + //~^ println_empty_string + } +} diff --git a/tests/ui/println_empty_string.stderr b/tests/ui/println_empty_string.stderr index 8b997aef9069..bdac1bb3b8ef 100644 --- a/tests/ui/println_empty_string.stderr +++ b/tests/ui/println_empty_string.stderr @@ -33,5 +33,42 @@ LL | _ => eprintln!(""), | | | help: remove the empty string -error: aborting due to 4 previous errors +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:26:5 + | +LL | / println!( +LL | |/ "\ +LL | || \ +LL | || " +LL | || , +LL | || ); + | ||____-^ + | |____| + | help: remove the empty string + +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:34:14 + | +LL | _ => println!("" ,), // there is a space between "" and comma + | ^^^^^^^^^----^ + | | + | help: remove the empty string + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string.rs:38:5 + | +LL | eprintln!("" ,); // there is a tab between "" and comma + | ^^^^^^^^^^-------^ + | | + | help: remove the empty string + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string.rs:42:14 + | +LL | _ => eprintln!("" ,), // tab and space between "" and comma + | ^^^^^^^^^^--------^ + | | + | help: remove the empty string + +error: aborting due to 8 previous errors diff --git a/tests/ui/println_empty_string_unfixable.rs b/tests/ui/println_empty_string_unfixable.rs new file mode 100644 index 000000000000..d6c30f627a58 --- /dev/null +++ b/tests/ui/println_empty_string_unfixable.rs @@ -0,0 +1,30 @@ +#![allow(clippy::match_single_binding)] + +// If there is a comment in the span of macro call, we don't provide an auto-fix suggestion. +#[rustfmt::skip] +fn issue_16167() { + //~v println_empty_string + println!("" /* comment */); + //~v println_empty_string + eprintln!("" /* comment */); + + //~v println_empty_string + println!( // comment + ""); + //~v println_empty_string + eprintln!( // comment + ""); + + //~v println_empty_string + println!("", /* comment */); + + //~v println_empty_string + println!( + "\ + \ + ", + + // there is a comment in the macro span regardless of its position + + ); +} diff --git a/tests/ui/println_empty_string_unfixable.stderr b/tests/ui/println_empty_string_unfixable.stderr new file mode 100644 index 000000000000..648fd7cdbccd --- /dev/null +++ b/tests/ui/println_empty_string_unfixable.stderr @@ -0,0 +1,85 @@ +error: empty string literal in `println!` + --> tests/ui/println_empty_string_unfixable.rs:7:5 + | +LL | println!("" /* comment */); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:7:14 + | +LL | println!("" /* comment */); + | ^^ + = note: `-D clippy::println-empty-string` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::println_empty_string)]` + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string_unfixable.rs:9:5 + | +LL | eprintln!("" /* comment */); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:9:15 + | +LL | eprintln!("" /* comment */); + | ^^ + +error: empty string literal in `println!` + --> tests/ui/println_empty_string_unfixable.rs:12:5 + | +LL | / println!( // comment +LL | | ""); + | |___________________^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:13:17 + | +LL | ""); + | ^^ + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string_unfixable.rs:15:5 + | +LL | / eprintln!( // comment +LL | | ""); + | |___________________^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:16:17 + | +LL | ""); + | ^^ + +error: empty string literal in `println!` + --> tests/ui/println_empty_string_unfixable.rs:19:5 + | +LL | println!("", /* comment */); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:19:14 + | +LL | println!("", /* comment */); + | ^^ + +error: empty string literal in `println!` + --> tests/ui/println_empty_string_unfixable.rs:22:5 + | +LL | / println!( +LL | | "\ +LL | | \ +LL | | ", +... | +LL | | ); + | |_____^ + | +note: remove the empty string + --> tests/ui/println_empty_string_unfixable.rs:23:9 + | +LL | / "\ +LL | | \ +LL | | ", + | |_____________^ + +error: aborting due to 6 previous errors + diff --git a/tests/ui/ref_as_ptr.fixed b/tests/ui/ref_as_ptr.fixed index ce144508581e..eadbb7c36415 100644 --- a/tests/ui/ref_as_ptr.fixed +++ b/tests/ui/ref_as_ptr.fixed @@ -86,6 +86,16 @@ fn main() { f(std::ptr::from_mut::<[usize; 9]>(&mut std::array::from_fn(|i| i * i))); //~^ ref_as_ptr + let x = (10, 20); + let _ = std::ptr::from_ref(&x); + //~^ ref_as_ptr + let _ = std::ptr::from_ref(&x.0); + //~^ ref_as_ptr + + let x = Box::new(10); + let _ = std::ptr::from_ref(&*x); + //~^ ref_as_ptr + let _ = &String::new() as *const _; let _ = &mut String::new() as *mut _; const FOO: *const String = &String::new() as *const _; diff --git a/tests/ui/ref_as_ptr.rs b/tests/ui/ref_as_ptr.rs index acdff2c2ba29..ef96a3ff5693 100644 --- a/tests/ui/ref_as_ptr.rs +++ b/tests/ui/ref_as_ptr.rs @@ -86,6 +86,16 @@ fn main() { f(&mut std::array::from_fn(|i| i * i) as *mut [usize; 9]); //~^ ref_as_ptr + let x = (10, 20); + let _ = &x as *const _; + //~^ ref_as_ptr + let _ = &x.0 as *const _; + //~^ ref_as_ptr + + let x = Box::new(10); + let _ = &*x as *const _; + //~^ ref_as_ptr + let _ = &String::new() as *const _; let _ = &mut String::new() as *mut _; const FOO: *const String = &String::new() as *const _; diff --git a/tests/ui/ref_as_ptr.stderr b/tests/ui/ref_as_ptr.stderr index 79db29e596bd..587e4fb809cd 100644 --- a/tests/ui/ref_as_ptr.stderr +++ b/tests/ui/ref_as_ptr.stderr @@ -200,61 +200,67 @@ LL | f(&mut std::array::from_fn(|i| i * i) as *mut [usize; 9]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_mut::<[usize; 9]>(&mut std::array::from_fn(|i| i * i))` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:109:7 + --> tests/ui/ref_as_ptr.rs:90:13 + | +LL | let _ = &x as *const _; + | ^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(&x)` + +error: reference as raw pointer + --> tests/ui/ref_as_ptr.rs:92:13 + | +LL | let _ = &x.0 as *const _; + | ^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(&x.0)` + +error: reference as raw pointer + --> tests/ui/ref_as_ptr.rs:96:13 + | +LL | let _ = &*x as *const _; + | ^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(&*x)` + +error: reference as raw pointer + --> tests/ui/ref_as_ptr.rs:119:7 | LL | f(val as *const i32); | ^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref::(val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:111:7 + --> tests/ui/ref_as_ptr.rs:121:7 | LL | f(mut_val as *mut i32); | ^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_mut::(mut_val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:116:7 + --> tests/ui/ref_as_ptr.rs:126:7 | LL | f(val as *const _); | ^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:118:7 + --> tests/ui/ref_as_ptr.rs:128:7 | LL | f(val as *const [u8]); | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref::<[u8]>(val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:123:7 + --> tests/ui/ref_as_ptr.rs:133:7 | LL | f(val as *mut _); | ^^^^^^^^^^^^^ help: try: `std::ptr::from_mut(val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:125:7 + --> tests/ui/ref_as_ptr.rs:135:7 | LL | f(val as *mut str); | ^^^^^^^^^^^^^^^ help: try: `std::ptr::from_mut::(val)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:133:9 + --> tests/ui/ref_as_ptr.rs:143:9 | LL | self.0 as *const _ as *const _ | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:138:9 - | -LL | self.0 as *const _ as *const _ - | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` - -error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:147:9 - | -LL | self.0 as *const _ as *const _ - | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` - -error: reference as raw pointer - --> tests/ui/ref_as_ptr.rs:152:9 + --> tests/ui/ref_as_ptr.rs:148:9 | LL | self.0 as *const _ as *const _ | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` @@ -262,8 +268,20 @@ LL | self.0 as *const _ as *const _ error: reference as raw pointer --> tests/ui/ref_as_ptr.rs:157:9 | +LL | self.0 as *const _ as *const _ + | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` + +error: reference as raw pointer + --> tests/ui/ref_as_ptr.rs:162:9 + | +LL | self.0 as *const _ as *const _ + | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_ref(self.0)` + +error: reference as raw pointer + --> tests/ui/ref_as_ptr.rs:167:9 + | LL | self.0 as *mut _ as *mut _ | ^^^^^^^^^^^^^^^^ help: try: `std::ptr::from_mut(self.0)` -error: aborting due to 44 previous errors +error: aborting due to 47 previous errors diff --git a/tests/ui/result_large_err.rs b/tests/ui/result_large_err.rs index fa57b3f553fc..b4ad050df3b7 100644 --- a/tests/ui/result_large_err.rs +++ b/tests/ui/result_large_err.rs @@ -141,3 +141,12 @@ fn _empty_error() -> Result<(), Empty> { } fn main() {} + +fn issue16249() { + type Large = [u8; 1024]; + + let closure = || -> Result<(), Large> { Ok(()) }; + //~^ result_large_err + let closure = || Ok::<(), Large>(()); + //~^ result_large_err +} diff --git a/tests/ui/result_large_err.stderr b/tests/ui/result_large_err.stderr index 72fbc3f58961..fd39179c61cb 100644 --- a/tests/ui/result_large_err.stderr +++ b/tests/ui/result_large_err.stderr @@ -104,5 +104,21 @@ LL | pub fn array_error() -> Result<(), ArrayError<(i32, T), U>> { | = help: try reducing the size of `ArrayError<(i32, T), U>`, for example by boxing large elements or replacing it with `Box>` -error: aborting due to 12 previous errors +error: the `Err`-variant returned from this closure is very large + --> tests/ui/result_large_err.rs:148:25 + | +LL | let closure = || -> Result<(), Large> { Ok(()) }; + | ^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 1024 bytes + | + = help: try reducing the size of `[u8; 1024]`, for example by boxing large elements or replacing it with `Box<[u8; 1024]>` + +error: the `Err`-variant returned from this closure is very large + --> tests/ui/result_large_err.rs:150:19 + | +LL | let closure = || Ok::<(), Large>(()); + | ^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 1024 bytes + | + = help: try reducing the size of `[u8; 1024]`, for example by boxing large elements or replacing it with `Box<[u8; 1024]>` + +error: aborting due to 14 previous errors diff --git a/tests/ui/same_length_and_capacity.rs b/tests/ui/same_length_and_capacity.rs new file mode 100644 index 000000000000..999fcf89881d --- /dev/null +++ b/tests/ui/same_length_and_capacity.rs @@ -0,0 +1,27 @@ +#![warn(clippy::same_length_and_capacity)] + +fn main() { + let mut my_vec: Vec = Vec::with_capacity(20); + my_vec.extend([1, 2, 3, 4, 5]); + let (ptr, mut len, cap) = my_vec.into_raw_parts(); + len = 8; + + let _reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, len) }; + //~^ same_length_and_capacity + + // Don't want to lint different expressions for len and cap + let _properly_reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, cap) }; + + // Don't want to lint if len and cap are distinct variables but happen to be equal + let len_from_cap = cap; + let _another_properly_reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len_from_cap, cap) }; + + let my_string = String::from("hello"); + let (string_ptr, string_len, string_cap) = my_string.into_raw_parts(); + + let _reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_len) }; + //~^ same_length_and_capacity + + // Don't want to lint different expressions for len and cap + let _properly_reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_cap) }; +} diff --git a/tests/ui/same_length_and_capacity.stderr b/tests/ui/same_length_and_capacity.stderr new file mode 100644 index 000000000000..6fc852831269 --- /dev/null +++ b/tests/ui/same_length_and_capacity.stderr @@ -0,0 +1,20 @@ +error: usage of `Vec::from_raw_parts` with the same expression for length and capacity + --> tests/ui/same_length_and_capacity.rs:9:39 + | +LL | let _reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, len) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: try `Box::from(slice::from_raw_parts(...)).into::>()` + = note: `-D clippy::same-length-and-capacity` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::same_length_and_capacity)]` + +error: usage of `String::from_raw_parts` with the same expression for length and capacity + --> tests/ui/same_length_and_capacity.rs:22:42 + | +LL | let _reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_len) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: try `String::from(str::from_utf8_unchecked(slice::from_raw_parts(...)))` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/set_contains_or_insert.rs b/tests/ui/set_contains_or_insert.rs index 575cfda139a4..ac1d74f8afa4 100644 --- a/tests/ui/set_contains_or_insert.rs +++ b/tests/ui/set_contains_or_insert.rs @@ -164,3 +164,24 @@ fn main() { should_not_warn_hashset(); should_not_warn_btreeset(); } + +fn issue15990(s: &mut HashSet, v: usize) { + if !s.contains(&v) { + s.clear(); + s.insert(v); + } + + fn borrow_as_mut(v: usize, s: &mut HashSet) { + s.clear(); + } + if !s.contains(&v) { + borrow_as_mut(v, s); + s.insert(v); + } + + if !s.contains(&v) { + //~^ set_contains_or_insert + let _readonly_access = s.contains(&v); + s.insert(v); + } +} diff --git a/tests/ui/set_contains_or_insert.stderr b/tests/ui/set_contains_or_insert.stderr index 3152b1136458..3b06b63182ab 100644 --- a/tests/ui/set_contains_or_insert.stderr +++ b/tests/ui/set_contains_or_insert.stderr @@ -127,5 +127,14 @@ LL | LL | borrow_set.insert(value); | ^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:182:11 + | +LL | if !s.contains(&v) { + | ^^^^^^^^^^^^ +... +LL | s.insert(v); + | ^^^^^^^^^ + +error: aborting due to 15 previous errors diff --git a/tests/ui/track-diagnostics.rs b/tests/ui/track-diagnostics.rs index 0fbde867390d..1cd37e0570d7 100644 --- a/tests/ui/track-diagnostics.rs +++ b/tests/ui/track-diagnostics.rs @@ -3,6 +3,7 @@ // Normalize the emitted location so this doesn't need // updating everytime someone adds or removes a line. //@normalize-stderr-test: ".rs:\d+:\d+" -> ".rs:LL:CC" +//@normalize-stderr-test: "/rustc-dev/[0-9a-f]+/" -> "" struct A; struct B; diff --git a/tests/ui/transmuting_null.rs b/tests/ui/transmuting_null.rs index f3eb5060cd0d..0d3b26673452 100644 --- a/tests/ui/transmuting_null.rs +++ b/tests/ui/transmuting_null.rs @@ -30,7 +30,15 @@ fn transmute_const() { } } +fn transmute_const_int() { + unsafe { + let _: &u64 = std::mem::transmute(u64::MIN as *const u64); + //~^ transmuting_null + } +} + fn main() { one_liners(); transmute_const(); + transmute_const_int(); } diff --git a/tests/ui/transmuting_null.stderr b/tests/ui/transmuting_null.stderr index c68e4102e405..ed7c3396a243 100644 --- a/tests/ui/transmuting_null.stderr +++ b/tests/ui/transmuting_null.stderr @@ -19,5 +19,11 @@ error: transmuting a known null pointer into a reference LL | let _: &u64 = std::mem::transmute(ZPTR); | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 3 previous errors +error: transmuting a known null pointer into a reference + --> tests/ui/transmuting_null.rs:35:23 + | +LL | let _: &u64 = std::mem::transmute(u64::MIN as *const u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors diff --git a/tests/ui/unchecked_time_subtraction.fixed b/tests/ui/unchecked_time_subtraction.fixed index 2f923fef4c25..830b737f18e7 100644 --- a/tests/ui/unchecked_time_subtraction.fixed +++ b/tests/ui/unchecked_time_subtraction.fixed @@ -35,3 +35,28 @@ fn main() { let _ = (2 * dur1).checked_sub(dur2).unwrap(); //~^ unchecked_time_subtraction } + +fn issue16230() { + use std::ops::Sub as _; + + Duration::ZERO.checked_sub(Duration::MAX).unwrap(); + //~^ unchecked_time_subtraction + + let _ = Duration::ZERO.checked_sub(Duration::MAX).unwrap(); + //~^ unchecked_time_subtraction +} + +fn issue16234() { + use std::ops::Sub as _; + + macro_rules! duration { + ($secs:expr) => { + Duration::from_secs($secs) + }; + } + + duration!(0).checked_sub(duration!(1)).unwrap(); + //~^ unchecked_time_subtraction + let _ = duration!(0).checked_sub(duration!(1)).unwrap(); + //~^ unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction.rs b/tests/ui/unchecked_time_subtraction.rs index cf727f62aafa..e41860157c41 100644 --- a/tests/ui/unchecked_time_subtraction.rs +++ b/tests/ui/unchecked_time_subtraction.rs @@ -35,3 +35,28 @@ fn main() { let _ = 2 * dur1 - dur2; //~^ unchecked_time_subtraction } + +fn issue16230() { + use std::ops::Sub as _; + + Duration::ZERO.sub(Duration::MAX); + //~^ unchecked_time_subtraction + + let _ = Duration::ZERO - Duration::MAX; + //~^ unchecked_time_subtraction +} + +fn issue16234() { + use std::ops::Sub as _; + + macro_rules! duration { + ($secs:expr) => { + Duration::from_secs($secs) + }; + } + + duration!(0).sub(duration!(1)); + //~^ unchecked_time_subtraction + let _ = duration!(0) - duration!(1); + //~^ unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction.stderr b/tests/ui/unchecked_time_subtraction.stderr index c129497447fc..fa4bd1db81ae 100644 --- a/tests/ui/unchecked_time_subtraction.stderr +++ b/tests/ui/unchecked_time_subtraction.stderr @@ -49,5 +49,29 @@ error: unchecked subtraction of a `Duration` LL | let _ = 2 * dur1 - dur2; | ^^^^^^^^^^^^^^^ help: try: `(2 * dur1).checked_sub(dur2).unwrap()` -error: aborting due to 8 previous errors +error: unchecked subtraction of a `Duration` + --> tests/ui/unchecked_time_subtraction.rs:42:5 + | +LL | Duration::ZERO.sub(Duration::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::ZERO.checked_sub(Duration::MAX).unwrap()` + +error: unchecked subtraction of a `Duration` + --> tests/ui/unchecked_time_subtraction.rs:45:13 + | +LL | let _ = Duration::ZERO - Duration::MAX; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::ZERO.checked_sub(Duration::MAX).unwrap()` + +error: unchecked subtraction of a `Duration` + --> tests/ui/unchecked_time_subtraction.rs:58:5 + | +LL | duration!(0).sub(duration!(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `duration!(0).checked_sub(duration!(1)).unwrap()` + +error: unchecked subtraction of a `Duration` + --> tests/ui/unchecked_time_subtraction.rs:60:13 + | +LL | let _ = duration!(0) - duration!(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `duration!(0).checked_sub(duration!(1)).unwrap()` + +error: aborting due to 12 previous errors diff --git a/tests/ui/unnecessary_fold.fixed b/tests/ui/unnecessary_fold.fixed index 1c331be75094..c3eeafbc39cd 100644 --- a/tests/ui/unnecessary_fold.fixed +++ b/tests/ui/unnecessary_fold.fixed @@ -6,21 +6,35 @@ fn is_any(acc: bool, x: usize) -> bool { /// Calls which should trigger the `UNNECESSARY_FOLD` lint fn unnecessary_fold() { + use std::ops::{Add, Mul}; + // Can be replaced by .any let _ = (0..3).any(|x| x > 2); //~^ unnecessary_fold + // Can be replaced by .any (checking suggestion) let _ = (0..3).fold(false, is_any); //~^ redundant_closure + // Can be replaced by .all let _ = (0..3).all(|x| x > 2); //~^ unnecessary_fold + // Can be replaced by .sum let _: i32 = (0..3).sum(); //~^ unnecessary_fold + let _: i32 = (0..3).sum(); + //~^ unnecessary_fold + let _: i32 = (0..3).sum(); + //~^ unnecessary_fold + // Can be replaced by .product let _: i32 = (0..3).product(); //~^ unnecessary_fold + let _: i32 = (0..3).product(); + //~^ unnecessary_fold + let _: i32 = (0..3).product(); + //~^ unnecessary_fold } /// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` @@ -37,6 +51,43 @@ fn unnecessary_fold_should_ignore() { let _ = (0..3).fold(0, |acc, x| acc * x); let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + struct Adder; + impl Adder { + fn add(lhs: i32, rhs: i32) -> i32 { + unimplemented!() + } + fn mul(lhs: i32, rhs: i32) -> i32 { + unimplemented!() + } + } + // `add`/`mul` are inherent methods + let _: i32 = (0..3).fold(0, Adder::add); + let _: i32 = (0..3).fold(1, Adder::mul); + + trait FakeAdd { + type Output; + fn add(self, other: Rhs) -> Self::Output; + } + impl FakeAdd for i32 { + type Output = Self; + fn add(self, other: i32) -> Self::Output { + self + other + } + } + trait FakeMul { + type Output; + fn mul(self, other: Rhs) -> Self::Output; + } + impl FakeMul for i32 { + type Output = Self; + fn mul(self, other: i32) -> Self::Output { + self * other + } + } + // `add`/`mul` come from an unrelated trait + let _: i32 = (0..3).fold(0, FakeAdd::add); + let _: i32 = (0..3).fold(1, FakeMul::mul); + // We only match against an accumulator on the left // hand side. We could lint for .sum and .product when // it's on the right, but don't for now (and this wouldn't @@ -63,6 +114,7 @@ fn unnecessary_fold_over_multiple_lines() { fn issue10000() { use std::collections::HashMap; use std::hash::BuildHasher; + use std::ops::{Add, Mul}; fn anything(_: T) {} fn num(_: i32) {} @@ -74,23 +126,56 @@ fn issue10000() { // more cases: let _ = map.values().sum::(); //~^ unnecessary_fold + let _ = map.values().sum::(); + //~^ unnecessary_fold let _ = map.values().product::(); //~^ unnecessary_fold + let _ = map.values().product::(); + //~^ unnecessary_fold + let _: i32 = map.values().sum(); + //~^ unnecessary_fold let _: i32 = map.values().sum(); //~^ unnecessary_fold let _: i32 = map.values().product(); //~^ unnecessary_fold + let _: i32 = map.values().product(); + //~^ unnecessary_fold + anything(map.values().sum::()); + //~^ unnecessary_fold anything(map.values().sum::()); //~^ unnecessary_fold anything(map.values().product::()); //~^ unnecessary_fold + anything(map.values().product::()); + //~^ unnecessary_fold num(map.values().sum()); //~^ unnecessary_fold + num(map.values().sum()); + //~^ unnecessary_fold + num(map.values().product()); + //~^ unnecessary_fold num(map.values().product()); //~^ unnecessary_fold } smoketest_map(HashMap::new()); + + fn add_turbofish_not_necessary() -> i32 { + (0..3).sum() + //~^ unnecessary_fold + } + fn mul_turbofish_not_necessary() -> i32 { + (0..3).product() + //~^ unnecessary_fold + } + fn add_turbofish_necessary() -> impl Add { + (0..3).sum::() + //~^ unnecessary_fold + } + fn mul_turbofish_necessary() -> impl Mul { + (0..3).product::() + //~^ unnecessary_fold + } } fn main() {} diff --git a/tests/ui/unnecessary_fold.rs b/tests/ui/unnecessary_fold.rs index e2050e37e3b1..6ab41a942625 100644 --- a/tests/ui/unnecessary_fold.rs +++ b/tests/ui/unnecessary_fold.rs @@ -6,21 +6,35 @@ fn is_any(acc: bool, x: usize) -> bool { /// Calls which should trigger the `UNNECESSARY_FOLD` lint fn unnecessary_fold() { + use std::ops::{Add, Mul}; + // Can be replaced by .any let _ = (0..3).fold(false, |acc, x| acc || x > 2); //~^ unnecessary_fold + // Can be replaced by .any (checking suggestion) let _ = (0..3).fold(false, |acc, x| is_any(acc, x)); //~^ redundant_closure + // Can be replaced by .all let _ = (0..3).fold(true, |acc, x| acc && x > 2); //~^ unnecessary_fold + // Can be replaced by .sum let _: i32 = (0..3).fold(0, |acc, x| acc + x); //~^ unnecessary_fold + let _: i32 = (0..3).fold(0, Add::add); + //~^ unnecessary_fold + let _: i32 = (0..3).fold(0, i32::add); + //~^ unnecessary_fold + // Can be replaced by .product let _: i32 = (0..3).fold(1, |acc, x| acc * x); //~^ unnecessary_fold + let _: i32 = (0..3).fold(1, Mul::mul); + //~^ unnecessary_fold + let _: i32 = (0..3).fold(1, i32::mul); + //~^ unnecessary_fold } /// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` @@ -37,6 +51,43 @@ fn unnecessary_fold_should_ignore() { let _ = (0..3).fold(0, |acc, x| acc * x); let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + struct Adder; + impl Adder { + fn add(lhs: i32, rhs: i32) -> i32 { + unimplemented!() + } + fn mul(lhs: i32, rhs: i32) -> i32 { + unimplemented!() + } + } + // `add`/`mul` are inherent methods + let _: i32 = (0..3).fold(0, Adder::add); + let _: i32 = (0..3).fold(1, Adder::mul); + + trait FakeAdd { + type Output; + fn add(self, other: Rhs) -> Self::Output; + } + impl FakeAdd for i32 { + type Output = Self; + fn add(self, other: i32) -> Self::Output { + self + other + } + } + trait FakeMul { + type Output; + fn mul(self, other: Rhs) -> Self::Output; + } + impl FakeMul for i32 { + type Output = Self; + fn mul(self, other: i32) -> Self::Output { + self * other + } + } + // `add`/`mul` come from an unrelated trait + let _: i32 = (0..3).fold(0, FakeAdd::add); + let _: i32 = (0..3).fold(1, FakeMul::mul); + // We only match against an accumulator on the left // hand side. We could lint for .sum and .product when // it's on the right, but don't for now (and this wouldn't @@ -63,6 +114,7 @@ fn unnecessary_fold_over_multiple_lines() { fn issue10000() { use std::collections::HashMap; use std::hash::BuildHasher; + use std::ops::{Add, Mul}; fn anything(_: T) {} fn num(_: i32) {} @@ -74,23 +126,56 @@ fn issue10000() { // more cases: let _ = map.values().fold(0, |x, y| x + y); //~^ unnecessary_fold + let _ = map.values().fold(0, Add::add); + //~^ unnecessary_fold let _ = map.values().fold(1, |x, y| x * y); //~^ unnecessary_fold + let _ = map.values().fold(1, Mul::mul); + //~^ unnecessary_fold let _: i32 = map.values().fold(0, |x, y| x + y); //~^ unnecessary_fold + let _: i32 = map.values().fold(0, Add::add); + //~^ unnecessary_fold let _: i32 = map.values().fold(1, |x, y| x * y); //~^ unnecessary_fold + let _: i32 = map.values().fold(1, Mul::mul); + //~^ unnecessary_fold anything(map.values().fold(0, |x, y| x + y)); //~^ unnecessary_fold + anything(map.values().fold(0, Add::add)); + //~^ unnecessary_fold anything(map.values().fold(1, |x, y| x * y)); //~^ unnecessary_fold + anything(map.values().fold(1, Mul::mul)); + //~^ unnecessary_fold num(map.values().fold(0, |x, y| x + y)); //~^ unnecessary_fold + num(map.values().fold(0, Add::add)); + //~^ unnecessary_fold num(map.values().fold(1, |x, y| x * y)); //~^ unnecessary_fold + num(map.values().fold(1, Mul::mul)); + //~^ unnecessary_fold } smoketest_map(HashMap::new()); + + fn add_turbofish_not_necessary() -> i32 { + (0..3).fold(0, |acc, x| acc + x) + //~^ unnecessary_fold + } + fn mul_turbofish_not_necessary() -> i32 { + (0..3).fold(1, |acc, x| acc * x) + //~^ unnecessary_fold + } + fn add_turbofish_necessary() -> impl Add { + (0..3).fold(0, |acc, x| acc + x) + //~^ unnecessary_fold + } + fn mul_turbofish_necessary() -> impl Mul { + (0..3).fold(1, |acc, x| acc * x) + //~^ unnecessary_fold + } } fn main() {} diff --git a/tests/ui/unnecessary_fold.stderr b/tests/ui/unnecessary_fold.stderr index d82b1f39b48b..bb8aa7e18d34 100644 --- a/tests/ui/unnecessary_fold.stderr +++ b/tests/ui/unnecessary_fold.stderr @@ -1,5 +1,5 @@ error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:10:20 + --> tests/ui/unnecessary_fold.rs:12:20 | LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` @@ -8,7 +8,7 @@ LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2); = help: to override `-D warnings` add `#[allow(clippy::unnecessary_fold)]` error: redundant closure - --> tests/ui/unnecessary_fold.rs:13:32 + --> tests/ui/unnecessary_fold.rs:16:32 | LL | let _ = (0..3).fold(false, |acc, x| is_any(acc, x)); | ^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `is_any` @@ -17,88 +17,184 @@ LL | let _ = (0..3).fold(false, |acc, x| is_any(acc, x)); = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:16:20 + --> tests/ui/unnecessary_fold.rs:20:20 | LL | let _ = (0..3).fold(true, |acc, x| acc && x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `all(|x| x > 2)` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:19:25 + --> tests/ui/unnecessary_fold.rs:24:25 | LL | let _: i32 = (0..3).fold(0, |acc, x| acc + x); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:22:25 + --> tests/ui/unnecessary_fold.rs:26:25 + | +LL | let _: i32 = (0..3).fold(0, Add::add); + | ^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:28:25 + | +LL | let _: i32 = (0..3).fold(0, i32::add); + | ^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:32:25 | LL | let _: i32 = (0..3).fold(1, |acc, x| acc * x); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:28:41 + --> tests/ui/unnecessary_fold.rs:34:25 + | +LL | let _: i32 = (0..3).fold(1, Mul::mul); + | ^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:36:25 + | +LL | let _: i32 = (0..3).fold(1, i32::mul); + | ^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:42:41 | LL | let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:59:10 + --> tests/ui/unnecessary_fold.rs:110:10 | LL | .fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:71:33 + --> tests/ui/unnecessary_fold.rs:123:33 | LL | assert_eq!(map.values().fold(0, |x, y| x + y), 0); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:75:30 + --> tests/ui/unnecessary_fold.rs:127:30 | LL | let _ = map.values().fold(0, |x, y| x + y); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:77:30 + --> tests/ui/unnecessary_fold.rs:129:30 + | +LL | let _ = map.values().fold(0, Add::add); + | ^^^^^^^^^^^^^^^^^ help: try: `sum::()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:131:30 | LL | let _ = map.values().fold(1, |x, y| x * y); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `product::()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:79:35 + --> tests/ui/unnecessary_fold.rs:133:30 + | +LL | let _ = map.values().fold(1, Mul::mul); + | ^^^^^^^^^^^^^^^^^ help: try: `product::()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:135:35 | LL | let _: i32 = map.values().fold(0, |x, y| x + y); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:81:35 + --> tests/ui/unnecessary_fold.rs:137:35 + | +LL | let _: i32 = map.values().fold(0, Add::add); + | ^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:139:35 | LL | let _: i32 = map.values().fold(1, |x, y| x * y); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:83:31 + --> tests/ui/unnecessary_fold.rs:141:35 + | +LL | let _: i32 = map.values().fold(1, Mul::mul); + | ^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:143:31 | LL | anything(map.values().fold(0, |x, y| x + y)); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:85:31 + --> tests/ui/unnecessary_fold.rs:145:31 + | +LL | anything(map.values().fold(0, Add::add)); + | ^^^^^^^^^^^^^^^^^ help: try: `sum::()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:147:31 | LL | anything(map.values().fold(1, |x, y| x * y)); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `product::()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:87:26 + --> tests/ui/unnecessary_fold.rs:149:31 + | +LL | anything(map.values().fold(1, Mul::mul)); + | ^^^^^^^^^^^^^^^^^ help: try: `product::()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:151:26 | LL | num(map.values().fold(0, |x, y| x + y)); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` error: this `.fold` can be written more succinctly using another method - --> tests/ui/unnecessary_fold.rs:89:26 + --> tests/ui/unnecessary_fold.rs:153:26 + | +LL | num(map.values().fold(0, Add::add)); + | ^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:155:26 | LL | num(map.values().fold(1, |x, y| x * y)); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` -error: aborting due to 16 previous errors +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:157:26 + | +LL | num(map.values().fold(1, Mul::mul)); + | ^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:164:16 + | +LL | (0..3).fold(0, |acc, x| acc + x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:168:16 + | +LL | (0..3).fold(1, |acc, x| acc * x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:172:16 + | +LL | (0..3).fold(0, |acc, x| acc + x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `sum::()` + +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:176:16 + | +LL | (0..3).fold(1, |acc, x| acc * x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product::()` + +error: aborting due to 32 previous errors diff --git a/tests/ui/use_self.fixed b/tests/ui/use_self.fixed index 075e31d202b0..37559f9a2367 100644 --- a/tests/ui/use_self.fixed +++ b/tests/ui/use_self.fixed @@ -10,6 +10,9 @@ clippy::needless_lifetimes, clippy::missing_transmute_annotations )] +#![allow(incomplete_features)] +#![feature(adt_const_params)] +#![feature(unsized_const_params)] #[macro_use] extern crate proc_macro_derive; @@ -769,3 +772,25 @@ mod issue_13277 { type Item<'foo> = Option>; } } + +mod issue16164 { + trait Bits { + fn bit(self) -> bool; + } + + impl Bits for u8 { + fn bit(self) -> bool { + todo!() + } + } + + trait T { + fn f(self) -> bool; + } + + impl T for u8 { + fn f(self) -> bool { + todo!() + } + } +} diff --git a/tests/ui/use_self.rs b/tests/ui/use_self.rs index 6fbba0bbc550..74abd2f61bf9 100644 --- a/tests/ui/use_self.rs +++ b/tests/ui/use_self.rs @@ -10,6 +10,9 @@ clippy::needless_lifetimes, clippy::missing_transmute_annotations )] +#![allow(incomplete_features)] +#![feature(adt_const_params)] +#![feature(unsized_const_params)] #[macro_use] extern crate proc_macro_derive; @@ -769,3 +772,25 @@ mod issue_13277 { type Item<'foo> = Option>; } } + +mod issue16164 { + trait Bits { + fn bit(self) -> bool; + } + + impl Bits for u8 { + fn bit(self) -> bool { + todo!() + } + } + + trait T { + fn f(self) -> bool; + } + + impl T for u8 { + fn f(self) -> bool { + todo!() + } + } +} diff --git a/tests/ui/use_self.stderr b/tests/ui/use_self.stderr index 5f65c53ea25c..8ce341d22d4f 100644 --- a/tests/ui/use_self.stderr +++ b/tests/ui/use_self.stderr @@ -1,5 +1,5 @@ error: unnecessary structure name repetition - --> tests/ui/use_self.rs:23:21 + --> tests/ui/use_self.rs:26:21 | LL | fn new() -> Foo { | ^^^ help: use the applicable keyword: `Self` @@ -8,247 +8,247 @@ LL | fn new() -> Foo { = help: to override `-D warnings` add `#[allow(clippy::use_self)]` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:25:13 + --> tests/ui/use_self.rs:28:13 | LL | Foo {} | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:28:22 + --> tests/ui/use_self.rs:31:22 | LL | fn test() -> Foo { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:30:13 + --> tests/ui/use_self.rs:33:13 | LL | Foo::new() | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:36:25 + --> tests/ui/use_self.rs:39:25 | LL | fn default() -> Foo { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:38:13 + --> tests/ui/use_self.rs:41:13 | LL | Foo::new() | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:80:28 + --> tests/ui/use_self.rs:83:28 | LL | fn clone(&self) -> Foo<'a> { | ^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:114:24 + --> tests/ui/use_self.rs:117:24 | LL | fn bad(foos: &[Foo]) -> impl Iterator { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:114:55 + --> tests/ui/use_self.rs:117:55 | LL | fn bad(foos: &[Foo]) -> impl Iterator { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:131:13 + --> tests/ui/use_self.rs:134:13 | LL | TS(0) | ^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:167:29 + --> tests/ui/use_self.rs:170:29 | LL | fn bar() -> Bar { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:169:21 + --> tests/ui/use_self.rs:172:21 | LL | Bar { foo: Foo {} } | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:181:21 + --> tests/ui/use_self.rs:184:21 | LL | fn baz() -> Foo { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:183:13 + --> tests/ui/use_self.rs:186:13 | LL | Foo {} | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:201:21 + --> tests/ui/use_self.rs:204:21 | LL | let _ = Enum::B(42); | ^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:203:21 + --> tests/ui/use_self.rs:206:21 | LL | let _ = Enum::C { field: true }; | ^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:205:21 + --> tests/ui/use_self.rs:208:21 | LL | let _ = Enum::A; | ^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:248:13 + --> tests/ui/use_self.rs:251:13 | LL | nested::A::fun_1(); | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:250:13 + --> tests/ui/use_self.rs:253:13 | LL | nested::A::A; | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:253:13 + --> tests/ui/use_self.rs:256:13 | LL | nested::A {}; | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:273:13 + --> tests/ui/use_self.rs:276:13 | LL | TestStruct::from_something() | ^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:288:25 + --> tests/ui/use_self.rs:291:25 | LL | async fn g() -> S { | ^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:290:13 + --> tests/ui/use_self.rs:293:13 | LL | S {} | ^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:295:16 + --> tests/ui/use_self.rs:298:16 | LL | &p[S::A..S::B] | ^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:295:22 + --> tests/ui/use_self.rs:298:22 | LL | &p[S::A..S::B] | ^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:320:29 + --> tests/ui/use_self.rs:323:29 | LL | fn foo(value: T) -> Foo { | ^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:322:13 + --> tests/ui/use_self.rs:325:13 | LL | Foo:: { value } | ^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:495:13 + --> tests/ui/use_self.rs:498:13 | LL | A::new::(submod::B {}) | ^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:571:17 + --> tests/ui/use_self.rs:574:17 | LL | Foo::Bar => unimplemented!(), | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:573:17 + --> tests/ui/use_self.rs:576:17 | LL | Foo::Baz => unimplemented!(), | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:580:20 + --> tests/ui/use_self.rs:583:20 | LL | if let Foo::Bar = self { | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:605:17 + --> tests/ui/use_self.rs:608:17 | LL | Something::Num(n) => *n, | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:607:17 + --> tests/ui/use_self.rs:610:17 | LL | Something::TupleNums(n, _m) => *n, | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:609:17 + --> tests/ui/use_self.rs:612:17 | LL | Something::StructNums { one, two: _ } => *one, | ^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:616:17 + --> tests/ui/use_self.rs:619:17 | LL | crate::issue8845::Something::Num(n) => *n, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:618:17 + --> tests/ui/use_self.rs:621:17 | LL | crate::issue8845::Something::TupleNums(n, _m) => *n, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:620:17 + --> tests/ui/use_self.rs:623:17 | LL | crate::issue8845::Something::StructNums { one, two: _ } => *one, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:637:17 + --> tests/ui/use_self.rs:640:17 | LL | let Foo(x) = self; | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:643:17 + --> tests/ui/use_self.rs:646:17 | LL | let crate::issue8845::Foo(x) = self; | ^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:651:17 + --> tests/ui/use_self.rs:654:17 | LL | let Bar { x, .. } = self; | ^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:657:17 + --> tests/ui/use_self.rs:660:17 | LL | let crate::issue8845::Bar { x, .. } = self; | ^^^^^^^^^^^^^^^^^^^^^ help: use the applicable keyword: `Self` error: unnecessary structure name repetition - --> tests/ui/use_self.rs:697:17 + --> tests/ui/use_self.rs:700:17 | LL | E::A => {}, | ^ help: use the applicable keyword: `Self` diff --git a/tests/ui/writeln_empty_string_unfixable.rs b/tests/ui/writeln_empty_string_unfixable.rs new file mode 100644 index 000000000000..ca570fd1fc7b --- /dev/null +++ b/tests/ui/writeln_empty_string_unfixable.rs @@ -0,0 +1,26 @@ +#![allow(unused_must_use)] +#![warn(clippy::writeln_empty_string)] + +use std::io::Write; + +// If there is a comment in the span of macro call, we don't provide an auto-fix suggestion. +#[rustfmt::skip] +fn issue_16251() { + let mut v = Vec::new(); + + writeln!(v, /* comment */ ""); + //~^ writeln_empty_string + + writeln!(v, "" /* comment */); + //~^ writeln_empty_string + + //~v writeln_empty_string + writeln!(v, + "\ + \ + " + + // there is a comment in the macro span regardless of its position + + ); +} diff --git a/tests/ui/writeln_empty_string_unfixable.stderr b/tests/ui/writeln_empty_string_unfixable.stderr new file mode 100644 index 000000000000..0ed802ba84ba --- /dev/null +++ b/tests/ui/writeln_empty_string_unfixable.stderr @@ -0,0 +1,47 @@ +error: empty string literal in `writeln!` + --> tests/ui/writeln_empty_string_unfixable.rs:11:5 + | +LL | writeln!(v, /* comment */ ""); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: remove the empty string + --> tests/ui/writeln_empty_string_unfixable.rs:11:31 + | +LL | writeln!(v, /* comment */ ""); + | ^^ + = note: `-D clippy::writeln-empty-string` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::writeln_empty_string)]` + +error: empty string literal in `writeln!` + --> tests/ui/writeln_empty_string_unfixable.rs:14:5 + | +LL | writeln!(v, "" /* comment */); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: remove the empty string + --> tests/ui/writeln_empty_string_unfixable.rs:14:17 + | +LL | writeln!(v, "" /* comment */); + | ^^ + +error: empty string literal in `writeln!` + --> tests/ui/writeln_empty_string_unfixable.rs:18:5 + | +LL | / writeln!(v, +LL | | "\ +LL | | \ +LL | | " +... | +LL | | ); + | |_____^ + | +note: remove the empty string + --> tests/ui/writeln_empty_string_unfixable.rs:19:9 + | +LL | / "\ +LL | | \ +LL | | " + | |_____________^ + +error: aborting due to 3 previous errors + diff --git a/triagebot.toml b/triagebot.toml index 09dec7675e7e..5f637205fa65 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -63,6 +63,7 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", + "samueltardieu", ] [assign.owners]