diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs index a0371eb71556b..b119f2dc3ce84 100644 --- a/src/bootstrap/src/core/build_steps/clippy.rs +++ b/src/bootstrap/src/core/build_steps/clippy.rs @@ -19,6 +19,7 @@ const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[ "too_many_arguments", "needless_lifetimes", // people want to keep the lifetimes "wrong_self_convention", + "approx_constant", // libcore is what defines those ]; fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec { diff --git a/src/tools/clippy/.github/workflows/feature_freeze.yml b/src/tools/clippy/.github/workflows/feature_freeze.yml index 7ad58af77d4ad..ec59be3e7f67c 100644 --- a/src/tools/clippy/.github/workflows/feature_freeze.yml +++ b/src/tools/clippy/.github/workflows/feature_freeze.yml @@ -20,16 +20,26 @@ jobs: # of the pull request, as malicious code would be able to access the private # GitHub token. steps: - - name: Check PR Changes - id: pr-changes - run: echo "::set-output name=changes::${{ toJson(github.event.pull_request.changed_files) }}" - - - name: Create Comment - if: steps.pr-changes.outputs.changes != '[]' - run: | - # Use GitHub API to create a comment on the PR - PR_NUMBER=${{ github.event.pull_request.number }} - COMMENT="**Seems that you are trying to add a new lint!**\nWe are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team\n\n@rustbot note Feature-freeze\n@rustbot blocked\n@rustbot label +A-lint\n" - GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" - curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" + - name: Add freeze warning comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + COMMENT=$(echo "**Seems that you are trying to add a new lint!**\n\ + \n\ + We are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and [focusing on bugfixes](https://github.com/rust-lang/rust-clippy/issues/15086).\n\ + \n\ + Thanks a lot for your contribution, and sorry for the inconvenience.\n\ + \n\ + With ❤ from the Clippy team.\n\ + \n\ + @rustbot note Feature-freeze\n\ + @rustbot blocked\n\ + @rustbot label +A-lint" + ) + curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Content-Type: application/vnd.github.raw+json" \ + -X POST \ + --data "{\"body\":\"${COMMENT}\"}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" diff --git a/src/tools/clippy/.gitignore b/src/tools/clippy/.gitignore index a7c25b29021fb..36a4cdc1c3528 100644 --- a/src/tools/clippy/.gitignore +++ b/src/tools/clippy/.gitignore @@ -19,8 +19,10 @@ out # Generated by Cargo *Cargo.lock +!/clippy_test_deps/Cargo.lock /target /clippy_lints/target +/clippy_lints_internal/target /clippy_utils/target /clippy_dev/target /lintcheck/target diff --git a/src/tools/clippy/clippy_dev/src/fmt.rs b/src/tools/clippy/clippy_dev/src/fmt.rs index bd9e57c9f6dac..2b2138d3108df 100644 --- a/src/tools/clippy/clippy_dev/src/fmt.rs +++ b/src/tools/clippy/clippy_dev/src/fmt.rs @@ -3,7 +3,7 @@ use crate::utils::{ walk_dir_no_dot_or_target, }; use itertools::Itertools; -use rustc_lexer::{TokenKind, tokenize}; +use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use std::fmt::Write; use std::fs; use std::io::{self, Read}; @@ -92,7 +92,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> { let mut fields = Vec::new(); let mut state = State::Start; - for (i, t) in tokenize(conf) + for (i, t) in tokenize(conf, FrontmatterAllowed::No) .map(|x| { let start = pos; pos += x.len; diff --git a/src/tools/clippy/clippy_lints/src/approx_const.rs b/src/tools/clippy/clippy_lints/src/approx_const.rs index 5ed4c82634aa8..184fbbf779623 100644 --- a/src/tools/clippy/clippy_lints/src/approx_const.rs +++ b/src/tools/clippy/clippy_lints/src/approx_const.rs @@ -92,9 +92,11 @@ impl LateLintPass<'_> for ApproxConstant { impl ApproxConstant { fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol, module: &str) { let s = s.as_str(); - if s.parse::().is_ok() { + if let Ok(maybe_constant) = s.parse::() { for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS { - if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) { + if is_approx_const(constant, s, maybe_constant, min_digits) + && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) + { span_lint_and_help( cx, APPROX_CONSTANT, @@ -112,18 +114,35 @@ impl ApproxConstant { impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); +fn count_digits_after_dot(input: &str) -> usize { + input + .char_indices() + .find(|(_, ch)| *ch == '.') + .map_or(0, |(i, _)| input.len() - i - 1) +} + /// Returns `false` if the number of significant figures in `value` are /// less than `min_digits`; otherwise, returns true if `value` is equal -/// to `constant`, rounded to the number of digits present in `value`. +/// to `constant`, rounded to the number of significant digits present in `value`. #[must_use] -fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { +fn is_approx_const(constant: f64, value: &str, f_value: f64, min_digits: usize) -> bool { if value.len() <= min_digits { + // The value is not precise enough false - } else if constant.to_string().starts_with(value) { - // The value is a truncated constant + } else if f_value.to_string().len() > min_digits && constant.to_string().starts_with(&f_value.to_string()) { + // The value represents the same value true } else { - let round_const = format!("{constant:.*}", value.len() - 2); + // The value is a truncated constant + + // Print constant with numeric formatting (`0`), with the length of `value` as minimum width + // (`value_len$`), and with the same precision as `value` (`.value_prec$`). + // See https://doc.rust-lang.org/std/fmt/index.html. + let round_const = format!( + "{constant:0value_len$.value_prec$}", + value_len = value.len(), + value_prec = count_digits_after_dot(value) + ); value == round_const } } diff --git a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs index a9d3015ce5c48..7b4cf03367418 100644 --- a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -8,11 +8,11 @@ use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::is_cfg_test; use rustc_attr_data_structures::AttributeKind; use rustc_hir::{ - Attribute, FieldDef, HirId, IsAuto, ImplItemId, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, - Variant, VariantData, + Attribute, FieldDef, HirId, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant, + VariantData, }; -use rustc_middle::ty::AssocKind; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::AssocKind; use rustc_session::impl_lint_pass; use rustc_span::Ident; @@ -469,13 +469,14 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { /// This is implemented here because `rustc_hir` is not a dependency of /// `clippy_config`. fn convert_assoc_item_kind(cx: &LateContext<'_>, owner_id: OwnerId) -> SourceItemOrderingTraitAssocItemKind { - let kind = cx.tcx.associated_item(owner_id.def_id).kind; - #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. use SourceItemOrderingTraitAssocItemKind::*; + + let kind = cx.tcx.associated_item(owner_id.def_id).kind; + match kind { - AssocKind::Const{..} => Const, - AssocKind::Type {..}=> Type, + AssocKind::Const { .. } => Const, + AssocKind::Type { .. } => Type, AssocKind::Fn { .. } => Fn, } } diff --git a/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs b/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs index 9e09fb5bb4396..085029a744bec 100644 --- a/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs +++ b/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { diag.note(format!( "`Arc<{arg_ty}>` is not `Send` and `Sync` as `{arg_ty}` is {reason}" )); - diag.help("if the `Arc` will not used be across threads replace it with an `Rc`"); + diag.help("if the `Arc` will not be used across threads replace it with an `Rc`"); diag.help(format!( "otherwise make `{arg_ty}` `Send` and `Sync` or consider a wrapper type such as `Mutex`" )); diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs index 4059f9603c33c..b9b5cedb5aa76 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs @@ -36,6 +36,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { | sym::unused_braces | sym::unused_import_braces | sym::unused_imports + | sym::redundant_imports ) { return; diff --git a/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs b/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs index 769cc120c9504..849de22cfbaa8 100644 --- a/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs +++ b/src/tools/clippy/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs @@ -59,9 +59,8 @@ fn get_const_name_and_ty_name( pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We allow casts from any function type to any function type. - match cast_to.kind() { - ty::FnDef(..) | ty::FnPtr(..) => return, - _ => { /* continue to checks */ }, + if cast_to.is_fn() { + return; } if let ty::FnDef(def_id, generics) = cast_from.kind() diff --git a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs index 55e27a05f3c0d..c5d9643f56a51 100644 --- a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs +++ b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::{FN_TO_NUMERIC_CAST, utils}; @@ -13,23 +13,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, return; }; - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let mut applicability = Applicability::MaybeIncorrect; + if cast_from.is_fn() { + let mut applicability = Applicability::MaybeIncorrect; - if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() { - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`"), - "try", - format!("{from_snippet} as usize"), - applicability, - ); - } - }, - _ => {}, + if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() { + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`"), + "try", + format!("{from_snippet} as usize"), + applicability, + ); + } } } diff --git a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_any.rs b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_any.rs index b22e8f4ee8919..43ee91af6e5a0 100644 --- a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_any.rs @@ -3,18 +3,17 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::FN_TO_NUMERIC_CAST_ANY; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We allow casts from any function type to any function type. - match cast_to.kind() { - ty::FnDef(..) | ty::FnPtr(..) => return, - _ => { /* continue to checks */ }, + if cast_to.is_fn() { + return; } - if let ty::FnDef(..) | ty::FnPtr(..) = cast_from.kind() { + if cast_from.is_fn() { let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); diff --git a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs index 4da79205e208b..9a2e44e07d4b1 100644 --- a/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs +++ b/src/tools/clippy/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils}; @@ -12,23 +12,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { return; }; - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + if cast_from.is_fn() { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - if to_nbits < cx.tcx.data_layout.pointer_size().bits() { - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"), - "try", - format!("{from_snippet} as usize"), - applicability, - ); - } - }, - _ => {}, + if to_nbits < cx.tcx.data_layout.pointer_size().bits() { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"), + "try", + format!("{from_snippet} as usize"), + applicability, + ); + } } } diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs index 6f944914b8fd6..ee0f3fa81c6d9 100644 --- a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,10 +4,9 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind}; -use rustc_hir_pretty::qpath_to_string; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::sym; +use rustc_span::{Span, sym}; use super::PTR_AS_PTR; @@ -74,7 +73,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { let (help, final_suggestion) = if let Some(method) = omit_cast.corresponding_item() { // don't force absolute path - let method = qpath_to_string(&cx.tcx, method); + let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); @@ -96,3 +95,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { ); } } + +fn qpath_span_without_turbofish(qpath: &QPath<'_>) -> Span { + if let QPath::Resolved(_, path) = qpath + && let [.., last_ident] = path.segments + && last_ident.args.is_some() + { + return qpath.span().shrink_to_lo().to(last_ident.ident.span); + } + + qpath.span() +} diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index d5d937d913385..518535e8c8bfa 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -110,7 +110,6 @@ impl CognitiveComplexity { FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span, FnKind::Closure => { let header_span = body_span.with_hi(decl.output.span().lo()); - #[expect(clippy::range_plus_one)] if let Some(range) = header_span.map_range(cx, |_, src, range| { let mut idxs = src.get(range.clone())?.match_indices('|'); Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1) diff --git a/src/tools/clippy/clippy_lints/src/copies.rs b/src/tools/clippy/clippy_lints/src/copies.rs index 27918698cd6ba..4bd34527d21f8 100644 --- a/src/tools/clippy/clippy_lints/src/copies.rs +++ b/src/tools/clippy/clippy_lints/src/copies.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then}; +use clippy_utils::higher::has_let_expr; use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet}; use clippy_utils::ty::{InteriorMut, needs_ordered_drop}; use clippy_utils::visitors::for_each_expr_without_closures; @@ -11,7 +12,7 @@ use clippy_utils::{ use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; +use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; @@ -189,24 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> { } } -/// Checks if the given expression is a let chain. -fn contains_let(e: &Expr<'_>) -> bool { - match e.kind { - ExprKind::Let(..) => true, - ExprKind::Binary(op, lhs, rhs) if op.node == BinOpKind::And => { - matches!(lhs.kind, ExprKind::Let(..)) || contains_let(rhs) - }, - _ => false, - } -} - fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { let mut eq = SpanlessEq::new(cx); blocks .array_windows::<2>() .enumerate() .fold(true, |all_eq, (i, &[lhs, rhs])| { - if eq.eq_block(lhs, rhs) && !contains_let(conds[i]) && conds.get(i + 1).is_none_or(|e| !contains_let(e)) { + if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) { span_lint_and_note( cx, IF_SAME_THEN_ELSE, diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs index 062f7cef3a72a..49dd1bb09c61c 100644 --- a/src/tools/clippy/clippy_lints/src/derive.rs +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -432,6 +432,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::Block(block, _) = expr.kind && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + && block + .span + .source_callee() + .and_then(|expr| expr.macro_def_id) + .is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did)) { return ControlFlow::Break(()); } diff --git a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs index d55aeae98edec..23e7c7251cf15 100644 --- a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs +++ b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs @@ -72,11 +72,11 @@ pub struct DisallowedMacros { // When a macro is disallowed in an early pass, it's stored // and emitted during the late pass. This happens for attributes. - earlies: AttrStorage, + early_macro_cache: AttrStorage, } impl DisallowedMacros { - pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, early_macro_cache: AttrStorage) -> Self { let (disallowed, _) = create_disallowed_map( tcx, &conf.disallowed_macros, @@ -89,7 +89,7 @@ impl DisallowedMacros { disallowed, seen: FxHashSet::default(), derive_src: None, - earlies, + early_macro_cache, } } @@ -130,7 +130,7 @@ impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl LateLintPass<'_> for DisallowedMacros { fn check_crate(&mut self, cx: &LateContext<'_>) { // once we check a crate in the late pass we can emit the early pass lints - if let Some(attr_spans) = self.earlies.clone().0.get() { + if let Some(attr_spans) = self.early_macro_cache.clone().0.get() { for span in attr_spans { self.check(cx, *span, None); } diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index 22b781b89294b..ea0da0d246755 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -1232,7 +1232,6 @@ fn check_doc<'a, Events: Iterator, Range) -> Option> { if range.end < range.start { return None; diff --git a/src/tools/clippy/clippy_lints/src/empty_with_brackets.rs b/src/tools/clippy/clippy_lints/src/empty_with_brackets.rs index 4414aebbf9a39..f2757407ba57b 100644 --- a/src/tools/clippy/clippy_lints/src/empty_with_brackets.rs +++ b/src/tools/clippy/clippy_lints/src/empty_with_brackets.rs @@ -92,8 +92,10 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA impl LateLintPass<'_> for EmptyWithBrackets { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // FIXME: handle `struct $name {}` if let ItemKind::Struct(ident, _, var_data) = &item.kind && !item.span.from_expansion() + && !ident.span.from_expansion() && has_brackets(var_data) && let span_after_ident = item.span.with_lo(ident.span.hi()) && has_no_fields(cx, var_data, span_after_ident) @@ -116,10 +118,12 @@ impl LateLintPass<'_> for EmptyWithBrackets { } fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) { - // the span of the parentheses/braces - let span_after_ident = variant.span.with_lo(variant.ident.span.hi()); - - if has_no_fields(cx, &variant.data, span_after_ident) { + // 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()) + && has_no_fields(cx, &variant.data, span_after_ident) + { match variant.data { VariantData::Struct { .. } => { // Empty struct variants can be linted immediately diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs index db2fea1aae95d..fc224fa5f9246 100644 --- a/src/tools/clippy/clippy_lints/src/escape.rs +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir; use rustc_abi::ExternAbi; -use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit}; use rustc_hir::def::DefKind; +use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; @@ -87,16 +87,14 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { let mut trait_self_ty = None; match cx.tcx.def_kind(parent_id) { // If the method is an impl for a trait, don't warn. - DefKind::Impl { of_trait: true } => { - return - } + DefKind::Impl { of_trait: true } => return, // find `self` ty for this trait if relevant DefKind::Trait => { trait_self_ty = Some(TraitRef::identity(cx.tcx, parent_id.to_def_id()).self_ty()); - } + }, - _ => {} + _ => {}, } let mut v = EscapeDelegate { diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 0288747d6f3ed..9b627678bd35d 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -29,12 +29,6 @@ declare_clippy_lint! { /// Needlessly creating a closure adds code for no benefit /// and gives the optimizer more work. /// - /// ### Known problems - /// If creating the closure inside the closure has a side- - /// effect then moving the closure creation out will change when that side- - /// effect runs. - /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details. - /// /// ### Example /// ```rust,ignore /// xs.map(|x| foo(x)) diff --git a/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs index 552cd721f4ef9..fdfcbb540bce6 100644 --- a/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs +++ b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs @@ -64,8 +64,8 @@ impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { } fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Span) { - use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::Expr; + use rustc_hir::intravisit::{self, Visitor}; struct FindPanicUnwrap<'a, 'tcx> { lcx: &'a LateContext<'tcx>, @@ -96,10 +96,12 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp } } - for impl_item in cx.tcx.associated_items(item_def_id) + for impl_item in cx + .tcx + .associated_items(item_def_id) .filter_by_name_unhygienic_and_kind(sym::from, ty::AssocTag::Fn) { - let impl_item_def_id= impl_item.def_id.expect_local(); + let impl_item_def_id = impl_item.def_id.expect_local(); // check the body for `begin_panic` or `unwrap` let body = cx.tcx.hir_body_owned_by(impl_item_def_id); diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs index 16c58ecb455ea..a251f15ba3da7 100644 --- a/src/tools/clippy/clippy_lints/src/format_args.rs +++ b/src/tools/clippy/clippy_lints/src/format_args.rs @@ -17,7 +17,7 @@ use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, FormatPlaceholder, FormatTrait, }; -use rustc_attr_data_structures::RustcVersion; +use rustc_attr_data_structures::{AttributeKind, RustcVersion, find_attr}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; @@ -30,7 +30,6 @@ use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol, sym}; use rustc_trait_selection::infer::TyCtxtInferExt; use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; declare_clippy_lint! { /// ### What it does @@ -129,6 +128,7 @@ declare_clippy_lint! { /// # let width = 1; /// # let prec = 2; /// format!("{}", var); + /// format!("{:?}", var); /// format!("{v:?}", v = var); /// format!("{0} {0}", var); /// format!("{0:1$}", var, width); @@ -141,6 +141,7 @@ declare_clippy_lint! { /// # let prec = 2; /// format!("{var}"); /// format!("{var:?}"); + /// format!("{var:?}"); /// format!("{var} {var}"); /// format!("{var:width$}"); /// format!("{var:.prec$}"); @@ -164,7 +165,7 @@ declare_clippy_lint! { /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`. #[clippy::version = "1.66.0"] pub UNINLINED_FORMAT_ARGS, - style, + pedantic, "using non-inlined variables in `format!` calls" } @@ -657,7 +658,10 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { }; let selection = SelectionContext::new(&infcx).select(&obligation); let derived = if let Ok(Some(Selection::UserDefined(data))) = selection { - find_attr!(tcx.get_all_attrs(data.impl_def_id), AttributeKind::AutomaticallyDerived(..)) + find_attr!( + tcx.get_all_attrs(data.impl_def_id), + AttributeKind::AutomaticallyDerived(..) + ) } else { false }; diff --git a/src/tools/clippy/clippy_lints/src/from_over_into.rs b/src/tools/clippy/clippy_lints/src/from_over_into.rs index 85b40ba7419b3..1da6952eb64c4 100644 --- a/src/tools/clippy/clippy_lints/src/from_over_into.rs +++ b/src/tools/clippy/clippy_lints/src/from_over_into.rs @@ -9,7 +9,7 @@ use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_path}; use rustc_hir::{ - FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemId, Item, ItemKind, PatKind, Path, + FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemId, ImplItemKind, Item, ItemKind, PatKind, Path, PathSegment, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs index d959981a83cee..b8d0cec5aeb17 100644 --- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs +++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs @@ -10,7 +10,7 @@ use rustc_span::{Span, sym}; use clippy_utils::attrs::is_proc_macro; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::snippet_indent; use clippy_utils::ty::is_must_use_ty; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{return_ty, trait_ref_of_method}; @@ -28,6 +28,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> if let hir::ItemKind::Fn { ref sig, body: ref body_id, + ident, .. } = item.kind { @@ -51,8 +52,8 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> sig.decl, cx.tcx.hir_body(*body_id), item.span, + ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this function could have a `#[must_use]` attribute", ); } @@ -84,8 +85,8 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp sig.decl, cx.tcx.hir_body(*body_id), item.span, + item.ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); } @@ -120,8 +121,8 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr sig.decl, body, item.span, + item.ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); } @@ -198,8 +199,8 @@ fn check_must_use_candidate<'tcx>( decl: &'tcx hir::FnDecl<'_>, body: &'tcx hir::Body<'_>, item_span: Span, + ident_span: Span, item_id: hir::OwnerId, - fn_span: Span, msg: &'static str, ) { if has_mutable_arg(cx, body) @@ -208,18 +209,18 @@ fn check_must_use_candidate<'tcx>( || returns_unit(decl) || !cx.effective_visibilities.is_exported(item_id.def_id) || is_must_use_ty(cx, return_ty(cx, item_id)) + || item_span.from_expansion() { return; } - span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { - if let Some(snippet) = fn_span.get_source_text(cx) { - diag.span_suggestion( - fn_span, - "add the attribute", - format!("#[must_use] {snippet}"), - Applicability::MachineApplicable, - ); - } + span_lint_and_then(cx, MUST_USE_CANDIDATE, ident_span, msg, |diag| { + let indent = snippet_indent(cx, item_span).unwrap_or_default(); + diag.span_suggestion( + item_span.shrink_to_lo(), + "add the attribute", + format!("#[must_use] \n{indent}"), + Applicability::MachineApplicable, + ); }); } diff --git a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs index 9e94280fc0746..7158f9419c1cc 100644 --- a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs +++ b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs @@ -5,7 +5,8 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks, + contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, + path_res, peel_blocks, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -92,6 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { expr.span, format!("this could be simplified with `bool::{method_name}`"), |diag| { + if expr_adjustment_requires_coercion(cx, then_arg) { + return; + } + let mut app = Applicability::MachineApplicable; let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) .maybe_paren() diff --git a/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs index 5d0bd3e8ca303..116d63c3bb15d 100644 --- a/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs +++ b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs @@ -1,10 +1,11 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::{is_in_const_context, is_in_test}; use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Expr, ExprKind, HirId, QPath}; +use rustc_hir::def::DefKind; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; @@ -33,15 +34,54 @@ declare_clippy_lint! { /// /// To fix this problem, either increase your MSRV or use another item /// available in your current MSRV. + /// + /// You can also locally change the MSRV that should be checked by Clippy, + /// for example if a feature in your crate (e.g., `modern_compiler`) should + /// allow you to use an item: + /// + /// ```no_run + /// //! This crate has a MSRV of 1.3.0, but we also have an optional feature + /// //! `sleep_well` which requires at least Rust 1.4.0. + /// + /// // When the `sleep_well` feature is set, do not warn for functions available + /// // in Rust 1.4.0 and below. + /// #![cfg_attr(feature = "sleep_well", clippy::msrv = "1.4.0")] + /// + /// use std::time::Duration; + /// + /// #[cfg(feature = "sleep_well")] + /// fn sleep_for_some_time() { + /// std::thread::sleep(Duration::new(1, 0)); // Will not trigger the lint + /// } + /// ``` + /// + /// You can also increase the MSRV in tests, by using: + /// + /// ```no_run + /// // Use a much higher MSRV for tests while keeping the main one low + /// #![cfg_attr(test, clippy::msrv = "1.85.0")] + /// + /// #[test] + /// fn my_test() { + /// // The tests can use items introduced in Rust 1.85.0 and lower + /// // without triggering the `incompatible_msrv` lint. + /// } + /// ``` #[clippy::version = "1.78.0"] pub INCOMPATIBLE_MSRV, suspicious, "ensures that all items used in the crate are available for the current MSRV" } +#[derive(Clone, Copy)] +enum Availability { + FeatureEnabled, + Since(RustcVersion), +} + pub struct IncompatibleMsrv { msrv: Msrv, - is_above_msrv: FxHashMap, + availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, } @@ -51,38 +91,50 @@ impl IncompatibleMsrv { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv, - is_above_msrv: FxHashMap::default(), + availability_cache: FxHashMap::default(), check_in_tests: conf.check_incompatible_msrv_in_tests, } } - fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { - if let Some(version) = self.is_above_msrv.get(&def_id) { - return *version; + /// Returns the availability of `def_id`, whether it is enabled through a feature or + /// available since a given version (the default being Rust 1.0.0). `needs_const` requires + /// the `const`-stability to be looked up instead of the stability in non-`const` contexts. + fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId, needs_const: bool) -> Availability { + if let Some(availability) = self.availability_cache.get(&(def_id, needs_const)) { + return *availability; } - let version = if let Some(version) = tcx - .lookup_stability(def_id) - .and_then(|stability| match stability.level { - StabilityLevel::Stable { - since: StableSince::Version(version), - .. - } => Some(version), - _ => None, - }) { - version + let (feature, stability_level) = if needs_const { + tcx.lookup_const_stability(def_id) + .map(|stability| (stability.feature, stability.level)) + .unzip() + } else { + tcx.lookup_stability(def_id) + .map(|stability| (stability.feature, stability.level)) + .unzip() + }; + let version = if feature.is_some_and(|feature| tcx.features().enabled(feature)) { + Availability::FeatureEnabled + } else if let Some(StableSince::Version(version)) = + stability_level.as_ref().and_then(StabilityLevel::stable_since) + { + Availability::Since(version) + } else if needs_const { + // Fallback to regular stability + self.get_def_id_availability(tcx, def_id, false) } else if let Some(parent_def_id) = tcx.opt_parent(def_id) { - self.get_def_id_version(tcx, parent_def_id) + self.get_def_id_availability(tcx, parent_def_id, needs_const) } else { - RustcVersion { + Availability::Since(RustcVersion { major: 1, minor: 0, patch: 0, - } + }) }; - self.is_above_msrv.insert(def_id, version); + self.availability_cache.insert((def_id, needs_const), version); version } + /// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV. fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) { if def_id.is_local() { // We don't check local items since their MSRV is supposed to always be valid. @@ -108,18 +160,28 @@ impl IncompatibleMsrv { return; } + let needs_const = cx.enclosing_body.is_some() + && is_in_const_context(cx) + && matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn); + if (self.check_in_tests || !is_in_test(cx.tcx, node)) && let Some(current) = self.msrv.current(cx) - && let version = self.get_def_id_version(cx.tcx, def_id) + && let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const) && version > current { - span_lint( + span_lint_and_then( cx, INCOMPATIBLE_MSRV, span, format!( - "current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`" + "current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable{} since `{version}`", + if needs_const { " in a `const` context" } else { "" }, ), + |diag| { + if is_under_cfg_attribute(cx, node) { + diag.note_once("you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute"); + } + }, ); } } @@ -133,17 +195,38 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); } }, - ExprKind::Call(call, _) => { - // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should - // not be linted as they will not be generated in older compilers if the function is not available, - // and the compiler is allowed to call unstable functions. - if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = call.kind - && let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id() - { - self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span); + // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should + // not be linted as they will not be generated in older compilers if the function is not available, + // and the compiler is allowed to call unstable functions. + ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) => { + if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() { + self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span); } }, _ => {}, } } + + fn check_ty(&mut self, cx: &LateContext<'tcx>, hir_ty: &'tcx hir::Ty<'tcx, AmbigArg>) { + if let hir::TyKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = hir_ty.kind + && let Some(ty_def_id) = cx.qpath_res(&qpath, hir_ty.hir_id).opt_def_id() + // `CStr` and `CString` have been moved around but have been available since Rust 1.0.0 + && !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type)) + { + self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span); + } + } +} + +/// Heuristic checking if the node `hir_id` is under a `#[cfg()]` or `#[cfg_attr()]` +/// attribute. +fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool { + cx.tcx.hir_parent_id_iter(hir_id).any(|id| { + cx.tcx.hir_attrs(id).iter().any(|attr| { + matches!( + attr.ident().map(|ident| ident.name), + Some(sym::cfg_trace | sym::cfg_attr_trace) + ) + }) + }) } diff --git a/src/tools/clippy/clippy_lints/src/ineffective_open_options.rs b/src/tools/clippy/clippy_lints/src/ineffective_open_options.rs index 7a751514b6476..a159f61571839 100644 --- a/src/tools/clippy/clippy_lints/src/ineffective_open_options.rs +++ b/src/tools/clippy/clippy_lints/src/ineffective_open_options.rs @@ -1,13 +1,12 @@ -use crate::methods::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{peel_blocks, sym}; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{peel_blocks, peel_hir_expr_while, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -43,53 +42,58 @@ declare_clippy_lint! { declare_lint_pass!(IneffectiveOpenOptions => [INEFFECTIVE_OPEN_OPTIONS]); -fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option { - if let [arg] = args - && let ExprKind::Lit(lit) = peel_blocks(arg).kind - && lit.node == LitKind::Bool(true) - { - // The `.` is not included in the span so we cheat a little bit to include it as well. - Some(call_span.with_lo(call_span.lo() - BytePos(1))) - } else { - None - } -} - impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else { - return; - }; - let receiver_ty = cx.typeck_results().expr_ty(receiver); - match receiver_ty.peel_refs().kind() { - ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::FsOpenOptions, adt.did()) => {}, - _ => return, - } - - let mut append = None; - let mut write = None; + if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind + && name.ident.name == sym::open + && !expr.span.from_expansion() + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions) + { + let mut append = false; + let mut write = None; + peel_hir_expr_while(recv, |e| { + if let ExprKind::MethodCall(name, recv, args, call_span) = e.kind + && !e.span.from_expansion() + { + if let [arg] = args + && let ExprKind::Lit(lit) = peel_blocks(arg).kind + && matches!(lit.node, LitKind::Bool(true)) + && !arg.span.from_expansion() + && !lit.span.from_expansion() + { + match name.ident.name { + sym::append => append = true, + sym::write + if let Some(range) = call_span.map_range(cx, |_, text, range| { + if text.get(..range.start)?.ends_with('.') { + Some(range.start - 1..range.end) + } else { + None + } + }) => + { + write = Some(call_span.with_lo(range.start)); + }, + _ => {}, + } + } + Some(recv) + } else { + None + } + }); - while let Some((name, recv, args, _, span)) = method_call(receiver) { - if name == sym::append { - append = index_if_arg_is_boolean(args, span); - } else if name == sym::write { - write = index_if_arg_is_boolean(args, span); + if append && let Some(write_span) = write { + span_lint_and_sugg( + cx, + INEFFECTIVE_OPEN_OPTIONS, + write_span, + "unnecessary use of `.write(true)` because there is `.append(true)`", + "remove `.write(true)`", + String::new(), + Applicability::MachineApplicable, + ); } - receiver = recv; - } - - if let Some(write_span) = write - && append.is_some() - { - span_lint_and_sugg( - cx, - INEFFECTIVE_OPEN_OPTIONS, - write_span, - "unnecessary use of `.write(true)` because there is `.append(true)`", - "remove `.write(true)`", - String::new(), - Applicability::MachineApplicable, - ); } } } diff --git a/src/tools/clippy/clippy_lints/src/infallible_try_from.rs b/src/tools/clippy/clippy_lints/src/infallible_try_from.rs index e79fcec6e6acf..f7cdf05359a3e 100644 --- a/src/tools/clippy/clippy_lints/src/infallible_try_from.rs +++ b/src/tools/clippy/clippy_lints/src/infallible_try_from.rs @@ -52,13 +52,17 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { if !cx.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id) { return; } - for ii in cx.tcx.associated_items(item.owner_id.def_id) + for ii in cx + .tcx + .associated_items(item.owner_id.def_id) .filter_by_name_unhygienic_and_kind(sym::Error, AssocTag::Type) { let ii_ty = cx.tcx.type_of(ii.def_id).instantiate_identity(); if !ii_ty.is_inhabited_from(cx.tcx, ii.def_id, cx.typing_env()) { let mut span = MultiSpan::from_span(cx.tcx.def_span(item.owner_id.to_def_id())); - let ii_ty_span = cx.tcx.hir_node_by_def_id(ii.def_id.expect_local()) + let ii_ty_span = cx + .tcx + .hir_node_by_def_id(ii.def_id.expect_local()) .expect_impl_item() .expect_type() .span; diff --git a/src/tools/clippy/clippy_lints/src/item_name_repetitions.rs b/src/tools/clippy/clippy_lints/src/item_name_repetitions.rs index 9c91cf6808512..95e16aae40f9a 100644 --- a/src/tools/clippy/clippy_lints/src/item_name_repetitions.rs +++ b/src/tools/clippy/clippy_lints/src/item_name_repetitions.rs @@ -8,6 +8,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; +use rustc_span::MacroKind; use rustc_span::symbol::Symbol; declare_clippy_lint! { @@ -502,7 +503,8 @@ impl LateLintPass<'_> for ItemNameRepetitions { ); } - if both_are_public && item_camel.len() > mod_camel.len() { + let is_macro_rule = matches!(item.kind, ItemKind::Macro(_, _, MacroKind::Bang)); + if both_are_public && item_camel.len() > mod_camel.len() && !is_macro_rule { let matching = count_match_start(mod_camel, &item_camel); let rmatching = count_match_end(mod_camel, &item_camel); let nchars = mod_camel.chars().count(); diff --git a/src/tools/clippy/clippy_lints/src/iter_without_into_iter.rs b/src/tools/clippy/clippy_lints/src/iter_without_into_iter.rs index 03038f0ab49c9..b89f91f7255fb 100644 --- a/src/tools/clippy/clippy_lints/src/iter_without_into_iter.rs +++ b/src/tools/clippy/clippy_lints/src/iter_without_into_iter.rs @@ -139,11 +139,17 @@ impl LateLintPass<'_> for IterWithoutIntoIter { // We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() }) - && let Some(iter_assoc_span) = cx.tcx.associated_items(item.owner_id) + && let Some(iter_assoc_span) = cx + .tcx + .associated_items(item.owner_id) .filter_by_name_unhygienic_and_kind(sym::IntoIter, ty::AssocTag::Type) .next() .map(|assoc_item| { - cx.tcx.hir_node_by_def_id(assoc_item.def_id.expect_local()).expect_impl_item().expect_type().span + cx.tcx + .hir_node_by_def_id(assoc_item.def_id.expect_local()) + .expect_impl_item() + .expect_type() + .span }) && is_ty_exported(cx, ty) { diff --git a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs index e85d779b48806..c2b7394310601 100644 --- a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs +++ b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_no_std_crate; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_copy}; use rustc_errors::Applicability; @@ -83,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { let mut difference = variants_size[0].size - variants_size[1].size; if difference > self.maximum_size_difference_allowed { - let help_text = "consider boxing the large fields to reduce the total size of the enum"; + let help_text = "consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum"; span_lint_and_then( cx, LARGE_ENUM_VARIANT, @@ -117,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { ident.span, "boxing a variant would require the type no longer be `Copy`", ); - } else { + } else if !is_no_std_crate(cx) { let sugg: Vec<(Span, String)> = variants_size[0] .fields_size .iter() diff --git a/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs b/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs index b3c63f022d353..42c636505c015 100644 --- a/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs +++ b/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs @@ -1,7 +1,8 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{get_parent_expr, is_from_proc_macro}; +use clippy_utils::source::SpanRangeExt; use hir::def_id::DefId; use rustc_errors::Applicability; use rustc_hir as hir; @@ -102,39 +103,45 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - let ExprKind::Path(qpath) = &expr.kind else { - return; - }; - // `std::::` check - let (span, sugg, msg) = if let QPath::Resolved(None, path) = qpath + let (sugg, msg) = if let ExprKind::Path(qpath) = &expr.kind + && let QPath::Resolved(None, path) = qpath && let Some(def_id) = path.res.opt_def_id() && is_numeric_const(cx, def_id) - && let def_path = cx.get_def_path(def_id) - && let [.., mod_name, name] = &*def_path + && let [.., mod_name, name] = &*cx.get_def_path(def_id) // Skip linting if this usage looks identical to the associated constant, // since this would only require removing a `use` import (which is already linted). && !is_numeric_const_path_canonical(path, [*mod_name, *name]) { ( - expr.span, - format!("{mod_name}::{name}"), + vec![(expr.span, format!("{mod_name}::{name}"))], "usage of a legacy numeric constant", ) // `::xxx_value` check - } else if let QPath::TypeRelative(_, last_segment) = qpath - && let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id() - && let Some(par_expr) = get_parent_expr(cx, expr) - && let ExprKind::Call(_, []) = par_expr.kind + } else if let ExprKind::Call(func, []) = &expr.kind + && let ExprKind::Path(qpath) = &func.kind + && let QPath::TypeRelative(ty, last_segment) = qpath + && let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() && is_integer_method(cx, def_id) { - let name = last_segment.ident.name.as_str(); - - ( - last_segment.ident.span.with_hi(par_expr.span.hi()), - name[..=2].to_ascii_uppercase(), - "usage of a legacy numeric method", - ) + let mut sugg = vec![ + // Replace the function name up to the end by the constant name + ( + last_segment.ident.span.to(expr.span.shrink_to_hi()), + last_segment.ident.name.as_str()[..=2].to_ascii_uppercase(), + ), + ]; + let before_span = expr.span.shrink_to_lo().until(ty.span); + if !before_span.is_empty() { + // Remove everything before the type name + sugg.push((before_span, String::new())); + } + // Use `::` between the type name and the constant + let between_span = ty.span.shrink_to_hi().until(last_segment.ident.span); + if !between_span.check_source_text(cx, |s| s == "::") { + sugg.push((between_span, String::from("::"))); + } + (sugg, "usage of a legacy numeric method") } else { return; }; @@ -143,9 +150,8 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { && self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS) && !is_from_proc_macro(cx, expr) { - span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| { - diag.span_suggestion_verbose( - span, + span_lint_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.span, msg, |diag| { + diag.multipart_suggestion_verbose( "use the associated constant instead", sugg, Applicability::MaybeIncorrect, diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 1bf03480c8250..6beddc1be1443 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -10,9 +10,9 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ - BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, - ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, - QPath, TraitItemId, TyKind, + BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, + Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemId, + TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FnSig, Ty}; @@ -266,11 +266,14 @@ fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span { } fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) { - fn is_named_self(cx: &LateContext<'_>, item: &TraitItemId, name: Symbol) -> bool { + fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool { cx.tcx.item_name(item.owner_id) == name && matches!( cx.tcx.fn_arg_idents(item.owner_id), - [Some(Ident { name: kw::SelfLower, .. })], + [Some(Ident { + name: kw::SelfLower, + .. + })], ) } @@ -284,7 +287,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden } if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id) - && trait_items.iter().any(|i| is_named_self(cx, i, sym::len)) + && trait_items.iter().any(|&i| is_named_self(cx, i, sym::len)) { let mut current_and_super_traits = DefIdSet::default(); fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx); diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 7837b18bcd36c..972b0b110e0e9 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -39,7 +39,9 @@ pub(super) fn check<'tcx>( var: canonical_id, indexed_mut: FxHashSet::default(), indexed_indirectly: FxHashMap::default(), + unnamed_indexed_indirectly: false, indexed_directly: FxIndexMap::default(), + unnamed_indexed_directly: false, referenced: FxHashSet::default(), nonindex: false, prefer_mutable: false, @@ -47,7 +49,11 @@ pub(super) fn check<'tcx>( walk_expr(&mut visitor, body); // linting condition: we only indexed one variable, and indexed it directly - if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + if visitor.indexed_indirectly.is_empty() + && !visitor.unnamed_indexed_indirectly + && !visitor.unnamed_indexed_directly + && visitor.indexed_directly.len() == 1 + { let (indexed, (indexed_extent, indexed_ty)) = visitor .indexed_directly .into_iter() @@ -217,6 +223,7 @@ fn is_end_eq_array_len<'tcx>( false } +#[expect(clippy::struct_excessive_bools)] struct VarVisitor<'a, 'tcx> { /// context reference cx: &'a LateContext<'tcx>, @@ -226,9 +233,13 @@ struct VarVisitor<'a, 'tcx> { indexed_mut: FxHashSet, /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global indexed_indirectly: FxHashMap>, + /// indirectly indexed literals, like `[1, 2, 3][(i + 4) % N]` + unnamed_indexed_indirectly: bool, /// subset of `indexed` of vars that are indexed directly: `v[i]` /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` indexed_directly: FxIndexMap, Ty<'tcx>)>, + /// directly indexed literals, like `[1, 2, 3][i]` + unnamed_indexed_directly: bool, /// Any names that are used outside an index operation. /// Used to detect things like `&mut vec` used together with `vec[i]` referenced: FxHashSet, @@ -242,6 +253,7 @@ struct VarVisitor<'a, 'tcx> { impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name && let QPath::Resolved(None, seqvar) = *seqpath @@ -251,7 +263,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); } - let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); match res { Res::Local(hir_id) => { @@ -286,6 +297,13 @@ impl<'tcx> VarVisitor<'_, 'tcx> { }, _ => (), } + } else if let ExprKind::Repeat(..) | ExprKind::Array(..) = seqexpr.kind { + if index_used_directly { + self.unnamed_indexed_directly = true; + } else { + self.unnamed_indexed_indirectly = true; + } + return false; } true } diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs index 69c84bc7038ef..8a253ae5810f6 100644 --- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs @@ -6,9 +6,11 @@ use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::snippet; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use rustc_errors::Applicability; -use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr}; +use rustc_hir::{ + Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, +}; use rustc_lint::LateContext; -use rustc_span::{Span, sym}; +use rustc_span::{BytePos, Span, sym}; use std::iter::once; use std::ops::ControlFlow; @@ -20,7 +22,7 @@ pub(super) fn check<'tcx>( for_loop: Option<&ForLoop<'_>>, ) { match never_loop_block(cx, block, &mut Vec::new(), loop_id) { - NeverLoopResult::Diverging => { + NeverLoopResult::Diverging { ref break_spans } => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { arg: iterator, @@ -38,10 +40,15 @@ pub(super) fn check<'tcx>( Applicability::Unspecified }; - diag.span_suggestion_verbose( + let mut suggestions = vec![( for_span.with_hi(iterator.span.hi()), - "if you need the first element of the iterator, try writing", for_to_if_let_sugg(cx, iterator, pat), + )]; + // Make sure to clear up the diverging sites when we remove a loopp. + suggestions.extend(break_spans.iter().map(|span| (*span, String::new()))); + diag.multipart_suggestion_verbose( + "if you need the first element of the iterator, try writing", + suggestions, app, ); } @@ -70,22 +77,22 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool { /// The first two bits of information are in this enum, and the last part is in the /// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by /// scope. -#[derive(Copy, Clone)] +#[derive(Clone)] enum NeverLoopResult { /// A continue may occur for the main loop. MayContinueMainLoop, /// We have not encountered any main loop continue, /// but we are diverging (subsequent control flow is not reachable) - Diverging, + Diverging { break_spans: Vec }, /// We have not encountered any main loop continue, /// and subsequent control flow is (possibly) reachable Normal, } #[must_use] -fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { match arg { - NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal, NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, } } @@ -94,7 +101,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { #[must_use] fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult { match first { - NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop => first, NeverLoopResult::Normal => second(), } } @@ -103,7 +110,7 @@ fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) #[must_use] fn combine_seq_many(iter: impl IntoIterator) -> NeverLoopResult { for e in iter { - if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e { + if let NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop = e { return e; } } @@ -118,7 +125,19 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult NeverLoopResult::MayContinueMainLoop }, (NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal, - (NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging, + ( + NeverLoopResult::Diverging { + break_spans: mut break_spans1, + }, + NeverLoopResult::Diverging { + break_spans: mut break_spans2, + }, + ) => { + break_spans1.append(&mut break_spans2); + NeverLoopResult::Diverging { + break_spans: break_spans1, + } + }, } } @@ -136,7 +155,7 @@ fn never_loop_block<'tcx>( combine_seq_many(iter.map(|(e, els)| { let e = never_loop_expr(cx, e, local_labels, main_loop_id); // els is an else block in a let...else binding - els.map_or(e, |els| { + els.map_or(e.clone(), |els| { combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) { // Returning MayContinueMainLoop here means that // we will not evaluate the rest of the body @@ -144,7 +163,7 @@ fn never_loop_block<'tcx>( // An else block always diverges, so the Normal case should not happen, // but the analysis is approximate so it might return Normal anyway. // Returning Normal here says that nothing more happens on the main path - NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal, }) }) })) @@ -159,6 +178,45 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t } } +fn stmt_source_span(stmt: &Stmt<'_>) -> Span { + let call_span = stmt.span.source_callsite(); + // if it is a macro call, the span will be missing the trailing semicolon + if stmt.span == call_span { + return call_span; + } + + // An expression without a trailing semi-colon (must have unit type). + if let StmtKind::Expr(..) = stmt.kind { + return call_span; + } + + call_span.with_hi(call_span.hi() + BytePos(1)) +} + +/// Returns a Vec of all the individual spans after the highlighted expression in a block +fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { + if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) { + if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) { + return block + .stmts + .iter() + .skip_while(|inner| inner.hir_id != stmt.hir_id) + .map(stmt_source_span) + .chain(if let Some(e) = block.expr { vec![e.span] } else { vec![] }) + .collect(); + } + + return vec![stmt.span]; + } + + vec![] +} + +fn is_label_for_block(cx: &LateContext<'_>, dest: &Destination) -> bool { + dest.target_id + .is_ok_and(|hir_id| matches!(cx.tcx.hir_node(hir_id), Node::Block(_))) +} + #[allow(clippy::too_many_lines)] fn never_loop_expr<'tcx>( cx: &LateContext<'tcx>, @@ -197,7 +255,7 @@ fn never_loop_expr<'tcx>( ExprKind::Loop(b, _, _, _) => { // We don't attempt to track reachability after a loop, // just assume there may have been a break somewhere - absorb_break(never_loop_block(cx, b, local_labels, main_loop_id)) + absorb_break(&never_loop_block(cx, b, local_labels, main_loop_id)) }, ExprKind::If(e, e2, e3) => { let e1 = never_loop_expr(cx, e, local_labels, main_loop_id); @@ -212,9 +270,10 @@ fn never_loop_expr<'tcx>( ExprKind::Match(e, arms, _) => { let e = never_loop_expr(cx, e, local_labels, main_loop_id); combine_seq(e, || { - arms.iter().fold(NeverLoopResult::Diverging, |a, b| { - combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) - }) + arms.iter() + .fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| { + combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) + }) }) }, ExprKind::Block(b, _) => { @@ -224,7 +283,7 @@ fn never_loop_expr<'tcx>( let ret = never_loop_block(cx, b, local_labels, main_loop_id); let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1; match ret { - NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } if jumped_to => NeverLoopResult::Normal, _ => ret, } }, @@ -235,25 +294,39 @@ fn never_loop_expr<'tcx>( if id == main_loop_id { NeverLoopResult::MayContinueMainLoop } else { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { + break_spans: all_spans_after_expr(cx, expr), + } } }, - ExprKind::Break(_, e) | ExprKind::Ret(e) => { + ExprKind::Ret(e) => { let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| { never_loop_expr(cx, e, local_labels, main_loop_id) }); combine_seq(first, || { // checks if break targets a block instead of a loop - if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind - && let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) - { - *reachable = true; + mark_block_as_reachable(expr, local_labels); + NeverLoopResult::Diverging { break_spans: vec![] } + }) + }, + ExprKind::Break(dest, e) => { + let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| { + never_loop_expr(cx, e, local_labels, main_loop_id) + }); + combine_seq(first, || { + // checks if break targets a block instead of a loop + mark_block_as_reachable(expr, local_labels); + NeverLoopResult::Diverging { + break_spans: if is_label_for_block(cx, &dest) { + vec![] + } else { + all_spans_after_expr(cx, expr) + }, } - NeverLoopResult::Diverging }) }, ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { break_spans: vec![] } }), ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { @@ -283,12 +356,12 @@ fn never_loop_expr<'tcx>( }; let result = combine_seq(result, || { if cx.typeck_results().expr_ty(expr).is_never() { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { break_spans: vec![] } } else { NeverLoopResult::Normal } }); - if let NeverLoopResult::Diverging = result + if let NeverLoopResult::Diverging { .. } = result && let Some(macro_call) = root_macro_call_first_node(cx, expr) && let Some(sym::todo_macro) = cx.tcx.get_diagnostic_name(macro_call.def_id) { @@ -316,3 +389,11 @@ fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) format!("if let Some({pat_snippet}) = {iter_snippet}.next()") } + +fn mark_block_as_reachable(expr: &Expr<'_>, local_labels: &mut [(HirId, bool)]) { + if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind + && let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) + { + *reachable = true; + } +} diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs index d1a54df988f52..3aa449f641183 100644 --- a/src/tools/clippy/clippy_lints/src/macro_use.rs +++ b/src/tools/clippy/clippy_lints/src/macro_use.rs @@ -1,15 +1,15 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use hir::def::{DefKind, Res}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::Span; use rustc_span::edition::Edition; -use rustc_span::{Span}; use std::collections::BTreeMap; -use rustc_attr_data_structures::{AttributeKind, find_attr}; declare_clippy_lint! { /// ### What it does diff --git a/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs b/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs index bac4b3d32f2a5..288f27db8ca2c 100644 --- a/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs +++ b/src/tools/clippy/clippy_lints/src/manual_abs_diff.rs @@ -5,7 +5,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::HasSession as _; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment}; +use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { && let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind && let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) = (op.node, rhs, lhs) - && let Some(ty) = self.are_ty_eligible(cx, a, b) + && let Some((ty, b_n_refs)) = self.are_ty_eligible(cx, a, b) && is_sub_expr(cx, if_expr.then, a, b, ty) && is_sub_expr(cx, r#else, b, a, ty) { @@ -86,8 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { } }; let sugg = format!( - "{}.abs_diff({})", + "{}.abs_diff({}{})", Sugg::hir(cx, a, "..").maybe_paren(), + "*".repeat(b_n_refs), Sugg::hir(cx, b, "..") ); diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability); @@ -100,13 +101,15 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { impl ManualAbsDiff { /// Returns a type if `a` and `b` are both of it, and this lint can be applied to that /// type (currently, any primitive int, or a `Duration`) - fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option> { + fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<(Ty<'tcx>, usize)> { let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF); let is_duration = |ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); - (a_ty == cx.typeck_results().expr_ty(b).peel_refs() && (is_int(a_ty) || is_duration(a_ty))).then_some(a_ty) + let (b_ty, b_n_refs) = peel_middle_ty_refs(cx.typeck_results().expr_ty(b)); + + (a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs)) } } diff --git a/src/tools/clippy/clippy_lints/src/manual_assert.rs b/src/tools/clippy/clippy_lints/src/manual_assert.rs index 8378e15c581c6..ea6b01a053a31 100644 --- a/src/tools/clippy/clippy_lints/src/manual_assert.rs +++ b/src/tools/clippy/clippy_lints/src/manual_assert.rs @@ -60,7 +60,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert { ExprKind::Unary(UnOp::Not, e) => (e, ""), _ => (cond, "!"), }; - let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren(); + let cond_sugg = + sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability).maybe_paren(); let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}"); // we show to the user the suggestion without the comments, but when applying the fix, include the diff --git a/src/tools/clippy/clippy_lints/src/matches/single_match.rs b/src/tools/clippy/clippy_lints/src/matches/single_match.rs index 08c0caa4266cc..7e530e98ac4a0 100644 --- a/src/tools/clippy/clippy_lints/src/matches/single_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/single_match.rs @@ -152,21 +152,26 @@ fn report_single_pattern( }) if lit.node.is_str() || lit.node.is_bytestr() => pat_ref_count + 1, _ => pat_ref_count, }; - // References are only implicitly added to the pattern, so no overflow here. - // e.g. will work: match &Some(_) { Some(_) => () } - // will not: match Some(_) { &Some(_) => () } - let ref_count_diff = ty_ref_count - pat_ref_count; - // Try to remove address of expressions first. - let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); - let ref_count_diff = ref_count_diff - removed; + // References are implicitly removed when `deref_patterns` are used. + // They are implicitly added when match ergonomics are used. + let (ex, ref_or_deref_adjust) = if ty_ref_count > pat_ref_count { + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + + (ex, String::from(if ref_count_diff == removed { "" } else { "&" })) + } else { + (ex, "*".repeat(pat_ref_count - ty_ref_count)) + }; let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; let sugg = format!( "if {} == {}{} {}{els_str}", snippet_with_context(cx, ex.span, ctxt, "..", &mut app).0, // PartialEq for different reference counts may not exist. - "&".repeat(ref_count_diff), + ref_or_deref_adjust, snippet_with_applicability(cx, arm.pat.span, "..", &mut app), expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app), ); diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs index 82e5a6d5a4120..6e5da5bda8c9d 100644 --- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs @@ -2,13 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{contains_return, is_inside_always_const_context, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; +use std::ops::ControlFlow; use super::EXPECT_FUN_CALL; @@ -23,10 +25,10 @@ pub(super) fn check<'tcx>( receiver: &'tcx hir::Expr<'tcx>, args: &'tcx [hir::Expr<'tcx>], ) { - // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or + // Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or // `&str` fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - let mut arg_root = arg; + let mut arg_root = peel_blocks(arg); loop { arg_root = match &arg_root.kind { hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, @@ -47,124 +49,68 @@ pub(super) fn check<'tcx>( arg_root } - // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be - // converted to string. - fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - let arg_ty = cx.typeck_results().expr_ty(arg); - if is_type_lang_item(cx, arg_ty, hir::LangItem::String) { - return false; - } - if let ty::Ref(_, ty, ..) = arg_ty.kind() - && ty.is_str() - && can_be_static_str(cx, arg) - { - return false; - } - true + fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool { + for_each_expr(cx, arg, |expr| { + if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. }) + && !is_inside_always_const_context(cx.tcx, expr.hir_id) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() } - // Check if an expression could have type `&'static str`, knowing that it - // has type `&str` for some lifetime. - fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - match arg.kind { - hir::ExprKind::Lit(_) => true, - hir::ExprKind::Call(fun, _) => { - if let hir::ExprKind::Path(ref p) = fun.kind { - match cx.qpath_res(p, fun.hir_id) { - hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( - cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder().kind(), - ty::Ref(re, ..) if re.is_static(), - ), - _ => false, - } - } else { - false - } - }, - hir::ExprKind::MethodCall(..) => { - cx.typeck_results() - .type_dependent_def_id(arg.hir_id) - .is_some_and(|method_id| { - matches!( - cx.tcx.fn_sig(method_id).instantiate_identity().output().skip_binder().kind(), - ty::Ref(re, ..) if re.is_static() - ) - }) - }, - hir::ExprKind::Path(ref p) => matches!( - cx.qpath_res(p, arg.hir_id), - hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _) - ), - _ => false, - } - } + if name == sym::expect + && let [arg] = args + && let arg_root = get_arg_root(cx, arg) + && contains_call(cx, arg_root) + && !contains_return(arg_root) + { + let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) { + "||" + } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) { + "|_|" + } else { + return; + }; - fn is_call(node: &hir::ExprKind<'_>) -> bool { - match node { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { - is_call(&expr.kind) - }, - hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - // These variants are debatable or require further examination - | hir::ExprKind::If(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Block{ .. } => true, - _ => false, - } - } + let span_replace_word = method_span.with_hi(expr.span.hi()); - if args.len() != 1 || name != sym::expect || !is_call(&args[0].kind) { - return; - } + let mut applicability = Applicability::MachineApplicable; - let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); - let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) { - "||" - } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) { - "|_|" - } else { - return; - }; - - let arg_root = get_arg_root(cx, &args[0]); - - let span_replace_word = method_span.with_hi(expr.span.hi()); - - let mut applicability = Applicability::MachineApplicable; - - // Special handling for `format!` as arg_root - if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { - if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) - { - let span = format_args_inputs_span(format_args); - let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("unwrap_or_else({closure_args} panic!({sugg}))"), - applicability, - ); + // Special handling for `format!` as arg_root + if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { + if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) + && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) + { + let span = format_args_inputs_span(format_args); + let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("unwrap_or_else({closure_args} panic!({sugg}))"), + applicability, + ); + } + return; } - return; - } - let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); - if requires_to_string(cx, arg_root) { - arg_root_snippet.to_mut().push_str(".to_string()"); - } + let arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"), - applicability, - ); + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"), + applicability, + ); + } } diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs index 965993808f6b5..94944bd9445b8 100644 --- a/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,6 +1,6 @@ use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks, @@ -45,9 +45,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) .count() && let Some(param_snippet) = param.span.get_source_text(cx) - && let Some(filter) = recv.span.get_source_text(cx) - && let Some(map) = then_body.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (filter, _) = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut applicability); + let (map, _) = snippet_with_context(cx, then_body.span, expr.span.ctxt(), "..", &mut applicability); + span_lint_and_then( cx, FILTER_MAP_BOOL_THEN, @@ -62,7 +64,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", derefs = "*".repeat(needed_derefs) ), - Applicability::MachineApplicable, + applicability, ); } else { diag.help("consider using `filter` then `map` instead"); diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs b/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs index 21f2ce8b7c900..bc96815944d5f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs @@ -100,7 +100,6 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: match x { UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())), UseKind::Borrowed(s) => { - #[expect(clippy::range_plus_one)] let range = s.map_range(cx, |_, src, range| { let src = src.get(range.clone())?; let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']); diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index f2dabdd343875..bcd54557331ba 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -3859,6 +3859,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// Also checks for equality comparisons like `option.map(f) == Some(true)` and `result.map(f) == Ok(true)`. /// /// ### Why is this bad? /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. @@ -3869,6 +3870,11 @@ declare_clippy_lint! { /// # let result: Result = Ok(1); /// option.map(|a| a > 10).unwrap_or_default(); /// result.map(|a| a > 10).unwrap_or_default(); + /// + /// option.map(|a| a > 10) == Some(true); + /// result.map(|a| a > 10) == Ok(true); + /// option.map(|a| a > 10) != Some(true); + /// result.map(|a| a > 10) != Ok(true); /// ``` /// Use instead: /// ```no_run @@ -3876,11 +3882,16 @@ declare_clippy_lint! { /// # let result: Result = Ok(1); /// option.is_some_and(|a| a > 10); /// result.is_ok_and(|a| a > 10); + /// + /// option.is_some_and(|a| a > 10); + /// result.is_ok_and(|a| a > 10); + /// option.is_none_or(|a| a > 10); + /// !result.is_ok_and(|a| a > 10); /// ``` #[clippy::version = "1.77.0"] pub MANUAL_IS_VARIANT_AND, pedantic, - "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" + "using `.map(f).unwrap_or_default()` or `.map(f) == Some/Ok(true)`, which are more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" } declare_clippy_lint! { @@ -5275,10 +5286,6 @@ impl Methods { } map_identity::check(cx, expr, recv, m_arg, name, span); manual_inspect::check(cx, expr, m_arg, name, span, self.msrv); - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); - }, - (sym::map_break | sym::map_continue, [m_arg]) => { - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); }, (sym::map_or, [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); @@ -5546,7 +5553,7 @@ impl Methods { // Handle method calls whose receiver and arguments may come from expansion if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind { match (path.ident.name, args) { - (sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => { + (sym::expect, [_]) => { unwrap_expect_used::check( cx, expr, diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs index 6ce7dd3d4d0a8..04f0e3c0479e0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs @@ -242,15 +242,23 @@ pub(super) fn check<'tcx>( let inner_arg = peel_blocks(arg); for_each_expr(cx, inner_arg, |ex| { let is_top_most_expr = ex.hir_id == inner_arg.hir_id; - if let hir::ExprKind::Call(fun, fun_args) = ex.kind { - let fun_span = if fun_args.is_empty() && is_top_most_expr { - Some(fun.span) - } else { - None - }; - if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) { - return ControlFlow::Break(()); - } + match ex.kind { + hir::ExprKind::Call(fun, fun_args) => { + let fun_span = if fun_args.is_empty() && is_top_most_expr { + Some(fun.span) + } else { + None + }; + if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) { + return ControlFlow::Break(()); + } + }, + hir::ExprKind::MethodCall(..) => { + if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, None) { + return ControlFlow::Break(()); + } + }, + _ => {}, } ControlFlow::Continue(()) }); diff --git a/src/tools/clippy/clippy_lints/src/missing_fields_in_debug.rs b/src/tools/clippy/clippy_lints/src/missing_fields_in_debug.rs index 760ecf0758921..18e2b384a463d 100644 --- a/src/tools/clippy/clippy_lints/src/missing_fields_in_debug.rs +++ b/src/tools/clippy/clippy_lints/src/missing_fields_in_debug.rs @@ -7,9 +7,7 @@ use clippy_utils::{is_path_lang_item, sym}; use rustc_ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{ - Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData, -}; +use rustc_hir::{Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{Ty, TypeckResults}; use rustc_session::declare_lint_pass; diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs index c4a3d10299b62..5a5025973b5b3 100644 --- a/src/tools/clippy/clippy_lints/src/missing_inline.rs +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; use rustc_attr_data_structures::{AttributeKind, find_attr}; -use rustc_hir as hir; -use rustc_hir::Attribute; +use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, Attribute}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocItemContainer; use rustc_session::declare_lint_pass; @@ -97,11 +97,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } match it.kind { hir::ItemKind::Fn { .. } => { + if fn_is_externally_exported(cx, it.owner_id.to_def_id()) { + return; + } + let desc = "a function"; let attrs = cx.tcx.hir_attrs(it.hir_id()); check_missing_inline_attrs(cx, attrs, it.span, desc); }, - hir::ItemKind::Trait(ref _constness, ref _is_auto, ref _unsafe, _ident, _generics, _bounds, trait_items) => { + hir::ItemKind::Trait( + ref _constness, + ref _is_auto, + ref _unsafe, + _ident, + _generics, + _bounds, + trait_items, + ) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for &tit in trait_items { @@ -173,3 +185,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { check_missing_inline_attrs(cx, attrs, impl_item.span, desc); } } + +/// Checks if this function is externally exported, where #[inline] wouldn't have the desired effect +/// and a rustc warning would be triggered, see #15301 +fn fn_is_externally_exported(cx: &LateContext<'_>, def_id: DefId) -> bool { + let attrs = cx.tcx.codegen_fn_attrs(def_id); + attrs.contains_extern_indicator() +} diff --git a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs index fa61d0fa11afc..399bf4e18064c 100644 --- a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs +++ b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs @@ -66,7 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods { }) = item.kind && let Some(trait_id) = trait_ref.trait_def_id() { - let trait_item_ids: DefIdSet = cx.tcx.associated_items(item.owner_id) + let trait_item_ids: DefIdSet = cx + .tcx + .associated_items(item.owner_id) .in_definition_order() .filter_map(|assoc_item| assoc_item.trait_item_def_id) .collect(); diff --git a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs index d9f4fb271fb4b..a489c0a4a5a18 100644 --- a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs @@ -171,14 +171,11 @@ impl<'tcx> Visitor<'tcx> for DivergenceVisitor<'_, 'tcx> { ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e), ExprKind::Call(func, _) => { let typ = self.cx.typeck_results().expr_ty(func); - match typ.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let sig = typ.fn_sig(self.cx.tcx); - if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never { - self.report_diverging_sub_expr(e); - } - }, - _ => {}, + if typ.is_fn() { + let sig = typ.fn_sig(self.cx.tcx); + if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never { + self.report_diverging_sub_expr(e); + } } }, ExprKind::MethodCall(..) => { diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs index 2f1ab3d2652ae..31f51b4575405 100644 --- a/src/tools/clippy/clippy_lints/src/mut_reference.rs +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -79,7 +79,7 @@ fn check_arguments<'tcx>( name: &str, fn_kind: &str, ) { - if let ty::FnDef(..) | ty::FnPtr(..) = type_definition.kind() { + if type_definition.is_fn() { let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); for (argument, parameter) in iter::zip(arguments, parameters) { if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() diff --git a/src/tools/clippy/clippy_lints/src/needless_for_each.rs b/src/tools/clippy/clippy_lints/src/needless_for_each.rs index 6a7c8436bad4f..a67545e419ce7 100644 --- a/src/tools/clippy/clippy_lints/src/needless_for_each.rs +++ b/src/tools/clippy/clippy_lints/src/needless_for_each.rs @@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass; use rustc_span::Span; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::has_iter_method; use clippy_utils::{is_trait_method, sym}; @@ -101,18 +101,23 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { let body_param_sugg = snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability); let for_each_rev_sugg = snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability); - let body_value_sugg = snippet_with_applicability(cx, body.value.span, "..", &mut applicability); + let (body_value_sugg, is_macro_call) = + snippet_with_context(cx, body.value.span, for_each_recv.span.ctxt(), "..", &mut applicability); let sugg = format!( "for {} in {} {}", body_param_sugg, for_each_rev_sugg, - match body.value.kind { - ExprKind::Block(block, _) if is_let_desugar(block) => { - format!("{{ {body_value_sugg} }}") - }, - ExprKind::Block(_, _) => body_value_sugg.to_string(), - _ => format!("{{ {body_value_sugg}; }}"), + if is_macro_call { + format!("{{ {body_value_sugg}; }}") + } else { + match body.value.kind { + ExprKind::Block(block, _) if is_let_desugar(block) => { + format!("{{ {body_value_sugg} }}") + }, + ExprKind::Block(_, _) => body_value_sugg.to_string(), + _ => format!("{{ {body_value_sugg}; }}"), + } } ); diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index c97ecce75b468..2006a824402d6 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -246,8 +246,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - span.get_source_text(cx) - .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), + span.get_source_text(cx).map_or_else( + || "change the call to".to_owned(), + |src| format!("change `{src}` to"), + ), suggestion, Applicability::Unspecified, ); @@ -275,8 +277,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - span.get_source_text(cx) - .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), + span.get_source_text(cx).map_or_else( + || "change the call to".to_owned(), + |src| format!("change `{src}` to"), + ), suggestion, Applicability::Unspecified, ); diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs index 3b86f1d1f593c..b598a390005ba 100644 --- a/src/tools/clippy/clippy_lints/src/new_without_default.rs +++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs @@ -65,11 +65,16 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { .. }) = item.kind { - for assoc_item in cx.tcx.associated_items(item.owner_id.def_id) + for assoc_item in cx + .tcx + .associated_items(item.owner_id.def_id) .filter_by_name_unhygienic(sym::new) { if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind { - let impl_item = cx.tcx.hir_node_by_def_id(assoc_item.def_id.expect_local()).expect_impl_item(); + let impl_item = cx + .tcx + .hir_node_by_def_id(assoc_item.def_id.expect_local()) + .expect_impl_item(); if impl_item.span.in_external_macro(cx.sess().source_map()) { return; } diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs index a78a342d4fe39..466beb04b0747 100644 --- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -3,12 +3,11 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; +use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; @@ -89,6 +88,18 @@ impl ArithmeticSideEffects { self.allowed_unary.contains(ty_string_elem) } + fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, substs) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_type = substs.type_at(0) + && matches!(int_type.kind(), ty::Uint(_)) + { + true + } else { + false + } + } + /// Verifies built-in types that have specific allowed operations fn has_specific_allowed_type_and_operation<'tcx>( cx: &LateContext<'tcx>, @@ -97,33 +108,12 @@ impl ArithmeticSideEffects { rhs_ty: Ty<'tcx>, ) -> bool { let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_non_zero_u = |cx: &LateContext<'tcx>, ty: Ty<'tcx>| { - let tcx = cx.tcx; - - let ty::Adt(adt, substs) = ty.kind() else { return false }; - - if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { - return false; - } - - let int_type = substs.type_at(0); - let unsigned_int_types = [ - tcx.types.u8, - tcx.types.u16, - tcx.types.u32, - tcx.types.u64, - tcx.types.u128, - tcx.types.usize, - ]; - - unsigned_int_types.contains(&int_type) - }; let is_sat_or_wrap = |ty: Ty<'_>| { is_type_diagnostic_item(cx, ty, sym::Saturating) || is_type_diagnostic_item(cx, ty, sym::Wrapping) }; // If the RHS is `NonZero`, then division or module by zero will never occur. - if is_non_zero_u(cx, rhs_ty) && is_div_or_rem { + if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { return true; } @@ -219,6 +209,18 @@ impl ArithmeticSideEffects { let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); actual_lhs = expr_or_init(cx, actual_lhs); actual_rhs = expr_or_init(cx, actual_rhs); + + // `NonZeroU*.get() - 1`, will never overflow + if let hir::BinOpKind::Sub = op + && let hir::ExprKind::MethodCall(method, receiver, [], _) = actual_lhs.kind + && method.ident.name == sym::get + && let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs() + && Self::is_non_zero_u(cx, receiver_ty) + && let Some(1) = Self::literal_integer(cx, actual_rhs) + { + return; + } + let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs(); if self.has_allowed_binary(lhs_ty, rhs_ty) { @@ -227,6 +229,7 @@ impl ArithmeticSideEffects { if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { return; } + let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { // At least for integers, shifts are already handled by the CTFE diff --git a/src/tools/clippy/clippy_lints/src/operators/manual_is_multiple_of.rs b/src/tools/clippy/clippy_lints/src/operators/manual_is_multiple_of.rs index 821178a431588..55bb78cfce5fc 100644 --- a/src/tools/clippy/clippy_lints/src/operators/manual_is_multiple_of.rs +++ b/src/tools/clippy/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -2,11 +2,12 @@ use clippy_utils::consts::is_zero_integer_const; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::sugg::Sugg; +use clippy_utils::ty::expr_type_is_certain; use rustc_ast::BinOpKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use super::MANUAL_IS_MULTIPLE_OF; @@ -22,9 +23,21 @@ pub(super) fn check<'tcx>( && let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs) && let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind && operand_op.node == BinOpKind::Rem + && matches!( + cx.typeck_results().expr_ty_adjusted(operand_left).peel_refs().kind(), + ty::Uint(_) + ) + && matches!( + cx.typeck_results().expr_ty_adjusted(operand_right).peel_refs().kind(), + ty::Uint(_) + ) + && expr_type_is_certain(cx, operand_left) { let mut app = Applicability::MachineApplicable; - let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app); + let divisor = deref_sugg( + Sugg::hir_with_applicability(cx, operand_right, "_", &mut app), + cx.typeck_results().expr_ty_adjusted(operand_right), + ); span_lint_and_sugg( cx, MANUAL_IS_MULTIPLE_OF, @@ -64,3 +77,11 @@ fn uint_compare_to_zero<'tcx>( matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand) } + +fn deref_sugg<'a>(sugg: Sugg<'a>, ty: Ty<'_>) -> Sugg<'a> { + if let ty::Ref(_, target_ty, _) = ty.kind() { + deref_sugg(sugg.deref(), *target_ty) + } else { + sugg + } +} diff --git a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs index 19d9acfc9305a..4197680dd0472 100644 --- a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs +++ b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs @@ -96,6 +96,12 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Match(_, arms, _) = expr.kind { + // if the match is generated by an external macro, the writer does not control + // how the scrutinee (`match &scrutiny { ... }`) is matched + if expr.span.in_external_macro(cx.sess().source_map()) { + return; + } + for arm in arms { let pat = &arm.pat; if apply_lint(cx, pat, DerefPossible::Possible) { diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index 94cdcf0005484..b3058c51afdbe 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -584,7 +584,13 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ Some((Node::Stmt(_), _)) => (), Some((Node::LetStmt(l), _)) => { // Only trace simple bindings. e.g `let x = y;` - if let PatKind::Binding(BindingMode::NONE, id, _, None) = l.pat.kind { + if let PatKind::Binding(BindingMode::NONE, id, ident, None) = l.pat.kind + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter through this local let binding + // with the underscore being only temporary. + && !ident.name.as_str().starts_with('_') + { self.bindings.insert(id, args_idx); } else { set_skip_flag(); @@ -650,7 +656,14 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ .filter_map(|(i, arg)| { let param = &body.params[arg.idx]; match param.pat.kind { - PatKind::Binding(BindingMode::NONE, id, _, None) if !is_lint_allowed(cx, PTR_ARG, param.hir_id) => { + PatKind::Binding(BindingMode::NONE, id, ident, None) + if !is_lint_allowed(cx, PTR_ARG, param.hir_id) + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter with the underscore being only + // temporary. + && !ident.name.as_str().starts_with('_') => + { Some((id, i)) }, _ => { diff --git a/src/tools/clippy/clippy_lints/src/ranges.rs b/src/tools/clippy/clippy_lints/src/ranges.rs index d292ed86ea4c6..9281678b3d837 100644 --- a/src/tools/clippy/clippy_lints/src/ranges.rs +++ b/src/tools/clippy/clippy_lints/src/ranges.rs @@ -4,15 +4,20 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::{get_parent_expr, higher, is_in_const_context, is_integer_const, path_to_local}; +use clippy_utils::ty::implements_trait; +use clippy_utils::{ + expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item, + path_to_local, +}; +use rustc_ast::Mutability; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, HirId}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, LangItem, Node}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::Span; use rustc_span::source_map::Spanned; +use rustc_span::{Span, sym}; use std::cmp::Ordering; declare_clippy_lint! { @@ -24,6 +29,12 @@ declare_clippy_lint! { /// The code is more readable with an inclusive range /// like `x..=y`. /// + /// ### Limitations + /// The lint is conservative and will trigger only when switching + /// from an exclusive to an inclusive range is provably safe from + /// a typing point of view. This corresponds to situations where + /// the range is used as an iterator, or for indexing. + /// /// ### Known problems /// Will add unnecessary pair of parentheses when the /// expression is not wrapped in a pair but starts with an opening parenthesis @@ -34,11 +45,6 @@ declare_clippy_lint! { /// exclusive ranges, because they essentially add an extra branch that /// LLVM may fail to hoist out of the loop. /// - /// This will cause a warning that cannot be fixed if the consumer of the - /// range only accepts a specific range type, instead of the generic - /// `RangeBounds` trait - /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). - /// /// ### Example /// ```no_run /// # let x = 0; @@ -71,11 +77,11 @@ declare_clippy_lint! { /// The code is more readable with an exclusive range /// like `x..y`. /// - /// ### Known problems - /// This will cause a warning that cannot be fixed if - /// the consumer of the range only accepts a specific range type, instead of - /// the generic `RangeBounds` trait - /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). + /// ### Limitations + /// The lint is conservative and will trigger only when switching + /// from an inclusive to an exclusive range is provably safe from + /// a typing point of view. This corresponds to situations where + /// the range is used as an iterator, or for indexing. /// /// ### Example /// ```no_run @@ -344,70 +350,188 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> None } -// exclusive range plus one: `x..(y+1)` -fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { - if expr.span.can_be_used_for_suggestions() - && let Some(higher::Range { - start, - end: Some(end), - limits: RangeLimits::HalfOpen, - }) = higher::Range::hir(expr) - && let Some(y) = y_plus_one(cx, end) +/// Check whether `expr` could switch range types without breaking the typing requirements. This is +/// generally the case when `expr` is used as an iterator for example, or as a slice or `&str` +/// index. +/// +/// FIXME: Note that the current implementation may still return false positives. A proper fix would +/// check that the obligations are still satisfied after switching the range type. +fn can_switch_ranges<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + original: RangeLimits, + inner_ty: Ty<'tcx>, +) -> bool { + let use_ctxt = expr_use_ctxt(cx, expr); + let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else { + return false; + }; + + // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)` + if let ExprKind::Call(func, [arg]) = parent_expr.kind + && arg.hir_id == use_ctxt.child_id + && is_path_lang_item(cx, func, LangItem::IntoIterIntoIter) { - let span = expr.span; - span_lint_and_then( - cx, - RANGE_PLUS_ONE, - span, - "an inclusive range would be more readable", - |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_paren(); - match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { - Some(true) => { - diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect); - }, - Some(false) => { - diag.span_suggestion( - span, - "use", - format!("{start}..={end}"), - Applicability::MachineApplicable, // snippet - ); - }, - None => {}, - } - }, - ); + return true; + } + + // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`, + // or `RangeBounds` traits. + if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind + && receiver.hir_id == use_ctxt.child_id + && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) + && let Some(trait_did) = cx.tcx.trait_of_item(method_did) + && matches!( + cx.tcx.get_diagnostic_name(trait_did), + Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) + ) + { + return true; + } + + // Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`, + // or `RangeBounds` trait. + if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind + && let Some(id) = fn_def_id(cx, parent_expr) + && let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id) + { + let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) { + arg_idx + 1 + } else { + arg_idx + }; + let inputs = cx + .tcx + .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) + .inputs(); + let expr_ty = inputs[input_idx]; + // Check that the `expr` type is present only once, otherwise modifying just one of them might be + // risky if they are referenced using the same generic type for example. + if inputs.iter().enumerate().all(|(n, ty)| + n == input_idx + || !ty.walk().any(|arg| matches!(arg.kind(), + GenericArgKind::Type(ty) if ty == expr_ty))) + // Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`. + && cx + .tcx + .param_env(id) + .caller_bounds() + .into_iter() + .any(|p| { + if let ClauseKind::Trait(t) = p.kind().skip_binder() + && t.polarity == PredicatePolarity::Positive + && matches!( + cx.tcx.get_diagnostic_name(t.trait_ref.def_id), + Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) + ) + { + t.self_ty() == expr_ty + } else { + false + } + }) + { + return true; + } + } + + // Check if `expr` is used for indexing, and if the switched range type could be used + // as well. + if let ExprKind::Index(outer_expr, index, _) = parent_expr.kind + && index.hir_id == expr.hir_id + // Build the switched range type (for example `RangeInclusive`). + && let Some(switched_range_def_id) = match original { + RangeLimits::HalfOpen => cx.tcx.lang_items().range_inclusive_struct(), + RangeLimits::Closed => cx.tcx.lang_items().range_struct(), + } + && let switched_range_ty = cx + .tcx + .type_of(switched_range_def_id) + .instantiate(cx.tcx, &[inner_ty.into()]) + // Check that the switched range type can be used for indexing the original expression + // through the `Index` or `IndexMut` trait. + && let ty::Ref(_, outer_ty, mutability) = cx.typeck_results().expr_ty_adjusted(outer_expr).kind() + && let Some(index_def_id) = match mutability { + Mutability::Not => cx.tcx.lang_items().index_trait(), + Mutability::Mut => cx.tcx.lang_items().index_mut_trait(), + } + && implements_trait(cx, *outer_ty, index_def_id, &[switched_range_ty.into()]) + // We could also check that the associated item of the `index_def_id` trait with the switched range type + // return the same type, but it is reasonable to expect so. We can't check that the result is identical + // in both `Index>` and `Index>` anyway. + { + return true; } + + false +} + +// exclusive range plus one: `x..(y+1)` +fn check_exclusive_range_plus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_range_switch( + cx, + expr, + RangeLimits::HalfOpen, + y_plus_one, + RANGE_PLUS_ONE, + "an inclusive range would be more readable", + "..=", + ); } // inclusive range minus one: `x..=(y-1)` -fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { +fn check_inclusive_range_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_range_switch( + cx, + expr, + RangeLimits::Closed, + y_minus_one, + RANGE_MINUS_ONE, + "an exclusive range would be more readable", + "..", + ); +} + +/// Check for a `kind` of range in `expr`, check for `predicate` on the end, +/// and emit the `lint` with `msg` and the `operator`. +fn check_range_switch<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + kind: RangeLimits, + predicate: impl for<'hir> FnOnce(&LateContext<'_>, &Expr<'hir>) -> Option<&'hir Expr<'hir>>, + lint: &'static Lint, + msg: &'static str, + operator: &str, +) { if expr.span.can_be_used_for_suggestions() && let Some(higher::Range { start, end: Some(end), - limits: RangeLimits::Closed, + limits, }) = higher::Range::hir(expr) - && let Some(y) = y_minus_one(cx, end) + && limits == kind + && let Some(y) = predicate(cx, end) + && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) { - span_lint_and_then( - cx, - RANGE_MINUS_ONE, - expr.span, - "an exclusive range would be more readable", - |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_paren(); - diag.span_suggestion( - expr.span, - "use", - format!("{start}..{end}"), - Applicability::MachineApplicable, // snippet - ); - }, - ); + let span = expr.span; + span_lint_and_then(cx, lint, span, msg, |diag| { + let mut app = Applicability::MachineApplicable; + let start = start.map_or(String::new(), |x| { + Sugg::hir_with_applicability(cx, x, "", &mut app) + .maybe_paren() + .to_string() + }); + let end = Sugg::hir_with_applicability(cx, y, "", &mut app).maybe_paren(); + match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { + Some(true) => { + diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app); + }, + Some(false) => { + diag.span_suggestion(span, "use", format!("{start}{operator}{end}"), app); + }, + None => {}, + } + }); } } @@ -494,7 +618,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { } } -fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { +fn y_plus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Binary( Spanned { @@ -515,7 +639,7 @@ fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<' } } -fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { +fn y_minus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Binary( Spanned { diff --git a/src/tools/clippy/clippy_lints/src/same_name_method.rs b/src/tools/clippy/clippy_lints/src/same_name_method.rs index 85fde780e6819..67eb71f7d0747 100644 --- a/src/tools/clippy/clippy_lints/src/same_name_method.rs +++ b/src/tools/clippy/clippy_lints/src/same_name_method.rs @@ -3,7 +3,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{AssocKind, AssocItem}; +use rustc_middle::ty::{AssocItem, AssocKind}; use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; @@ -53,11 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { for id in cx.tcx.hir_free_items() { if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. }) && let item = cx.tcx.hir_item(id) - && let ItemKind::Impl(Impl { - of_trait, - self_ty, - .. - }) = &item.kind + && let ItemKind::Impl(Impl { of_trait, self_ty, .. }) = &item.kind && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind { if !map.contains_key(res) { @@ -127,7 +123,9 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }, None => { for assoc_item in cx.tcx.associated_items(id.owner_id).in_definition_order() { - let AssocKind::Fn { name, .. } = assoc_item.kind else { continue }; + let AssocKind::Fn { name, .. } = assoc_item.kind else { + continue; + }; let impl_span = cx.tcx.def_span(assoc_item.def_id); let hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local()); if let Some(trait_spans) = existing_name.trait_methods.get(&name) { @@ -140,10 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { |diag| { // TODO should we `span_note` on every trait? // iterate on trait_spans? - diag.span_note( - trait_spans[0], - format!("existing `{name}` defined here"), - ); + diag.span_note(trait_spans[0], format!("existing `{name}` defined here")); }, ); } diff --git a/src/tools/clippy/clippy_lints/src/unused_async.rs b/src/tools/clippy/clippy_lints/src/unused_async.rs index e67afc7f5a8b5..5a3e4b7adf641 100644 --- a/src/tools/clippy/clippy_lints/src/unused_async.rs +++ b/src/tools/clippy/clippy_lints/src/unused_async.rs @@ -1,8 +1,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_def_id_trait_method; +use clippy_utils::usage::is_todo_unimplemented_stub; use rustc_hir::def::DefKind; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; -use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource}; +use rustc_hir::{ + Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, + TraitItem, YieldSource, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::impl_lint_pass; @@ -81,11 +85,8 @@ impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> { let is_async_block = matches!( ex.kind, - ExprKind::Closure(rustc_hir::Closure { - kind: rustc_hir::ClosureKind::Coroutine(rustc_hir::CoroutineKind::Desugared( - rustc_hir::CoroutineDesugaring::Async, - _ - )), + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), .. }) ); @@ -120,6 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) && !is_default_trait_impl(cx, def_id) + && !async_fn_contains_todo_unimplemented_macro(cx, body) { let mut visitor = AsyncFnVisitor { cx, @@ -203,3 +205,18 @@ fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { }) ) } + +fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool { + if let ExprKind::Closure(closure) = body.value.kind + && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind + && let body = cx.tcx.hir_body(closure.body) + && let ExprKind::Block(block, _) = body.value.kind + && block.stmts.is_empty() + && let Some(expr) = block.expr + && let ExprKind::DropTemps(inner) = expr.kind + { + return is_todo_unimplemented_stub(cx, inner); + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/unused_self.rs b/src/tools/clippy/clippy_lints/src/unused_self.rs index 12da891a71b11..dff39974a3737 100644 --- a/src/tools/clippy/clippy_lints/src/unused_self.rs +++ b/src/tools/clippy/clippy_lints/src/unused_self.rs @@ -1,12 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::sym; +use clippy_utils::usage::is_todo_unimplemented_stub; use clippy_utils::visitors::is_local_used; -use rustc_hir::{Body, Impl, ImplItem, ImplItemKind, ItemKind}; +use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does @@ -60,18 +58,6 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; let parent_item = cx.tcx.hir_expect_item(parent); let assoc_item = cx.tcx.associated_item(impl_item.owner_id); - let contains_todo = |cx, body: &'_ Body<'_>| -> bool { - clippy_utils::visitors::for_each_expr_without_closures(body.value, |e| { - if let Some(macro_call) = root_macro_call_first_node(cx, e) - && cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() - }; if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind && assoc_item.is_method() && let ImplItemKind::Fn(.., body_id) = &impl_item.kind @@ -79,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { && let body = cx.tcx.hir_body(*body_id) && let [self_param, ..] = body.params && !is_local_used(cx, body, self_param.pat.hir_id) - && !contains_todo(cx, body) + && !is_todo_unimplemented_stub(cx, body.value) { span_lint_and_help( cx, diff --git a/src/tools/clippy/clippy_lints/src/unused_trait_names.rs b/src/tools/clippy/clippy_lints/src/unused_trait_names.rs index b7a1d5b2123ea..12f2804dbaa1d 100644 --- a/src/tools/clippy/clippy_lints/src/unused_trait_names.rs +++ b/src/tools/clippy/clippy_lints/src/unused_trait_names.rs @@ -6,7 +6,7 @@ use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Item, ItemKind, UseKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext as _}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Visibility; use rustc_session::impl_lint_pass; use rustc_span::symbol::kw; @@ -59,7 +59,7 @@ impl_lint_pass!(UnusedTraitNames => [UNUSED_TRAIT_NAMES]); impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if !item.span.in_external_macro(cx.sess().source_map()) + if !item.span.from_expansion() && let ItemKind::Use(path, UseKind::Single(ident)) = item.kind // Ignore imports that already use Underscore && ident.name != kw::Underscore diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs index 380ddea4e1e83..e5b20c0e0a133 100644 --- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs +++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs @@ -176,6 +176,33 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } }, + ExprKind::MethodCall(path, recv, [arg], _) => { + if matches!( + path.ident.name, + sym::map | sym::map_err | sym::map_break | sym::map_continue + ) && has_eligible_receiver(cx, recv, e) + && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) + && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() + && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() + && same_type_and_consts(from_ty, to_ty) + { + span_lint_and_then( + cx, + USELESS_CONVERSION, + e.span.with_lo(recv.span.hi()), + format!("useless conversion to the same type: `{from_ty}`"), + |diag| { + diag.suggest_remove_item( + cx, + e.span.with_lo(recv.span.hi()), + "consider removing", + Applicability::MachineApplicable, + ); + }, + ); + } + }, + ExprKind::MethodCall(name, recv, [], _) => { if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); @@ -412,32 +439,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } } -/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a -/// higher-order mapping function. -pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if has_eligible_receiver(cx, recv, expr) - && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) - && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() - && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() - && same_type_and_consts(from_ty, to_ty) - { - span_lint_and_then( - cx, - USELESS_CONVERSION, - expr.span.with_lo(recv.span.hi()), - format!("useless conversion to the same type: `{from_ty}`"), - |diag| { - diag.suggest_remove_item( - cx, - expr.span.with_lo(recv.span.hi()), - "consider removing", - Applicability::MachineApplicable, - ); - }, - ); - } -} - fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { if is_inherent_method_call(cx, expr) { matches!( diff --git a/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs index 88b099c477f99..41fafc08c2594 100644 --- a/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs +++ b/src/tools/clippy/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::paths; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::{AttrStyle, DelimArgs}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::{ @@ -11,7 +12,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_lint_defs::declare_tool_lint; use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_tool_lint! { /// ### What it does @@ -88,7 +88,10 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown { } // Is it derived? - if !find_attr!(cx.tcx.get_all_attrs(item.owner_id), AttributeKind::AutomaticallyDerived(..)) { + if !find_attr!( + cx.tcx.get_all_attrs(item.owner_id), + AttributeKind::AutomaticallyDerived(..) + ) { return; } diff --git a/src/tools/clippy/clippy_lints_internal/src/lint_without_lint_pass.rs b/src/tools/clippy/clippy_lints_internal/src/lint_without_lint_pass.rs index 45a866030b2dd..fda65bc84eda3 100644 --- a/src/tools/clippy/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/src/tools/clippy/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -1,7 +1,7 @@ use crate::internal_paths; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::is_lint_allowed; use clippy_utils::macros::root_macro_call_first_node; +use clippy_utils::{is_lint_allowed, sym}; use rustc_ast::ast::LitKind; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_hir as hir; @@ -12,9 +12,9 @@ use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; use rustc_span::source_map::Spanned; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; declare_tool_lint! { /// ### What it does @@ -160,9 +160,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { let body = cx.tcx.hir_body_owned_by( impl_item_refs .iter() - .find(|iiref| iiref.ident.as_str() == "lint_vec") + .find(|&&iiref| cx.tcx.item_name(iiref.owner_id) == sym::lint_vec) .expect("LintPass needs to implement lint_vec") - .id .owner_id .def_id, ); diff --git a/src/tools/clippy/clippy_lints_internal/src/msrv_attr_impl.rs b/src/tools/clippy/clippy_lints_internal/src/msrv_attr_impl.rs index 70b3c03d2bbd3..66aeb910891a2 100644 --- a/src/tools/clippy/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/src/tools/clippy/clippy_lints_internal/src/msrv_attr_impl.rs @@ -1,6 +1,7 @@ use crate::internal_paths; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; +use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -40,7 +41,9 @@ impl LateLintPass<'_> for MsrvAttrImpl { .filter(|t| matches!(t.kind(), GenericArgKind::Type(_))) .any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty())) }) - && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") + && !items + .iter() + .any(|&item| cx.tcx.item_name(item.owner_id) == sym::check_attributes) { let span = cx.sess().source_map().span_through_char(item.span, '{'); span_lint_and_sugg( diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index 645b644d9f4e8..19e71f6af1de1 100644 --- a/src/tools/clippy/clippy_utils/README.md +++ b/src/tools/clippy/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-07-10 +nightly-2025-07-25 ``` diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs index 8453165818b31..625e1eead213d 100644 --- a/src/tools/clippy/clippy_utils/src/diagnostics.rs +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs @@ -22,10 +22,13 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { { diag.help(format!( "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}", - &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| { - // extract just major + minor version and ignore patch versions - format!("rust-{}", n.rsplit_once('.').unwrap().1) - }) + &option_env!("RUST_RELEASE_NUM").map_or_else( + || "master".to_string(), + |n| { + // extract just major + minor version and ignore patch versions + format!("rust-{}", n.rsplit_once('.').unwrap().1) + } + ) )); } } diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index ff1ee663f9bf2..ce5af4d2f482e 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -89,8 +89,8 @@ use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; use rustc_abi::Integer; -use rustc_ast::join_path_syms; use rustc_ast::ast::{self, LitKind, RangeLimits}; +use rustc_ast::join_path_syms; use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::packed::Pu128; @@ -114,7 +114,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::hir::place::PlaceBase; use rustc_middle::lint::LevelAndSource; use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, PointerCoercion}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, @@ -1897,6 +1897,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// * `|x| { return x }` /// * `|x| { return x; }` /// * `|(x, y)| (x, y)` +/// * `|[x, y]| [x, y]` /// /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead. fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { @@ -1907,9 +1908,9 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { .get(pat.hir_id) .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) { - // If a tuple `(x, y)` is of type `&(i32, i32)`, then due to match ergonomics, - // the inner patterns become references. Don't consider this the identity function - // as that changes types. + // If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then + // due to match ergonomics, the inner patterns become references. Don't consider this + // the identity function as that changes types. return false; } @@ -1922,6 +1923,13 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { { pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr)) }, + (PatKind::Slice(before, slice, after), ExprKind::Array(arr)) + if slice.is_none() && before.len() + after.len() == arr.len() => + { + (before.iter().chain(after)) + .zip(arr) + .all(|(pat, expr)| check_pat(cx, pat, expr)) + }, _ => false, } } @@ -3269,15 +3277,13 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St if go_up_by > max_super { // `super` chain would be too long, just use the absolute path instead - join_path_syms( - once(kw::Crate).chain(to.data.iter().filter_map(|el| { - if let DefPathData::TypeNs(sym) = el.data { - Some(sym) - } else { - None - } - })) - ) + join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| { + if let DefPathData::TypeNs(sym) = el.data { + Some(sym) + } else { + None + } + }))) } else { join_path_syms(repeat_n(kw::Super, go_up_by).chain(path)) } @@ -3560,3 +3566,14 @@ pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) // enclosing body. false } + +/// Checks if the expression has adjustments that require coercion, for example: dereferencing with +/// overloaded deref, coercing pointers and `dyn` objects. +pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_adjustments(expr).iter().any(|adj| { + matches!( + adj.kind, + Adjust::Deref(Some(_)) | Adjust::Pointer(PointerCoercion::Unsize) | Adjust::NeverToAny + ) + }) +} diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index c681806517afb..ea8cfc59356a7 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -308,10 +308,11 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n None } }), - ItemKind::Impl(..) | ItemKind::Trait(..) - => tcx.associated_items(local_id).filter_by_name_unhygienic(name) - .find(|assoc_item| ns.matches(Some(assoc_item.namespace()))) - .map(|assoc_item| assoc_item.def_id), + ItemKind::Impl(..) | ItemKind::Trait(..) => tcx + .associated_items(local_id) + .filter_by_name_unhygienic(name) + .find(|assoc_item| ns.matches(Some(assoc_item.namespace()))) + .map(|assoc_item| assoc_item.def_id), _ => None, } } diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs index 8a8218c6976f3..934be97d94e51 100644 --- a/src/tools/clippy/clippy_utils/src/sym.rs +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -98,6 +98,7 @@ generate! { ceil_char_boundary, chain, chars, + check_attributes, checked_abs, checked_add, checked_isqrt, @@ -196,6 +197,7 @@ generate! { kw, last, lazy_static, + lint_vec, ln, lock, lock_api, @@ -261,6 +263,7 @@ generate! { read_to_end, read_to_string, read_unaligned, + redundant_imports, redundant_pub_crate, regex, rem_euclid, diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs index fe208c032f4c0..d70232ef3aa5a 100644 --- a/src/tools/clippy/clippy_utils/src/ty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs @@ -492,10 +492,7 @@ pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { /// Returns `true` if the given type is an `unsafe` function. pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match ty.kind() { - ty::FnDef(..) | ty::FnPtr(..) => ty.fn_sig(cx.tcx).safety().is_unsafe(), - _ => false, - } + ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe() } /// Returns the base type for HIR references and pointers. diff --git a/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs index 84df36c75bf89..d9c7e6eac9f67 100644 --- a/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs @@ -12,10 +12,11 @@ //! be considered a bug. use crate::paths::{PathNS, lookup_path}; +use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty}; -use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind}; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, Param, PathSegment, QPath, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty}; use rustc_span::Span; @@ -24,22 +25,24 @@ mod certainty; use certainty::{Certainty, Meet, join, meet}; pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - expr_type_certainty(cx, expr).is_certain() + expr_type_certainty(cx, expr, false).is_certain() } -fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { +/// Determine the type certainty of `expr`. `in_arg` indicates that the expression happens within +/// the evaluation of a function or method call argument. +fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> Certainty { let certainty = match &expr.kind { ExprKind::Unary(_, expr) | ExprKind::Field(expr, _) | ExprKind::Index(expr, _, _) - | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), + | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr, in_arg), - ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))), ExprKind::Call(callee, args) => { - let lhs = expr_type_certainty(cx, callee); + let lhs = expr_type_certainty(cx, callee, false); let rhs = if type_is_inferable_from_arguments(cx, expr) { - meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) + meet(args.iter().map(|arg| expr_type_certainty(cx, arg, true))) } else { Certainty::Uncertain }; @@ -47,7 +50,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { }, ExprKind::MethodCall(method, receiver, args, _) => { - let mut receiver_type_certainty = expr_type_certainty(cx, receiver); + let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false); // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`, // for example. So update the `DefId` in `receiver_type_certainty` (if any). @@ -59,7 +62,8 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); let rhs = if type_is_inferable_from_arguments(cx, expr) { meet( - std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), + std::iter::once(receiver_type_certainty) + .chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))), ) } else { Certainty::Uncertain @@ -67,16 +71,39 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { lhs.join(rhs) }, - ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))), - ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)), + ExprKind::Binary(_, lhs, rhs) => { + // If one of the side of the expression is uncertain, the certainty will come from the other side, + // with no information on the type. + match ( + expr_type_certainty(cx, lhs, in_arg), + expr_type_certainty(cx, rhs, in_arg), + ) { + (Certainty::Uncertain, Certainty::Certain(_)) | (Certainty::Certain(_), Certainty::Uncertain) => { + Certainty::Certain(None) + }, + (l, r) => l.meet(r), + } + }, - ExprKind::Lit(_) => Certainty::Certain(None), + ExprKind::Lit(lit) => { + if !in_arg + && matches!( + lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) + ) + { + Certainty::Uncertain + } else { + Certainty::Certain(None) + } + }, ExprKind::Cast(_, ty) => type_certainty(cx, ty), ExprKind::If(_, if_expr, Some(else_expr)) => { - expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr)) + expr_type_certainty(cx, if_expr, in_arg).join(expr_type_certainty(cx, else_expr, in_arg)) }, ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false), @@ -188,6 +215,20 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo certainty } +/// Tries to tell whether `param` resolves to something certain, e.g., a non-wildcard type if +/// present. The certainty `DefId` is cleared before returning. +fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty { + let owner_did = cx.tcx.hir_enclosing_body_owner(param.hir_id); + let Some(fn_decl) = cx.tcx.hir_fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(owner_did)) else { + return Certainty::Uncertain; + }; + let inputs = fn_decl.inputs; + let body_params = cx.tcx.hir_body_owned_by(owner_did).params; + std::iter::zip(body_params, inputs) + .find(|(p, _)| p.hir_id == param.hir_id) + .map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id()) +} + fn path_segment_certainty( cx: &LateContext<'_>, parent_certainty: Certainty, @@ -240,15 +281,16 @@ fn path_segment_certainty( // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`. Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) { - // An argument's type is always certain. - Node::Param(..) => Certainty::Certain(None), + // A parameter's type is not always certain, as it may come from an untyped closure definition, + // or from a wildcard in a typed closure definition. + Node::Param(param) => param_certainty(cx, param), // A local's type is certain if its type annotation is certain or it has an initializer whose // type is certain. Node::LetStmt(local) => { let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)); let rhs = local .init - .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init)); + .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init, false)); let certainty = lhs.join(rhs); if resolves_to_type { certainty diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index 1b049b6d12c4c..76d43feee12ea 100644 --- a/src/tools/clippy/clippy_utils/src/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -1,3 +1,4 @@ +use crate::macros::root_macro_call_first_node; use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures}; use crate::{self as utils, get_enclosing_loop_or_multi_call_closure}; use core::ops::ControlFlow; @@ -9,6 +10,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty; +use rustc_span::sym; /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option { @@ -140,6 +142,46 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { } } +/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`. +pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + root_macro_call_first_node(cx, expr).is_some_and(|macro_call| { + [sym::todo_macro, sym::unimplemented_macro] + .iter() + .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id)) + }) +} + +/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression, +/// or a block whose last expression is a `todo!()` or `unimplemented!()`. +pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Block(block, _) = expr.kind { + if let Some(last_expr) = block.expr { + return is_todo_unimplemented_macro(cx, last_expr); + } + + return block.stmts.last().is_some_and(|stmt| { + if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind { + return is_todo_unimplemented_macro(cx, expr); + } + false + }); + } + + is_todo_unimplemented_macro(cx, expr) +} + +/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`. +pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { + for_each_expr_without_closures(expr, |e| { + if is_todo_unimplemented_macro(cx, e) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { for_each_expr_without_closures(expression, |e| { match e.kind { diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml index f46e079db3f10..0edb80edd04e1 100644 --- a/src/tools/clippy/rust-toolchain.toml +++ b/src/tools/clippy/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-07-10" +channel = "nightly-2025-07-25" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/tools/clippy/rustc_tools_util/src/lib.rs b/src/tools/clippy/rustc_tools_util/src/lib.rs index b45edf2345585..194ed84d04c28 100644 --- a/src/tools/clippy/rustc_tools_util/src/lib.rs +++ b/src/tools/clippy/rustc_tools_util/src/lib.rs @@ -157,7 +157,8 @@ pub fn get_commit_date() -> Option { #[must_use] pub fn get_compiler_version() -> Option { - get_output("rustc", &["-V"]) + let compiler = std::option_env!("RUSTC").unwrap_or("rustc"); + get_output(compiler, &["-V"]) } #[must_use] @@ -172,6 +173,8 @@ pub fn get_channel(compiler_version: Option) -> String { return String::from("beta"); } else if rustc_output.contains("nightly") { return String::from("nightly"); + } else if rustc_output.contains("dev") { + return String::from("dev"); } } diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index 83f91ccaa7b4b..464efc45c6b4c 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -162,6 +162,10 @@ impl TestContext { // however has some staging logic that is hurting us here, so to work around // that we set both the "real" and "staging" rustc to TEST_RUSTC, including the // associated library paths. + #[expect( + clippy::option_env_unwrap, + reason = "TEST_RUSTC will ensure that the requested env vars are set during compile time" + )] if let Some(rustc) = option_env!("TEST_RUSTC") { let libdir = option_env!("TEST_RUSTC_LIB").unwrap(); let sysroot = option_env!("TEST_SYSROOT").unwrap(); @@ -169,10 +173,7 @@ impl TestContext { p.envs.push(("RUSTC_REAL_LIBDIR".into(), Some(libdir.into()))); p.envs.push(("RUSTC_SNAPSHOT".into(), Some(rustc.into()))); p.envs.push(("RUSTC_SNAPSHOT_LIBDIR".into(), Some(libdir.into()))); - p.envs.push(( - "RUSTC_SYSROOT".into(), - Some(sysroot.into()), - )); + p.envs.push(("RUSTC_SYSROOT".into(), Some(sysroot.into()))); } p }, diff --git a/src/tools/clippy/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr b/src/tools/clippy/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr index 8a85d38fba3c2..608264beb102b 100644 --- a/src/tools/clippy/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr +++ b/src/tools/clippy/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr @@ -18,6 +18,8 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is | LL | sleep(Duration::new(1, 0)); | ^^^^^ + | + = note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute error: aborting due to 3 previous errors diff --git a/src/tools/clippy/tests/ui-toml/enum_variant_size/enum_variant_size.stderr b/src/tools/clippy/tests/ui-toml/enum_variant_size/enum_variant_size.stderr index 020b3cc787823..a5dfd7015a397 100644 --- a/src/tools/clippy/tests/ui-toml/enum_variant_size/enum_variant_size.stderr +++ b/src/tools/clippy/tests/ui-toml/enum_variant_size/enum_variant_size.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 501]), LL + B(Box<[u8; 501]>), diff --git a/src/tools/clippy/tests/ui/approx_const.rs b/src/tools/clippy/tests/ui/approx_const.rs index 6461666be8f5c..fc493421a165c 100644 --- a/src/tools/clippy/tests/ui/approx_const.rs +++ b/src/tools/clippy/tests/ui/approx_const.rs @@ -106,4 +106,19 @@ fn main() { //~^ approx_constant let no_tau = 6.3; + + // issue #15194 + #[allow(clippy::excessive_precision)] + let x: f64 = 3.1415926535897932384626433832; + //~^ approx_constant + + #[allow(clippy::excessive_precision)] + let _: f64 = 003.14159265358979311599796346854418516159057617187500; + //~^ approx_constant + + let almost_frac_1_sqrt_2 = 00.70711; + //~^ approx_constant + + let almost_frac_1_sqrt_2 = 00.707_11; + //~^ approx_constant } diff --git a/src/tools/clippy/tests/ui/approx_const.stderr b/src/tools/clippy/tests/ui/approx_const.stderr index f7bda0468cbdc..32a3517ff2e41 100644 --- a/src/tools/clippy/tests/ui/approx_const.stderr +++ b/src/tools/clippy/tests/ui/approx_const.stderr @@ -184,5 +184,37 @@ LL | let almost_tau = 6.28; | = help: consider using the constant directly -error: aborting due to 23 previous errors +error: approximate value of `f{32, 64}::consts::PI` found + --> tests/ui/approx_const.rs:112:18 + | +LL | let x: f64 = 3.1415926535897932384626433832; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::PI` found + --> tests/ui/approx_const.rs:116:18 + | +LL | let _: f64 = 003.14159265358979311599796346854418516159057617187500; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found + --> tests/ui/approx_const.rs:119:32 + | +LL | let almost_frac_1_sqrt_2 = 00.70711; + | ^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found + --> tests/ui/approx_const.rs:122:32 + | +LL | let almost_frac_1_sqrt_2 = 00.707_11; + | ^^^^^^^^^ + | + = help: consider using the constant directly + +error: aborting due to 27 previous errors diff --git a/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr b/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr index 5556b0df88c91..ce726206b0cdd 100644 --- a/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr +++ b/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr @@ -5,7 +5,7 @@ LL | let _ = Arc::new(RefCell::new(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc>` is not `Send` and `Sync` as `RefCell` is not `Sync` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `RefCell` `Send` and `Sync` or consider a wrapper type such as `Mutex` = note: `-D clippy::arc-with-non-send-sync` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::arc_with_non_send_sync)]` @@ -17,7 +17,7 @@ LL | let _ = Arc::new(mutex.lock().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc>` is not `Send` and `Sync` as `MutexGuard<'_, i32>` is not `Send` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `MutexGuard<'_, i32>` `Send` and `Sync` or consider a wrapper type such as `Mutex` error: usage of an `Arc` that is not `Send` and `Sync` @@ -27,7 +27,7 @@ LL | let _ = Arc::new(&42 as *const i32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc<*const i32>` is not `Send` and `Sync` as `*const i32` is neither `Send` nor `Sync` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `*const i32` `Send` and `Sync` or consider a wrapper type such as `Mutex` error: aborting due to 3 previous errors diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs index 21be2af201f07..3245b2c983e19 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs @@ -664,6 +664,20 @@ pub fn issue_12318() { //~^ arithmetic_side_effects } +pub fn issue_15225() { + use core::num::{NonZero, NonZeroU8}; + + let one = const { NonZeroU8::new(1).unwrap() }; + let _ = one.get() - 1; + + let one: NonZero = const { NonZero::new(1).unwrap() }; + let _ = one.get() - 1; + + type AliasedType = u8; + let one: NonZero = const { NonZero::new(1).unwrap() }; + let _ = one.get() - 1; +} + pub fn explicit_methods() { use core::ops::Add; let one: i32 = 1; diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr index e15fb612be5e5..4150493ba94a3 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr @@ -758,13 +758,13 @@ LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:670:5 + --> tests/ui/arithmetic_side_effects.rs:684:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:672:5 + --> tests/ui/arithmetic_side_effects.rs:686:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/assign_ops.fixed b/src/tools/clippy/tests/ui/assign_ops.fixed index eee61f949e76b..3754b9dfe7443 100644 --- a/src/tools/clippy/tests/ui/assign_ops.fixed +++ b/src/tools/clippy/tests/ui/assign_ops.fixed @@ -84,6 +84,7 @@ mod issue14871 { const ONE: Self; } + #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) pub const trait NumberConstants { fn constant(value: usize) -> Self; } diff --git a/src/tools/clippy/tests/ui/assign_ops.rs b/src/tools/clippy/tests/ui/assign_ops.rs index 13ffcee0a3c8a..0b878d4f490b8 100644 --- a/src/tools/clippy/tests/ui/assign_ops.rs +++ b/src/tools/clippy/tests/ui/assign_ops.rs @@ -84,6 +84,7 @@ mod issue14871 { const ONE: Self; } + #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) pub const trait NumberConstants { fn constant(value: usize) -> Self; } diff --git a/src/tools/clippy/tests/ui/auxiliary/external_item.rs b/src/tools/clippy/tests/ui/auxiliary/external_item.rs index ca4bc369e4494..621e18f5c017a 100644 --- a/src/tools/clippy/tests/ui/auxiliary/external_item.rs +++ b/src/tools/clippy/tests/ui/auxiliary/external_item.rs @@ -4,4 +4,4 @@ impl _ExternalStruct { pub fn _foo(self) {} } -pub fn _exernal_foo() {} +pub fn _external_foo() {} diff --git a/src/tools/clippy/tests/ui/checked_conversions.fixed b/src/tools/clippy/tests/ui/checked_conversions.fixed index 279a5b6e1ff8b..6175275ef0473 100644 --- a/src/tools/clippy/tests/ui/checked_conversions.fixed +++ b/src/tools/clippy/tests/ui/checked_conversions.fixed @@ -95,7 +95,7 @@ pub const fn issue_8898(i: u32) -> bool { #[clippy::msrv = "1.33"] fn msrv_1_33() { let value: i64 = 33; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; } #[clippy::msrv = "1.34"] diff --git a/src/tools/clippy/tests/ui/checked_conversions.rs b/src/tools/clippy/tests/ui/checked_conversions.rs index c339bc674bbb5..9ed0e8f660d0e 100644 --- a/src/tools/clippy/tests/ui/checked_conversions.rs +++ b/src/tools/clippy/tests/ui/checked_conversions.rs @@ -95,13 +95,13 @@ pub const fn issue_8898(i: u32) -> bool { #[clippy::msrv = "1.33"] fn msrv_1_33() { let value: i64 = 33; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; } #[clippy::msrv = "1.34"] fn msrv_1_34() { let value: i64 = 34; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; //~^ checked_conversions } diff --git a/src/tools/clippy/tests/ui/checked_conversions.stderr b/src/tools/clippy/tests/ui/checked_conversions.stderr index 3841b9d5a4ddd..624876dacb260 100644 --- a/src/tools/clippy/tests/ui/checked_conversions.stderr +++ b/src/tools/clippy/tests/ui/checked_conversions.stderr @@ -100,8 +100,8 @@ LL | let _ = value <= u16::MAX as u32 && value as i32 == 5; error: checked cast can be simplified --> tests/ui/checked_conversions.rs:104:13 | -LL | let _ = value <= (u32::MAX as i64) && value >= 0; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` +LL | let _ = value <= (u32::max_value() as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/expect.rs b/src/tools/clippy/tests/ui/expect.rs index 8f7379f002143..1ab01ecfcfe3a 100644 --- a/src/tools/clippy/tests/ui/expect.rs +++ b/src/tools/clippy/tests/ui/expect.rs @@ -16,7 +16,26 @@ fn expect_result() { //~^ expect_used } +#[allow(clippy::ok_expect)] +#[allow(clippy::err_expect)] +fn issue_15247() { + let x: Result = Err(0); + x.ok().expect("Huh"); + //~^ expect_used + + { x.ok() }.expect("..."); + //~^ expect_used + + let y: Result = Ok(0); + y.err().expect("Huh"); + //~^ expect_used + + { y.err() }.expect("..."); + //~^ expect_used +} + fn main() { expect_option(); expect_result(); + issue_15247(); } diff --git a/src/tools/clippy/tests/ui/expect.stderr b/src/tools/clippy/tests/ui/expect.stderr index 70cf3072003e6..353fb77653157 100644 --- a/src/tools/clippy/tests/ui/expect.stderr +++ b/src/tools/clippy/tests/ui/expect.stderr @@ -24,5 +24,37 @@ LL | let _ = res.expect_err(""); | = note: if this value is an `Ok`, it will panic -error: aborting due to 3 previous errors +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:23:5 + | +LL | x.ok().expect("Huh"); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:26:5 + | +LL | { x.ok() }.expect("..."); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:30:5 + | +LL | y.err().expect("Huh"); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:33:5 + | +LL | { y.err() }.expect("..."); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/expect_fun_call.fixed b/src/tools/clippy/tests/ui/expect_fun_call.fixed index 73eaebf773c83..b923521afde1c 100644 --- a/src/tools/clippy/tests/ui/expect_fun_call.fixed +++ b/src/tools/clippy/tests/ui/expect_fun_call.fixed @@ -90,17 +90,30 @@ fn main() { "foo" } - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + const fn const_evaluable() -> &'static str { + "foo" + } + + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_static_str())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_non_static_str(&0))); + //~^ expect_fun_call + + Some("foo").unwrap_or_else(|| panic!("{}", const_evaluable())); //~^ expect_fun_call + + const { + Some("foo").expect(const_evaluable()); + } + + Some("foo").expect(const { const_evaluable() }); } //Issue #3839 @@ -122,4 +135,15 @@ fn main() { let format_capture_and_value: Option = None; format_capture_and_value.unwrap_or_else(|| panic!("{error_code}, {}", 1)); //~^ expect_fun_call + + // Issue #15056 + let a = false; + Some(5).expect(if a { "a" } else { "b" }); + + let return_in_expect: Option = None; + return_in_expect.expect(if true { + "Error" + } else { + return; + }); } diff --git a/src/tools/clippy/tests/ui/expect_fun_call.rs b/src/tools/clippy/tests/ui/expect_fun_call.rs index ecebc9ebfb6f0..bc58d24bc8128 100644 --- a/src/tools/clippy/tests/ui/expect_fun_call.rs +++ b/src/tools/clippy/tests/ui/expect_fun_call.rs @@ -90,6 +90,10 @@ fn main() { "foo" } + const fn const_evaluable() -> &'static str { + "foo" + } + Some("foo").expect(&get_string()); //~^ expect_fun_call Some("foo").expect(get_string().as_ref()); @@ -101,6 +105,15 @@ fn main() { //~^ expect_fun_call Some("foo").expect(get_non_static_str(&0)); //~^ expect_fun_call + + Some("foo").expect(const_evaluable()); + //~^ expect_fun_call + + const { + Some("foo").expect(const_evaluable()); + } + + Some("foo").expect(const { const_evaluable() }); } //Issue #3839 @@ -122,4 +135,15 @@ fn main() { let format_capture_and_value: Option = None; format_capture_and_value.expect(&format!("{error_code}, {}", 1)); //~^ expect_fun_call + + // Issue #15056 + let a = false; + Some(5).expect(if a { "a" } else { "b" }); + + let return_in_expect: Option = None; + return_in_expect.expect(if true { + "Error" + } else { + return; + }); } diff --git a/src/tools/clippy/tests/ui/expect_fun_call.stderr b/src/tools/clippy/tests/ui/expect_fun_call.stderr index 36713196cb920..0692ecb4862e8 100644 --- a/src/tools/clippy/tests/ui/expect_fun_call.stderr +++ b/src/tools/clippy/tests/ui/expect_fun_call.stderr @@ -38,58 +38,64 @@ LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{} {}", 1, 2))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:93:21 + --> tests/ui/expect_fun_call.rs:97:21 | LL | Some("foo").expect(&get_string()); - | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:95:21 + --> tests/ui/expect_fun_call.rs:99:21 | LL | Some("foo").expect(get_string().as_ref()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:97:21 + --> tests/ui/expect_fun_call.rs:101:21 | LL | Some("foo").expect(get_string().as_str()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:100:21 + --> tests/ui/expect_fun_call.rs:104:21 | LL | Some("foo").expect(get_static_str()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_static_str()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_static_str()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:102:21 + --> tests/ui/expect_fun_call.rs:106:21 | LL | Some("foo").expect(get_non_static_str(&0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_non_static_str(&0)))` + +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:109:21 + | +LL | Some("foo").expect(const_evaluable()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", const_evaluable()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:107:16 + --> tests/ui/expect_fun_call.rs:120:16 | LL | Some(true).expect(&format!("key {}, {}", 1, 2)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:114:17 + --> tests/ui/expect_fun_call.rs:127:17 | LL | opt_ref.expect(&format!("{:?}", opt_ref)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{:?}", opt_ref))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:119:20 + --> tests/ui/expect_fun_call.rs:132:20 | LL | format_capture.expect(&format!("{error_code}")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}"))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:123:30 + --> tests/ui/expect_fun_call.rs:136:30 | LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.fixed b/src/tools/clippy/tests/ui/filter_map_bool_then.fixed index b3e112f19eb4a..d370b85a67eff 100644 --- a/src/tools/clippy/tests/ui/filter_map_bool_then.fixed +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.fixed @@ -89,3 +89,24 @@ fn issue11503() { let _: Vec = bools.iter().enumerate().filter(|&(i, b)| ****b).map(|(i, b)| i).collect(); //~^ filter_map_bool_then } + +fn issue15047() { + #[derive(Clone, Copy)] + enum MyEnum { + A, + B, + C, + } + + macro_rules! foo { + ($e:expr) => { + $e + 1 + }; + } + + let x = 1; + let _ = [(MyEnum::A, "foo", 1i32)] + .iter() + .filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x)); + //~^ filter_map_bool_then +} diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.rs b/src/tools/clippy/tests/ui/filter_map_bool_then.rs index d996b3cb3c52a..12295cc24823e 100644 --- a/src/tools/clippy/tests/ui/filter_map_bool_then.rs +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.rs @@ -89,3 +89,24 @@ fn issue11503() { let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); //~^ filter_map_bool_then } + +fn issue15047() { + #[derive(Clone, Copy)] + enum MyEnum { + A, + B, + C, + } + + macro_rules! foo { + ($e:expr) => { + $e + 1 + }; + } + + let x = 1; + let _ = [(MyEnum::A, "foo", 1i32)] + .iter() + .filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x))); + //~^ filter_map_bool_then +} diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.stderr b/src/tools/clippy/tests/ui/filter_map_bool_then.stderr index aeb1baeb35e6e..edf6c6559396c 100644 --- a/src/tools/clippy/tests/ui/filter_map_bool_then.stderr +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.stderr @@ -61,5 +61,11 @@ error: usage of `bool::then` in `filter_map` LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| ****b).map(|(i, b)| i)` -error: aborting due to 10 previous errors +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then.rs:110:10 + | +LL | .filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x))` + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/flat_map_identity.fixed b/src/tools/clippy/tests/ui/flat_map_identity.fixed index f62062326126f..06a3eee9d84c9 100644 --- a/src/tools/clippy/tests/ui/flat_map_identity.fixed +++ b/src/tools/clippy/tests/ui/flat_map_identity.fixed @@ -16,3 +16,16 @@ fn main() { let _ = iterator.flatten(); //~^ flat_map_identity } + +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: this is an `Iterator` + // match ergonomics makes the binding patterns into references + // so that its type changes to `Iterator` + let _ = x.iter().flat_map(|[x, y]| [x, y]); + let _ = x.iter().flat_map(|x| [x[0]]); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().flatten(); + //~^ flat_map_identity +} diff --git a/src/tools/clippy/tests/ui/flat_map_identity.rs b/src/tools/clippy/tests/ui/flat_map_identity.rs index c59e749474ee2..1cab7d559d8f1 100644 --- a/src/tools/clippy/tests/ui/flat_map_identity.rs +++ b/src/tools/clippy/tests/ui/flat_map_identity.rs @@ -16,3 +16,16 @@ fn main() { let _ = iterator.flat_map(|x| return x); //~^ flat_map_identity } + +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: this is an `Iterator` + // match ergonomics makes the binding patterns into references + // so that its type changes to `Iterator` + let _ = x.iter().flat_map(|[x, y]| [x, y]); + let _ = x.iter().flat_map(|x| [x[0]]); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().flat_map(|[x, y]| [x, y]); + //~^ flat_map_identity +} diff --git a/src/tools/clippy/tests/ui/flat_map_identity.stderr b/src/tools/clippy/tests/ui/flat_map_identity.stderr index 75137f5d9e573..18c863bf96d5f 100644 --- a/src/tools/clippy/tests/ui/flat_map_identity.stderr +++ b/src/tools/clippy/tests/ui/flat_map_identity.stderr @@ -19,5 +19,11 @@ error: use of `flat_map` with an identity function LL | let _ = iterator.flat_map(|x| return x); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` -error: aborting due to 3 previous errors +error: use of `flat_map` with an identity function + --> tests/ui/flat_map_identity.rs:29:31 + | +LL | let _ = x.iter().copied().flat_map(|[x, y]| [x, y]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.fixed b/src/tools/clippy/tests/ui/if_then_some_else_none.fixed index f774608712d1a..d14a805b66679 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.fixed +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.fixed @@ -122,3 +122,46 @@ const fn issue12103(x: u32) -> Option { // Should not issue an error in `const` context if x > 42 { Some(150) } else { None } } + +mod issue15257 { + struct Range { + start: u8, + end: u8, + } + + fn can_be_safely_rewrite(rs: &[&Range]) -> Option> { + (rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start]) + } + + fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> { + let modulo = unsafe { *i % 2 }; + (modulo == 0).then_some(i) + } + + fn reborrow_as_fn_ptr(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something((i % 2 == 0).then_some(item_fn)); + } + + fn reborrow_as_fn_unsafe(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something((i % 2 == 0).then_some(item_fn)); + + let closure_fn = |i: i32| {}; + do_something((i % 2 == 0).then_some(closure_fn)); + } +} diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.rs b/src/tools/clippy/tests/ui/if_then_some_else_none.rs index 8b8ff0a6ea006..bb0072f31573f 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.rs +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.rs @@ -143,3 +143,71 @@ const fn issue12103(x: u32) -> Option { // Should not issue an error in `const` context if x > 42 { Some(150) } else { None } } + +mod issue15257 { + struct Range { + start: u8, + end: u8, + } + + fn can_be_safely_rewrite(rs: &[&Range]) -> Option> { + if rs.len() == 1 && rs[0].start == rs[0].end { + //~^ if_then_some_else_none + Some(vec![rs[0].start]) + } else { + None + } + } + + fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> { + let modulo = unsafe { *i % 2 }; + if modulo == 0 { + //~^ if_then_some_else_none + Some(i) + } else { + None + } + } + + fn reborrow_as_fn_ptr(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(item_fn) + } else { + None + }); + } + + fn reborrow_as_fn_unsafe(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(item_fn) + } else { + None + }); + + let closure_fn = |i: i32| {}; + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(closure_fn) + } else { + None + }); + } +} diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.stderr b/src/tools/clippy/tests/ui/if_then_some_else_none.stderr index 71285574ef242..c2e624a0a73b4 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.stderr +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.stderr @@ -58,5 +58,63 @@ error: this could be simplified with `bool::then` LL | if s == "1" { Some(true) } else { None } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(s == "1").then(|| true)` -error: aborting due to 6 previous errors +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:154:9 + | +LL | / if rs.len() == 1 && rs[0].start == rs[0].end { +LL | | +LL | | Some(vec![rs[0].start]) +LL | | } else { +LL | | None +LL | | } + | |_________^ help: try: `(rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start])` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:164:9 + | +LL | / if modulo == 0 { +LL | | +LL | | Some(i) +LL | | } else { +LL | | None +LL | | } + | |_________^ help: try: `(modulo == 0).then_some(i)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:181:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(item_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(item_fn)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:198:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(item_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(item_fn)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:206:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(closure_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)` + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.rs b/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.rs new file mode 100644 index 0000000000000..be04299a6ab15 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.rs @@ -0,0 +1,35 @@ +#![warn(clippy::if_then_some_else_none)] +#![allow(clippy::manual_is_multiple_of)] + +mod issue15257 { + use std::pin::Pin; + + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn pointer_unsized_coercion(i: u32) -> Option> { + if i % 2 == 0 { + //~^ if_then_some_else_none + Some(Box::new(Foo::default())) + } else { + None + } + } + + fn reborrow_as_pin(i: Pin<&mut i32>) { + use std::ops::Rem; + + fn do_something(i: Option<&i32>) { + todo!() + } + + do_something(if i.rem(2) == 0 { + //~^ if_then_some_else_none + Some(&i) + } else { + None + }); + } +} diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.stderr b/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.stderr new file mode 100644 index 0000000000000..f77ce7910e764 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_then_some_else_none_unfixable.stderr @@ -0,0 +1,28 @@ +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none_unfixable.rs:13:9 + | +LL | / if i % 2 == 0 { +LL | | +LL | | Some(Box::new(Foo::default())) +LL | | } else { +LL | | None +LL | | } + | |_________^ + | + = 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)]` + +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none_unfixable.rs:28:22 + | +LL | do_something(if i.rem(2) == 0 { + | ______________________^ +LL | | +LL | | Some(&i) +LL | | } else { +LL | | None +LL | | }); + | |_________^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/incompatible_msrv.rs b/src/tools/clippy/tests/ui/incompatible_msrv.rs index 99101b2bb8f26..f7f21e1850d0a 100644 --- a/src/tools/clippy/tests/ui/incompatible_msrv.rs +++ b/src/tools/clippy/tests/ui/incompatible_msrv.rs @@ -1,8 +1,10 @@ #![warn(clippy::incompatible_msrv)] #![feature(custom_inner_attributes)] -#![feature(panic_internals)] +#![allow(stable_features)] +#![feature(strict_provenance)] // For use in test #![clippy::msrv = "1.3.0"] +use std::cell::Cell; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::future::Future; @@ -13,6 +15,8 @@ fn foo() { let mut map: HashMap<&str, u32> = HashMap::new(); assert_eq!(map.entry("poneyland").key(), &"poneyland"); //~^ incompatible_msrv + //~| NOTE: `-D clippy::incompatible-msrv` implied by `-D warnings` + //~| HELP: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]` if let Entry::Vacant(v) = map.entry("poneyland") { v.into_key(); @@ -23,6 +27,18 @@ fn foo() { //~^ incompatible_msrv } +#[clippy::msrv = "1.2.0"] +static NO_BODY_BAD_MSRV: Option = None; +//~^ incompatible_msrv + +static NO_BODY_GOOD_MSRV: Option = None; + +#[clippy::msrv = "1.2.0"] +fn bad_type_msrv() { + let _: Option = None; + //~^ incompatible_msrv +} + #[test] fn test() { sleep(Duration::new(1, 0)); @@ -43,21 +59,22 @@ fn core_special_treatment(p: bool) { // But still lint code calling `core` functions directly if p { - core::panicking::panic("foo"); - //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + let _ = core::iter::once_with(|| 0); + //~^ incompatible_msrv } // Lint code calling `core` from non-`core` macros macro_rules! my_panic { ($msg:expr) => { - core::panicking::panic($msg) - }; //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + let _ = core::iter::once_with(|| $msg); + //~^ incompatible_msrv + }; } my_panic!("foo"); // Lint even when the macro comes from `core` and calls `core` functions - assert!(core::panicking::panic("out of luck")); - //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + assert!(core::iter::once_with(|| 0).next().is_some()); + //~^ incompatible_msrv } #[clippy::msrv = "1.26.0"] @@ -70,7 +87,85 @@ fn lang_items() { #[clippy::msrv = "1.80.0"] fn issue14212() { let _ = std::iter::repeat_n((), 5); - //~^ ERROR: is `1.80.0` but this item is stable since `1.82.0` + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.0.0"] +fn cstr_and_cstring_ok() { + let _: Option<&'static std::ffi::CStr> = None; + let _: Option = None; +} + +fn local_msrv_change_suggestion() { + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + + #[cfg(any(test, not(test)))] + { + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + //~| NOTE: you may want to conditionally increase the MSRV + + // Emit the additional note only once + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + } +} + +#[clippy::msrv = "1.78.0"] +fn feature_enable_14425(ptr: *const u8) -> usize { + // Do not warn, because it is enabled through a feature even though + // it is stabilized only since Rust 1.84.0. + let r = ptr.addr(); + + // Warn about this which has been introduced in the same Rust version + // but is not allowed through a feature. + r.isqrt() + //~^ incompatible_msrv +} + +fn non_fn_items() { + let _ = std::io::ErrorKind::CrossesDevices; + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.87.0"] +fn msrv_non_ok_in_const() { + { + let c = Cell::new(42); + _ = c.get(); + } + const { + let c = Cell::new(42); + _ = c.get(); + //~^ incompatible_msrv + } +} + +#[clippy::msrv = "1.88.0"] +fn msrv_ok_in_const() { + { + let c = Cell::new(42); + _ = c.get(); + } + const { + let c = Cell::new(42); + _ = c.get(); + } +} + +#[clippy::msrv = "1.86.0"] +fn enum_variant_not_ok() { + let _ = std::io::ErrorKind::InvalidFilename; + //~^ incompatible_msrv + let _ = const { std::io::ErrorKind::InvalidFilename }; + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.87.0"] +fn enum_variant_ok() { + let _ = std::io::ErrorKind::InvalidFilename; + let _ = const { std::io::ErrorKind::InvalidFilename }; } fn main() {} diff --git a/src/tools/clippy/tests/ui/incompatible_msrv.stderr b/src/tools/clippy/tests/ui/incompatible_msrv.stderr index 5ea2bb9cc58b1..e42360d296f58 100644 --- a/src/tools/clippy/tests/ui/incompatible_msrv.stderr +++ b/src/tools/clippy/tests/ui/incompatible_msrv.stderr @@ -1,5 +1,5 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0` - --> tests/ui/incompatible_msrv.rs:14:39 + --> tests/ui/incompatible_msrv.rs:16:39 | LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); | ^^^^^ @@ -8,45 +8,107 @@ LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); = help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]` error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0` - --> tests/ui/incompatible_msrv.rs:18:11 + --> tests/ui/incompatible_msrv.rs:22:11 | LL | v.into_key(); | ^^^^^^^^^^ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0` - --> tests/ui/incompatible_msrv.rs:22:5 + --> tests/ui/incompatible_msrv.rs:26:5 | LL | sleep(Duration::new(1, 0)); | ^^^^^ -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:46:9 +error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0` + --> tests/ui/incompatible_msrv.rs:31:33 | -LL | core::panicking::panic("foo"); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | static NO_BODY_BAD_MSRV: Option = None; + | ^^^^^^^^ -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:53:13 +error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0` + --> tests/ui/incompatible_msrv.rs:38:19 | -LL | core::panicking::panic($msg) - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Option = None; + | ^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:62:17 + | +LL | let _ = core::iter::once_with(|| 0); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:69:21 + | +LL | let _ = core::iter::once_with(|| $msg); + | ^^^^^^^^^^^^^^^^^^^^^ ... LL | my_panic!("foo"); | ---------------- in this macro invocation | = note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info) -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:59:13 +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:76:13 | -LL | assert!(core::panicking::panic("out of luck")); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | assert!(core::iter::once_with(|| 0).next().is_some()); + | ^^^^^^^^^^^^^^^^^^^^^ error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0` - --> tests/ui/incompatible_msrv.rs:72:13 + --> tests/ui/incompatible_msrv.rs:89:13 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:100:13 | LL | let _ = std::iter::repeat_n((), 5); | ^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:105:17 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:110:17 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.78.0` but this item is stable since `1.84.0` + --> tests/ui/incompatible_msrv.rs:123:7 + | +LL | r.isqrt() + | ^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.85.0` + --> tests/ui/incompatible_msrv.rs:128:13 + | +LL | let _ = std::io::ErrorKind::CrossesDevices; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.87.0` but this item is stable in a `const` context since `1.88.0` + --> tests/ui/incompatible_msrv.rs:140:15 + | +LL | _ = c.get(); + | ^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0` + --> tests/ui/incompatible_msrv.rs:159:13 + | +LL | let _ = std::io::ErrorKind::InvalidFilename; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0` + --> tests/ui/incompatible_msrv.rs:161:21 + | +LL | let _ = const { std::io::ErrorKind::InvalidFilename }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/large_enum_variant.32bit.stderr b/src/tools/clippy/tests/ui/large_enum_variant.32bit.stderr index 80ca5daa1d573..ac1ed27a6b3b9 100644 --- a/src/tools/clippy/tests/ui/large_enum_variant.32bit.stderr +++ b/src/tools/clippy/tests/ui/large_enum_variant.32bit.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([i32; 8000]), LL + B(Box<[i32; 8000]>), @@ -30,7 +30,7 @@ LL | | ContainingLargeEnum(LargeEnum), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingLargeEnum(LargeEnum), LL + ContainingLargeEnum(Box), @@ -49,7 +49,7 @@ LL | | StructLikeLittle { x: i32, y: i32 }, LL | | } | |_^ the entire enum is at least 70008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), LL + ContainingMoreThanOneField(i32, Box<[i32; 8000]>, Box<[i32; 9500]>), @@ -67,7 +67,7 @@ LL | | StructLikeLarge { x: [i32; 8000], y: i32 }, LL | | } | |_^ the entire enum is at least 32008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge { x: [i32; 8000], y: i32 }, LL + StructLikeLarge { x: Box<[i32; 8000]>, y: i32 }, @@ -85,7 +85,7 @@ LL | | StructLikeLarge2 { x: [i32; 8000] }, LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge2 { x: [i32; 8000] }, LL + StructLikeLarge2 { x: Box<[i32; 8000]> }, @@ -104,7 +104,7 @@ LL | | C([u8; 200]), LL | | } | |_^ the entire enum is at least 1256 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 1255]), LL + B(Box<[u8; 1255]>), @@ -122,7 +122,7 @@ LL | | ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; LL | | } | |_^ the entire enum is at least 70132 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]), LL + ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]), @@ -140,7 +140,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -158,7 +158,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -176,7 +176,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -199,7 +199,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum CopyableLargeEnum { | ^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:118:5 | LL | B([u64; 8000]), @@ -222,7 +222,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum ManuallyCopyLargeEnum { | ^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:124:5 | LL | B([u64; 8000]), @@ -245,7 +245,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum SomeGenericPossiblyCopyEnum { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:138:5 | LL | B([u64; 4000]), @@ -263,7 +263,7 @@ LL | | Large((T, [u8; 512])), LL | | } | |_^ the entire enum is at least 512 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large((T, [u8; 512])), LL + Large(Box<(T, [u8; 512])>), @@ -281,7 +281,7 @@ LL | | Small(u8), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([Foo; 64]), LL + Large(Box<[Foo; 64]>), @@ -299,7 +299,7 @@ LL | | Error(PossiblyLargeEnumWithConst<256>), LL | | } | |_^ the entire enum is at least 514 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(PossiblyLargeEnumWithConst<256>), LL + Error(Box>), @@ -317,7 +317,7 @@ LL | | Recursive(Box), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([u64; 64]), LL + Large(Box<[u64; 64]>), @@ -335,7 +335,7 @@ LL | | Error(WithRecursionAndGenerics), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(WithRecursionAndGenerics), LL + Error(Box>), diff --git a/src/tools/clippy/tests/ui/large_enum_variant.64bit.stderr b/src/tools/clippy/tests/ui/large_enum_variant.64bit.stderr index 559bdf2a2f50c..d8199f9090f60 100644 --- a/src/tools/clippy/tests/ui/large_enum_variant.64bit.stderr +++ b/src/tools/clippy/tests/ui/large_enum_variant.64bit.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([i32; 8000]), LL + B(Box<[i32; 8000]>), @@ -30,7 +30,7 @@ LL | | ContainingLargeEnum(LargeEnum), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingLargeEnum(LargeEnum), LL + ContainingLargeEnum(Box), @@ -49,7 +49,7 @@ LL | | StructLikeLittle { x: i32, y: i32 }, LL | | } | |_^ the entire enum is at least 70008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), LL + ContainingMoreThanOneField(i32, Box<[i32; 8000]>, Box<[i32; 9500]>), @@ -67,7 +67,7 @@ LL | | StructLikeLarge { x: [i32; 8000], y: i32 }, LL | | } | |_^ the entire enum is at least 32008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge { x: [i32; 8000], y: i32 }, LL + StructLikeLarge { x: Box<[i32; 8000]>, y: i32 }, @@ -85,7 +85,7 @@ LL | | StructLikeLarge2 { x: [i32; 8000] }, LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge2 { x: [i32; 8000] }, LL + StructLikeLarge2 { x: Box<[i32; 8000]> }, @@ -104,7 +104,7 @@ LL | | C([u8; 200]), LL | | } | |_^ the entire enum is at least 1256 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 1255]), LL + B(Box<[u8; 1255]>), @@ -122,7 +122,7 @@ LL | | ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; LL | | } | |_^ the entire enum is at least 70132 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]), LL + ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]), @@ -140,7 +140,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -158,7 +158,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -176,7 +176,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -199,7 +199,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum CopyableLargeEnum { | ^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:118:5 | LL | B([u64; 8000]), @@ -222,7 +222,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum ManuallyCopyLargeEnum { | ^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:124:5 | LL | B([u64; 8000]), @@ -245,7 +245,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum SomeGenericPossiblyCopyEnum { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:138:5 | LL | B([u64; 4000]), @@ -263,7 +263,7 @@ LL | | Large((T, [u8; 512])), LL | | } | |_^ the entire enum is at least 512 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large((T, [u8; 512])), LL + Large(Box<(T, [u8; 512])>), @@ -281,7 +281,7 @@ LL | | Small(u8), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([Foo; 64]), LL + Large(Box<[Foo; 64]>), @@ -299,7 +299,7 @@ LL | | Error(PossiblyLargeEnumWithConst<256>), LL | | } | |_^ the entire enum is at least 514 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(PossiblyLargeEnumWithConst<256>), LL + Error(Box>), @@ -317,7 +317,7 @@ LL | | Recursive(Box), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([u64; 64]), LL + Large(Box<[u64; 64]>), @@ -335,7 +335,7 @@ LL | | Error(WithRecursionAndGenerics), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(WithRecursionAndGenerics), LL + Error(Box>), @@ -353,7 +353,7 @@ LL | | _SmallBoi(u8), LL | | } | |_____^ the entire enum is at least 296 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - BigBoi(PublishWithBytes), LL + BigBoi(Box), @@ -371,7 +371,7 @@ LL | | _SmallBoi(u8), LL | | } | |_____^ the entire enum is at least 224 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - BigBoi(PublishWithVec), LL + BigBoi(Box), diff --git a/src/tools/clippy/tests/ui/large_enum_variant_no_std.rs b/src/tools/clippy/tests/ui/large_enum_variant_no_std.rs new file mode 100644 index 0000000000000..ff0213155b6ca --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant_no_std.rs @@ -0,0 +1,8 @@ +#![no_std] +#![warn(clippy::large_enum_variant)] + +enum Myenum { + //~^ ERROR: large size difference between variants + Small(u8), + Large([u8; 1024]), +} diff --git a/src/tools/clippy/tests/ui/large_enum_variant_no_std.stderr b/src/tools/clippy/tests/ui/large_enum_variant_no_std.stderr new file mode 100644 index 0000000000000..4f32e3e4835fb --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant_no_std.stderr @@ -0,0 +1,22 @@ +error: large size difference between variants + --> tests/ui/large_enum_variant_no_std.rs:4:1 + | +LL | / enum Myenum { +LL | | +LL | | Small(u8), + | | --------- the second-largest variant contains at least 1 bytes +LL | | Large([u8; 1024]), + | | ----------------- the largest variant contains at least 1024 bytes +LL | | } + | |_^ the entire enum is at least 1025 bytes + | +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum + --> tests/ui/large_enum_variant_no_std.rs:7:5 + | +LL | Large([u8; 1024]), + | ^^^^^^^^^^^^^^^^^ + = note: `-D clippy::large-enum-variant` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` + +error: aborting due to 1 previous error + diff --git a/src/tools/clippy/tests/ui/legacy_numeric_constants.fixed b/src/tools/clippy/tests/ui/legacy_numeric_constants.fixed index 30bb549a9d654..d90e7bec027c7 100644 --- a/src/tools/clippy/tests/ui/legacy_numeric_constants.fixed +++ b/src/tools/clippy/tests/ui/legacy_numeric_constants.fixed @@ -79,9 +79,31 @@ fn main() { f64::consts::E; b!(); + std::primitive::i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead [(0, "", i128::MAX)]; //~^ ERROR: usage of a legacy numeric constant //~| HELP: use the associated constant instead + i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + assert_eq!(0, -i32::MAX); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + i128::MAX; + //~^ ERROR: usage of a legacy numeric constant + //~| HELP: use the associated constant instead + u32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + type Ω = i32; + Ω::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead } #[warn(clippy::legacy_numeric_constants)] diff --git a/src/tools/clippy/tests/ui/legacy_numeric_constants.rs b/src/tools/clippy/tests/ui/legacy_numeric_constants.rs index d3878199055f5..4a2ef3f70c216 100644 --- a/src/tools/clippy/tests/ui/legacy_numeric_constants.rs +++ b/src/tools/clippy/tests/ui/legacy_numeric_constants.rs @@ -79,9 +79,31 @@ fn main() { f64::consts::E; b!(); + ::max_value(); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead [(0, "", std::i128::MAX)]; //~^ ERROR: usage of a legacy numeric constant //~| HELP: use the associated constant instead + (i32::max_value()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + assert_eq!(0, -(i32::max_value())); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + (std::i128::MAX); + //~^ ERROR: usage of a legacy numeric constant + //~| HELP: use the associated constant instead + (::max_value()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + ((i32::max_value)()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + type Ω = i32; + Ω::max_value(); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead } #[warn(clippy::legacy_numeric_constants)] diff --git a/src/tools/clippy/tests/ui/legacy_numeric_constants.stderr b/src/tools/clippy/tests/ui/legacy_numeric_constants.stderr index 4d69b8165a34b..0b4f32e0abc31 100644 --- a/src/tools/clippy/tests/ui/legacy_numeric_constants.stderr +++ b/src/tools/clippy/tests/ui/legacy_numeric_constants.stderr @@ -72,10 +72,10 @@ LL | u32::MAX; | +++++ error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:50:10 + --> tests/ui/legacy_numeric_constants.rs:50:5 | LL | i32::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -84,10 +84,10 @@ LL + i32::MAX; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:53:9 + --> tests/ui/legacy_numeric_constants.rs:53:5 | LL | u8::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -96,10 +96,10 @@ LL + u8::MAX; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:56:9 + --> tests/ui/legacy_numeric_constants.rs:56:5 | LL | u8::min_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -120,10 +120,10 @@ LL + u8::MIN; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:62:27 + --> tests/ui/legacy_numeric_constants.rs:62:5 | LL | ::std::primitive::u8::min_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -132,10 +132,10 @@ LL + ::std::primitive::u8::MIN; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:65:26 + --> tests/ui/legacy_numeric_constants.rs:65:5 | LL | std::primitive::i32::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -171,8 +171,20 @@ LL - let x = std::u64::MAX; LL + let x = u64::MAX; | +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:82:5 + | +LL | ::max_value(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - ::max_value(); +LL + std::primitive::i32::MAX; + | + error: usage of a legacy numeric constant - --> tests/ui/legacy_numeric_constants.rs:82:14 + --> tests/ui/legacy_numeric_constants.rs:85:14 | LL | [(0, "", std::i128::MAX)]; | ^^^^^^^^^^^^^^ @@ -183,8 +195,80 @@ LL - [(0, "", std::i128::MAX)]; LL + [(0, "", i128::MAX)]; | +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:88:5 + | +LL | (i32::max_value()); + | ^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (i32::max_value()); +LL + i32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:91:20 + | +LL | assert_eq!(0, -(i32::max_value())); + | ^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - assert_eq!(0, -(i32::max_value())); +LL + assert_eq!(0, -i32::MAX); + | + +error: usage of a legacy numeric constant + --> tests/ui/legacy_numeric_constants.rs:94:5 + | +LL | (std::i128::MAX); + | ^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (std::i128::MAX); +LL + i128::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:97:5 + | +LL | (::max_value()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (::max_value()); +LL + u32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:100:5 + | +LL | ((i32::max_value)()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - ((i32::max_value)()); +LL + i32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:104:5 + | +LL | Ω::max_value(); + | ^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - Ω::max_value(); +LL + Ω::MAX; + | + error: usage of a legacy numeric constant - --> tests/ui/legacy_numeric_constants.rs:116:5 + --> tests/ui/legacy_numeric_constants.rs:138:5 | LL | std::u32::MAX; | ^^^^^^^^^^^^^ @@ -195,5 +279,5 @@ LL - std::u32::MAX; LL + u32::MAX; | -error: aborting due to 16 previous errors +error: aborting due to 23 previous errors diff --git a/src/tools/clippy/tests/ui/manual_abs_diff.fixed b/src/tools/clippy/tests/ui/manual_abs_diff.fixed index f1b1278ea6d22..2766942140ce4 100644 --- a/src/tools/clippy/tests/ui/manual_abs_diff.fixed +++ b/src/tools/clippy/tests/ui/manual_abs_diff.fixed @@ -104,3 +104,7 @@ fn non_primitive_ty() { let (a, b) = (S(10), S(20)); let _ = if a < b { b - a } else { a - b }; } + +fn issue15254(a: &usize, b: &usize) -> usize { + b.abs_diff(*a) +} diff --git a/src/tools/clippy/tests/ui/manual_abs_diff.rs b/src/tools/clippy/tests/ui/manual_abs_diff.rs index 60ef819c12d30..2c408f2be3754 100644 --- a/src/tools/clippy/tests/ui/manual_abs_diff.rs +++ b/src/tools/clippy/tests/ui/manual_abs_diff.rs @@ -114,3 +114,12 @@ fn non_primitive_ty() { let (a, b) = (S(10), S(20)); let _ = if a < b { b - a } else { a - b }; } + +fn issue15254(a: &usize, b: &usize) -> usize { + if a < b { + //~^ manual_abs_diff + b - a + } else { + a - b + } +} diff --git a/src/tools/clippy/tests/ui/manual_abs_diff.stderr b/src/tools/clippy/tests/ui/manual_abs_diff.stderr index c14c1dc830fbd..bb6d312b435f8 100644 --- a/src/tools/clippy/tests/ui/manual_abs_diff.stderr +++ b/src/tools/clippy/tests/ui/manual_abs_diff.stderr @@ -79,5 +79,16 @@ error: manual absolute difference pattern without using `abs_diff` LL | let _ = if a > b { (a - b) as u32 } else { (b - a) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `abs_diff`: `a.abs_diff(b)` -error: aborting due to 11 previous errors +error: manual absolute difference pattern without using `abs_diff` + --> tests/ui/manual_abs_diff.rs:119:5 + | +LL | / if a < b { +LL | | +LL | | b - a +LL | | } else { +LL | | a - b +LL | | } + | |_____^ help: replace with `abs_diff`: `b.abs_diff(*a)` + +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr b/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr index 8cedf2c68636a..221cddf069db3 100644 --- a/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr +++ b/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr @@ -189,5 +189,23 @@ LL - }; LL + const BAR: () = assert!(!(N == 0), ); | -error: aborting due to 10 previous errors +error: only a `panic!` in `if`-then statement + --> tests/ui/manual_assert.rs:116:5 + | +LL | / if !is_x86_feature_detected!("ssse3") { +LL | | +LL | | panic!("SSSE3 is not supported"); +LL | | } + | |_____^ + | +help: try instead + | +LL - if !is_x86_feature_detected!("ssse3") { +LL - +LL - panic!("SSSE3 is not supported"); +LL - } +LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + | + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/manual_assert.edition2021.stderr b/src/tools/clippy/tests/ui/manual_assert.edition2021.stderr index 8cedf2c68636a..221cddf069db3 100644 --- a/src/tools/clippy/tests/ui/manual_assert.edition2021.stderr +++ b/src/tools/clippy/tests/ui/manual_assert.edition2021.stderr @@ -189,5 +189,23 @@ LL - }; LL + const BAR: () = assert!(!(N == 0), ); | -error: aborting due to 10 previous errors +error: only a `panic!` in `if`-then statement + --> tests/ui/manual_assert.rs:116:5 + | +LL | / if !is_x86_feature_detected!("ssse3") { +LL | | +LL | | panic!("SSSE3 is not supported"); +LL | | } + | |_____^ + | +help: try instead + | +LL - if !is_x86_feature_detected!("ssse3") { +LL - +LL - panic!("SSSE3 is not supported"); +LL - } +LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + | + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/manual_assert.rs b/src/tools/clippy/tests/ui/manual_assert.rs index 46a42c3d00af5..ab02bd5f5e533 100644 --- a/src/tools/clippy/tests/ui/manual_assert.rs +++ b/src/tools/clippy/tests/ui/manual_assert.rs @@ -105,3 +105,17 @@ fn issue12505() { }; } } + +fn issue15227(left: u64, right: u64) -> u64 { + macro_rules! is_x86_feature_detected { + ($feature:literal) => { + $feature.len() > 0 && $feature.starts_with("ss") + }; + } + + if !is_x86_feature_detected!("ssse3") { + //~^ manual_assert + panic!("SSSE3 is not supported"); + } + unsafe { todo!() } +} diff --git a/src/tools/clippy/tests/ui/manual_is_multiple_of.fixed b/src/tools/clippy/tests/ui/manual_is_multiple_of.fixed index 6735b99f298c5..03f75e725ed59 100644 --- a/src/tools/clippy/tests/ui/manual_is_multiple_of.fixed +++ b/src/tools/clippy/tests/ui/manual_is_multiple_of.fixed @@ -23,3 +23,81 @@ fn f(a: u64, b: u64) { fn g(a: u64, b: u64) { let _ = a % b == 0; } + +fn needs_deref(a: &u64, b: &u64) { + let _ = a.is_multiple_of(*b); //~ manual_is_multiple_of +} + +fn closures(a: u64, b: u64) { + // Do not lint, types are ambiguous at this point + let cl = |a, b| a % b == 0; + let _ = cl(a, b); + + // Do not lint, types are ambiguous at this point + let cl = |a: _, b: _| a % b == 0; + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: &u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = cl(&a, b); + + // Type of `b` is not enough + let cl = |a, b: u64| a % b == 0; + let _ = cl(&a, b); +} + +fn any_rem>(a: T, b: T) { + // An arbitrary `Rem` implementation should not lint + let _ = a % b == 0; +} + +mod issue15103 { + fn foo() -> Option { + let mut n: u64 = 150_000_000; + + (2..).find(|p| { + while n.is_multiple_of(*p) { + //~^ manual_is_multiple_of + n /= p; + } + n <= 1 + }) + } + + const fn generate_primes() -> [u64; N] { + let mut result = [0; N]; + if N == 0 { + return result; + } + result[0] = 2; + if N == 1 { + return result; + } + let mut idx = 1; + let mut p = 3; + while idx < N { + let mut j = 0; + while j < idx && p % result[j] != 0 { + j += 1; + } + if j == idx { + result[idx] = p; + idx += 1; + } + p += 1; + } + result + } + + fn bar() -> u32 { + let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n.is_multiple_of(*i)).sum() }; + //~^ manual_is_multiple_of + + let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum(); + (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() + } +} diff --git a/src/tools/clippy/tests/ui/manual_is_multiple_of.rs b/src/tools/clippy/tests/ui/manual_is_multiple_of.rs index 00b638e4fd9f3..7b6fa64c843da 100644 --- a/src/tools/clippy/tests/ui/manual_is_multiple_of.rs +++ b/src/tools/clippy/tests/ui/manual_is_multiple_of.rs @@ -23,3 +23,81 @@ fn f(a: u64, b: u64) { fn g(a: u64, b: u64) { let _ = a % b == 0; } + +fn needs_deref(a: &u64, b: &u64) { + let _ = a % b == 0; //~ manual_is_multiple_of +} + +fn closures(a: u64, b: u64) { + // Do not lint, types are ambiguous at this point + let cl = |a, b| a % b == 0; + let _ = cl(a, b); + + // Do not lint, types are ambiguous at this point + let cl = |a: _, b: _| a % b == 0; + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: u64, b| a % b == 0; //~ manual_is_multiple_of + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: &u64, b| a % b == 0; //~ manual_is_multiple_of + let _ = cl(&a, b); + + // Type of `b` is not enough + let cl = |a, b: u64| a % b == 0; + let _ = cl(&a, b); +} + +fn any_rem>(a: T, b: T) { + // An arbitrary `Rem` implementation should not lint + let _ = a % b == 0; +} + +mod issue15103 { + fn foo() -> Option { + let mut n: u64 = 150_000_000; + + (2..).find(|p| { + while n % p == 0 { + //~^ manual_is_multiple_of + n /= p; + } + n <= 1 + }) + } + + const fn generate_primes() -> [u64; N] { + let mut result = [0; N]; + if N == 0 { + return result; + } + result[0] = 2; + if N == 1 { + return result; + } + let mut idx = 1; + let mut p = 3; + while idx < N { + let mut j = 0; + while j < idx && p % result[j] != 0 { + j += 1; + } + if j == idx { + result[idx] = p; + idx += 1; + } + p += 1; + } + result + } + + fn bar() -> u32 { + let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() }; + //~^ manual_is_multiple_of + + let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum(); + (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() + } +} diff --git a/src/tools/clippy/tests/ui/manual_is_multiple_of.stderr b/src/tools/clippy/tests/ui/manual_is_multiple_of.stderr index 0b1ae70c2a70f..8523599ec402f 100644 --- a/src/tools/clippy/tests/ui/manual_is_multiple_of.stderr +++ b/src/tools/clippy/tests/ui/manual_is_multiple_of.stderr @@ -37,5 +37,35 @@ error: manual implementation of `.is_multiple_of()` LL | let _ = 0 < a % b; | ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)` -error: aborting due to 6 previous errors +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:28:13 + | +LL | let _ = a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(*b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:41:26 + | +LL | let cl = |a: u64, b| a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:45:27 + | +LL | let cl = |a: &u64, b| a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:63:19 + | +LL | while n % p == 0 { + | ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*p)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:97:58 + | +LL | let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() }; + | ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*i)` + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/map_identity.fixed b/src/tools/clippy/tests/ui/map_identity.fixed index 83b2dac5fc517..b82d3e6d9567c 100644 --- a/src/tools/clippy/tests/ui/map_identity.fixed +++ b/src/tools/clippy/tests/ui/map_identity.fixed @@ -87,3 +87,15 @@ fn issue13904() { let _ = { it }.next(); //~^ map_identity } + +// same as `issue11764`, but for arrays +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: `&[i32; 2]` becomes `[&i32; 2]` + let _ = x.iter().map(|[x, y]| [x, y]); + let _ = x.iter().map(|x| [x[0]]).map(|[x]| x); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied(); + //~^ map_identity +} diff --git a/src/tools/clippy/tests/ui/map_identity.rs b/src/tools/clippy/tests/ui/map_identity.rs index e839c551364be..c295bf872701f 100644 --- a/src/tools/clippy/tests/ui/map_identity.rs +++ b/src/tools/clippy/tests/ui/map_identity.rs @@ -93,3 +93,15 @@ fn issue13904() { let _ = { it }.map(|x| x).next(); //~^ map_identity } + +// same as `issue11764`, but for arrays +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: `&[i32; 2]` becomes `[&i32; 2]` + let _ = x.iter().map(|[x, y]| [x, y]); + let _ = x.iter().map(|x| [x[0]]).map(|[x]| x); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().map(|[x, y]| [x, y]); + //~^ map_identity +} diff --git a/src/tools/clippy/tests/ui/map_identity.stderr b/src/tools/clippy/tests/ui/map_identity.stderr index 9836f3b4cc5f8..9b624a0dc755e 100644 --- a/src/tools/clippy/tests/ui/map_identity.stderr +++ b/src/tools/clippy/tests/ui/map_identity.stderr @@ -87,5 +87,11 @@ error: unnecessary map of the identity function LL | let _ = { it }.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` -error: aborting due to 13 previous errors +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:105:30 + | +LL | let _ = x.iter().copied().map(|[x, y]| [x, y]); + | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: aborting due to 14 previous errors diff --git a/src/tools/clippy/tests/ui/missing_inline.rs b/src/tools/clippy/tests/ui/missing_inline.rs index c1801005b777a..223c7447975aa 100644 --- a/src/tools/clippy/tests/ui/missing_inline.rs +++ b/src/tools/clippy/tests/ui/missing_inline.rs @@ -80,3 +80,20 @@ impl PubFoo { // do not lint this since users cannot control the external code #[derive(Debug)] pub struct S; + +pub mod issue15301 { + #[unsafe(no_mangle)] + pub extern "C" fn call_from_c() { + println!("Just called a Rust function from C!"); + } + + #[unsafe(no_mangle)] + pub extern "Rust" fn call_from_rust() { + println!("Just called a Rust function from Rust!"); + } + + #[unsafe(no_mangle)] + pub fn call_from_rust_no_extern() { + println!("Just called a Rust function from Rust!"); + } +} diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.rs b/src/tools/clippy/tests/ui/module_name_repetitions.rs index 2fde98d7927c2..5d16858bf85ec 100644 --- a/src/tools/clippy/tests/ui/module_name_repetitions.rs +++ b/src/tools/clippy/tests/ui/module_name_repetitions.rs @@ -55,3 +55,21 @@ pub mod foo { } fn main() {} + +pub mod issue14095 { + pub mod widget { + #[macro_export] + macro_rules! define_widget { + ($id:ident) => { + /* ... */ + }; + } + + #[macro_export] + macro_rules! widget_impl { + ($id:ident) => { + /* ... */ + }; + } + } +} diff --git a/src/tools/clippy/tests/ui/must_use_candidates.fixed b/src/tools/clippy/tests/ui/must_use_candidates.fixed index 4c1d6b1ccb596..1e8589cf39d6b 100644 --- a/src/tools/clippy/tests/ui/must_use_candidates.fixed +++ b/src/tools/clippy/tests/ui/must_use_candidates.fixed @@ -13,13 +13,15 @@ use std::sync::atomic::{AtomicBool, Ordering}; pub struct MyAtomic(AtomicBool); pub struct MyPure; -#[must_use] pub fn pure(i: u8) -> u8 { +#[must_use] +pub fn pure(i: u8) -> u8 { //~^ must_use_candidate i } impl MyPure { - #[must_use] pub fn inherent_pure(&self) -> u8 { + #[must_use] + pub fn inherent_pure(&self) -> u8 { //~^ must_use_candidate 0 } @@ -51,7 +53,8 @@ pub fn with_callback bool>(f: &F) -> bool { f(0) } -#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { +#[must_use] +pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { //~^ must_use_candidate true } @@ -64,7 +67,8 @@ pub fn atomics(b: &AtomicBool) -> bool { b.load(Ordering::SeqCst) } -#[must_use] pub fn rcd(_x: Rc) -> bool { +#[must_use] +pub fn rcd(_x: Rc) -> bool { //~^ must_use_candidate true } @@ -73,7 +77,8 @@ pub fn rcmut(_x: Rc<&mut u32>) -> bool { true } -#[must_use] pub fn arcd(_x: Arc) -> bool { +#[must_use] +pub fn arcd(_x: Arc) -> bool { //~^ must_use_candidate false } diff --git a/src/tools/clippy/tests/ui/must_use_candidates.stderr b/src/tools/clippy/tests/ui/must_use_candidates.stderr index 590253d95f9e3..5ddbd02606291 100644 --- a/src/tools/clippy/tests/ui/must_use_candidates.stderr +++ b/src/tools/clippy/tests/ui/must_use_candidates.stderr @@ -1,35 +1,64 @@ error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:16:1 + --> tests/ui/must_use_candidates.rs:16:8 | LL | pub fn pure(i: u8) -> u8 { - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn pure(i: u8) -> u8` + | ^^^^ | = note: `-D clippy::must-use-candidate` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::must_use_candidate)]` +help: add the attribute + | +LL + #[must_use] +LL | pub fn pure(i: u8) -> u8 { + | error: this method could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:22:5 + --> tests/ui/must_use_candidates.rs:22:12 | LL | pub fn inherent_pure(&self) -> u8 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn inherent_pure(&self) -> u8` + | ^^^^^^^^^^^^^ + | +help: add the attribute + | +LL ~ #[must_use] +LL ~ pub fn inherent_pure(&self) -> u8 { + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:54:1 + --> tests/ui/must_use_candidates.rs:54:8 + | +LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + | ^^^^^^^^^^^ + | +help: add the attribute | +LL + #[must_use] LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool` + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:67:1 + --> tests/ui/must_use_candidates.rs:67:8 | LL | pub fn rcd(_x: Rc) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn rcd(_x: Rc) -> bool` + | ^^^ + | +help: add the attribute + | +LL + #[must_use] +LL | pub fn rcd(_x: Rc) -> bool { + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:76:1 + --> tests/ui/must_use_candidates.rs:76:8 | LL | pub fn arcd(_x: Arc) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn arcd(_x: Arc) -> bool` + | ^^^^ + | +help: add the attribute + | +LL + #[must_use] +LL | pub fn arcd(_x: Arc) -> bool { + | error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/needless_for_each_fixable.fixed b/src/tools/clippy/tests/ui/needless_for_each_fixable.fixed index a73aff556399e..a6d64d9afc1ac 100644 --- a/src/tools/clippy/tests/ui/needless_for_each_fixable.fixed +++ b/src/tools/clippy/tests/ui/needless_for_each_fixable.fixed @@ -143,3 +143,9 @@ mod issue14734 { //~^ needless_for_each } } + +fn issue15256() { + let vec: Vec = Vec::new(); + for v in vec.iter() { println!("{v}"); } + //~^ needless_for_each +} diff --git a/src/tools/clippy/tests/ui/needless_for_each_fixable.rs b/src/tools/clippy/tests/ui/needless_for_each_fixable.rs index d92f055d3f45b..7e74d2b428fd9 100644 --- a/src/tools/clippy/tests/ui/needless_for_each_fixable.rs +++ b/src/tools/clippy/tests/ui/needless_for_each_fixable.rs @@ -143,3 +143,9 @@ mod issue14734 { //~^ needless_for_each } } + +fn issue15256() { + let vec: Vec = Vec::new(); + vec.iter().for_each(|v| println!("{v}")); + //~^ needless_for_each +} diff --git a/src/tools/clippy/tests/ui/needless_for_each_fixable.stderr b/src/tools/clippy/tests/ui/needless_for_each_fixable.stderr index f801445609769..204cfa36b0221 100644 --- a/src/tools/clippy/tests/ui/needless_for_each_fixable.stderr +++ b/src/tools/clippy/tests/ui/needless_for_each_fixable.stderr @@ -148,5 +148,11 @@ error: needless use of `for_each` LL | rows.iter().for_each(|x| do_something(x, 1u8)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in rows.iter() { do_something(x, 1u8); }` -error: aborting due to 10 previous errors +error: needless use of `for_each` + --> tests/ui/needless_for_each_fixable.rs:149:5 + | +LL | vec.iter().for_each(|v| println!("{v}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for v in vec.iter() { println!("{v}"); }` + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/needless_range_loop.rs b/src/tools/clippy/tests/ui/needless_range_loop.rs index 8a1c1be289cf8..70cf9fa7369ff 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.rs +++ b/src/tools/clippy/tests/ui/needless_range_loop.rs @@ -185,3 +185,28 @@ mod issue_2496 { unimplemented!() } } + +fn needless_loop() { + use std::hint::black_box; + let x = [0; 64]; + for i in 0..64 { + let y = [0; 64]; + + black_box(x[i]); + black_box(y[i]); + } + + for i in 0..64 { + black_box(x[i]); + black_box([0; 64][i]); + } + + for i in 0..64 { + black_box(x[i]); + black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); + } + + for i in 0..64 { + black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); + } +} diff --git a/src/tools/clippy/tests/ui/never_loop.rs b/src/tools/clippy/tests/ui/never_loop.rs index e0f54ef899b05..48d4b8ad15196 100644 --- a/src/tools/clippy/tests/ui/never_loop.rs +++ b/src/tools/clippy/tests/ui/never_loop.rs @@ -466,3 +466,35 @@ fn main() { test13(); test14(); } + +fn issue15059() { + 'a: for _ in 0..1 { + //~^ never_loop + break 'a; + } + + let mut b = 1; + 'a: for i in 0..1 { + //~^ never_loop + match i { + 0 => { + b *= 2; + break 'a; + }, + x => { + b += x; + break 'a; + }, + } + } + + #[allow(clippy::unused_unit)] + for v in 0..10 { + //~^ never_loop + break; + println!("{v}"); + // This is comment and should be kept + println!("This is a comment"); + () + } +} diff --git a/src/tools/clippy/tests/ui/never_loop.stderr b/src/tools/clippy/tests/ui/never_loop.stderr index bc9a7ec48b487..54b463266a3a5 100644 --- a/src/tools/clippy/tests/ui/never_loop.stderr +++ b/src/tools/clippy/tests/ui/never_loop.stderr @@ -176,8 +176,10 @@ LL | | } | help: if you need the first element of the iterator, try writing | -LL - for v in 0..10 { -LL + if let Some(v) = (0..10).next() { +LL ~ if let Some(v) = (0..10).next() { +LL | +LL ~ +LL ~ | error: this loop never actually loops @@ -232,5 +234,68 @@ LL | | break 'inner; LL | | } | |_________^ -error: aborting due to 21 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:471:5 + | +LL | / 'a: for _ in 0..1 { +LL | | +LL | | break 'a; +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(_) = (0..1).next() { +LL | +LL ~ + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:477:5 + | +LL | / 'a: for i in 0..1 { +LL | | +LL | | match i { +LL | | 0 => { +... | +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(i) = (0..1).next() { +LL | +... +LL | b *= 2; +LL ~ +LL | }, +LL | x => { +LL | b += x; +LL ~ + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:492:5 + | +LL | / for v in 0..10 { +LL | | +LL | | break; +LL | | println!("{v}"); +... | +LL | | () +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(v) = (0..10).next() { +LL | +LL ~ +LL ~ +LL | // This is comment and should be kept +LL ~ +LL ~ + | + +error: aborting due to 24 previous errors diff --git a/src/tools/clippy/tests/ui/or_fun_call.fixed b/src/tools/clippy/tests/ui/or_fun_call.fixed index bcd2602edb6ad..0a8525a12f5e0 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.fixed +++ b/src/tools/clippy/tests/ui/or_fun_call.fixed @@ -283,6 +283,8 @@ mod issue8993 { let _ = Some(4).map_or_else(g, f); //~^ or_fun_call let _ = Some(4).map_or(0, f); + let _ = Some(4).map_or_else(|| "asd".to_string().len() as i32, f); + //~^ or_fun_call } } @@ -426,6 +428,8 @@ mod result_map_or { let _ = x.map_or_else(|_| g(), f); //~^ or_fun_call let _ = x.map_or(0, f); + let _ = x.map_or_else(|_| "asd".to_string().len() as i32, f); + //~^ or_fun_call } } diff --git a/src/tools/clippy/tests/ui/or_fun_call.rs b/src/tools/clippy/tests/ui/or_fun_call.rs index 8d1202ebf914a..b4f9b950a7fe0 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.rs +++ b/src/tools/clippy/tests/ui/or_fun_call.rs @@ -283,6 +283,8 @@ mod issue8993 { let _ = Some(4).map_or(g(), f); //~^ or_fun_call let _ = Some(4).map_or(0, f); + let _ = Some(4).map_or("asd".to_string().len() as i32, f); + //~^ or_fun_call } } @@ -426,6 +428,8 @@ mod result_map_or { let _ = x.map_or(g(), f); //~^ or_fun_call let _ = x.map_or(0, f); + let _ = x.map_or("asd".to_string().len() as i32, f); + //~^ or_fun_call } } diff --git a/src/tools/clippy/tests/ui/or_fun_call.stderr b/src/tools/clippy/tests/ui/or_fun_call.stderr index 585ee2d0e19d1..3e4df772668d7 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.stderr +++ b/src/tools/clippy/tests/ui/or_fun_call.stderr @@ -154,62 +154,68 @@ error: function call inside of `map_or` LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` +error: function call inside of `map_or` + --> tests/ui/or_fun_call.rs:286:25 + | +LL | let _ = Some(4).map_or("asd".to_string().len() as i32, f); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| "asd".to_string().len() as i32, f)` + error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:315:18 + --> tests/ui/or_fun_call.rs:317:18 | LL | with_new.unwrap_or_else(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:319:28 + --> tests/ui/or_fun_call.rs:321:28 | LL | with_default_trait.unwrap_or_else(Default::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:323:27 + --> tests/ui/or_fun_call.rs:325:27 | LL | with_default_type.unwrap_or_else(u64::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:327:22 + --> tests/ui/or_fun_call.rs:329:22 | LL | real_default.unwrap_or_else(::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:331:23 + --> tests/ui/or_fun_call.rs:333:23 | LL | map.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:335:25 + --> tests/ui/or_fun_call.rs:337:25 | LL | btree.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:339:25 + --> tests/ui/or_fun_call.rs:341:25 | LL | let _ = stringy.unwrap_or_else(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:381:17 + --> tests/ui/or_fun_call.rs:383:17 | LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:386:17 + --> tests/ui/or_fun_call.rs:388:17 | LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:391:17 + --> tests/ui/or_fun_call.rs:393:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -229,52 +235,58 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:397:17 + --> tests/ui/or_fun_call.rs:399:17 | LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)` | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:402:17 + --> tests/ui/or_fun_call.rs:404:17 | LL | let _ = opt.unwrap_or({ i32::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:409:21 + --> tests/ui/or_fun_call.rs:411:21 | LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:424:19 + --> tests/ui/or_fun_call.rs:426:19 | LL | let _ = x.map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:426:19 + --> tests/ui/or_fun_call.rs:428:19 | LL | let _ = x.map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), f)` +error: function call inside of `map_or` + --> tests/ui/or_fun_call.rs:431:19 + | +LL | let _ = x.map_or("asd".to_string().len() as i32, f); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| "asd".to_string().len() as i32, f)` + error: function call inside of `get_or_insert` - --> tests/ui/or_fun_call.rs:438:15 + --> tests/ui/or_fun_call.rs:442:15 | LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:448:15 + --> tests/ui/or_fun_call.rs:452:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:458:15 + --> tests/ui/or_fun_call.rs:462:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` -error: aborting due to 43 previous errors +error: aborting due to 45 previous errors diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/auxiliary/external.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/auxiliary/external.rs new file mode 100644 index 0000000000000..cd27c5c74aa94 --- /dev/null +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/auxiliary/external.rs @@ -0,0 +1,13 @@ +//! **FAKE** external macro crate. + +#[macro_export] +macro_rules! macro_with_match { + ( $p:pat ) => { + let something = (); + + match &something { + $p => true, + _ => false, + } + }; +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs index 49ea1d3f7a67c..aa988a577df2b 100644 --- a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.rs @@ -6,6 +6,9 @@ clippy::single_match )] +//@aux-build:external.rs +use external::macro_with_match; + fn main() {} fn syntax_match() { @@ -159,3 +162,9 @@ fn macro_expansion() { let value = &Some(23); matching_macro!(value); } + +fn external_macro_expansion() { + macro_with_match! { + () + }; +} diff --git a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr index cd604d604c12c..636841e0a21a4 100644 --- a/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr +++ b/src/tools/clippy/tests/ui/pattern_type_mismatch/syntax.stderr @@ -1,5 +1,5 @@ error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:16:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:19:9 | LL | Some(_) => (), | ^^^^^^^ @@ -9,7 +9,7 @@ LL | Some(_) => (), = help: to override `-D warnings` add `#[allow(clippy::pattern_type_mismatch)]` error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:36:12 + --> tests/ui/pattern_type_mismatch/syntax.rs:39:12 | LL | if let Some(_) = ref_value {} | ^^^^^^^ @@ -17,7 +17,7 @@ LL | if let Some(_) = ref_value {} = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:48:15 + --> tests/ui/pattern_type_mismatch/syntax.rs:51:15 | LL | while let Some(_) = ref_value { | ^^^^^^^ @@ -25,7 +25,7 @@ LL | while let Some(_) = ref_value { = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:68:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:71:9 | LL | for (_a, _b) in slice.iter() {} | ^^^^^^^^ @@ -33,7 +33,7 @@ LL | for (_a, _b) in slice.iter() {} = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:79:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:82:9 | LL | let (_n, _m) = ref_value; | ^^^^^^^^ @@ -41,7 +41,7 @@ LL | let (_n, _m) = ref_value; = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:89:12 + --> tests/ui/pattern_type_mismatch/syntax.rs:92:12 | LL | fn foo((_a, _b): &(i32, i32)) {} | ^^^^^^^^ @@ -49,7 +49,7 @@ LL | fn foo((_a, _b): &(i32, i32)) {} = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:104:10 + --> tests/ui/pattern_type_mismatch/syntax.rs:107:10 | LL | foo(|(_a, _b)| ()); | ^^^^^^^^ @@ -57,7 +57,7 @@ LL | foo(|(_a, _b)| ()); = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:121:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:124:9 | LL | Some(_) => (), | ^^^^^^^ @@ -65,7 +65,7 @@ LL | Some(_) => (), = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:142:17 + --> tests/ui/pattern_type_mismatch/syntax.rs:145:17 | LL | Some(_) => (), | ^^^^^^^ diff --git a/src/tools/clippy/tests/ui/ptr_arg.rs b/src/tools/clippy/tests/ui/ptr_arg.rs index 578641e910dcf..be14e0762ff42 100644 --- a/src/tools/clippy/tests/ui/ptr_arg.rs +++ b/src/tools/clippy/tests/ui/ptr_arg.rs @@ -123,7 +123,7 @@ fn test_cow_with_ref(c: &Cow<[i32]>) {} //~^ ptr_arg fn test_cow(c: Cow<[i32]>) { - let _c = c; + let d = c; } trait Foo2 { @@ -141,36 +141,36 @@ mod issue_5644 { use std::path::PathBuf; fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } - fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + fn some_allowed(#[allow(clippy::ptr_arg)] v: &Vec, s: &String) {} //~^ ptr_arg struct S; impl S { fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } } trait T { fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } } @@ -182,22 +182,22 @@ mod issue6509 { fn foo_vec(vec: &Vec) { //~^ ptr_arg - let _ = vec.clone().pop(); - let _ = vec.clone().clone(); + let a = vec.clone().pop(); + let b = vec.clone().clone(); } fn foo_path(path: &PathBuf) { //~^ ptr_arg - let _ = path.clone().pop(); - let _ = path.clone().clone(); + let c = path.clone().pop(); + let d = path.clone().clone(); } - fn foo_str(str: &PathBuf) { + fn foo_str(str: &String) { //~^ ptr_arg - let _ = str.clone().pop(); - let _ = str.clone().clone(); + let e = str.clone().pop(); + let f = str.clone().clone(); } } @@ -340,8 +340,8 @@ mod issue_13308 { ToOwned::clone_into(source, destination); } - fn h1(_: &::Target) {} - fn h2(_: T, _: &T::Target) {} + fn h1(x: &::Target) {} + fn h2(x: T, y: &T::Target) {} // Other cases that are still ok to lint and ideally shouldn't regress fn good(v1: &String, v2: &String) { @@ -352,3 +352,91 @@ mod issue_13308 { h2(String::new(), v2); } } + +mod issue_13489_and_13728 { + // This is a no-lint from now on. + fn foo(_x: &Vec) { + todo!(); + } + + // But this still gives us a lint. + fn foo_used(x: &Vec) { + //~^ ptr_arg + + todo!(); + } + + // This is also a no-lint from now on. + fn foo_local(x: &Vec) { + let _y = x; + + todo!(); + } + + // But this still gives us a lint. + fn foo_local_used(x: &Vec) { + //~^ ptr_arg + + let y = x; + + todo!(); + } + + // This only lints once from now on. + fn foofoo(_x: &Vec, y: &String) { + //~^ ptr_arg + + todo!(); + } + + // And this is also a no-lint from now on. + fn foofoo_local(_x: &Vec, y: &String) { + let _z = y; + + todo!(); + } +} + +mod issue_13489_and_13728_mut { + // This is a no-lint from now on. + fn bar(_x: &mut Vec) { + todo!() + } + + // But this still gives us a lint. + fn bar_used(x: &mut Vec) { + //~^ ptr_arg + + todo!() + } + + // This is also a no-lint from now on. + fn bar_local(x: &mut Vec) { + let _y = x; + + todo!() + } + + // But this still gives us a lint. + fn bar_local_used(x: &mut Vec) { + //~^ ptr_arg + + let y = x; + + todo!() + } + + // This only lints once from now on. + fn barbar(_x: &mut Vec, y: &mut String) { + //~^ ptr_arg + + todo!() + } + + // And this is also a no-lint from now on. + fn barbar_local(_x: &mut Vec, y: &mut String) { + let _z = y; + + todo!() + } +} diff --git a/src/tools/clippy/tests/ui/ptr_arg.stderr b/src/tools/clippy/tests/ui/ptr_arg.stderr index fd9ceddfe11ce..87235057349e3 100644 --- a/src/tools/clippy/tests/ui/ptr_arg.stderr +++ b/src/tools/clippy/tests/ui/ptr_arg.stderr @@ -127,10 +127,10 @@ LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} | ^^^^^^^^^^^ help: change this to: `&[i32]` error: writing `&String` instead of `&str` involves a new object where a slice will do - --> tests/ui/ptr_arg.rs:152:66 + --> tests/ui/ptr_arg.rs:152:64 | -LL | fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} - | ^^^^^^^ help: change this to: `&str` +LL | fn some_allowed(#[allow(clippy::ptr_arg)] v: &Vec, s: &String) {} + | ^^^^^^^ help: change this to: `&str` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do --> tests/ui/ptr_arg.rs:182:21 @@ -143,8 +143,8 @@ help: change this to LL ~ fn foo_vec(vec: &[u8]) { LL | LL | -LL ~ let _ = vec.to_owned().pop(); -LL ~ let _ = vec.to_owned().clone(); +LL ~ let a = vec.to_owned().pop(); +LL ~ let b = vec.to_owned().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do @@ -158,23 +158,23 @@ help: change this to LL ~ fn foo_path(path: &Path) { LL | LL | -LL ~ let _ = path.to_path_buf().pop(); -LL ~ let _ = path.to_path_buf().clone(); +LL ~ let c = path.to_path_buf().pop(); +LL ~ let d = path.to_path_buf().clone(); | -error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do +error: writing `&String` instead of `&str` involves a new object where a slice will do --> tests/ui/ptr_arg.rs:196:21 | -LL | fn foo_str(str: &PathBuf) { - | ^^^^^^^^ +LL | fn foo_str(str: &String) { + | ^^^^^^^ | help: change this to | -LL ~ fn foo_str(str: &Path) { +LL ~ fn foo_str(str: &str) { LL | LL | -LL ~ let _ = str.to_path_buf().pop(); -LL ~ let _ = str.to_path_buf().clone(); +LL ~ let e = str.to_owned().pop(); +LL ~ let f = str.to_owned().clone(); | error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do @@ -231,6 +231,42 @@ error: writing `&String` instead of `&str` involves a new object where a slice w LL | fn good(v1: &String, v2: &String) { | ^^^^^^^ help: change this to: `&str` +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:363:20 + | +LL | fn foo_used(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i32]` + +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:377:26 + | +LL | fn foo_local_used(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i32]` + +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:386:33 + | +LL | fn foofoo(_x: &Vec, y: &String) { + | ^^^^^^^ help: change this to: `&str` + +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:407:20 + | +LL | fn bar_used(x: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` + +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:421:26 + | +LL | fn bar_local_used(x: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` + +error: writing `&mut String` instead of `&mut str` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:430:37 + | +LL | fn barbar(_x: &mut Vec, y: &mut String) { + | ^^^^^^^^^^^ help: change this to: `&mut str` + error: eliding a lifetime that's named elsewhere is confusing --> tests/ui/ptr_arg.rs:314:36 | @@ -248,5 +284,5 @@ help: consistently use `'a` LL | fn cow_good_ret_ty<'a>(input: &'a Cow<'a, str>) -> &'a str { | ++ -error: aborting due to 27 previous errors +error: aborting due to 33 previous errors diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed index 2033f31c1eec7..71fea6144e764 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed @@ -219,3 +219,11 @@ mod null_entire_infer { //~^ ptr_as_ptr } } + +#[allow(clippy::transmute_null_to_fn)] +fn issue15283() { + unsafe { + let _: fn() = std::mem::transmute(std::ptr::null::()); + //~^ ptr_as_ptr + } +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.rs b/src/tools/clippy/tests/ui/ptr_as_ptr.rs index 224d09b0eb6e5..4d507592a1e3c 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.rs +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.rs @@ -219,3 +219,11 @@ mod null_entire_infer { //~^ ptr_as_ptr } } + +#[allow(clippy::transmute_null_to_fn)] +fn issue15283() { + unsafe { + let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); + //~^ ptr_as_ptr + } +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr index 66dae8e0135e3..adad159bb0f27 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr @@ -201,5 +201,11 @@ error: `as` casting between raw pointers without changing their constness LL | core::ptr::null() as _ | ^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()` -error: aborting due to 33 previous errors +error: `as` casting between raw pointers without changing their constness + --> tests/ui/ptr_as_ptr.rs:226:43 + | +LL | let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` + +error: aborting due to 34 previous errors diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed index ee716ef3a6a02..5c6da6d5aed3a 100644 --- a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed @@ -1,5 +1,9 @@ +#![warn(clippy::range_minus_one, clippy::range_plus_one)] #![allow(unused_parens)] #![allow(clippy::iter_with_drain)] + +use std::ops::{Index, IndexMut, Range, RangeBounds, RangeInclusive}; + fn f() -> usize { 42 } @@ -20,8 +24,6 @@ macro_rules! macro_minus_one { }; } -#[warn(clippy::range_plus_one)] -#[warn(clippy::range_minus_one)] fn main() { for _ in 0..2 {} for _ in 0..=2 {} @@ -45,15 +47,13 @@ fn main() { //~^ range_plus_one for _ in 0..=(1 + f()) {} + // Those are not linted, as in the general case we cannot be sure that the exact type won't be + // important. let _ = ..11 - 1; - let _ = ..11; - //~^ range_minus_one - let _ = ..11; - //~^ range_minus_one - let _ = (1..=11); - //~^ range_plus_one - let _ = ((f() + 1)..=f()); - //~^ range_plus_one + let _ = ..=11 - 1; + let _ = ..=(11 - 1); + let _ = (1..11 + 1); + let _ = (f() + 1)..(f() + 1); const ONE: usize = 1; // integer consts are linted, too @@ -65,4 +65,118 @@ fn main() { macro_plus_one!(5); macro_minus_one!(5); + + // As an instance of `Iterator` + (1..=10).for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `IntoIterator` + #[allow(clippy::useless_conversion)] + (1..=10).into_iter().for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `RangeBounds` + { + let _ = (1..=10).start_bound(); + //~^ range_plus_one + } + + // As a `SliceIndex` + let a = [10, 20, 30]; + let _ = &a[1..=1]; + //~^ range_plus_one + + // As method call argument + vec.drain(2..=3); + //~^ range_plus_one + + // As function call argument + take_arg(10..=20); + //~^ range_plus_one + + // As function call argument inside a block + take_arg({ 10..=20 }); + //~^ range_plus_one + + // Do not lint in case types are unified + take_arg(if true { 10..20 } else { 10..20 + 1 }); + + // Do not lint, as the same type is used for both parameters + take_args(10..20 + 1, 10..21); + + // Do not lint, as the range type is also used indirectly in second parameter + take_arg_and_struct(10..20 + 1, S { t: 1..2 }); + + // As target of `IndexMut` + let mut a = [10, 20, 30]; + a[0..=2][0] = 1; + //~^ range_plus_one +} + +fn take_arg>(_: T) {} +fn take_args>(_: T, _: T) {} + +struct S { + t: T, +} +fn take_arg_and_struct>(_: T, _: S) {} + +fn no_index_by_range_inclusive(a: usize) { + struct S; + + impl Index> for S { + type Output = [u32]; + fn index(&self, _: Range) -> &Self::Output { + &[] + } + } + + _ = &S[0..a + 1]; +} + +fn no_index_mut_with_switched_range(a: usize) { + struct S(u32); + + impl Index> for S { + type Output = u32; + fn index(&self, _: Range) -> &Self::Output { + &self.0 + } + } + + impl IndexMut> for S { + fn index_mut(&mut self, _: Range) -> &mut Self::Output { + &mut self.0 + } + } + + impl Index> for S { + type Output = u32; + fn index(&self, _: RangeInclusive) -> &Self::Output { + &self.0 + } + } + + S(2)[0..a + 1] = 3; +} + +fn issue9908() { + // Simplified test case + let _ = || 0..=1; + + // Original test case + let full_length = 1024; + let range = { + // do some stuff, omit here + None + }; + + let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1)); + + assert_eq!(range, 0..=1023); +} + +fn issue9908_2(n: usize) -> usize { + (1..n).sum() + //~^ range_minus_one } diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.rs b/src/tools/clippy/tests/ui/range_plus_minus_one.rs index f2d5ae2c15065..7172da6034b83 100644 --- a/src/tools/clippy/tests/ui/range_plus_minus_one.rs +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.rs @@ -1,5 +1,9 @@ +#![warn(clippy::range_minus_one, clippy::range_plus_one)] #![allow(unused_parens)] #![allow(clippy::iter_with_drain)] + +use std::ops::{Index, IndexMut, Range, RangeBounds, RangeInclusive}; + fn f() -> usize { 42 } @@ -20,8 +24,6 @@ macro_rules! macro_minus_one { }; } -#[warn(clippy::range_plus_one)] -#[warn(clippy::range_minus_one)] fn main() { for _ in 0..2 {} for _ in 0..=2 {} @@ -45,15 +47,13 @@ fn main() { //~^ range_plus_one for _ in 0..=(1 + f()) {} + // Those are not linted, as in the general case we cannot be sure that the exact type won't be + // important. let _ = ..11 - 1; let _ = ..=11 - 1; - //~^ range_minus_one let _ = ..=(11 - 1); - //~^ range_minus_one let _ = (1..11 + 1); - //~^ range_plus_one let _ = (f() + 1)..(f() + 1); - //~^ range_plus_one const ONE: usize = 1; // integer consts are linted, too @@ -65,4 +65,118 @@ fn main() { macro_plus_one!(5); macro_minus_one!(5); + + // As an instance of `Iterator` + (1..10 + 1).for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `IntoIterator` + #[allow(clippy::useless_conversion)] + (1..10 + 1).into_iter().for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `RangeBounds` + { + let _ = (1..10 + 1).start_bound(); + //~^ range_plus_one + } + + // As a `SliceIndex` + let a = [10, 20, 30]; + let _ = &a[1..1 + 1]; + //~^ range_plus_one + + // As method call argument + vec.drain(2..3 + 1); + //~^ range_plus_one + + // As function call argument + take_arg(10..20 + 1); + //~^ range_plus_one + + // As function call argument inside a block + take_arg({ 10..20 + 1 }); + //~^ range_plus_one + + // Do not lint in case types are unified + take_arg(if true { 10..20 } else { 10..20 + 1 }); + + // Do not lint, as the same type is used for both parameters + take_args(10..20 + 1, 10..21); + + // Do not lint, as the range type is also used indirectly in second parameter + take_arg_and_struct(10..20 + 1, S { t: 1..2 }); + + // As target of `IndexMut` + let mut a = [10, 20, 30]; + a[0..2 + 1][0] = 1; + //~^ range_plus_one +} + +fn take_arg>(_: T) {} +fn take_args>(_: T, _: T) {} + +struct S { + t: T, +} +fn take_arg_and_struct>(_: T, _: S) {} + +fn no_index_by_range_inclusive(a: usize) { + struct S; + + impl Index> for S { + type Output = [u32]; + fn index(&self, _: Range) -> &Self::Output { + &[] + } + } + + _ = &S[0..a + 1]; +} + +fn no_index_mut_with_switched_range(a: usize) { + struct S(u32); + + impl Index> for S { + type Output = u32; + fn index(&self, _: Range) -> &Self::Output { + &self.0 + } + } + + impl IndexMut> for S { + fn index_mut(&mut self, _: Range) -> &mut Self::Output { + &mut self.0 + } + } + + impl Index> for S { + type Output = u32; + fn index(&self, _: RangeInclusive) -> &Self::Output { + &self.0 + } + } + + S(2)[0..a + 1] = 3; +} + +fn issue9908() { + // Simplified test case + let _ = || 0..=1; + + // Original test case + let full_length = 1024; + let range = { + // do some stuff, omit here + None + }; + + let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1)); + + assert_eq!(range, 0..=1023); +} + +fn issue9908_2(n: usize) -> usize { + (1..=n - 1).sum() + //~^ range_minus_one } diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.stderr b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr index 9b23a8b8c0b46..a419d935bd62f 100644 --- a/src/tools/clippy/tests/ui/range_plus_minus_one.stderr +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr @@ -1,5 +1,5 @@ error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:29:14 + --> tests/ui/range_plus_minus_one.rs:31:14 | LL | for _ in 0..3 + 1 {} | ^^^^^^^^ help: use: `0..=3` @@ -8,55 +8,85 @@ LL | for _ in 0..3 + 1 {} = help: to override `-D warnings` add `#[allow(clippy::range_plus_one)]` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:33:14 + --> tests/ui/range_plus_minus_one.rs:35:14 | LL | for _ in 0..1 + 5 {} | ^^^^^^^^ help: use: `0..=5` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:37:14 + --> tests/ui/range_plus_minus_one.rs:39:14 | LL | for _ in 1..1 + 1 {} | ^^^^^^^^ help: use: `1..=1` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:44:14 + --> tests/ui/range_plus_minus_one.rs:46:14 | LL | for _ in 0..(1 + f()) {} | ^^^^^^^^^^^^ help: use: `0..=f()` -error: an exclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:49:13 +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:60:14 | -LL | let _ = ..=11 - 1; - | ^^^^^^^^^ help: use: `..11` +LL | for _ in 1..ONE + ONE {} + | ^^^^^^^^^^^^ help: use: `1..=ONE` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:70:5 | - = note: `-D clippy::range-minus-one` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]` +LL | (1..10 + 1).for_each(|_| {}); + | ^^^^^^^^^^^ help: use: `(1..=10)` -error: an exclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:51:13 +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:75:5 | -LL | let _ = ..=(11 - 1); - | ^^^^^^^^^^^ help: use: `..11` +LL | (1..10 + 1).into_iter().for_each(|_| {}); + | ^^^^^^^^^^^ help: use: `(1..=10)` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:53:13 + --> tests/ui/range_plus_minus_one.rs:80:17 | -LL | let _ = (1..11 + 1); - | ^^^^^^^^^^^ help: use: `(1..=11)` +LL | let _ = (1..10 + 1).start_bound(); + | ^^^^^^^^^^^ help: use: `(1..=10)` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:55:13 + --> tests/ui/range_plus_minus_one.rs:86:16 | -LL | let _ = (f() + 1)..(f() + 1); - | ^^^^^^^^^^^^^^^^^^^^ help: use: `((f() + 1)..=f())` +LL | let _ = &a[1..1 + 1]; + | ^^^^^^^^ help: use: `1..=1` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:60:14 + --> tests/ui/range_plus_minus_one.rs:90:15 | -LL | for _ in 1..ONE + ONE {} - | ^^^^^^^^^^^^ help: use: `1..=ONE` +LL | vec.drain(2..3 + 1); + | ^^^^^^^^ help: use: `2..=3` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:94:14 + | +LL | take_arg(10..20 + 1); + | ^^^^^^^^^^ help: use: `10..=20` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:98:16 + | +LL | take_arg({ 10..20 + 1 }); + | ^^^^^^^^^^ help: use: `10..=20` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:112:7 + | +LL | a[0..2 + 1][0] = 1; + | ^^^^^^^^ help: use: `0..=2` + +error: an exclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:180:5 + | +LL | (1..=n - 1).sum() + | ^^^^^^^^^^^ help: use: `(1..n)` + | + = note: `-D clippy::range-minus-one` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]` -error: aborting due to 9 previous errors +error: aborting due to 14 previous errors diff --git a/src/tools/clippy/tests/ui/single_match_else_deref_patterns.fixed b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.fixed new file mode 100644 index 0000000000000..7a9f806309645 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.fixed @@ -0,0 +1,53 @@ +#![feature(deref_patterns)] +#![allow( + incomplete_features, + clippy::eq_op, + clippy::op_ref, + clippy::deref_addrof, + clippy::borrow_deref_ref, + clippy::needless_if +)] +#![deny(clippy::single_match_else)] + +fn string() { + if *"" == *"" {} + + if *&*&*&*"" == *"" {} + + if ***&&"" == *"" {} + + if *&*&*"" == *"" {} + + if **&&*"" == *"" {} +} + +fn int() { + if &&&1 == &&&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &&1 == &&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &&1 == &&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &1 == &2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &1 == &2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if 1 == 2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if 1 == 2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else +} diff --git a/src/tools/clippy/tests/ui/single_match_else_deref_patterns.rs b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.rs new file mode 100644 index 0000000000000..ef19c7cbde2ba --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.rs @@ -0,0 +1,94 @@ +#![feature(deref_patterns)] +#![allow( + incomplete_features, + clippy::eq_op, + clippy::op_ref, + clippy::deref_addrof, + clippy::borrow_deref_ref, + clippy::needless_if +)] +#![deny(clippy::single_match_else)] + +fn string() { + match *"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match *&*&*&*"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match ***&&"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match *&*&*"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match **&&*"" { + //~^ single_match + "" => {}, + _ => {}, + } +} + +fn int() { + match &&&1 { + &&&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + &&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + &&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + &2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + &2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + 2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + 2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else +} diff --git a/src/tools/clippy/tests/ui/single_match_else_deref_patterns.stderr b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.stderr new file mode 100644 index 0000000000000..a47df55459be1 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else_deref_patterns.stderr @@ -0,0 +1,188 @@ +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:13:5 + | +LL | / match *"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + = note: `-D clippy::single-match` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::single_match)]` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:19:5 + | +LL | / match *&*&*&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *&*&*&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:25:5 + | +LL | / match ***&&"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if ***&&"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:31:5 + | +LL | / match *&*&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *&*&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:37:5 + | +LL | / match **&&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if **&&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:45:5 + | +LL | / match &&&1 { +LL | | &&&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +note: the lint level is defined here + --> tests/ui/single_match_else_deref_patterns.rs:10:9 + | +LL | #![deny(clippy::single_match_else)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL ~ if &&&1 == &&&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:52:5 + | +LL | / match &&&1 { +LL | | &&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &&1 == &&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:59:5 + | +LL | / match &&1 { +LL | | &&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &&1 == &&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:66:5 + | +LL | / match &&&1 { +LL | | &2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &1 == &2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:73:5 + | +LL | / match &&1 { +LL | | &2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &1 == &2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:80:5 + | +LL | / match &&&1 { +LL | | 2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if 1 == 2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:87:5 + | +LL | / match &&1 { +LL | | 2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if 1 == 2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs index 14371bc203b3b..d0022f3b6d93d 100644 --- a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs @@ -82,3 +82,32 @@ impl H { } fn main() {} + +mod issue15120 { + macro_rules! uns { + ($e:expr) => { + unsafe { $e } + }; + } + + #[derive(serde::Deserialize)] + struct Foo; + + impl Foo { + fn foo(&self) { + // Do not lint if `unsafe` comes from the `core::pin::pin!()` macro + std::pin::pin!(()); + } + } + + //~v unsafe_derive_deserialize + #[derive(serde::Deserialize)] + struct Bar; + + impl Bar { + fn bar(&self) { + // Lint if `unsafe` comes from the another macro + _ = uns!(42); + } + } +} diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr index f2d4429f707a5..4b5dd6e61fcc0 100644 --- a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr @@ -36,5 +36,14 @@ LL | #[derive(Deserialize)] = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 4 previous errors +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> tests/ui/unsafe_derive_deserialize.rs:104:14 + | +LL | #[derive(serde::Deserialize)] + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in the derive macro `serde::Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/unused_async.rs b/src/tools/clippy/tests/ui/unused_async.rs index 433459253dd76..7a0be825a2de9 100644 --- a/src/tools/clippy/tests/ui/unused_async.rs +++ b/src/tools/clippy/tests/ui/unused_async.rs @@ -127,3 +127,13 @@ mod issue14704 { async fn cancel(self: Arc) {} } } + +mod issue15305 { + async fn todo_task() -> Result<(), String> { + todo!("Implement task"); + } + + async fn unimplemented_task() -> Result<(), String> { + unimplemented!("Implement task"); + } +} diff --git a/src/tools/clippy/tests/ui/unused_trait_names.fixed b/src/tools/clippy/tests/ui/unused_trait_names.fixed index 17e32ddfd9d95..6abbed01bb023 100644 --- a/src/tools/clippy/tests/ui/unused_trait_names.fixed +++ b/src/tools/clippy/tests/ui/unused_trait_names.fixed @@ -200,11 +200,11 @@ fn msrv_1_33() { MyStruct.do_things(); } +// Linting inside macro expansion is no longer supported mod lint_inside_macro_expansion_bad { macro_rules! foo { () => { - use std::any::Any as _; - //~^ unused_trait_names + use std::any::Any; fn bar() { "bar".type_id(); } diff --git a/src/tools/clippy/tests/ui/unused_trait_names.rs b/src/tools/clippy/tests/ui/unused_trait_names.rs index 3cf8597e53517..4a06f062dc37e 100644 --- a/src/tools/clippy/tests/ui/unused_trait_names.rs +++ b/src/tools/clippy/tests/ui/unused_trait_names.rs @@ -200,11 +200,11 @@ fn msrv_1_33() { MyStruct.do_things(); } +// Linting inside macro expansion is no longer supported mod lint_inside_macro_expansion_bad { macro_rules! foo { () => { use std::any::Any; - //~^ unused_trait_names fn bar() { "bar".type_id(); } diff --git a/src/tools/clippy/tests/ui/unused_trait_names.stderr b/src/tools/clippy/tests/ui/unused_trait_names.stderr index 3183289d85337..28067e17414f8 100644 --- a/src/tools/clippy/tests/ui/unused_trait_names.stderr +++ b/src/tools/clippy/tests/ui/unused_trait_names.stderr @@ -58,16 +58,5 @@ error: importing trait that is only used anonymously LL | use simple_trait::{MyStruct, MyTrait}; | ^^^^^^^ help: use: `MyTrait as _` -error: importing trait that is only used anonymously - --> tests/ui/unused_trait_names.rs:206:27 - | -LL | use std::any::Any; - | ^^^ help: use: `Any as _` -... -LL | foo!(); - | ------ in this macro invocation - | - = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/used_underscore_items.rs b/src/tools/clippy/tests/ui/used_underscore_items.rs index 7e8289f1406ba..aecdd32693c14 100644 --- a/src/tools/clippy/tests/ui/used_underscore_items.rs +++ b/src/tools/clippy/tests/ui/used_underscore_items.rs @@ -62,13 +62,13 @@ fn main() { //~^ used_underscore_items } -// should not lint exteranl crate. +// should not lint external crate. // user cannot control how others name their items fn external_item_call() { let foo_struct3 = external_item::_ExternalStruct {}; foo_struct3._foo(); - external_item::_exernal_foo(); + external_item::_external_foo(); } // should not lint foreign functions. diff --git a/src/tools/clippy/tests/ui/useless_attribute.fixed b/src/tools/clippy/tests/ui/useless_attribute.fixed index 930bc1eaecf9c..be4fb55ddfb86 100644 --- a/src/tools/clippy/tests/ui/useless_attribute.fixed +++ b/src/tools/clippy/tests/ui/useless_attribute.fixed @@ -146,3 +146,15 @@ pub mod unknown_namespace { #[allow(rustc::non_glob_import_of_type_ir_inherent)] use some_module::SomeType; } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/15316 +pub mod redundant_imports_issue { + macro_rules! empty { + () => {}; + } + + #[expect(redundant_imports)] + pub(crate) use empty; + + empty!(); +} diff --git a/src/tools/clippy/tests/ui/useless_attribute.rs b/src/tools/clippy/tests/ui/useless_attribute.rs index 50fafd478e51c..5a1bcf97a5b46 100644 --- a/src/tools/clippy/tests/ui/useless_attribute.rs +++ b/src/tools/clippy/tests/ui/useless_attribute.rs @@ -146,3 +146,15 @@ pub mod unknown_namespace { #[allow(rustc::non_glob_import_of_type_ir_inherent)] use some_module::SomeType; } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/15316 +pub mod redundant_imports_issue { + macro_rules! empty { + () => {}; + } + + #[expect(redundant_imports)] + pub(crate) use empty; + + empty!(); +} diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml index 805baf2af6dd0..a62b6269a3bfc 100644 --- a/src/tools/clippy/triagebot.toml +++ b/src/tools/clippy/triagebot.toml @@ -54,6 +54,7 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", + "samueltardieu", ] [assign.owners] diff --git a/src/tools/clippy/util/gh-pages/index_template.html b/src/tools/clippy/util/gh-pages/index_template.html index 6f380ec8feef3..5d65ea585df63 100644 --- a/src/tools/clippy/util/gh-pages/index_template.html +++ b/src/tools/clippy/util/gh-pages/index_template.html @@ -49,9 +49,7 @@ {# #}
{# #} - {# #} +

Clippy Lints

{# #} {# #} -
{# #} - {# #} lint.group != "deprecated"); const totalLints = allLints.length; - + const countElement = document.getElementById("lint-count"); if (countElement) { countElement.innerText = `Total number: ${totalLints}`; } } + +generateSettings(); +generateSearch(); +parseURLFilters(); +scrollToLintByURL(); +filters.filterLints(); +updateLintCount(); diff --git a/src/tools/clippy/util/gh-pages/style.css b/src/tools/clippy/util/gh-pages/style.css index 022ea87520004..66abf4598b0ed 100644 --- a/src/tools/clippy/util/gh-pages/style.css +++ b/src/tools/clippy/util/gh-pages/style.css @@ -30,17 +30,25 @@ blockquote { font-size: 1em; } background-color: var(--theme-hover); } -div.panel div.panel-body button { +.container > * { + margin-bottom: 20px; + border-radius: 4px; + background: var(--bg); + border: 1px solid var(--theme-popup-border); + box-shadow: 0 1px 1px rgba(0,0,0,.05); +} + +div.panel-body button { background: var(--searchbar-bg); color: var(--searchbar-fg); border-color: var(--theme-popup-border); } -div.panel div.panel-body button:hover { +div.panel-body button:hover { box-shadow: 0 0 3px var(--searchbar-shadow-color); } -div.panel div.panel-body button.open { +div.panel-body button.open { filter: brightness(90%); } @@ -48,8 +56,6 @@ div.panel div.panel-body button.open { background-color: #777; } -.panel-heading { cursor: pointer; } - .lint-title { cursor: pointer; margin-top: 0; @@ -70,8 +76,8 @@ div.panel div.panel-body button.open { .panel-title-name { flex: 1; min-width: 400px;} -.panel .panel-title-name .anchor { display: none; } -.panel:hover .panel-title-name .anchor { display: inline;} +.panel-title-name .anchor { display: none; } +article:hover .panel-title-name .anchor { display: inline;} .search-control { margin-top: 15px; @@ -111,40 +117,48 @@ div.panel div.panel-body button.open { padding-bottom: 0.3em; } -.label-lint-group { - min-width: 8em; -} -.label-lint-level { +.lint-level { min-width: 4em; } - -.label-lint-level-allow { +.level-allow { background-color: #5cb85c; } -.label-lint-level-warn { +.level-warn { background-color: #f0ad4e; } -.label-lint-level-deny { +.level-deny { background-color: #d9534f; } -.label-lint-level-none { +.level-none { background-color: #777777; opacity: 0.5; } -.label-group-deprecated { +.lint-group { + min-width: 8em; +} +.group-deprecated { opacity: 0.5; } -.label-doc-folding { +.doc-folding { color: #000; background-color: #fff; border: 1px solid var(--theme-popup-border); } -.label-doc-folding:hover { +.doc-folding:hover { background-color: #e6e6e6; } +.lint-doc-md { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background: 0%; + border-bottom: 1px solid var(--theme-popup-border); + border-top: 1px solid var(--theme-popup-border); +} .lint-doc-md > h3 { border-top: 1px solid var(--theme-popup-border); padding: 10px 15px; @@ -157,32 +171,32 @@ div.panel div.panel-body button.open { } @media (max-width:749px) { - .lint-additional-info-container { + .lint-additional-info { display: flex; flex-flow: column; } - .lint-additional-info-container > div + div { + .lint-additional-info > div + div { border-top: 1px solid var(--theme-popup-border); } } @media (min-width:750px) { - .lint-additional-info-container { + .lint-additional-info { display: flex; flex-flow: row; } - .lint-additional-info-container > div + div { + .lint-additional-info > div + div { border-left: 1px solid var(--theme-popup-border); } } -.lint-additional-info-container > div { +.lint-additional-info > div { display: inline-flex; min-width: 200px; flex-grow: 1; padding: 9px 5px 5px 15px; } -.label-applicability { +.applicability { background-color: #777777; margin: auto 5px; } @@ -332,21 +346,12 @@ L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.649 border: 1px solid var(--theme-popup-border); } .page-header { - border-color: var(--theme-popup-border); -} -.panel-default .panel-heading { - background: var(--theme-hover); - color: var(--fg); - border: 1px solid var(--theme-popup-border); -} -.panel-default .panel-heading:hover { - filter: brightness(90%); -} -.list-group-item { - background: 0%; - border: 1px solid var(--theme-popup-border); + border: 0; + border-bottom: 1px solid var(--theme-popup-border); + padding-bottom: 19px; + border-radius: 0; } -.panel, pre, hr { +pre, hr { background: var(--bg); border: 1px solid var(--theme-popup-border); } @@ -442,14 +447,15 @@ article > label { article > input[type="checkbox"] { display: none; } -article > input[type="checkbox"] + label .label-doc-folding::before { +article > input[type="checkbox"] + label .doc-folding::before { content: "+"; } -article > input[type="checkbox"]:checked + label .label-doc-folding::before { +article > input[type="checkbox"]:checked + label .doc-folding::before { content: "−"; } .lint-docs { display: none; + margin-bottom: 0; } article > input[type="checkbox"]:checked ~ .lint-docs { display: block;