From f39ed7abd823701ae62a272c54af389b7c644070 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 23 Aug 2025 06:52:15 +0800 Subject: [PATCH 01/80] fix: `match_single_binding` suggests wrongly inside tuple --- .../src/matches/match_single_binding.rs | 54 +++--- tests/ui/match_single_binding.fixed | 79 +++++++++ tests/ui/match_single_binding.rs | 81 +++++++++ tests/ui/match_single_binding.stderr | 158 +++++++++++++++++- 4 files changed, 348 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 82d5310663ee0..449f5dabe23f2 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -307,26 +307,6 @@ fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { false } -fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { - let parent = cx.tcx.parent_hir_node(match_expr.hir_id); - if let Node::Expr(Expr { - kind: ExprKind::Closure(..) | ExprKind::Binary(..), - .. - }) - | Node::AnonConst(..) = parent - { - return true; - } - - if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) - && let ExprKind::Match(..) = arm.body.kind - { - return true; - } - - false -} - fn indent_of_nth_line(snippet: &str, nth: usize) -> Option { snippet .lines() @@ -379,14 +359,42 @@ fn sugg_with_curlies<'a>( let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if !expr_in_nested_block(cx, match_expr) - && ((needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies(cx, match_expr)) - { + let mut add_curlies = || { cbrace_end = format!("\n{indent}}}"); // Fix body indent due to the closure indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); cbrace_start = format!("{{\n{indent}"); snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty()); + }; + + if !expr_in_nested_block(cx, match_expr) { + let mut parent = cx.tcx.parent_hir_node(match_expr.hir_id); + if let Node::Expr(Expr { + kind: ExprKind::Assign(..), + hir_id, + .. + }) = parent + { + parent = cx.tcx.parent_hir_node(*hir_id); + } + if let Node::Stmt(stmt) = parent { + parent = cx.tcx.parent_hir_node(stmt.hir_id); + } + + match parent { + Node::Block(..) + | Node::Expr(Expr { + kind: ExprKind::Block(..) | ExprKind::ConstBlock(..), + .. + }) => { + if needs_var_binding && is_var_binding_used_later { + add_curlies(); + } + }, + Node::Expr(..) | Node::AnonConst(..) => add_curlies(), + Node::Arm(arm) if let ExprKind::Match(..) = arm.body.kind => add_curlies(), + _ => {}, + } } format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}") diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index 7e899a4766661..fa82a316d64d9 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -265,3 +265,82 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { a < b && b < c } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + { + { a }; + () + }, + (), + ); + + let y = [ + { todo!() }, + { + { a }; + () + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + { + { a }; + () + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + { + { a }; + () + }, + (), + ); + + -{ + { a }; + 1 + }; + + _ = { a }; + 1; + + if let x = { + { a }; + 1 + } {} + + if { + { a }; + true + } { + todo!() + } + + [1, 2, 3][{ + { a }; + 1usize + }]; + + todo!() +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index 37a96f2287c85..6c1fae89e230d 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -342,3 +342,84 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { (a, b) => b < c, } } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + let y = [ + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + -match { a } { + //~^ match_single_binding + _ => 1, + }; + + _ = match { a } { + //~^ match_single_binding + _ => 1, + }; + + if let x = match { a } { + //~^ match_single_binding + _ => 1, + } {} + + if match { a } { + //~^ match_single_binding + _ => true, + } { + todo!() + } + + [1, 2, 3][match { a } { + //~^ match_single_binding + _ => 1usize, + }]; + + todo!() +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index 82fc43aaa5eae..c2d9d168b4a15 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -525,5 +525,161 @@ LL | | (a, b) => b < c, LL | | } | |_________^ help: consider using the match body instead: `b < c` -error: aborting due to 37 previous errors +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:358:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:367:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:377:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:391:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:398:6 + | +LL | -match { a } { + | ______^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ -{ +LL + { a }; +LL + 1 +LL ~ }; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:403:9 + | +LL | _ = match { a } { + | _________^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ _ = { a }; +LL ~ 1; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:408:16 + | +LL | if let x = match { a } { + | ________________^ +LL | | +LL | | _ => 1, +LL | | } {} + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if let x = { +LL + { a }; +LL + 1 +LL ~ } {} + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:413:8 + | +LL | if match { a } { + | ________^ +LL | | +LL | | _ => true, +LL | | } { + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if { +LL + { a }; +LL + true +LL ~ } { + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:420:15 + | +LL | [1, 2, 3][match { a } { + | _______________^ +LL | | +LL | | _ => 1usize, +LL | | }]; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ [1, 2, 3][{ +LL + { a }; +LL + 1usize +LL ~ }]; + | + +error: aborting due to 46 previous errors From e1ed9ec9d4a1dc4d1d63e65a173cb9d7be98002f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 25 Sep 2025 11:09:55 +0200 Subject: [PATCH 02/80] clean-up --- clippy_utils/src/attrs.rs | 93 +++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 2d42e76dcbc9b..169f07f28aefd 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -69,51 +69,40 @@ pub fn get_attr<'a, A: AttributeExt + 'a>( name: Symbol, ) -> impl Iterator { attrs.iter().filter(move |attr| { - let Some(attr_segments) = attr.ident_path() else { - return false; - }; - - if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy { - BUILTIN_ATTRIBUTES + if let Some([clippy, segment2]) = attr.ident_path().as_deref() + && clippy.name == sym::clippy + { + let Some((_, deprecation_status)) = BUILTIN_ATTRIBUTES .iter() - .find_map(|(builtin_name, deprecation_status)| { - if attr_segments[1].name == *builtin_name { - Some(deprecation_status) - } else { - None - } - }) - .map_or_else( - || { - sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute"); - false - }, - |deprecation_status| { - let mut diag = sess - .dcx() - .struct_span_err(attr_segments[1].span, "usage of deprecated attribute"); - match *deprecation_status { - DeprecationStatus::Deprecated => { - diag.emit(); - false - }, - DeprecationStatus::Replaced(new_name) => { - diag.span_suggestion( - attr_segments[1].span, - "consider using", - new_name, - Applicability::MachineApplicable, - ); - diag.emit(); - false - }, - DeprecationStatus::None => { - diag.cancel(); - attr_segments[1].name == name - }, - } - }, - ) + .find(|(builtin_name, _)| segment2.name == *builtin_name) + else { + sess.dcx().span_err(segment2.span, "usage of unknown attribute"); + return false; + }; + + let mut diag = sess + .dcx() + .struct_span_err(segment2.span, "usage of deprecated attribute"); + match deprecation_status { + DeprecationStatus::Deprecated => { + diag.emit(); + false + }, + DeprecationStatus::Replaced(new_name) => { + diag.span_suggestion( + segment2.span, + "consider using", + new_name, + Applicability::MachineApplicable, + ); + diag.emit(); + false + }, + DeprecationStatus::None => { + diag.cancel(); + segment2.name == name + }, + } } else { false } @@ -122,15 +111,15 @@ pub fn get_attr<'a, A: AttributeExt + 'a>( fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { for attr in get_attr(sess, attrs, name) { - if let Some(value) = attr.value_str() { - if let Ok(value) = FromStr::from_str(value.as_str()) { - f(value); - } else { - sess.dcx().span_err(attr.span(), "not a number"); - } - } else { + let Some(value) = attr.value_str() else { sess.dcx().span_err(attr.span(), "bad clippy attribute"); - } + continue; + }; + let Ok(value) = u64::from_str(value.as_str()) else { + sess.dcx().span_err(attr.span(), "not a number"); + continue; + }; + f(value); } } From c3257f40d84626a85cf2352e4d27cf7c780397f7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 28 Sep 2025 12:49:12 +0200 Subject: [PATCH 03/80] merge `DeprecationStatus::{Replaced,Deprecated}` Their meanings, and the way they're handled in `get_attr`, are pretty similar --- clippy_utils/src/attrs.rs | 76 ++++++++++++++------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 169f07f28aefd..aef5c59e8f103 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -12,29 +12,6 @@ use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -/// Deprecation status of attributes known by Clippy. -pub enum DeprecationStatus { - /// Attribute is deprecated - Deprecated, - /// Attribute is deprecated and was replaced by the named attribute - Replaced(&'static str), - None, -} - -#[rustfmt::skip] -pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[ - (sym::author, DeprecationStatus::None), - (sym::version, DeprecationStatus::None), - (sym::cognitive_complexity, DeprecationStatus::None), - (sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")), - (sym::dump, DeprecationStatus::None), - (sym::msrv, DeprecationStatus::None), - // The following attributes are for the 3rd party crate authors. - // See book/src/attribs.md - (sym::has_significant_drop, DeprecationStatus::None), - (sym::format_args, DeprecationStatus::None), -]; - pub struct LimitStack { stack: Vec, } @@ -72,36 +49,37 @@ pub fn get_attr<'a, A: AttributeExt + 'a>( if let Some([clippy, segment2]) = attr.ident_path().as_deref() && clippy.name == sym::clippy { - let Some((_, deprecation_status)) = BUILTIN_ATTRIBUTES - .iter() - .find(|(builtin_name, _)| segment2.name == *builtin_name) - else { - sess.dcx().span_err(segment2.span, "usage of unknown attribute"); - return false; + let new_name = match segment2.name { + sym::cyclomatic_complexity => Some("cognitive_complexity"), + sym::author + | sym::version + | sym::cognitive_complexity + | sym::dump + | sym::msrv + // The following attributes are for the 3rd party crate authors. + // See book/src/attribs.md + | sym::has_significant_drop + | sym::format_args => None, + _ => { + sess.dcx().span_err(segment2.span, "usage of unknown attribute"); + return false; + }, }; - let mut diag = sess - .dcx() - .struct_span_err(segment2.span, "usage of deprecated attribute"); - match deprecation_status { - DeprecationStatus::Deprecated => { - diag.emit(); + match new_name { + Some(new_name) => { + sess.dcx() + .struct_span_err(segment2.span, "usage of deprecated attribute") + .with_span_suggestion( + segment2.span, + "consider using", + new_name, + Applicability::MachineApplicable, + ) + .emit(); false }, - DeprecationStatus::Replaced(new_name) => { - diag.span_suggestion( - segment2.span, - "consider using", - new_name, - Applicability::MachineApplicable, - ); - diag.emit(); - false - }, - DeprecationStatus::None => { - diag.cancel(); - segment2.name == name - }, + None => segment2.name == name, } } else { false From 0c013414b87984e6795160eb93a1d2a653134c6c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 25 Sep 2025 21:22:48 +0200 Subject: [PATCH 04/80] overhaul `LimitStack` - Move it and its helper function `parse_attrs` together to the end of the file, because it's surprising to see front-and-center a struct that's only really used in one place (`cognitive_complexity`). - Avoid panic path in `LimitStack::limit` - Replace `assert` with `debug_assert` to avoid panics in release builds --- clippy_utils/src/attrs.rs | 88 ++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index aef5c59e8f103..b4fafc9982295 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -12,34 +12,6 @@ use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -pub struct LimitStack { - stack: Vec, -} - -impl Drop for LimitStack { - fn drop(&mut self) { - assert_eq!(self.stack.len(), 1); - } -} - -impl LimitStack { - #[must_use] - pub fn new(limit: u64) -> Self { - Self { stack: vec![limit] } - } - pub fn limit(&self) -> u64 { - *self.stack.last().expect("there should always be a value in the stack") - } - pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| stack.push(val)); - } - pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val))); - } -} - pub fn get_attr<'a, A: AttributeExt + 'a>( sess: &'a Session, attrs: &'a [A], @@ -87,20 +59,6 @@ pub fn get_attr<'a, A: AttributeExt + 'a>( }) } -fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { - for attr in get_attr(sess, attrs, name) { - let Some(value) = attr.value_str() else { - sess.dcx().span_err(attr.span(), "bad clippy attribute"); - continue; - }; - let Ok(value) = u64::from_str(value.as_str()) else { - sess.dcx().span_err(attr.span(), "not a number"); - continue; - }; - f(value); - } -} - pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { let mut unique_attr: Option<&A> = None; for attr in get_attr(sess, attrs, name) { @@ -165,3 +123,49 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { false }) } +pub struct LimitStack { + default: u64, + stack: Vec, +} + +impl Drop for LimitStack { + fn drop(&mut self) { + debug_assert_eq!(self.stack, Vec::::new()); // avoid `.is_empty()`, for a nicer error message + } +} + +impl LimitStack { + #[must_use] + /// Initialize the stack starting with a default value, which usually comes from configuration + pub fn new(limit: u64) -> Self { + Self { + default: limit, + stack: vec![], + } + } + pub fn limit(&self) -> u64 { + self.stack.last().copied().unwrap_or(self.default) + } + pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| stack.push(val)); + } + pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val))); + } +} + +fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { + for attr in get_attr(sess, attrs, name) { + let Some(value) = attr.value_str() else { + sess.dcx().span_err(attr.span(), "bad clippy attribute"); + continue; + }; + let Ok(value) = u64::from_str(value.as_str()) else { + sess.dcx().span_err(attr.span(), "not a number"); + continue; + }; + f(value); + } +} From e55b435d9973e30ceef1aeccc2417e9bb7ec65d6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 25 Sep 2025 19:54:17 +0200 Subject: [PATCH 05/80] give functions more descriptive names, add docs --- .../matches/significant_drop_in_scrutinee.rs | 4 +-- .../src/significant_drop_tightening.rs | 4 +-- clippy_lints/src/utils/author.rs | 4 +-- clippy_lints/src/utils/dump_hir.rs | 4 +-- clippy_utils/src/attrs.rs | 25 +++++++++++++------ clippy_utils/src/lib.rs | 1 + clippy_utils/src/macros.rs | 4 +-- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 81fecc87256c4..bac35e7f8c700 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -4,7 +4,7 @@ use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{first_line_of_span, indent_of, snippet}; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; -use clippy_utils::{get_attr, is_lint_allowed, sym}; +use clippy_utils::{get_builtin_attr, is_lint_allowed, sym}; use itertools::Itertools; use rustc_ast::Mutability; use rustc_data_structures::fx::FxIndexSet; @@ -183,7 +183,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() - && get_attr( + && get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index dcce906499585..b4e7c771a54fc 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, peel_hir_expr_unary, sym}; +use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -167,7 +167,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { if let Some(adt) = ty.ty_adt_def() { - let mut iter = get_attr( + let mut iter = get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 68e51dace2db2..4b157dc411427 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -1,5 +1,5 @@ use clippy_utils::res::MaybeQPath; -use clippy_utils::{get_attr, higher, sym}; +use clippy_utils::{get_builtin_attr, higher, sym}; use itertools::Itertools; use rustc_ast::LitIntType; use rustc_ast::ast::{LitFloatType, LitKind}; @@ -856,5 +856,5 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::author).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0 } diff --git a/clippy_lints/src/utils/dump_hir.rs b/clippy_lints/src/utils/dump_hir.rs index d6cf07fdaf3f8..b490866f0a11e 100644 --- a/clippy_lints/src/utils/dump_hir.rs +++ b/clippy_lints/src/utils/dump_hir.rs @@ -1,4 +1,4 @@ -use clippy_utils::{get_attr, sym}; +use clippy_utils::{get_builtin_attr, sym}; use hir::TraitItem; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -60,5 +60,5 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir { fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::dump).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0 } diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index b4fafc9982295..671b266ba0086 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -1,3 +1,5 @@ +//! Utility functions for attributes, including Clippy's built-in ones + use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; use rustc_ast::attr; @@ -12,7 +14,8 @@ use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -pub fn get_attr<'a, A: AttributeExt + 'a>( +/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name` +pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( sess: &'a Session, attrs: &'a [A], name: Symbol, @@ -59,9 +62,11 @@ pub fn get_attr<'a, A: AttributeExt + 'a>( }) } -pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { +/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`, +/// returns that attribute, and `None` otherwise +pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { let mut unique_attr: Option<&A> = None; - for attr in get_attr(sess, attrs, name) { + for attr in get_builtin_attr(sess, attrs, name) { if let Some(duplicate) = unique_attr { sess.dcx() .struct_span_err(attr.span(), format!("`{name}` is defined multiple times")) @@ -74,13 +79,13 @@ pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], n unique_attr } -/// Returns true if the attributes contain any of `proc_macro`, -/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise +/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or +/// `proc_macro_attribute` pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { attrs.iter().any(AttributeExt::is_proc_macro_attr) } -/// Returns true if the attributes contain `#[doc(hidden)]` +/// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { attrs .iter() @@ -89,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { .any(|l| attr::list_contains_name(&l, sym::hidden)) } +/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) @@ -101,7 +107,7 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..))) } -/// Checks if the given span contains a `#[cfg(..)]` attribute +/// Checks whether the given span contains a `#[cfg(..)]` attribute pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { s.check_source_text(cx, |src| { let mut iter = tokenize_with_text(src); @@ -123,6 +129,8 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { false }) } + +/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]` pub struct LimitStack { default: u64, stack: Vec, @@ -134,6 +142,7 @@ impl Drop for LimitStack { } } +#[expect(missing_docs, reason = "they're all trivial...")] impl LimitStack { #[must_use] /// Initialize the stack starting with a default value, which usually comes from configuration @@ -157,7 +166,7 @@ impl LimitStack { } fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { - for attr in get_attr(sess, attrs, name) { + for attr in get_builtin_attr(sess, attrs, name) { let Some(value) = attr.value_str() else { sess.dcx().span_err(attr.span(), "bad clippy attribute"); continue; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8250104b0ca28..58aa82675aaeb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -52,6 +52,7 @@ extern crate rustc_trait_selection; extern crate smallvec; pub mod ast_utils; +#[deny(missing_docs)] pub mod attrs; mod check_proc_macro; pub mod comparisons; diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 7cd5a16f5b46d..4e06f010bd591 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock}; use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{get_unique_attr, sym}; +use crate::{get_unique_builtin_attr, sym}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; @@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { } else { // Allow users to tag any macro as being format!-like // TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method - get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() + get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() } } From 1aa5b99fe372c8a0f6640864b4e7e7683d225cc6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 1 Oct 2025 19:42:33 +0200 Subject: [PATCH 06/80] clean-up a bit --- clippy_lints/src/types/rc_buffer.rs | 8 ++++---- tests/ui/rc_buffer.fixed | 1 - tests/ui/rc_buffer.rs | 1 - tests/ui/rc_buffer.stderr | 32 ++++++++++++++--------------- tests/ui/rc_buffer_arc.fixed | 1 - tests/ui/rc_buffer_arc.rs | 1 - tests/ui/rc_buffer_arc.stderr | 32 ++++++++++++++--------------- 7 files changed, 36 insertions(+), 40 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 46d9febb187fc..0f0a83f455ef9 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ cx, RC_BUFFER, hir_ty.span, - "usage of `Rc` when T is a buffer type", + "usage of `Rc` when `T` is a buffer type", |diag| { diag.span_suggestion(hir_ty.span, "try", format!("Rc<{alternate}>"), app); }, @@ -41,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ cx, RC_BUFFER, hir_ty.span, - "usage of `Rc` when T is a buffer type", + "usage of `Rc` when `T` is a buffer type", |diag| { let mut applicability = app; diag.span_suggestion( @@ -64,7 +64,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ cx, RC_BUFFER, hir_ty.span, - "usage of `Arc` when T is a buffer type", + "usage of `Arc` when `T` is a buffer type", |diag| { diag.span_suggestion(hir_ty.span, "try", format!("Arc<{alternate}>"), app); }, @@ -82,7 +82,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ cx, RC_BUFFER, hir_ty.span, - "usage of `Arc` when T is a buffer type", + "usage of `Arc` when `T` is a buffer type", |diag| { let mut applicability = app; diag.span_suggestion( diff --git a/tests/ui/rc_buffer.fixed b/tests/ui/rc_buffer.fixed index c71a4072b9623..93811a0ad2fc4 100644 --- a/tests/ui/rc_buffer.fixed +++ b/tests/ui/rc_buffer.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; diff --git a/tests/ui/rc_buffer.rs b/tests/ui/rc_buffer.rs index 686c2644da170..a644a29a03c98 100644 --- a/tests/ui/rc_buffer.rs +++ b/tests/ui/rc_buffer.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; diff --git a/tests/ui/rc_buffer.stderr b/tests/ui/rc_buffer.stderr index 7500523ab4acc..e7000bf506b69 100644 --- a/tests/ui/rc_buffer.stderr +++ b/tests/ui/rc_buffer.stderr @@ -1,5 +1,5 @@ -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:11:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:10:11 | LL | bad1: Rc, | ^^^^^^^^^^ help: try: `Rc` @@ -7,44 +7,44 @@ LL | bad1: Rc, = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:13:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:12:11 | LL | bad2: Rc, | ^^^^^^^^^^^ help: try: `Rc` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:15:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:14:11 | LL | bad3: Rc>, | ^^^^^^^^^^^ help: try: `Rc<[u8]>` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:17:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:16:11 | LL | bad4: Rc, | ^^^^^^^^^^^^ help: try: `Rc` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:24:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:23:17 | LL | fn func_bad1(_: Rc) {} | ^^^^^^^^^^ help: try: `Rc` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:26:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:25:17 | LL | fn func_bad2(_: Rc) {} | ^^^^^^^^^^^ help: try: `Rc` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:28:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:27:17 | LL | fn func_bad3(_: Rc>) {} | ^^^^^^^^^^^ help: try: `Rc<[u8]>` -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:30:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:29:17 | LL | fn func_bad4(_: Rc) {} | ^^^^^^^^^^^^ help: try: `Rc` diff --git a/tests/ui/rc_buffer_arc.fixed b/tests/ui/rc_buffer_arc.fixed index 27059e3f2e1fd..8e0adec48da7e 100644 --- a/tests/ui/rc_buffer_arc.fixed +++ b/tests/ui/rc_buffer_arc.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; diff --git a/tests/ui/rc_buffer_arc.rs b/tests/ui/rc_buffer_arc.rs index 5261eae2f26ac..1ec8abbdae99c 100644 --- a/tests/ui/rc_buffer_arc.rs +++ b/tests/ui/rc_buffer_arc.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; diff --git a/tests/ui/rc_buffer_arc.stderr b/tests/ui/rc_buffer_arc.stderr index 786715463232b..6997a5a28349b 100644 --- a/tests/ui/rc_buffer_arc.stderr +++ b/tests/ui/rc_buffer_arc.stderr @@ -1,5 +1,5 @@ -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:10:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:9:11 | LL | bad1: Arc, | ^^^^^^^^^^^ help: try: `Arc` @@ -7,44 +7,44 @@ LL | bad1: Arc, = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:12:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:11:11 | LL | bad2: Arc, | ^^^^^^^^^^^^ help: try: `Arc` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:14:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:13:11 | LL | bad3: Arc>, | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:16:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:15:11 | LL | bad4: Arc, | ^^^^^^^^^^^^^ help: try: `Arc` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:23:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:22:17 | LL | fn func_bad1(_: Arc) {} | ^^^^^^^^^^^ help: try: `Arc` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:25:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:24:17 | LL | fn func_bad2(_: Arc) {} | ^^^^^^^^^^^^ help: try: `Arc` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:27:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:26:17 | LL | fn func_bad3(_: Arc>) {} | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:29:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:28:17 | LL | fn func_bad4(_: Arc) {} | ^^^^^^^^^^^^^ help: try: `Arc` From 12426b6e708eb9e69668fd4fa71b6d0b3915b8a2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 1 Oct 2025 19:03:18 +0200 Subject: [PATCH 07/80] fix: return `true` everywhere we emit the lint --- clippy_lints/src/types/rc_buffer.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 0f0a83f455ef9..d0fd5c63ed044 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -25,6 +25,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ diag.span_suggestion(hir_ty.span, "try", format!("Rc<{alternate}>"), app); }, ); + true } else { let Some(ty) = qpath_generic_tys(qpath).next() else { return false; @@ -55,7 +56,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); }, ); - return true; + true } } else if name == Some(sym::Arc) { if let Some(alternate) = match_buffer_type(cx, qpath) { @@ -69,6 +70,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ diag.span_suggestion(hir_ty.span, "try", format!("Arc<{alternate}>"), app); }, ); + true } else if let Some(ty) = qpath_generic_tys(qpath).next() { if !ty.basic_res().is_diag_item(cx, sym::Vec) { return false; @@ -96,11 +98,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); }, ); - return true; + true + } else { + false } + } else { + false } - - false } fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { From 6aecd1f6bd5e7ba9265cc18126c0507f7f46f8bd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 1 Oct 2025 19:34:51 +0200 Subject: [PATCH 08/80] refactor: also handle `Vec` in `match_buffer_type` Reduces code repetition --- clippy_lints/src/types/rc_buffer.rs | 88 ++++++++--------------------- 1 file changed, 22 insertions(+), 66 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index d0fd5c63ed044..91b180e203a8b 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -1,20 +1,21 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::qpath_generic_tys; -use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; +use std::borrow::Cow; use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { - let app = Applicability::Unspecified; + let mut app = Applicability::Unspecified; let name = cx.tcx.get_diagnostic_name(def_id); if name == Some(sym::Rc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { + if let Some(alternate) = match_buffer_type(cx, qpath, &mut app) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( cx, @@ -27,39 +28,10 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); true } else { - let Some(ty) = qpath_generic_tys(qpath).next() else { - return false; - }; - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when `T` is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Rc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - true + false } } else if name == Some(sym::Arc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { + if let Some(alternate) = match_buffer_type(cx, qpath, &mut app) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( cx, @@ -71,34 +43,6 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ }, ); true - } else if let Some(ty) = qpath_generic_tys(qpath).next() { - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when `T` is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Arc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - true } else { false } @@ -107,13 +51,25 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ } } -fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { +fn match_buffer_type( + cx: &LateContext<'_>, + qpath: &QPath<'_>, + applicability: &mut Applicability, +) -> Option> { let ty = qpath_generic_tys(qpath).next()?; let id = ty.basic_res().opt_def_id()?; let path = match cx.tcx.get_diagnostic_name(id) { - Some(sym::OsString) => "std::ffi::OsStr", - Some(sym::PathBuf) => "std::path::Path", - _ if Some(id) == cx.tcx.lang_items().string() => "str", + Some(sym::OsString) => "std::ffi::OsStr".into(), + Some(sym::PathBuf) => "std::path::Path".into(), + Some(sym::Vec) => { + let TyKind::Path(vec_qpath) = &ty.kind else { + return None; + }; + let vec_generic_ty = qpath_generic_tys(vec_qpath).next()?; + let snippet = snippet_with_applicability(cx, vec_generic_ty.span, "_", applicability); + format!("[{snippet}]").into() + }, + _ if Some(id) == cx.tcx.lang_items().string() => "str".into(), _ => return None, }; Some(path) From 3714236a69bdb5f6021c658ef51d91a342e08b88 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 1 Oct 2025 19:39:38 +0200 Subject: [PATCH 09/80] fix: make the suggestion apply only to the inner type Now we don't touch, and thus don't break, whatever path `Rc`/`Arc` was specified with --- clippy_lints/src/types/rc_buffer.rs | 21 ++++---- tests/ui/rc_buffer.fixed | 5 ++ tests/ui/rc_buffer.rs | 5 ++ tests/ui/rc_buffer.stderr | 77 +++++++++++++++++++++++++---- tests/ui/rc_buffer_arc.fixed | 5 ++ tests/ui/rc_buffer_arc.rs | 5 ++ tests/ui/rc_buffer_arc.stderr | 77 +++++++++++++++++++++++++---- 7 files changed, 167 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 91b180e203a8b..3d781d68237b6 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -4,26 +4,27 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, QPath, TyKind}; +use rustc_hir::{QPath, Ty, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use std::borrow::Cow; use super::RC_BUFFER; -pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let mut app = Applicability::Unspecified; let name = cx.tcx.get_diagnostic_name(def_id); if name == Some(sym::Rc) { - if let Some(alternate) = match_buffer_type(cx, qpath, &mut app) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + if let Some(ty) = qpath_generic_tys(qpath).next() + && let Some(alternate) = match_buffer_type(cx, ty, &mut app) + { span_lint_and_then( cx, RC_BUFFER, hir_ty.span, "usage of `Rc` when `T` is a buffer type", |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Rc<{alternate}>"), app); + diag.span_suggestion_verbose(ty.span, "try", alternate, app); }, ); true @@ -31,15 +32,16 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ false } } else if name == Some(sym::Arc) { - if let Some(alternate) = match_buffer_type(cx, qpath, &mut app) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + if let Some(ty) = qpath_generic_tys(qpath).next() + && let Some(alternate) = match_buffer_type(cx, ty, &mut app) + { span_lint_and_then( cx, RC_BUFFER, hir_ty.span, "usage of `Arc` when `T` is a buffer type", |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Arc<{alternate}>"), app); + diag.span_suggestion_verbose(ty.span, "try", alternate, app); }, ); true @@ -53,10 +55,9 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ fn match_buffer_type( cx: &LateContext<'_>, - qpath: &QPath<'_>, + ty: &Ty<'_>, applicability: &mut Applicability, ) -> Option> { - let ty = qpath_generic_tys(qpath).next()?; let id = ty.basic_res().opt_def_id()?; let path = match cx.tcx.get_diagnostic_name(id) { Some(sym::OsString) => "std::ffi::OsStr".into(), diff --git a/tests/ui/rc_buffer.fixed b/tests/ui/rc_buffer.fixed index 93811a0ad2fc4..a41f98c8fa35b 100644 --- a/tests/ui/rc_buffer.fixed +++ b/tests/ui/rc_buffer.fixed @@ -31,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer.rs b/tests/ui/rc_buffer.rs index a644a29a03c98..879f606474727 100644 --- a/tests/ui/rc_buffer.rs +++ b/tests/ui/rc_buffer.rs @@ -31,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer.stderr b/tests/ui/rc_buffer.stderr index e7000bf506b69..e31e9c9c8fdf5 100644 --- a/tests/ui/rc_buffer.stderr +++ b/tests/ui/rc_buffer.stderr @@ -2,52 +2,111 @@ error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:10:11 | LL | bad1: Rc, - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Rc, +LL + bad1: Rc, + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:12:11 | LL | bad2: Rc, - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad2: Rc, +LL + bad2: Rc, + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:14:11 | LL | bad3: Rc>, - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad3: Rc>, +LL + bad3: Rc<[u8]>, + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:16:11 | LL | bad4: Rc, - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Rc, +LL + bad4: Rc, + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:23:17 | LL | fn func_bad1(_: Rc) {} - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Rc) {} +LL + fn func_bad1(_: Rc) {} + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:25:17 | LL | fn func_bad2(_: Rc) {} - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Rc) {} +LL + fn func_bad2(_: Rc) {} + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:27:17 | LL | fn func_bad3(_: Rc>) {} - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Rc>) {} +LL + fn func_bad3(_: Rc<[u8]>) {} + | error: usage of `Rc` when `T` is a buffer type --> tests/ui/rc_buffer.rs:29:17 | LL | fn func_bad4(_: Rc) {} - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Rc) {} +LL + fn func_bad4(_: Rc) {} + | + +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:35:15 + | +LL | fn foo(_: std::rc::Rc>) {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::rc::Rc>) {} +LL + fn foo(_: std::rc::Rc<[u8]>) {} + | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/rc_buffer_arc.fixed b/tests/ui/rc_buffer_arc.fixed index 8e0adec48da7e..36b989ec1b605 100644 --- a/tests/ui/rc_buffer_arc.fixed +++ b/tests/ui/rc_buffer_arc.fixed @@ -30,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer_arc.rs b/tests/ui/rc_buffer_arc.rs index 1ec8abbdae99c..f8e78dc5c18f3 100644 --- a/tests/ui/rc_buffer_arc.rs +++ b/tests/ui/rc_buffer_arc.rs @@ -30,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer_arc.stderr b/tests/ui/rc_buffer_arc.stderr index 6997a5a28349b..043f7a15ec00c 100644 --- a/tests/ui/rc_buffer_arc.stderr +++ b/tests/ui/rc_buffer_arc.stderr @@ -2,52 +2,111 @@ error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:9:11 | LL | bad1: Arc, - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Arc, +LL + bad1: Arc, + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:11:11 | LL | bad2: Arc, - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad2: Arc, +LL + bad2: Arc, + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:13:11 | LL | bad3: Arc>, - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad3: Arc>, +LL + bad3: Arc<[u8]>, + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:15:11 | LL | bad4: Arc, - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Arc, +LL + bad4: Arc, + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:22:17 | LL | fn func_bad1(_: Arc) {} - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Arc) {} +LL + fn func_bad1(_: Arc) {} + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:24:17 | LL | fn func_bad2(_: Arc) {} - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Arc) {} +LL + fn func_bad2(_: Arc) {} + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:26:17 | LL | fn func_bad3(_: Arc>) {} - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Arc>) {} +LL + fn func_bad3(_: Arc<[u8]>) {} + | error: usage of `Arc` when `T` is a buffer type --> tests/ui/rc_buffer_arc.rs:28:17 | LL | fn func_bad4(_: Arc) {} - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Arc) {} +LL + fn func_bad4(_: Arc) {} + | + +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:34:15 + | +LL | fn foo(_: std::sync::Arc>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::sync::Arc>) {} +LL + fn foo(_: std::sync::Arc<[u8]>) {} + | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors From f2e66672a114fea90e223b9f600bb98da41980a6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 1 Oct 2025 20:27:59 +0200 Subject: [PATCH 10/80] refactor: reduce code duplication further --- clippy_lints/src/types/rc_buffer.rs | 68 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 3d781d68237b6..34fd3ed2dc86b 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -8,51 +8,49 @@ use rustc_hir::{QPath, Ty, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use std::borrow::Cow; +use std::fmt; use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let mut app = Applicability::Unspecified; - let name = cx.tcx.get_diagnostic_name(def_id); - if name == Some(sym::Rc) { - if let Some(ty) = qpath_generic_tys(qpath).next() - && let Some(alternate) = match_buffer_type(cx, ty, &mut app) - { - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when `T` is a buffer type", - |diag| { - diag.span_suggestion_verbose(ty.span, "try", alternate, app); - }, - ); - true - } else { - false - } - } else if name == Some(sym::Arc) { - if let Some(ty) = qpath_generic_tys(qpath).next() - && let Some(alternate) = match_buffer_type(cx, ty, &mut app) - { - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when `T` is a buffer type", - |diag| { - diag.span_suggestion_verbose(ty.span, "try", alternate, app); - }, - ); - true - } else { - false - } + let kind = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::Rc) => RcKind::Rc, + Some(sym::Arc) => RcKind::Arc, + _ => return false, + }; + if let Some(ty) = qpath_generic_tys(qpath).next() + && let Some(alternate) = match_buffer_type(cx, ty, &mut app) + { + span_lint_and_then( + cx, + RC_BUFFER, + hir_ty.span, + format!("usage of `{kind}` when `T` is a buffer type"), + |diag| { + diag.span_suggestion_verbose(ty.span, "try", alternate, app); + }, + ); + true } else { false } } +enum RcKind { + Rc, + Arc, +} + +impl fmt::Display for RcKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Rc => f.write_str("Rc"), + Self::Arc => f.write_str("Arc"), + } + } +} + fn match_buffer_type( cx: &LateContext<'_>, ty: &Ty<'_>, From a4924e2727d4f358396ea71f66da16e686a7038c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 12 Oct 2025 00:36:54 +0200 Subject: [PATCH 11/80] clean-up --- clippy_lints/src/methods/err_expect.rs | 13 +++++-------- clippy_lints/src/methods/ok_expect.rs | 10 ++++------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs index 6e9aebcf18ae4..4353f6302c4b9 100644 --- a/clippy_lints/src/methods/err_expect.rs +++ b/clippy_lints/src/methods/err_expect.rs @@ -1,7 +1,6 @@ use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; use clippy_utils::ty::has_debug_impl; use rustc_errors::Applicability; use rustc_lint::LateContext; @@ -17,12 +16,10 @@ pub(super) fn check( err_span: Span, msrv: Msrv, ) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // Grabs the `Result` type - && let result_type = cx.typeck_results().expr_ty(recv) - // Tests if the T type in a `Result` is not None - && let Some(data_type) = get_data_type(cx, result_type) - // Tests if the T type in a `Result` implements debug + let result_ty = cx.typeck_results().expr_ty(recv); + // Grabs the `Result` type + if let Some(data_type) = get_data_type(cx, result_ty) + // Tests if the T type in a `Result` implements Debug && has_debug_impl(cx, data_type) && msrv.meets(cx, msrvs::EXPECT_ERR) { @@ -41,7 +38,7 @@ pub(super) fn check( /// Given a `Result` type, return its data (`T`). fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().next(), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().next(), _ => None, } } diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs index c9c1f4865b813..400f225805820 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints/src/methods/ok_expect.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::res::MaybeDef; use clippy_utils::ty::has_debug_impl; use rustc_hir as hir; use rustc_lint::LateContext; @@ -10,10 +9,9 @@ use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // lint if the caller of `ok()` is a `Result` - && let result_type = cx.typeck_results().expr_ty(recv) - && let Some(error_type) = get_error_type(cx, result_type) + let result_ty = cx.typeck_results().expr_ty(recv); + // lint if the caller of `ok()` is a `Result` + if let Some(error_type) = get_error_type(cx, result_ty) && has_debug_impl(cx, error_type) { span_lint_and_help( @@ -30,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr /// Given a `Result` type, return its error type (`E`). fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().nth(1), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().nth(1), _ => None, } } From 98164377d7608795c3830dcf60477b60435abf32 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 12 Oct 2025 01:07:05 +0200 Subject: [PATCH 12/80] feat(ok_expect): add autofix --- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/methods/ok_expect.rs | 23 ++++--- tests/ui/ok_expect.fixed | 51 ++++++++++++++++ tests/ui/ok_expect.rs | 18 ++++++ tests/ui/ok_expect.stderr | 86 ++++++++++++++++++++++++--- 5 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 tests/ui/ok_expect.fixed diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c9066be51c44b..8e0733eff87ba 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5115,7 +5115,7 @@ impl Methods { }, (sym::expect, [_]) => { match method_call(recv) { - Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv), + Some((sym::ok, recv_inner, [], _, _)) => ok_expect::check(cx, expr, recv, recv_inner), Some((sym::err, recv, [], err_span, _)) => { err_expect::check(cx, expr, recv, span, err_span, self.msrv); }, diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs index 400f225805820..5f1cae130daed 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints/src/methods/ok_expect.rs @@ -1,26 +1,35 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::has_debug_impl; +use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - let result_ty = cx.typeck_results().expr_ty(recv); +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, recv_inner: &hir::Expr<'_>) { + let result_ty = cx.typeck_results().expr_ty(recv_inner); // lint if the caller of `ok()` is a `Result` if let Some(error_type) = get_error_type(cx, result_ty) && has_debug_impl(cx, error_type) + && let Some(span) = recv.span.trim_start(recv_inner.span) { - span_lint_and_help( + span_lint_and_then( cx, OK_EXPECT, expr.span, "called `ok().expect()` on a `Result` value", - None, - "you can call `expect()` directly on the `Result`", + |diag| { + let span = cx.sess().source_map().span_extend_while_whitespace(span); + diag.span_suggestion_verbose( + span, + "call `expect()` directly on the `Result`", + String::new(), + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/tests/ui/ok_expect.fixed b/tests/ui/ok_expect.fixed new file mode 100644 index 0000000000000..2a05b8805e4d5 --- /dev/null +++ b/tests/ui/ok_expect.fixed @@ -0,0 +1,51 @@ +#![allow(clippy::unnecessary_literal_unwrap)] + +use std::io; + +struct MyError(()); // doesn't implement Debug + +#[derive(Debug)] +struct MyErrorWithParam { + x: T, +} + +fn main() { + let res: Result = Ok(0); + let _ = res.unwrap(); + + res.expect("disaster!"); + //~^ ok_expect + + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output().expect("failed to get rustc version"); + //~^^^^^ ok_expect + + // the following should not warn, since `expect` isn't implemented unless + // the error type implements `Debug` + let res2: Result = Ok(0); + res2.ok().expect("oh noes!"); + let res3: Result> = Ok(0); + res3.expect("whoof"); + //~^ ok_expect + + let res4: Result = Ok(0); + res4.expect("argh"); + //~^ ok_expect + + let res5: io::Result = Ok(0); + res5.expect("oops"); + //~^ ok_expect + + let res6: Result = Ok(0); + res6.expect("meh"); + //~^ ok_expect +} diff --git a/tests/ui/ok_expect.rs b/tests/ui/ok_expect.rs index efb56f242a746..3761aa26f6e81 100644 --- a/tests/ui/ok_expect.rs +++ b/tests/ui/ok_expect.rs @@ -16,6 +16,24 @@ fn main() { res.ok().expect("disaster!"); //~^ ok_expect + res.ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres + .ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output() + .ok() + .expect("failed to get rustc version"); + //~^^^^^ ok_expect + // the following should not warn, since `expect` isn't implemented unless // the error type implements `Debug` let res2: Result = Ok(0); diff --git a/tests/ui/ok_expect.stderr b/tests/ui/ok_expect.stderr index a9e3533d8ca1a..848a10e671dbf 100644 --- a/tests/ui/ok_expect.stderr +++ b/tests/ui/ok_expect.stderr @@ -4,41 +4,109 @@ error: called `ok().expect()` on a `Result` value LL | res.ok().expect("disaster!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` = note: `-D clippy::ok-expect` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ok_expect)]` +help: call `expect()` directly on the `Result` + | +LL - res.ok().expect("disaster!"); +LL + res.expect("disaster!"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:19:5 + | +LL | / res.ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - res.ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | error: called `ok().expect()` on a `Result` value --> tests/ui/ok_expect.rs:24:5 | +LL | / resres +LL | | .ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - resres +LL - .ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:30:5 + | +LL | / std::process::Command::new("rustc") +LL | | .arg("-vV") +LL | | .output() +LL | | .ok() +LL | | .expect("failed to get rustc version"); + | |______________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - .output() +LL - .ok() +LL - .expect("failed to get rustc version"); +LL + .output().expect("failed to get rustc version"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:42:5 + | LL | res3.ok().expect("whoof"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res3.ok().expect("whoof"); +LL + res3.expect("whoof"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:28:5 + --> tests/ui/ok_expect.rs:46:5 | LL | res4.ok().expect("argh"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res4.ok().expect("argh"); +LL + res4.expect("argh"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:32:5 + --> tests/ui/ok_expect.rs:50:5 | LL | res5.ok().expect("oops"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res5.ok().expect("oops"); +LL + res5.expect("oops"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:36:5 + --> tests/ui/ok_expect.rs:54:5 | LL | res6.ok().expect("meh"); | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res6.ok().expect("meh"); +LL + res6.expect("meh"); + | -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors From dd943fd5afc84cd653f88b6b6d39059152900df8 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 11 Oct 2025 03:06:58 +0800 Subject: [PATCH 13/80] fix: `match_single_binding` misses curlies for const --- .../src/matches/match_single_binding.rs | 9 +++++++-- tests/ui/match_single_binding.stderr | 18 +++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 449f5dabe23f2..e40e21c490f3c 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -8,7 +8,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_path, walk_stmt}; -use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Node, PatKind, Path, Stmt, StmtKind}; +use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, PatKind, Path, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; @@ -391,7 +391,12 @@ fn sugg_with_curlies<'a>( add_curlies(); } }, - Node::Expr(..) | Node::AnonConst(..) => add_curlies(), + Node::Expr(..) + | Node::AnonConst(..) + | Node::Item(Item { + kind: ItemKind::Const(..), + .. + }) => add_curlies(), Node::Arm(arm) if let ExprKind::Match(..) = arm.body.kind => add_curlies(), _ => {}, } diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index c2d9d168b4a15..8a402dcca8470 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -526,7 +526,7 @@ LL | | } | |_________^ help: consider using the match body instead: `b < c` error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:358:9 + --> tests/ui/match_single_binding.rs:357:9 | LL | / match { a } { LL | | @@ -543,7 +543,7 @@ LL ~ }, | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:367:9 + --> tests/ui/match_single_binding.rs:366:9 | LL | / match { a } { LL | | @@ -560,7 +560,7 @@ LL ~ }, | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:377:9 + --> tests/ui/match_single_binding.rs:376:9 | LL | / match { a } { LL | | @@ -577,7 +577,7 @@ LL ~ }, | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:391:9 + --> tests/ui/match_single_binding.rs:390:9 | LL | / match { a } { LL | | @@ -594,7 +594,7 @@ LL ~ }, | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:398:6 + --> tests/ui/match_single_binding.rs:397:6 | LL | -match { a } { | ______^ @@ -612,7 +612,7 @@ LL ~ }; | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:403:9 + --> tests/ui/match_single_binding.rs:402:9 | LL | _ = match { a } { | _________^ @@ -628,7 +628,7 @@ LL ~ 1; | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:408:16 + --> tests/ui/match_single_binding.rs:407:16 | LL | if let x = match { a } { | ________________^ @@ -646,7 +646,7 @@ LL ~ } {} | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:413:8 + --> tests/ui/match_single_binding.rs:412:8 | LL | if match { a } { | ________^ @@ -664,7 +664,7 @@ LL ~ } { | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:420:15 + --> tests/ui/match_single_binding.rs:419:15 | LL | [1, 2, 3][match { a } { | _______________^ From 2fe16a574fbc386997b60b5067dad5fc0f8c539b Mon Sep 17 00:00:00 2001 From: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:06:20 -0700 Subject: [PATCH 14/80] Fix spacing typo in new inefficient_to_string info --- clippy_lints/src/methods/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c9066be51c44b..68e07dec9b720 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1083,7 +1083,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// In versions of the compiler before Rust 1.82.0, this bypasses the specialized - /// implementation of`ToString` and instead goes through the more expensive string + /// implementation of `ToString` and instead goes through the more expensive string /// formatting facilities. /// /// ### Example From b42f87c76e3bfbdfaaa464b370168094c998e803 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Mon, 21 Jul 2025 22:37:14 +0800 Subject: [PATCH 15/80] fix: `missing_inline_in_public_items` fail to fulfill `expect` in `--test` build --- clippy_lints/src/missing_inline.rs | 10 +++++++++- tests/ui/missing_inline_test_crate.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/ui/missing_inline_test_crate.rs diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 6323e728666ce..5e584e85a3c90 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_hir}; +use clippy_utils::fulfill_or_allowed; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; @@ -94,7 +95,14 @@ declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); impl<'tcx> LateLintPass<'tcx> for MissingInline { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { - if it.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if it.span.in_external_macro(cx.sess().source_map()) { + return; + } + + if is_executable_or_proc_macro(cx) + // Allow the lint if it is expected, when building with `--test` + && !(cx.sess().is_test_crate() && fulfill_or_allowed(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, [it.hir_id()])) + { return; } diff --git a/tests/ui/missing_inline_test_crate.rs b/tests/ui/missing_inline_test_crate.rs new file mode 100644 index 0000000000000..d7fb5ae2283f3 --- /dev/null +++ b/tests/ui/missing_inline_test_crate.rs @@ -0,0 +1,8 @@ +//@compile-flags: --test +//@check-pass +#![warn(clippy::missing_inline_in_public_items)] + +#[expect(clippy::missing_inline_in_public_items)] +pub fn foo() -> u32 { + 0 +} From ff9a1757f13d38ea337832b0e800ab5e3b9c2904 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sat, 3 May 2025 22:58:27 -0600 Subject: [PATCH 16/80] refactor ub_checks and contract_checks to share logic --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 90ea2616890a4..0cf1ad348953b 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -194,7 +194,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, _) + Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, cx.tcx); From 892cb18e97772f98690722aa441d9ece4382f254 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 21 Oct 2025 17:02:16 +0200 Subject: [PATCH 17/80] clean-up --- clippy_lints/src/matches/manual_filter.rs | 4 +-- clippy_lints/src/matches/manual_utils.rs | 2 +- .../src/methods/unnecessary_literal_unwrap.rs | 28 ++++++++++--------- clippy_lints/src/non_canonical_impls.rs | 5 +--- clippy_lints/src/option_if_let_else.rs | 13 +++++---- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index d7224052ebc58..afa6f22ea75bb 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -52,13 +52,13 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr) } -// function called for each expression: +/// Checks whether resolves to `Some(target)` +// NOTE: called for each expression: // Some(x) => if { // // } else { // // } -// Returns true if resolves to `Some(x)`, `false` otherwise fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 235cb9e4ecce8..1026bc49f1f64 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -281,7 +281,7 @@ pub(super) fn try_parse_pattern<'tcx>( f(cx, pat, 0, ctxt) } -// Checks for the `None` value. +/// Checks for the `None` value, possibly in a block. fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) } diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 410e973f855b6..54d2062bfc12d 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -23,6 +23,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -38,19 +39,20 @@ pub(super) fn check( } let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind { - let Some((qpath, hir_id)) = call.opt_qpath() else { - return; - }; - - let args = last_path_segment(qpath).args.map(|args| args.args); - let res = cx.qpath_res(qpath, hir_id); - - if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionSome) { - (sym::Some, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultOk) { - (sym::Ok, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultErr) { - (sym::Err, call_args, get_ty_from_args(args, 1)) + if let Some((qpath, hir_id)) = call.opt_qpath() + && let args = last_path_segment(qpath).args.map(|args| args.args) + && let Some(did) = cx.qpath_res(qpath, hir_id).ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { + (sym::Some, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_ok_variant() { + (sym::Ok, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_err_variant() { + (sym::Err, call_args, get_ty_from_args(args, 1)) + } else { + return; + } } else { return; } diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 3285023b34aa8..0f821027fe751 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -354,10 +354,7 @@ fn self_cmp_call<'tcx>( needs_fully_qualified: &mut bool, ) -> bool { match cmp_expr.kind { - ExprKind::Call(path, [_, _]) => path - .res(typeck) - .opt_def_id() - .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), + ExprKind::Call(path, [_, _]) => path.res(typeck).is_diag_item(cx, sym::ord_cmp_method), ExprKind::MethodCall(_, recv, [_], ..) => { let ExprKind::Path(path) = recv.kind else { return false; diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index c32c74a8fe608..da6da32889cb3 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -10,7 +10,7 @@ use clippy_utils::{ }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::LangItem::{OptionNone, ResultErr}; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; use rustc_hir::{ @@ -313,11 +313,14 @@ impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { } fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> { - if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { - let res = cx.qpath_res(qpath, pat.hir_id); - if res.ctor_parent(cx).is_lang_item(cx, OptionSome) { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind + && let res = cx.qpath_res(qpath, pat.hir_id) + && let Some(did) = res.ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { return Some((inner_pat, false)); - } else if res.ctor_parent(cx).is_lang_item(cx, ResultOk) { + } else if Some(did) == lang_items.result_ok_variant() { return Some((inner_pat, true)); } } From 59890a973646c609ee20e29e00d66601c6434ed0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 21 Oct 2025 17:25:47 +0200 Subject: [PATCH 18/80] introduce `as_some_pattern` --- .../src/loops/while_let_on_iterator.rs | 7 ++--- clippy_lints/src/manual_option_as_slice.rs | 9 ++---- clippy_lints/src/match_result_ok.rs | 7 ++--- clippy_lints/src/matches/manual_utils.rs | 28 +++++-------------- clippy_lints/src/matches/match_as_ref.rs | 8 ++---- clippy_lints/src/option_if_let_else.rs | 16 +++-------- clippy_utils/src/lib.rs | 25 +++++++++++++---- 7 files changed, 42 insertions(+), 58 deletions(-) diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 3ea6ba341bedb..2545f81f1afad 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -5,11 +5,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::visitors::is_res_used; -use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; +use clippy_utils::{as_some_pattern, get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Closure, Expr, ExprKind, HirId, LangItem, LetStmt, Mutability, PatKind, UnOp}; +use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::ty::adjustment::Adjust; @@ -19,8 +19,7 @@ use rustc_span::symbol::sym; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { if_then, let_pat, let_expr, label, .. }) = higher::WhileLet::hir(expr) // check for `Some(..)` pattern - && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(some_pat) = as_some_pattern(cx, let_pat) // check for call to `Iterator::next` && let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind && method_name.ident.name == sym::next diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index 3342bf8347859..9cf2d5d854337 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym}; +use clippy_utils::{as_some_pattern, is_none_pattern, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind, QPath, is_range_literal}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -154,10 +153,8 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { } fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { - if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind - && let Res::Def(DefKind::Ctor(..), def_id) = path.res + if let Some([binding]) = as_some_pattern(cx, pat) && let PatKind::Binding(_mode, _hir_id, ident, _inner_pat) = binding.kind - && clippy_utils::is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) { Some(ident.name) } else { diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index fb83f7cf65ddb..1ebbd209ae52d 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::{higher, sym}; +use clippy_utils::{as_some_pattern, higher, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -55,10 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { }; if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result.ok(, _) - && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation && ok_path.ident.name == sym::ok && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some([ok_pat]) = as_some_pattern(cx, let_pat) //get operation && let ctxt = expr.span.ctxt() && let_expr.span.ctxt() == ctxt && let_pat.span.ctxt() == ctxt diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 1026bc49f1f64..485d6bdc6ee82 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -5,14 +5,14 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_copy, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, peel_blocks, - peel_hir_expr_refs, peel_hir_expr_while, + CaptureKind, as_some_pattern, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, + is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionNone; use rustc_hir::def::Res; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -255,23 +255,9 @@ pub(super) fn try_parse_pattern<'tcx>( match pat.kind { PatKind::Wild => Some(OptionPat::Wild), PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) if cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - Some(OptionPat::None) - }, - PatKind::TupleStruct(ref qpath, [pattern], _) - if cx - .qpath_res(qpath, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) - && pat.span.ctxt() == ctxt => + _ if is_none_pattern(cx, pat) => Some(OptionPat::None), + _ if let Some([pattern]) = as_some_pattern(cx, pat) + && pat.span.ctxt() == ctxt => { Some(OptionPat::Some { pattern, ref_count }) }, diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 7ac57fae690cb..f633c5839739e 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::option_arg_ty; -use clippy_utils::{is_none_arm, peel_blocks}; +use clippy_utils::{as_some_pattern, is_none_arm, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; use rustc_lint::LateContext; @@ -82,11 +82,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { - if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind - && cx - .qpath_res(qpath, arm.pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, LangItem::OptionSome) + if let Some([first_pat, ..]) = as_some_pattern(cx, arm.pat) && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., ident, _) = first_pat.kind && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind && e.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index da6da32889cb3..85cf483fce90d 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -6,16 +6,15 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, eager_or_lazy, expr_requires_coercion, higher, is_else_clause, - is_in_const_context, peel_blocks, peel_hir_expr_while, + is_in_const_context, is_none_pattern, peel_blocks, peel_hir_expr_while, }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, ResultErr}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; use rustc_hir::{ - Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path, - QPath, UnOp, + Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -379,14 +378,7 @@ fn try_convert_match<'tcx>( fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { match arm.pat.kind { - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) => cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone), + _ if is_none_pattern(cx, arm.pat) => true, PatKind::TupleStruct(ref qpath, [first_pat], _) => { cx.qpath_res(qpath, arm.pat.hir_id) .ctor_parent(cx) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index c8943523e1798..e1ba40934620e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -320,6 +320,25 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } +/// If `pat` is: +/// - `Some(inner)`, returns `inner` +/// - it will _usually_ contain just one element, but could have two, given patterns like +/// `Some(inner, ..)` or `Some(.., inner)` +/// - `Some`, returns `[]` +/// - otherwise, returns `None` +pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> { + if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind + && cx + .qpath_res(qpath, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionSome) + { + Some(inner) + } else { + None + } +} + /// Checks if the `pat` is `None`. pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { matches!(pat.kind, @@ -2783,11 +2802,7 @@ pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( pat: &'a Pat<'hir>, else_body: &Expr<'_>, ) -> Option<&'a Pat<'hir>> { - if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind - && cx - .qpath_res(&pat_path, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) + if let Some([inner_pat]) = as_some_pattern(cx, pat) && !is_refutable(cx, inner_pat) && let else_body = peel_blocks(else_body) && let ExprKind::Ret(Some(ret_val)) = else_body.kind From 605cc879bceed280865054abb4ca268fe0f606a7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 21 Oct 2025 16:27:32 +0200 Subject: [PATCH 19/80] introduce `as_some_expr`, `is_none_expr` --- clippy_lints/src/if_then_some_else_none.rs | 10 ++++------ clippy_lints/src/loops/manual_find.rs | 5 ++--- clippy_lints/src/matches/manual_filter.rs | 9 ++++----- clippy_lints/src/matches/manual_ok_err.rs | 11 +++++------ clippy_lints/src/matches/manual_utils.rs | 17 ++++++++--------- clippy_lints/src/matches/match_as_ref.rs | 8 +++----- clippy_lints/src/mem_replace.rs | 12 ++++++------ .../iter_on_single_or_empty_collections.rs | 15 +++------------ clippy_lints/src/methods/option_map_or_none.rs | 5 +++-- .../src/methods/result_map_or_else_none.rs | 6 +++--- .../src/methods/unnecessary_filter_map.rs | 5 ++--- .../src/methods/unnecessary_literal_unwrap.rs | 4 ++-- clippy_utils/src/lib.rs | 18 +++++++++++++++++- 13 files changed, 62 insertions(+), 63 deletions(-) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index dfc8411baa005..7f3ef58c93d1e 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -2,14 +2,13 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, peel_blocks, sym, + as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, + is_none_expr, peel_blocks, sym, }; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -70,11 +69,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { }) = higher::If::hir(expr) && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr - && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let Some(then_arg) = as_some_expr(cx, then_expr) && !expr.span.from_expansion() && !then_expr.span.from_expansion() - && then_call.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && peel_blocks(els).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(els)) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index c38cf83f44100..34917252d0493 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -5,7 +5,7 @@ use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{higher, peel_blocks_with_stmt}; +use clippy_utils::{as_some_expr, higher, peel_blocks_with_stmt}; use rustc_errors::Applicability; use rustc_hir::lang_items::LangItem; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind}; @@ -33,8 +33,7 @@ pub(super) fn check<'tcx>( && let [stmt] = block.stmts && let StmtKind::Semi(semi) = stmt.kind && let ExprKind::Ret(Some(ret_value)) = semi.kind - && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind - && ctor.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(inner_ret) = as_some_expr(cx, ret_value) && inner_ret.res_local_id() == Some(binding_id) && !contains_return_break_continue_macro(cond) && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr) diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index afa6f22ea75bb..da68f8421c168 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -1,8 +1,9 @@ +use clippy_utils::as_some_expr; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::visitors::contains_unsafe_block; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -62,11 +63,9 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` - && let ExprKind::Call(callee, [arg]) = inner_expr.kind + && let Some(arg) = as_some_expr(cx, inner_expr) { - return ctxt == expr.span.ctxt() - && callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && arg.res_local_id() == Some(target); + return ctxt == expr.span.ctxt() && arg.res_local_id() == Some(target); } false } diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index c9293412fba82..3259655c9a1fc 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs}; -use clippy_utils::{get_parent_expr, peel_blocks, span_contains_comment}; +use clippy_utils::{as_some_expr, get_parent_expr, is_none_expr, peel_blocks, span_contains_comment}; use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::{LateContext, LintContext}; @@ -106,8 +106,7 @@ fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &' /// Check if `expr` contains `Some(ident)`, possibly as a block fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, ty: Ty<'tcx>) -> bool { - if let ExprKind::Call(body_callee, [body_arg]) = peel_blocks(expr).kind - && body_callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(body_arg) = as_some_expr(cx, peel_blocks(expr)) && cx.typeck_results().expr_ty(body_arg) == ty && let ExprKind::Path(QPath::Resolved( _, @@ -124,7 +123,7 @@ fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, t /// Check if `expr` is `None`, possibly as a block fn is_none(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + is_none_expr(cx, peel_blocks(expr)) } /// Suggest replacing `expr` by `scrutinee.METHOD()`, where `METHOD` is either `ok` or diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 485d6bdc6ee82..09e49c16624c4 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -1,16 +1,15 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN; use crate::matches::MATCH_AS_REF; -use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_copy, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ CaptureKind, as_some_pattern, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, - is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, + is_none_expr, is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::LangItem::OptionNone; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; @@ -44,16 +43,16 @@ where try_parse_pattern(cx, then_pat, expr_ctxt), else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)), ) { - (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, true) }, - (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, false) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, true) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, false) }, _ => return None, @@ -268,6 +267,6 @@ pub(super) fn try_parse_pattern<'tcx>( } /// Checks for the `None` value, possibly in a block. -fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +fn is_none_arm_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + is_none_expr(cx, peel_blocks(expr)) } diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index f633c5839739e..30d703df4df48 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::option_arg_ty; -use clippy_utils::{as_some_pattern, is_none_arm, peel_blocks}; +use clippy_utils::{as_some_expr, as_some_pattern, is_none_arm, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, Mutability, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -84,8 +83,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { if let Some([first_pat, ..]) = as_some_pattern(cx, arm.pat) && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., ident, _) = first_pat.kind - && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind - && e.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(arg) = as_some_expr(cx, peel_blocks(arm.body)) && let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind && path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index ac3cbaec55f30..0f32f89666a0f 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,13 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_expr_used_or_unified, peel_ref_operators, std_or_core}; +use clippy_utils::{ + as_some_expr, is_default_equivalent, is_expr_used_or_unified, is_none_expr, peel_ref_operators, std_or_core, +}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -128,7 +129,7 @@ impl_lint_pass!(MemReplace => [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_OPTION_WITH_SOME, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) -> bool { - if src.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if is_none_expr(cx, src) { // Since this is a late pass (already type-checked), // and we already know that the second argument is an // `Option`, we do not need to check the first @@ -161,8 +162,7 @@ fn check_replace_option_with_some( expr_span: Span, msrv: Msrv, ) -> bool { - if let ExprKind::Call(src_func, [src_arg]) = src.kind - && src_func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(src_arg) = as_some_expr(cx, src) && msrv.meets(cx, msrvs::OPTION_REPLACE) { // We do not have to check for a `const` context here, because `core::mem::replace()` and diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 8183c30f8c56b..cdef98be14af3 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,13 +1,11 @@ use std::iter::once; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig}; -use clippy_utils::{get_expr_use_or_unification_node, std_or_core, sym}; +use clippy_utils::{as_some_expr, get_expr_use_or_unification_node, is_none_expr, std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -68,15 +66,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), - ExprKind::Path(ref p) - if cx - .qpath_res(p, recv.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - None - }, - ExprKind::Call(f, [arg]) if f.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) => Some(arg), + _ if is_none_expr(cx, recv) => None, + _ if let Some(arg) = as_some_expr(cx, recv) => Some(arg), _ => return, }; let iter_type = match method_name { diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 342ffea51d656..817388915f189 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_none_expr; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -48,7 +49,7 @@ pub(super) fn check<'tcx>( return; } - if !def_arg.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if !is_none_expr(cx, def_arg) { // nothing to lint! return; } diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints/src/methods/result_map_or_else_none.rs index e2946c22a46b5..d5477b9be4c1e 100644 --- a/clippy_lints/src/methods/result_map_or_else_none.rs +++ b/clippy_lints/src/methods/result_map_or_else_none.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::peel_blocks; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; +use clippy_utils::{is_none_expr, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -25,7 +25,7 @@ pub(super) fn check<'tcx>( && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = def_arg.kind && let body = cx.tcx.hir_body(body) // And finally we check that we return a `None` in the "else case". - && peel_blocks(body.value).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(body.value)) { let msg = "called `map_or_else(|_| None, Some)` on a `Result` value"; let self_snippet = snippet(cx, recv.span, ".."); diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 7f729ac7ca94e..72f1c42da2ee3 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,10 +1,10 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; -use clippy_utils::sym; use clippy_utils::ty::{is_copy, option_arg_ty}; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{as_some_expr, sym}; use core::ops::ControlFlow; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -47,8 +47,7 @@ pub(super) fn check<'tcx>( let sugg = if !found_filtering { // Check if the closure is .filter_map(|x| Some(x)) if kind.is_filter_map() - && let hir::ExprKind::Call(expr, [arg]) = body.value.kind - && expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + && let Some(arg) = as_some_expr(cx, body.value) && let hir::ExprKind::Path(_) = arg.kind { span_lint( diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 54d2062bfc12d..da6f03931e24b 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath}; -use clippy_utils::{last_path_segment, sym}; +use clippy_utils::{is_none_expr, last_path_segment, sym}; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::LateContext; @@ -56,7 +56,7 @@ pub(super) fn check( } else { return; } - } else if init.res(cx).ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionNone) { + } else if is_none_expr(cx, init) { let call_args: &[hir::Expr<'_>] = &[]; (sym::None, call_args, None) } else { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e1ba40934620e..cfe2442461ca7 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -131,7 +131,7 @@ use crate::ast_utils::unordered_over; use crate::consts::{ConstEvalCtxt, Constant}; use crate::higher::Range; use crate::msrvs::Msrv; -use crate::res::{MaybeDef, MaybeResPath}; +use crate::res::{MaybeDef, MaybeQPath, MaybeResPath}; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; @@ -300,6 +300,22 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks is `expr` is `None` +pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +} + +/// If `expr` is `Some(inner)`, returns `inner` +pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call(e, [arg]) = expr.kind + && e.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + { + Some(arg) + } else { + None + } +} + /// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( From b7f921646ee12b0ffdde877fed8439ec647fdc88 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 27 Oct 2025 15:41:05 -0300 Subject: [PATCH 20/80] Consider type conversion that won't overflow --- .../src/operators/arithmetic_side_effects.rs | 240 +++++++++------ tests/ui/arithmetic_side_effects.rs | 56 ++++ tests/ui/arithmetic_side_effects.stderr | 282 ++++++++++-------- 3 files changed, 356 insertions(+), 222 deletions(-) diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 0a6499e095832..91a069559f7b4 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,4 +1,5 @@ use super::ARITHMETIC_SIDE_EFFECTS; +use crate::clippy_utils::res::MaybeQPath as _; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; @@ -6,7 +7,7 @@ use clippy_utils::res::MaybeDef; 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_middle::ty::{self, Ty, UintTy}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; @@ -88,74 +89,16 @@ 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>, - lhs_ty: Ty<'tcx>, - op: hir::BinOpKind, - rhs_ty: Ty<'tcx>, - ) -> bool { - let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); - - // If the RHS is `NonZero`, then division or module by zero will never occur. - if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { - return true; - } - - // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. - if is_sat_or_wrap(lhs_ty) { - return !is_div_or_rem; - } - - false - } - - // For example, 8i32 or &i64::MAX. - fn is_integral(ty: Ty<'_>) -> bool { - ty.peel_refs().is_integral() - } - // Common entry-point to avoid code duplication. fn issue_lint<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if is_from_proc_macro(cx, expr) { return; } - let msg = "arithmetic operation that can potentially result in unexpected side-effects"; span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg); self.expr_span = Some(expr.span); } - /// Returns the numeric value of a literal integer originated from `expr`, if any. - /// - /// Literal integers can be originated from adhoc declarations like `1`, associated constants - /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, - fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { - let actual = peel_hir_expr_unary(expr).0; - if let hir::ExprKind::Lit(lit) = actual.kind - && let ast::LitKind::Int(n, _) = lit.node - { - return Some(n.get()); - } - if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { - return Some(n); - } - None - } - /// Methods like `add_assign` are send to their `BinOps` references. fn manage_sugar_methods<'tcx>( &mut self, @@ -213,59 +156,53 @@ impl ArithmeticSideEffects { && 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) + && is_non_zero_u(cx, receiver_ty) + && literal_integer(cx, actual_rhs) == Some(1) { 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) { - return; - } - if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { + if self.has_allowed_binary(lhs_ty, rhs_ty) + | has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) + | is_safe_due_to_smaller_source_type(cx, op, (actual_lhs, lhs_ty), actual_rhs) + | is_safe_due_to_smaller_source_type(cx, op, (actual_rhs, rhs_ty), actual_lhs) + { return; } - - let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { + if is_integer(lhs_ty) && is_integer(rhs_ty) { if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { // At least for integers, shifts are already handled by the CTFE return; } - match ( - Self::literal_integer(cx, actual_lhs), - Self::literal_integer(cx, actual_rhs), - ) { - (None, None) => false, + match (literal_integer(cx, actual_lhs), literal_integer(cx, actual_rhs)) { (None, Some(n)) => match (&op, n) { // Division and module are always valid if applied to non-zero integers - (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true, - // Adding or subtracting zeros is always a no-op - (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) - // Multiplication by 1 or 0 will never overflow - | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, - }, - (Some(n), None) => match (&op, n) { + (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => return, // Adding or subtracting zeros is always a no-op (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) // Multiplication by 1 or 0 will never overflow | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, + => return, + _ => {}, }, - (Some(_), Some(_)) => { - matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) + (Some(n), None) + if matches!( + (&op, n), + // Adding or subtracting zeros is always a no-op + (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) + // Multiplication by 1 or 0 will never overflow + | (hir::BinOpKind::Mul, 0 | 1) + ) => + { + return; }, + (Some(_), Some(_)) if matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) => return, + _ => {}, } - } else { - false - }; - if !has_valid_op { - self.issue_lint(cx, expr); } + self.issue_lint(cx, expr); } /// There are some integer methods like `wrapping_div` that will panic depending on the @@ -285,7 +222,7 @@ impl ArithmeticSideEffects { return; } let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); - if !Self::is_integral(instance_ty) { + if !is_integer(instance_ty) { return; } self.manage_sugar_methods(cx, expr, receiver, ps, arg); @@ -293,7 +230,7 @@ impl ArithmeticSideEffects { return; } let (actual_arg, _) = peel_hir_expr_refs(arg); - match Self::literal_integer(cx, actual_arg) { + match literal_integer(cx, actual_arg) { None | Some(0) => self.issue_lint(cx, arg), Some(_) => {}, } @@ -317,7 +254,7 @@ impl ArithmeticSideEffects { return; } let actual_un_expr = peel_hir_expr_refs(un_expr).0; - if Self::literal_integer(cx, actual_un_expr).is_some() { + if literal_integer(cx, actual_un_expr).is_some() { return; } self.issue_lint(cx, expr); @@ -385,3 +322,120 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { } } } + +/// Detects a type-casting conversion and returns the type of the original expression. For +/// example, `let foo = u64::from(bar)`. +fn find_original_primitive_ty<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option> { + if let hir::ExprKind::Call(path, [arg]) = &expr.kind + && path.res(cx).opt_def_id().is_diag_item(&cx.tcx, sym::from_fn) + { + Some(cx.typeck_results().expr_ty(arg)) + } else { + None + } +} + +/// Verifies built-in types that have specific allowed operations +fn has_specific_allowed_type_and_operation<'tcx>( + cx: &LateContext<'tcx>, + lhs_ty: Ty<'tcx>, + op: hir::BinOpKind, + rhs_ty: Ty<'tcx>, +) -> bool { + let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); + let is_sat_or_wrap = |ty: Ty<'_>| matches!(ty.opt_diag_name(cx), Some(sym::Saturating | 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 { + return true; + } + + // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. + if is_sat_or_wrap(lhs_ty) { + return !is_div_or_rem; + } + + false +} + +// For example, `i8` or `u128` and possible associated references like `&&u16`. +fn is_integer(ty: Ty<'_>) -> bool { + ty.peel_refs().is_integral() +} + +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 + } +} + +/// If one side is a literal it is possible to evaluate overflows as long as the other side has a +/// smaller type. `0` and `1` suffixes indicate different sides. +/// +/// For example, `1000u64 + u64::from(some_runtime_variable_of_type_u8)`. +fn is_safe_due_to_smaller_source_type( + cx: &LateContext<'_>, + op: hir::BinOpKind, + (expr0, ty0): (&hir::Expr<'_>, Ty<'_>), + expr1: &hir::Expr<'_>, +) -> bool { + let Some(num0) = literal_integer(cx, expr0) else { + return false; + }; + let Some(orig_ty1) = find_original_primitive_ty(cx, expr1) else { + return false; + }; + let Some(num1) = max_int_num(orig_ty1) else { + return false; + }; + let Some(rslt) = (match op { + hir::BinOpKind::Add => num0.checked_add(num1), + hir::BinOpKind::Mul => num0.checked_mul(num1), + _ => None, + }) else { + return false; + }; + match ty0.peel_refs().kind() { + ty::Uint(UintTy::U16) => u16::try_from(rslt).is_ok(), + ty::Uint(UintTy::U32) => u32::try_from(rslt).is_ok(), + ty::Uint(UintTy::U64) => u64::try_from(rslt).is_ok(), + ty::Uint(UintTy::U128) => true, + ty::Uint(UintTy::Usize) => usize::try_from(rslt).is_ok(), + _ => false, + } +} + +/// Returns the numeric value of a literal integer originated from `expr`, if any. +/// +/// Literal integers can be originated from adhoc declarations like `1`, associated constants +/// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, +fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { + let actual = peel_hir_expr_unary(expr).0; + if let hir::ExprKind::Lit(lit) = actual.kind + && let ast::LitKind::Int(n, _) = lit.node + { + return Some(n.get()); + } + if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { + return Some(n); + } + None +} + +fn max_int_num(ty: Ty<'_>) -> Option { + match ty.peel_refs().kind() { + ty::Uint(UintTy::U8) => Some(u8::MAX.into()), + ty::Uint(UintTy::U16) => Some(u16::MAX.into()), + ty::Uint(UintTy::U32) => Some(u32::MAX.into()), + ty::Uint(UintTy::U64) => Some(u64::MAX.into()), + ty::Uint(UintTy::U128) => Some(u128::MAX), + ty::Uint(UintTy::Usize) => usize::MAX.try_into().ok(), + _ => None, + } +} diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 3245b2c983e19..b7ed596d811e5 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -17,6 +17,7 @@ extern crate proc_macro_derive; use core::num::{NonZero, Saturating, Wrapping}; +use core::time::Duration; const ONE: i32 = 1; const ZERO: i32 = 0; @@ -687,4 +688,59 @@ pub fn explicit_methods() { //~^ arithmetic_side_effects } +pub fn issue_15943(days: u8) -> Duration { + Duration::from_secs(86400 * u64::from(days)) +} + +pub fn type_conversion_add() { + let _ = u128::MAX + u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 + u128::from(1u16); + let _ = 1u128 + u128::from(1u32); + let _ = 1u128 + u128::from(1u64); + + let _ = 1u64 + u64::from(1u8); + let _ = 1u64 + u64::from(1u16); + let _ = 1u64 + u64::from(1u32); + + let _ = 1u32 + u32::from(1u8); + let _ = 1u32 + u32::from(1u16); + + let _ = 1u16 + u16::from(1u8); +} + +pub fn type_conversion_mul() { + let _ = u128::MAX * u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 * u128::from(1u16); + let _ = 1u128 * u128::from(1u32); + let _ = 1u128 * u128::from(1u64); + + let _ = 1u64 * u64::from(1u8); + let _ = 1u64 * u64::from(1u16); + let _ = 1u64 * u64::from(1u32); + + let _ = 1u32 * u32::from(1u8); + let _ = 1u32 * u32::from(1u16); + + let _ = 1u16 * u16::from(1u8); +} + +pub fn type_conversion_does_not_escape_its_context() { + struct Foo; + impl Foo { + fn from(n: u8) -> u64 { + u64::from(n) + } + } + let _ = Duration::from_secs(86400 * Foo::from(1)); + //~^ arithmetic_side_effects + + fn shift(x: u8) -> u64 { + 1 << u64::from(x) + } + let _ = Duration::from_secs(86400 * shift(1)); + //~^ arithmetic_side_effects +} + fn main() {} diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 4150493ba94a3..22742a82601ab 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:166:13 + --> tests/ui/arithmetic_side_effects.rs:167:13 | LL | let _ = 1f16 + 1f16; | ^^^^^^^^^^^ @@ -8,766 +8,790 @@ LL | let _ = 1f16 + 1f16; = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:170:13 + --> tests/ui/arithmetic_side_effects.rs:171:13 | LL | let _ = 1f128 + 1f128; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:175:13 + --> tests/ui/arithmetic_side_effects.rs:176:13 | LL | let _ = String::new() + &String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:311:5 + --> tests/ui/arithmetic_side_effects.rs:312:5 | LL | _n += 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:313:5 + --> tests/ui/arithmetic_side_effects.rs:314:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:315:5 + --> tests/ui/arithmetic_side_effects.rs:316:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:317:5 + --> tests/ui/arithmetic_side_effects.rs:318:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:319:5 + --> tests/ui/arithmetic_side_effects.rs:320:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:321:5 + --> tests/ui/arithmetic_side_effects.rs:322:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:323:5 + --> tests/ui/arithmetic_side_effects.rs:324:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:325:5 + --> tests/ui/arithmetic_side_effects.rs:326:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:327:5 + --> tests/ui/arithmetic_side_effects.rs:328:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:329:5 + --> tests/ui/arithmetic_side_effects.rs:330:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:331:5 + --> tests/ui/arithmetic_side_effects.rs:332:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:333:5 + --> tests/ui/arithmetic_side_effects.rs:334:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:335:5 + --> tests/ui/arithmetic_side_effects.rs:336:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:337:5 + --> tests/ui/arithmetic_side_effects.rs:338:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:339:5 + --> tests/ui/arithmetic_side_effects.rs:340:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:341:5 + --> tests/ui/arithmetic_side_effects.rs:342:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:343:5 + --> tests/ui/arithmetic_side_effects.rs:344:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:345:5 + --> tests/ui/arithmetic_side_effects.rs:346:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:347:5 + --> tests/ui/arithmetic_side_effects.rs:348:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:349:5 + --> tests/ui/arithmetic_side_effects.rs:350:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:351:5 + --> tests/ui/arithmetic_side_effects.rs:352:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:353:5 + --> tests/ui/arithmetic_side_effects.rs:354:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:355:5 + --> tests/ui/arithmetic_side_effects.rs:356:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:357:5 + --> tests/ui/arithmetic_side_effects.rs:358:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:359:5 + --> tests/ui/arithmetic_side_effects.rs:360:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:361:5 + --> tests/ui/arithmetic_side_effects.rs:362:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:363:5 + --> tests/ui/arithmetic_side_effects.rs:364:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:365:5 + --> tests/ui/arithmetic_side_effects.rs:366:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:367:5 + --> tests/ui/arithmetic_side_effects.rs:368:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:369:5 + --> tests/ui/arithmetic_side_effects.rs:370:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:371:5 + --> tests/ui/arithmetic_side_effects.rs:372:5 | LL | _custom >>= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:373:5 + --> tests/ui/arithmetic_side_effects.rs:374:5 | LL | _custom >>= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:375:5 + --> tests/ui/arithmetic_side_effects.rs:376:5 | LL | _custom <<= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:377:5 + --> tests/ui/arithmetic_side_effects.rs:378:5 | LL | _custom <<= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:379:5 + --> tests/ui/arithmetic_side_effects.rs:380:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:381:5 + --> tests/ui/arithmetic_side_effects.rs:382:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:383:5 + --> tests/ui/arithmetic_side_effects.rs:384:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:385:5 + --> tests/ui/arithmetic_side_effects.rs:386:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:387:5 + --> tests/ui/arithmetic_side_effects.rs:388:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:389:5 + --> tests/ui/arithmetic_side_effects.rs:390:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:391:5 + --> tests/ui/arithmetic_side_effects.rs:392:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:393:5 + --> tests/ui/arithmetic_side_effects.rs:394:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:395:5 + --> tests/ui/arithmetic_side_effects.rs:396:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:397:5 + --> tests/ui/arithmetic_side_effects.rs:398:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:399:5 + --> tests/ui/arithmetic_side_effects.rs:400:5 | LL | _custom >>= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:401:5 + --> tests/ui/arithmetic_side_effects.rs:402:5 | LL | _custom >>= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:403:5 + --> tests/ui/arithmetic_side_effects.rs:404:5 | LL | _custom <<= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:405:5 + --> tests/ui/arithmetic_side_effects.rs:406:5 | LL | _custom <<= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:409:10 + --> tests/ui/arithmetic_side_effects.rs:410:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:411:10 + --> tests/ui/arithmetic_side_effects.rs:412:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:413:10 + --> tests/ui/arithmetic_side_effects.rs:414:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:415:10 + --> tests/ui/arithmetic_side_effects.rs:416:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:417:10 + --> tests/ui/arithmetic_side_effects.rs:418:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:419:10 + --> tests/ui/arithmetic_side_effects.rs:420:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:421:10 + --> tests/ui/arithmetic_side_effects.rs:422:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:423:10 + --> tests/ui/arithmetic_side_effects.rs:424:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:425:10 + --> tests/ui/arithmetic_side_effects.rs:426:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:427:10 + --> tests/ui/arithmetic_side_effects.rs:428:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:429:10 + --> tests/ui/arithmetic_side_effects.rs:430:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:431:10 + --> tests/ui/arithmetic_side_effects.rs:432:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:433:10 + --> tests/ui/arithmetic_side_effects.rs:434:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:435:10 + --> tests/ui/arithmetic_side_effects.rs:436:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:437:10 + --> tests/ui/arithmetic_side_effects.rs:438:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:439:10 + --> tests/ui/arithmetic_side_effects.rs:440:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:441:10 + --> tests/ui/arithmetic_side_effects.rs:442:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:443:10 + --> tests/ui/arithmetic_side_effects.rs:444:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:445:10 + --> tests/ui/arithmetic_side_effects.rs:446:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:447:15 + --> tests/ui/arithmetic_side_effects.rs:448:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:449:15 + --> tests/ui/arithmetic_side_effects.rs:450:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:451:15 + --> tests/ui/arithmetic_side_effects.rs:452:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:453:15 + --> tests/ui/arithmetic_side_effects.rs:454:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:455:15 + --> tests/ui/arithmetic_side_effects.rs:456:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:457:15 + --> tests/ui/arithmetic_side_effects.rs:458:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:459:15 + --> tests/ui/arithmetic_side_effects.rs:460:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:461:15 + --> tests/ui/arithmetic_side_effects.rs:462:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:463:15 + --> tests/ui/arithmetic_side_effects.rs:464:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:465:15 + --> tests/ui/arithmetic_side_effects.rs:466:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:467:15 + --> tests/ui/arithmetic_side_effects.rs:468:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:469:15 + --> tests/ui/arithmetic_side_effects.rs:470:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:471:15 + --> tests/ui/arithmetic_side_effects.rs:472:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:473:15 + --> tests/ui/arithmetic_side_effects.rs:474:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:475:15 + --> tests/ui/arithmetic_side_effects.rs:476:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:477:15 + --> tests/ui/arithmetic_side_effects.rs:478:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:479:15 + --> tests/ui/arithmetic_side_effects.rs:480:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:481:15 + --> tests/ui/arithmetic_side_effects.rs:482:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:483:15 + --> tests/ui/arithmetic_side_effects.rs:484:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:485:15 + --> tests/ui/arithmetic_side_effects.rs:486:15 | LL | _custom = _custom >> _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:487:15 + --> tests/ui/arithmetic_side_effects.rs:488:15 | LL | _custom = _custom >> &_custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:489:15 + --> tests/ui/arithmetic_side_effects.rs:490:15 | LL | _custom = Custom << _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:491:15 + --> tests/ui/arithmetic_side_effects.rs:492:15 | LL | _custom = &Custom << _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:495:23 + --> tests/ui/arithmetic_side_effects.rs:496:23 | LL | _n.saturating_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:497:21 + --> tests/ui/arithmetic_side_effects.rs:498:21 | LL | _n.wrapping_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:499:21 + --> tests/ui/arithmetic_side_effects.rs:500:21 | LL | _n.wrapping_rem(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:501:28 + --> tests/ui/arithmetic_side_effects.rs:502:28 | LL | _n.wrapping_rem_euclid(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:504:23 + --> tests/ui/arithmetic_side_effects.rs:505:23 | LL | _n.saturating_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:506:21 + --> tests/ui/arithmetic_side_effects.rs:507:21 | LL | _n.wrapping_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:508:21 + --> tests/ui/arithmetic_side_effects.rs:509:21 | LL | _n.wrapping_rem(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:510:28 + --> tests/ui/arithmetic_side_effects.rs:511:28 | LL | _n.wrapping_rem_euclid(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:513:23 + --> tests/ui/arithmetic_side_effects.rs:514:23 | LL | _n.saturating_div(*Box::new(_n)); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:517:10 + --> tests/ui/arithmetic_side_effects.rs:518:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:519:10 + --> tests/ui/arithmetic_side_effects.rs:520:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:521:15 + --> tests/ui/arithmetic_side_effects.rs:522:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:523:15 + --> tests/ui/arithmetic_side_effects.rs:524:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:525:9 + --> tests/ui/arithmetic_side_effects.rs:526:9 | LL | _ = -*Box::new(_n); | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:535:5 + --> tests/ui/arithmetic_side_effects.rs:536:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:537:5 + --> tests/ui/arithmetic_side_effects.rs:538:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:539:5 + --> tests/ui/arithmetic_side_effects.rs:540:5 | LL | 1 % i / 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:541:5 + --> tests/ui/arithmetic_side_effects.rs:542:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:543:5 + --> tests/ui/arithmetic_side_effects.rs:544:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:555:5 + --> tests/ui/arithmetic_side_effects.rs:556:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:557:5 + --> tests/ui/arithmetic_side_effects.rs:558:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:559:5 + --> tests/ui/arithmetic_side_effects.rs:560:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:562:5 + --> tests/ui/arithmetic_side_effects.rs:563:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:565:5 + --> tests/ui/arithmetic_side_effects.rs:566:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:567:5 + --> tests/ui/arithmetic_side_effects.rs:568:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:570:5 + --> tests/ui/arithmetic_side_effects.rs:571:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:573:5 + --> tests/ui/arithmetic_side_effects.rs:574:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:575:5 + --> tests/ui/arithmetic_side_effects.rs:576:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:586:5 + --> tests/ui/arithmetic_side_effects.rs:587:5 | LL | 10 / a | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:641:9 + --> tests/ui/arithmetic_side_effects.rs:642:9 | LL | x / maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:646:9 + --> tests/ui/arithmetic_side_effects.rs:647:9 | LL | x % maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:658:5 + --> tests/ui/arithmetic_side_effects.rs:659:5 | LL | one.add_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:663:5 + --> tests/ui/arithmetic_side_effects.rs:664:5 | LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:684:5 + --> tests/ui/arithmetic_side_effects.rs:685:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:686:5 + --> tests/ui/arithmetic_side_effects.rs:687:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 128 previous errors +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:696:13 + | +LL | let _ = u128::MAX + u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:713:13 + | +LL | let _ = u128::MAX * u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:736:33 + | +LL | let _ = Duration::from_secs(86400 * Foo::from(1)); + | ^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:742:33 + | +LL | let _ = Duration::from_secs(86400 * shift(1)); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 132 previous errors From bd22913666869c92b7be6217d439a87986f9afcb Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 28 Oct 2025 03:48:01 -0400 Subject: [PATCH 21/80] `unused_enumerate_index`: move to loops lint pass Move `unused_enumerate_index.rs` to `methods`. --- clippy_lints/src/loops/mod.rs | 12 +- .../src/loops/unused_enumerate_index.rs | 142 +++++++++++++++++- clippy_lints/src/methods/mod.rs | 32 ++-- .../src/methods/unused_enumerate_index.rs | 138 ----------------- 4 files changed, 157 insertions(+), 167 deletions(-) delete mode 100644 clippy_lints/src/methods/unused_enumerate_index.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index a064a5910ef98..edd2a1e0b858e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -880,6 +880,16 @@ impl<'tcx> LateLintPass<'tcx> for Loops { missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + && !recv.span.from_expansion() + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } @@ -908,7 +918,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check_loop(cx, pat, arg, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index b893b0baad490..ef30d4d51ef8e 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,18 +1,18 @@ use super::UNUSED_ENUMERATE_INDEX; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::{expr_or_init, pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, FnDecl, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_span::sym; +use rustc_span::{Span, sym}; /// Checks for the `UNUSED_ENUMERATE_INDEX` lint. /// /// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { +pub(super) fn check_loop<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { if let PatKind::Tuple([index, elem], _) = pat.kind && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind && let ty = cx.typeck_results().expr_ty(arg) @@ -40,3 +40,131 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_ ); } } + +/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. +/// +/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is +/// checked: +/// ```ignore +/// for (_, x) in some_iter.enumerate() { +/// // Index is ignored +/// } +/// ``` +/// +/// This `check` function checks for chained method calls constructs where we can detect that the +/// index is unused. Currently, this checks only for the following patterns: +/// ```ignore +/// some_iter.enumerate().map_function(|(_, x)| ..) +/// let x = some_iter.enumerate(); +/// x.map_function(|(_, x)| ..) +/// ``` +/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or +/// `map`. +/// +/// # Preconditions +/// This function must be called not on the `enumerate` call expression itself, but on any of the +/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and +/// that the method call is one of the `std::iter::Iterator` trait. +/// +/// * `call_expr`: The map function call expression +/// * `recv`: The receiver of the call +/// * `closure_arg`: The argument to the map function call containing the closure/function to apply +pub(super) fn check_method(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { + let recv_ty = cx.typeck_results().expr_ty(recv); + // If we call a method on a `std::iter::Enumerate` instance + if recv_ty.is_diag_item(cx, sym::Enumerate) + // If we are calling a method of the `Iterator` trait + && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + // And the map argument is a closure + && let ExprKind::Closure(closure) = closure_arg.kind + && let closure_body = cx.tcx.hir_body(closure.body) + // And that closure has one argument ... + && let [closure_param] = closure_body.params + // .. which is a tuple of 2 elements + && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind + // And that the first element (the index) is either `_` or unused in the body + && pat_is_wild(cx, &index.kind, closure_body) + // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the + // first example below, `expr_or_init` would return `recv`. + // ``` + // iter.enumerate().map(|(_, x)| x) + // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` + // + // let binding = iter.enumerate(); + // ^^^^^^^^^^^^^^^^ `recv_init_expr` + // binding.map(|(_, x)| x) + // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` + // ``` + && let recv_init_expr = expr_or_init(cx, recv) + // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something + // that we cannot control. + // This would for instance happen with: + // ``` + // external_lib::some_function_returning_enumerate().map(|(_, x)| x) + // ``` + && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind + && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) + // Make sure the method call is `std::iter::Iterator::enumerate`. + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) + { + // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element + // that would be explicitly in the closure. + let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { + // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. + // Fallback to `..` if we fail getting either snippet. + Some(ty_span) => elem + .span + .get_source_text(cx) + .and_then(|binding_name| { + ty_span + .get_source_text(cx) + .map(|ty_name| format!("{binding_name}: {ty_name}")) + }) + .unwrap_or_else(|| "..".to_string()), + // Otherwise, we have no explicit type. We can replace with the binding name of the element. + None => snippet(cx, elem.span, "..").into_owned(), + }; + + // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we + // can get from the `MethodCall`. + span_lint_hir_and_then( + cx, + UNUSED_ENUMERATE_INDEX, + recv_init_expr.hir_id, + enumerate_span, + "you seem to use `.enumerate()` and immediately discard the index", + |diag| { + diag.multipart_suggestion( + "remove the `.enumerate()` call", + vec![ + (closure_param.span, new_closure_param), + ( + enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), + String::new(), + ), + ], + Applicability::MachineApplicable, + ); + }, + ); + } +} + +/// Find the span of the explicit type of the element. +/// +/// # Returns +/// If the tuple argument: +/// * Has no explicit type, returns `None` +/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` +/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for +/// the element type. +fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { + if let [tuple_ty] = fn_decl.inputs + && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind + && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) + { + Some(elem_ty.span) + } else { + None + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index a26dd1deebb1a..c1af8a5757b9c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -138,7 +138,6 @@ mod unnecessary_option_map_or_else; mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; @@ -5026,7 +5025,6 @@ impl Methods { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => { @@ -5056,7 +5054,6 @@ impl Methods { } }, (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, false); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5216,7 +5213,6 @@ impl Methods { } }, (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); @@ -5231,11 +5227,9 @@ impl Methods { ); }, (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap); }, (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); lines_filter_map_ok::check_filter_or_flat_map( @@ -5263,20 +5257,17 @@ impl Methods { manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); unnecessary_fold::check(cx, expr, init, acc, span); }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - _ => {}, - } + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, }, (sym::get, [arg]) => { get_first::check(cx, expr, recv, arg); @@ -5337,7 +5328,6 @@ impl Methods { }, (name @ (sym::map | sym::map_err), [m_arg]) => { if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, self.msrv); map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); manual_is_variant_and::check_map(cx, expr); diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index a7d9b2e0fab01..0000000000000 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,138 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - // If we call a method on a `std::iter::Enumerate` instance - if recv_ty.is_diag_item(cx, sym::Enumerate) - // If we are calling a method of the `Iterator` trait - && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} From ce6f8bce1f7347330c4b151ecec12dd82ac692be Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 28 Oct 2025 04:53:18 -0400 Subject: [PATCH 22/80] Refactor `unused_enumerate_index`. --- clippy_lints/src/loops/mod.rs | 3 +- .../src/loops/unused_enumerate_index.rs | 209 ++++++------------ tests/ui/unused_enumerate_index.stderr | 32 +-- 3 files changed, 80 insertions(+), 164 deletions(-) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index edd2a1e0b858e..21198c3c8bc20 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -886,7 +886,6 @@ impl<'tcx> LateLintPass<'tcx> for Loops { path.ident.name, sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map ) - && !recv.span.from_expansion() { unused_enumerate_index::check_method(cx, expr, recv, arg); } @@ -918,7 +917,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check_loop(cx, pat, arg, body); + unused_enumerate_index::check(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index ef30d4d51ef8e..82ded453616d4 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,148 +1,56 @@ use super::UNUSED_ENUMERATE_INDEX; -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, pat_is_wild, sugg}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::{expr_or_init, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, FnDecl, Pat, PatKind, TyKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_span::{Span, sym}; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check_loop<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && ty.is_diag_item(cx, sym::Enumerate) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + iter_expr: &'tcx Expr<'tcx>, + pat: &Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && cx.typeck_results().expr_ty(iter_expr).is_diag_item(cx, sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, iter_expr) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) { - span_lint_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - arg.span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check_method(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - // If we call a method on a `std::iter::Enumerate` instance - if recv_ty.is_diag_item(cx, sym::Enumerate) - // If we are calling a method of the `Iterator` trait - && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); span_lint_hir_and_then( cx, UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, + enumerate_call.hir_id, enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], + spans, Applicability::MachineApplicable, ); }, @@ -150,21 +58,30 @@ pub(super) fn check_method(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Ex } } -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() { - Some(elem_ty.span) - } else { - None + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check(cx, recv, param.pat, ty_spans, body.value); } } diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4b..c742cc8a85ba6 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | From 07b6b325e2b92c08aedfe8db56df7e76825d191b Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 21 Jul 2025 10:07:35 +0000 Subject: [PATCH 23/80] Trait aliases are rare large ast nodes, box them --- clippy_utils/src/ast_utils/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 22b74ab16d615..4df1eb508713c 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -473,9 +473,18 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && over(lb, rb, eq_generic_bound) && over(lis, ris, |l, r| eq_item(l, r, eq_assoc_item_kind)) }, - (TraitAlias(li, lg, lb), TraitAlias(ri, rg, rb)) => { - eq_id(*li, *ri) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) - }, + ( + TraitAlias(box ast::TraitAlias { + ident: li, + generics: lg, + bounds: lb, + }), + TraitAlias(box ast::TraitAlias { + ident: ri, + generics: rg, + bounds: rb, + }), + ) => eq_id(*li, *ri) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound), ( Impl(ast::Impl { generics: lg, From 6d7072b5fdac1bccc5e8049193fbc1543f3b765e Mon Sep 17 00:00:00 2001 From: lengyijun Date: Thu, 6 Mar 2025 19:29:26 +0800 Subject: [PATCH 24/80] extend `needless_collect` Fix https://github.com/rust-lang/rust-clippy/issues/14350 Co-authored-by: Timo <30553356+y21@users.noreply.github.com> --- clippy_lints/src/methods/needless_collect.rs | 33 ++++++++++------ tests/ui/needless_collect.fixed | 4 ++ tests/ui/needless_collect.rs | 4 ++ tests/ui/needless_collect.stderr | 40 +++++++++++--------- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 4f005103d23f8..055fdcabdd210 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -38,11 +38,14 @@ pub(super) fn check<'tcx>( Node::Expr(parent) => { check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); + let sugg: String; + let mut app; + if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { - let mut app = Applicability::MachineApplicable; + app = Applicability::MachineApplicable; let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name.ident.name { + sugg = match name.ident.name { sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( @@ -78,17 +81,23 @@ pub(super) fn check<'tcx>( }, _ => return, }; - - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - call_span.with_hi(parent.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - app, - ); + } else if let ExprKind::Index(_, index, _) = parent.kind { + app = Applicability::MaybeIncorrect; + let snip = snippet_with_applicability(cx, index.span, "_", &mut app); + sugg = format!("nth({snip}).unwrap()"); + } else { + return; } + + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + call_span.with_hi(parent.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + app, + ); }, Node::LetStmt(l) => { if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index 842d77dbc8c50..ba1451bf9704f 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().any(|x| x == 1); //~^ needless_collect + + let _ = sample.iter().cloned().nth(1).unwrap(); + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 98d8d27321d24..e054cd01e6f59 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().collect::>().contains(&1); //~^ needless_collect + + let _ = sample.iter().cloned().collect::>()[1]; + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 00745eb2923c6..c77674dc55d4c 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -20,100 +20,106 @@ LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:27:35 + --> tests/ui/needless_collect.rs:24:36 + | +LL | let _ = sample.iter().cloned().collect::>()[1]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `nth(1).unwrap()` + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:31:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:29:35 + --> tests/ui/needless_collect.rs:33:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:37:19 + --> tests/ui/needless_collect.rs:41:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:39:19 + --> tests/ui/needless_collect.rs:43:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:41:28 + --> tests/ui/needless_collect.rs:45:28 | LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:43:19 + --> tests/ui/needless_collect.rs:47:19 | LL | sample.iter().collect::>().contains(&&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:47:19 + --> tests/ui/needless_collect.rs:51:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:49:19 + --> tests/ui/needless_collect.rs:53:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:55:27 + --> tests/ui/needless_collect.rs:59:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:57:27 + --> tests/ui/needless_collect.rs:61:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:80:27 + --> tests/ui/needless_collect.rs:84:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:82:27 + --> tests/ui/needless_collect.rs:86:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:87:40 + --> tests/ui/needless_collect.rs:91:40 | LL | Vec::::new().extend((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:89:20 + --> tests/ui/needless_collect.rs:93:20 | LL | foo((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:91:49 + --> tests/ui/needless_collect.rs:95:49 | LL | bar((0..10).collect::>(), (0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:93:37 + --> tests/ui/needless_collect.rs:97:37 | LL | baz((0..10), (), ('a'..='z').collect::>()) | ^^^^^^^^^^^^^^^^^^^^ help: remove this call -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors From 42f074df6207d12bb708fa03be1ef6b7e78b6964 Mon Sep 17 00:00:00 2001 From: Frank King Date: Sun, 13 Apr 2025 22:57:37 +0800 Subject: [PATCH 25/80] Implement pattern matching for `&pin mut|const T` --- clippy_lints/src/index_refutable_slice.rs | 2 +- clippy_lints/src/matches/match_as_ref.rs | 2 +- clippy_lints/src/matches/needless_match.rs | 2 +- clippy_lints/src/matches/redundant_guards.rs | 2 +- clippy_lints/src/methods/clone_on_copy.rs | 2 +- clippy_lints/src/question_mark.rs | 8 ++++---- clippy_lints/src/toplevel_ref_arg.rs | 4 ++-- clippy_lints/src/utils/author.rs | 4 ++++ clippy_utils/src/eager_or_lazy.rs | 7 ++++++- clippy_utils/src/lib.rs | 4 ++-- 10 files changed, 23 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 919702c5714ad..e95816353df61 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -94,7 +94,7 @@ fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap, arm: &Arm<'_>) -> Option { .qpath_res(qpath, arm.pat.hir_id) .ctor_parent(cx) .is_lang_item(cx, LangItem::OptionSome) - && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., ident, _) = first_pat.kind + && let PatKind::Binding(BindingMode(ByRef::Yes(_, mutabl), _), .., ident, _) = first_pat.kind && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind && e.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) && let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index c9b6821ad98fd..9c6cf66019f01 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -172,7 +172,7 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { }, )), ) => { - return !matches!(annot, BindingMode(ByRef::Yes(_), _)) && pat_ident.name == first_seg.ident.name; + return !matches!(annot, BindingMode(ByRef::Yes(..), _)) && pat_ident.name == first_seg.ident.name; }, // Example: `Custom::TypeA => Custom::TypeB`, or `None => None` ( diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index d39e315cae1f2..7a1dd94567b14 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -176,7 +176,7 @@ fn get_pat_binding<'tcx>( if let PatKind::Binding(bind_annot, hir_id, ident, _) = pat.kind && hir_id == local { - if matches!(bind_annot.0, rustc_ast::ByRef::Yes(_)) { + if matches!(bind_annot.0, rustc_ast::ByRef::Yes(..)) { let _ = byref_ident.insert(ident); } // the second call of `replace()` returns a `Some(span)`, meaning a multi-binding pattern diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs index 2a0ae14a4b088..9cdad980f238b 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -56,7 +56,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) _ => false, }, // local binding capturing a reference - Node::LetStmt(l) if matches!(l.pat.kind, PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..)) => { + Node::LetStmt(l) if matches!(l.pat.kind, PatKind::Binding(BindingMode(ByRef::Yes(..), _), ..)) => { return; }, _ => false, diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index e67ea1f5e370d..928a4e02fa975 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -150,7 +150,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); // Take care when binding is `ref` let sugg = if let PatKind::Binding( - BindingMode(ByRef::Yes(ref_mutability), binding_mutability), + BindingMode(ByRef::Yes(_,ref_mutability), binding_mutability), _hir_id, ident, subpattern, @@ -169,7 +169,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { // Handle subpattern (@ subpattern) let maybe_subpattern = match subpattern { Some(Pat { - kind: PatKind::Binding(BindingMode(ByRef::Yes(_), _), _, subident, None), + kind: PatKind::Binding(BindingMode(ByRef::Yes(..), _), _, subident, None), .. }) => { // avoid `&ref` @@ -504,8 +504,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_)); let method_call_str = match by_ref { - ByRef::Yes(Mutability::Mut) => ".as_mut()", - ByRef::Yes(Mutability::Not) => ".as_ref()", + ByRef::Yes(_, Mutability::Mut) => ".as_mut()", + ByRef::Yes(_, Mutability::Not) => ".as_ref()", ByRef::No => "", }; let sugg = format!( diff --git a/clippy_lints/src/toplevel_ref_arg.rs b/clippy_lints/src/toplevel_ref_arg.rs index 074b79263d377..250c277ab5e1f 100644 --- a/clippy_lints/src/toplevel_ref_arg.rs +++ b/clippy_lints/src/toplevel_ref_arg.rs @@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for ToplevelRefArg { ) { if !matches!(k, FnKind::Closure) { for arg in iter_input_pats(decl, body) { - if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind + if let PatKind::Binding(BindingMode(ByRef::Yes(..), _), ..) = arg.pat.kind && is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) && !arg.span.in_external_macro(cx.tcx.sess.source_map()) { @@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for ToplevelRefArg { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if let StmtKind::Let(local) = stmt.kind - && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind + && let PatKind::Binding(BindingMode(ByRef::Yes(_, mutabl), _), .., name, None) = local.pat.kind && let Some(init) = local.init // Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue. && is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 68e51dace2db2..92cc11dae8b12 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -745,10 +745,14 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { let ann = match ann { BindingMode::NONE => "NONE", BindingMode::REF => "REF", + BindingMode::REF_PIN => "REF_PIN", BindingMode::MUT => "MUT", BindingMode::REF_MUT => "REF_MUT", + BindingMode::REF_PIN_MUT => "REF_PIN_MUT", BindingMode::MUT_REF => "MUT_REF", + BindingMode::MUT_REF_PIN => "MUT_REF_PIN", BindingMode::MUT_REF_MUT => "MUT_REF_MUT", + BindingMode::MUT_REF_PIN_MUT => "MUT_REF_PIN_MUT", }; kind!("Binding(BindingMode::{ann}, _, {name}, {sub})"); self.ident(name); diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index eb3f442ac754b..6b922a20ca207 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -212,7 +212,12 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS // Custom `Deref` impl might have side effects ExprKind::Unary(UnOp::Deref, e) - if self.cx.typeck_results().expr_ty(e).builtin_deref(true).is_none() => + if self + .cx + .typeck_results() + .expr_ty(e) + .builtin_deref(true) + .is_none() => { self.eagerness |= NoChange; }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 6ee991eae137f..7b3de69d90868 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -783,7 +783,7 @@ pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind { ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => { capture = CaptureKind::Value; }, - ByRef::Yes(Mutability::Mut) if capture != CaptureKind::Value => { + ByRef::Yes(_, Mutability::Mut) if capture != CaptureKind::Value => { capture = CaptureKind::Ref(Mutability::Mut); }, _ => (), @@ -1831,7 +1831,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< .typeck_results() .pat_binding_modes() .get(pat.hir_id) - .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) + .is_some_and(|mode| matches!(mode.0, ByRef::Yes(..))) { // 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 From 8992878605e2ffcc603f1def512e7b73754869e6 Mon Sep 17 00:00:00 2001 From: Frank King Date: Fri, 24 Oct 2025 20:59:33 +0800 Subject: [PATCH 26/80] Rename `#[pin_project]` to `#[pin_v2]` to avoid naming conflicts --- tests/ui/explicit_write_in_test.stderr | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/ui/explicit_write_in_test.stderr diff --git a/tests/ui/explicit_write_in_test.stderr b/tests/ui/explicit_write_in_test.stderr deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 0fd7406e4d646fb9711004baa157187a7802c659 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Wed, 22 Oct 2025 09:58:23 -0600 Subject: [PATCH 27/80] fix: Only special case single line item attribute suggestions --- tests/ui/new_without_default.stderr | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 1e0d5e2131994..0593dbb00fb6b 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -191,7 +191,6 @@ LL + fn default() -> Self { LL + Self::new() LL + } LL + } -LL | impl NewWithCfg { | error: you should consider adding a `Default` implementation for `NewWith2Cfgs` @@ -212,7 +211,6 @@ LL + fn default() -> Self { LL + Self::new() LL + } LL + } -LL | impl NewWith2Cfgs { | error: you should consider adding a `Default` implementation for `NewWithExtraneous` @@ -250,7 +248,6 @@ LL + fn default() -> Self { LL + Self::new() LL + } LL + } -LL | impl NewWithCfgAndExtraneous { | error: aborting due to 13 previous errors From 3f5ea6beb4d7944006f8eb97ba3e43fbf450f2cd Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 21 Jul 2025 10:04:10 +0000 Subject: [PATCH 28/80] Constify trait aliases --- clippy_lints/src/item_name_repetitions.rs | 2 +- clippy_utils/src/ast_utils/mod.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 76f5fdfaa8dcf..22767901614e1 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -528,7 +528,7 @@ impl LateLintPass<'_> for ItemNameRepetitions { | ItemKind::Macro(ident, ..) | ItemKind::Static(_, ident, ..) | ItemKind::Trait(_, _, _, ident, ..) - | ItemKind::TraitAlias(ident, ..) + | ItemKind::TraitAlias(_, ident, ..) | ItemKind::TyAlias(ident, ..) | ItemKind::Union(ident, ..) | ItemKind::Use(_, UseKind::Single(ident)) => ident, diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 4df1eb508713c..839b46325b5ea 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -478,13 +478,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ident: li, generics: lg, bounds: lb, + constness: lc, }), TraitAlias(box ast::TraitAlias { ident: ri, generics: rg, bounds: rb, + constness: rc, }), - ) => eq_id(*li, *ri) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound), + ) => { + matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No) + && eq_id(*li, *ri) + && eq_generics(lg, rg) + && over(lb, rb, eq_generic_bound) + }, ( Impl(ast::Impl { generics: lg, From 938dd5f404115abdbda4a6c63d2369e580527a34 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 10 Oct 2025 15:28:46 +0200 Subject: [PATCH 29/80] misc: test cases with escaping are mostly autofixable now --- tests/ui/write_literal.fixed | 49 +++++++ tests/ui/write_literal.rs | 53 ++++++++ tests/ui/write_literal.stderr | 163 ++++++++++++++++++++++- tests/ui/write_literal_2.rs | 65 ---------- tests/ui/write_literal_2.stderr | 165 ------------------------ tests/ui/write_literal_unfixable.rs | 16 +++ tests/ui/write_literal_unfixable.stderr | 17 +++ 7 files changed, 291 insertions(+), 237 deletions(-) delete mode 100644 tests/ui/write_literal_2.rs delete mode 100644 tests/ui/write_literal_2.stderr create mode 100644 tests/ui/write_literal_unfixable.rs create mode 100644 tests/ui/write_literal_unfixable.stderr diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed index 29352fd468ea4..ae29f3a574625 100644 --- a/tests/ui/write_literal.fixed +++ b/tests/ui/write_literal.fixed @@ -70,6 +70,55 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{{hello}}"); + //~^ write_literal + + writeln!(v, r"{{hello}}"); + //~^ write_literal + + writeln!(v, "'"); + //~^ write_literal + + writeln!(v, "\""); + //~^ write_literal + + writeln!(v, r"'"); + //~^ write_literal + + writeln!( + v, + "some hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some 1\ + 2 \\ 3", + //~^^^ write_literal + ); + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, r"\"); + //~^ write_literal + + writeln!(v, r#"\"#); + //~^ write_literal + + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "\""); diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index 928727527592e..d930339e106cf 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -70,6 +70,59 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{}", "{hello}"); + //~^ write_literal + + writeln!(v, r"{}", r"{hello}"); + //~^ write_literal + + writeln!(v, "{}", '\''); + //~^ write_literal + + writeln!(v, "{}", '"'); + //~^ write_literal + + writeln!(v, r"{}", '\''); + //~^ write_literal + + writeln!( + v, + "some {}", + "hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some {}\ + {} \\ {}", + "1", + "2", + "3", + //~^^^ write_literal + ); + writeln!(v, "{}", "\\"); + //~^ write_literal + + writeln!(v, r"{}", "\\"); + //~^ write_literal + + writeln!(v, r#"{}"#, "\\"); + //~^ write_literal + + writeln!(v, "{}", r"\"); + //~^ write_literal + + writeln!(v, "{}", "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "{}", r#"""#); diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index ca37406c81141..374098fa2b14d 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -145,7 +145,156 @@ LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | error: literal with an empty format string - --> tests/ui/write_literal.rs:75:23 + --> tests/ui/write_literal.rs:76:23 + | +LL | writeln!(v, "{}", "{hello}"); + | ^^^^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", "{hello}"); +LL + writeln!(v, "{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:79:24 + | +LL | writeln!(v, r"{}", r"{hello}"); + | ^^^^^^^^^^ + | +help: try + | +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:82:23 + | +LL | writeln!(v, "{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", '\''); +LL + writeln!(v, "'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:85:23 + | +LL | writeln!(v, "{}", '"'); + | ^^^ + | +help: try + | +LL - writeln!(v, "{}", '"'); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:88:24 + | +LL | writeln!(v, r"{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", '\''); +LL + writeln!(v, r"'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:94:9 + | +LL | / "hello \ +LL | | +LL | | world!", + | |_______________^ + | +help: try + | +LL ~ "some hello \ +LL + +LL ~ world!", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:102:9 + | +LL | / "1", +LL | | "2", +LL | | "3", + | |___________^ + | +help: try + | +LL ~ "some 1\ +LL ~ 2 \\ 3", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:107:23 + | +LL | writeln!(v, "{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:110:24 + | +LL | writeln!(v, r"{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", "\\"); +LL + writeln!(v, r"\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:113:26 + | +LL | writeln!(v, r#"{}"#, "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r#"{}"#, "\\"); +LL + writeln!(v, r#"\"#); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:116:23 + | +LL | writeln!(v, "{}", r"\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", r"\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:119:23 + | +LL | writeln!(v, "{}", "\r"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\r"); +LL + writeln!(v, "\r"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:128:23 | LL | writeln!(v, "{}", r#"""#); | ^^^^^^ @@ -157,7 +306,7 @@ LL + writeln!(v, "\""); | error: literal with an empty format string - --> tests/ui/write_literal.rs:80:9 + --> tests/ui/write_literal.rs:133:9 | LL | / r#" LL | | @@ -182,7 +331,7 @@ LL ~ " | error: literal with an empty format string - --> tests/ui/write_literal.rs:94:55 + --> tests/ui/write_literal.rs:147:55 | LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); | ^^^ @@ -194,7 +343,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:96:52 + --> tests/ui/write_literal.rs:149:52 | LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); | ^^^ @@ -206,7 +355,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:98:49 + --> tests/ui/write_literal.rs:151:49 | LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); | ^^^ @@ -218,7 +367,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:100:43 + --> tests/ui/write_literal.rs:153:43 | LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); | ^^^ @@ -229,5 +378,5 @@ LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 18 previous errors +error: aborting due to 30 previous errors diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs deleted file mode 100644 index f896782aaf3b5..0000000000000 --- a/tests/ui/write_literal_2.rs +++ /dev/null @@ -1,65 +0,0 @@ -//@no-rustfix: overlapping suggestions -#![allow(unused_must_use)] -#![warn(clippy::write_literal)] - -use std::io::Write; - -fn main() { - let mut v = Vec::new(); - - writeln!(v, "{}", "{hello}"); - //~^ write_literal - - writeln!(v, r"{}", r"{hello}"); - //~^ write_literal - - writeln!(v, "{}", '\''); - //~^ write_literal - - writeln!(v, "{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '\''); - //~^ write_literal - - writeln!( - v, - "some {}", - "hello \ - //~^ write_literal - world!", - ); - writeln!( - v, - "some {}\ - {} \\ {}", - "1", - "2", - "3", - //~^^^ write_literal - ); - writeln!(v, "{}", "\\"); - //~^ write_literal - - writeln!(v, r"{}", "\\"); - //~^ write_literal - - writeln!(v, r#"{}"#, "\\"); - //~^ write_literal - - writeln!(v, "{}", r"\"); - //~^ write_literal - - writeln!(v, "{}", "\r"); - //~^ write_literal - - // hard mode - writeln!(v, r#"{}{}"#, '#', '"'); - //~^ write_literal - - // should not lint - writeln!(v, r"{}", "\r"); -} diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr deleted file mode 100644 index 29803d6a8b185..0000000000000 --- a/tests/ui/write_literal_2.stderr +++ /dev/null @@ -1,165 +0,0 @@ -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:10:23 - | -LL | writeln!(v, "{}", "{hello}"); - | ^^^^^^^^^ - | - = note: `-D clippy::write-literal` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` -help: try - | -LL - writeln!(v, "{}", "{hello}"); -LL + writeln!(v, "{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:13:24 - | -LL | writeln!(v, r"{}", r"{hello}"); - | ^^^^^^^^^^ - | -help: try - | -LL - writeln!(v, r"{}", r"{hello}"); -LL + writeln!(v, r"{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:16:23 - | -LL | writeln!(v, "{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", '\''); -LL + writeln!(v, "'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:19:23 - | -LL | writeln!(v, "{}", '"'); - | ^^^ - | -help: try - | -LL - writeln!(v, "{}", '"'); -LL + writeln!(v, "\""); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:22:24 - | -LL | writeln!(v, r"{}", '"'); - | ^^^ - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:25:24 - | -LL | writeln!(v, r"{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", '\''); -LL + writeln!(v, r"'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:31:9 - | -LL | / "hello \ -LL | | -LL | | world!", - | |_______________^ - | -help: try - | -LL ~ "some hello \ -LL + -LL ~ world!", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:39:9 - | -LL | / "1", -LL | | "2", -LL | | "3", - | |___________^ - | -help: try - | -LL ~ "some 1\ -LL ~ 2 \\ 3", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:44:23 - | -LL | writeln!(v, "{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:47:24 - | -LL | writeln!(v, r"{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", "\\"); -LL + writeln!(v, r"\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:50:26 - | -LL | writeln!(v, r#"{}"#, "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r#"{}"#, "\\"); -LL + writeln!(v, r#"\"#); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:53:23 - | -LL | writeln!(v, "{}", r"\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", r"\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:56:23 - | -LL | writeln!(v, "{}", "\r"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\r"); -LL + writeln!(v, "\r"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:60:28 - | -LL | writeln!(v, r#"{}{}"#, '#', '"'); - | ^^^^^^^^ - -error: aborting due to 14 previous errors - diff --git a/tests/ui/write_literal_unfixable.rs b/tests/ui/write_literal_unfixable.rs new file mode 100644 index 0000000000000..3a5660180779d --- /dev/null +++ b/tests/ui/write_literal_unfixable.rs @@ -0,0 +1,16 @@ +//@no-rustfix +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn escaping() { + let mut v = vec![]; + + writeln!(v, r"{}", '"'); + //~^ write_literal + + // hard mode + writeln!(v, r#"{}{}"#, '#', '"'); + //~^ write_literal +} diff --git a/tests/ui/write_literal_unfixable.stderr b/tests/ui/write_literal_unfixable.stderr new file mode 100644 index 0000000000000..0dd40e891893d --- /dev/null +++ b/tests/ui/write_literal_unfixable.stderr @@ -0,0 +1,17 @@ +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:10:24 + | +LL | writeln!(v, r"{}", '"'); + | ^^^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` + +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:14:28 + | +LL | writeln!(v, r#"{}{}"#, '#', '"'); + | ^^^^^^^^ + +error: aborting due to 2 previous errors + From 6c9342782eca11c86991d2e0b33c621d3df1afa6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 10 Oct 2025 15:32:06 +0200 Subject: [PATCH 30/80] move `write.rs` to `write/mod.rs` --- clippy_lints/src/{write.rs => write/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clippy_lints/src/{write.rs => write/mod.rs} (100%) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write/mod.rs similarity index 100% rename from clippy_lints/src/write.rs rename to clippy_lints/src/write/mod.rs From 0fcb000aeda8a1abebc7d577342bd1ab102cee31 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 10 Oct 2025 15:43:13 +0200 Subject: [PATCH 31/80] extract each lint into its own module --- clippy_lints/src/write/empty_string.rs | 38 +++ clippy_lints/src/write/literal.rs | 285 ++++++++++++++++++ clippy_lints/src/write/mod.rs | 402 +------------------------ clippy_lints/src/write/use_debug.rs | 18 ++ clippy_lints/src/write/with_newline.rs | 78 +++++ 5 files changed, 430 insertions(+), 391 deletions(-) create mode 100644 clippy_lints/src/write/empty_string.rs create mode 100644 clippy_lints/src/write/literal.rs create mode 100644 clippy_lints/src/write/use_debug.rs create mode 100644 clippy_lints/src/write/with_newline.rs diff --git a/clippy_lints/src/write/empty_string.rs b/clippy_lints/src/write/empty_string.rs new file mode 100644 index 0000000000000..e7eb99eb34ec3 --- /dev/null +++ b/clippy_lints/src/write/empty_string.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::expand_past_previous_comma; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; + +use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { + let mut span = format_args.span; + + let lint = if name == "writeln" { + span = expand_past_previous_comma(cx, span); + + WRITELN_EMPTY_STRING + } else { + PRINTLN_EMPTY_STRING + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("empty string literal in `{name}!`"), + |diag| { + diag.span_suggestion( + span, + "remove the empty string", + String::new(), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/write/literal.rs b/clippy_lints/src/write/literal.rs new file mode 100644 index 0000000000000..699ac7ea7a5cd --- /dev/null +++ b/clippy_lints/src/write/literal.rs @@ -0,0 +1,285 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::format_arg_removal_span; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sym; +use rustc_ast::token::LitKind; +use rustc_ast::{ + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, +}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::{PRINT_LITERAL, WRITE_LITERAL}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { + let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); + + let lint_name = if name.starts_with("write") { + WRITE_LITERAL + } else { + PRINT_LITERAL + }; + + let mut counts = vec![0u32; format_args.arguments.all_args().len()]; + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece { + counts[arg_index(&placeholder.argument)] += 1; + } + } + + let mut suggestion: Vec<(Span, String)> = vec![]; + // holds index of replaced positional arguments; used to decrement the index of the remaining + // positional arguments. + let mut replaced_position: Vec = vec![]; + let mut sug_span: Option = None; + + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span: Some(placeholder_span), + format_trait: FormatTrait::Display, + format_options, + }) = piece + && *format_options == FormatOptions::default() + && let index = arg_index(argument) + && counts[index] == 1 + && let Some(arg) = format_args.arguments.by_index(index) + && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind + && !arg.expr.span.from_expansion() + && let Some(value_string) = arg.expr.span.get_source_text(cx) + { + let (replacement, replace_raw) = match lit.kind { + LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { + Some(extracted) => extracted, + None => return, + }, + LitKind::Char => ( + match lit.symbol { + sym::DOUBLE_QUOTE => "\\\"", + sym::BACKSLASH_SINGLE_QUOTE => "'", + _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + Some(stripped) => stripped, + None => return, + }, + } + .to_string(), + false, + ), + LitKind::Bool => (lit.symbol.to_string(), false), + _ => continue, + }; + + let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { + continue; + }; + let format_string_is_raw = format_string_snippet.starts_with('r'); + + let replacement = match (format_string_is_raw, replace_raw) { + (false, false) => Some(replacement), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), + (true, false) => match conservative_unescape(&replacement) { + Ok(unescaped) => Some(unescaped), + Err(UnescapeErr::Lint) => None, + Err(UnescapeErr::Ignore) => continue, + }, + (true, true) => { + if replacement.contains(['#', '"']) { + None + } else { + Some(replacement) + } + }, + }; + + sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); + + if let Some((_, index)) = format_arg_piece_span(piece) { + replaced_position.push(index); + } + + if let Some(replacement) = replacement + // `format!("{}", "a")`, `format!("{named}", named = "b") + // ~~~~~ ~~~~~~~~~~~~~ + && let Some(removal_span) = format_arg_removal_span(format_args, index) + { + let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); + suggestion.push((*placeholder_span, replacement)); + suggestion.push((removal_span, String::new())); + } + } + } + + // Decrement the index of the remaining by the number of replaced positional arguments + if !suggestion.is_empty() { + for piece in &format_args.template { + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); + } + } + + if let Some(span) = sug_span { + span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { + if !suggestion.is_empty() { + diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); + } + }); + } +} + +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { + match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { index: Ok(index), .. }, + span: Some(span), + .. + }) => Some((*span, *index)), + _ => None, + } +} + +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + +/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw +/// +/// `r#"a"#` -> (`a`, true) +/// +/// `"b"` -> (`b`, false) +fn extract_str_literal(literal: &str) -> Option<(String, bool)> { + let (literal, raw) = match literal.strip_prefix('r') { + Some(stripped) => (stripped.trim_matches('#'), true), + None => (literal, false), + }; + + Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) +} + +enum UnescapeErr { + /// Should still be linted, can be manually resolved by author, e.g. + /// + /// ```ignore + /// print!(r"{}", '"'); + /// ``` + Lint, + /// Should not be linted, e.g. + /// + /// ```ignore + /// print!(r"{}", '\r'); + /// ``` + Ignore, +} + +/// Unescape a normal string into a raw string +fn conservative_unescape(literal: &str) -> Result { + let mut unescaped = String::with_capacity(literal.len()); + let mut chars = literal.chars(); + let mut err = false; + + while let Some(ch) = chars.next() { + match ch { + '#' => err = true, + '\\' => match chars.next() { + Some('\\') => unescaped.push('\\'), + Some('"') => err = true, + _ => return Err(UnescapeErr::Ignore), + }, + _ => unescaped.push(ch), + } + } + + if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } +} + +/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces +/// in `\u{xxxx}` are left unmodified +#[expect(clippy::match_same_arms)] +fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { + #[derive(Clone, Copy)] + enum State { + Normal, + Backslash, + UnicodeEscape, + } + + let mut escaped = String::with_capacity(literal.len()); + let mut state = State::Normal; + + for ch in literal.chars() { + state = match (ch, state) { + // Escape braces outside of unicode escapes by doubling them up + ('{' | '}', State::Normal) => { + escaped.push(ch); + State::Normal + }, + // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: + // + // \u{aaaa} \\ \x01 + // ^ ^ ^ + ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, + // \u{aaaa} + // ^ + ('u', State::Backslash) => State::UnicodeEscape, + // \xAA \\ + // ^ ^ + (_, State::Backslash) => State::Normal, + // \u{aaaa} + // ^ + ('}', State::UnicodeEscape) => State::Normal, + _ => state, + }; + + escaped.push(ch); + } + + escaped +} diff --git a/clippy_lints/src/write/mod.rs b/clippy_lints/src/write/mod.rs index c39e4a4cc9562..c42c047745bbd 100644 --- a/clippy_lints/src/write/mod.rs +++ b/clippy_lints/src/write/mod.rs @@ -1,18 +1,15 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{FormatArgsStorage, MacroCall, format_arg_removal_span, root_macro_call_first_node}; -use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node}; use clippy_utils::{is_in_test, sym}; -use rustc_ast::token::LitKind; -use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, -}; -use rustc_errors::Applicability; use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, Span}; + +mod empty_string; +mod literal; +mod use_debug; +mod with_newline; declare_clippy_lint! { /// ### What it does @@ -326,27 +323,18 @@ impl<'tcx> LateLintPass<'tcx> for Write { match diag_name { sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, format_args, ¯o_call, name); + with_newline::check(cx, format_args, ¯o_call, name); }, sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, format_args, ¯o_call, name); + empty_string::check(cx, format_args, ¯o_call, name); }, _ => {}, } - check_literal(cx, format_args, name); + literal::check(cx, format_args, name); if !self.in_debug_impl() { - for piece in &format_args.template { - if let &FormatArgsPiece::Placeholder(FormatPlaceholder { - span: Some(span), - format_trait: FormatTrait::Debug, - .. - }) = piece - { - span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); - } - } + use_debug::check(cx, format_args); } } } @@ -364,371 +352,3 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { false } } - -fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { - return; - }; - - let count_vertical_whitespace = || { - format_args - .template - .iter() - .filter_map(|piece| match piece { - FormatArgsPiece::Literal(literal) => Some(literal), - FormatArgsPiece::Placeholder(_) => None, - }) - .flat_map(|literal| literal.as_str().chars()) - .filter(|ch| matches!(ch, '\r' | '\n')) - .count() - }; - - if last.as_str().ends_with('\n') - // ignore format strings with other internal vertical whitespace - && count_vertical_whitespace() == 1 - { - let mut format_string_span = format_args.span; - - let lint = if name == "write" { - format_string_span = expand_past_previous_comma(cx, format_string_span); - - WRITE_WITH_NEWLINE - } else { - PRINT_WITH_NEWLINE - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("using `{name}!()` with a format string that ends in a single newline"), - |diag| { - let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); - let Some(format_snippet) = format_string_span.get_source_text(cx) else { - return; - }; - - if format_args.template.len() == 1 && last == sym::LF { - // print!("\n"), write!(f, "\n") - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], - Applicability::MachineApplicable, - ); - } else if format_snippet.ends_with("\\n\"") { - // print!("...\n"), write!(f, "...\n") - - let hi = format_string_span.hi(); - let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (newline_span, String::new())], - Applicability::MachineApplicable, - ); - } - }, - ); - } -} - -fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { - let mut span = format_args.span; - - let lint = if name == "writeln" { - span = expand_past_previous_comma(cx, span); - - WRITELN_EMPTY_STRING - } else { - PRINTLN_EMPTY_STRING - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("empty string literal in `{name}!`"), - |diag| { - diag.span_suggestion( - span, - "remove the empty string", - String::new(), - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { - let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); - - let lint_name = if name.starts_with("write") { - WRITE_LITERAL - } else { - PRINT_LITERAL - }; - - let mut counts = vec![0u32; format_args.arguments.all_args().len()]; - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece { - counts[arg_index(&placeholder.argument)] += 1; - } - } - - let mut suggestion: Vec<(Span, String)> = vec![]; - // holds index of replaced positional arguments; used to decrement the index of the remaining - // positional arguments. - let mut replaced_position: Vec = vec![]; - let mut sug_span: Option = None; - - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument, - span: Some(placeholder_span), - format_trait: FormatTrait::Display, - format_options, - }) = piece - && *format_options == FormatOptions::default() - && let index = arg_index(argument) - && counts[index] == 1 - && let Some(arg) = format_args.arguments.by_index(index) - && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind - && !arg.expr.span.from_expansion() - && let Some(value_string) = arg.expr.span.get_source_text(cx) - { - let (replacement, replace_raw) = match lit.kind { - LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { - Some(extracted) => extracted, - None => return, - }, - LitKind::Char => ( - match lit.symbol { - sym::DOUBLE_QUOTE => "\\\"", - sym::BACKSLASH_SINGLE_QUOTE => "'", - _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { - Some(stripped) => stripped, - None => return, - }, - } - .to_string(), - false, - ), - LitKind::Bool => (lit.symbol.to_string(), false), - _ => continue, - }; - - let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { - continue; - }; - let format_string_is_raw = format_string_snippet.starts_with('r'); - - let replacement = match (format_string_is_raw, replace_raw) { - (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), - (true, false) => match conservative_unescape(&replacement) { - Ok(unescaped) => Some(unescaped), - Err(UnescapeErr::Lint) => None, - Err(UnescapeErr::Ignore) => continue, - }, - (true, true) => { - if replacement.contains(['#', '"']) { - None - } else { - Some(replacement) - } - }, - }; - - sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - - if let Some((_, index)) = format_arg_piece_span(piece) { - replaced_position.push(index); - } - - if let Some(replacement) = replacement - // `format!("{}", "a")`, `format!("{named}", named = "b") - // ~~~~~ ~~~~~~~~~~~~~ - && let Some(removal_span) = format_arg_removal_span(format_args, index) - { - let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); - suggestion.push((*placeholder_span, replacement)); - suggestion.push((removal_span, String::new())); - } - } - } - - // Decrement the index of the remaining by the number of replaced positional arguments - if !suggestion.is_empty() { - for piece in &format_args.template { - relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); - } - } - - if let Some(span) = sug_span { - span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { - if !suggestion.is_empty() { - diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); - } - }); - } -} - -/// Extract Span and its index from the given `piece` -fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { - match piece { - FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: FormatArgPosition { index: Ok(index), .. }, - span: Some(span), - .. - }) => Some((*span, *index)), - _ => None, - } -} - -/// Relocalizes the indexes of positional arguments in the format string -fn relocalize_format_args_indexes( - piece: &FormatArgsPiece, - suggestion: &mut Vec<(Span, String)>, - replaced_position: &[usize], -) { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - // Only consider positional arguments - kind: FormatArgPositionKind::Number, - span: Some(span), - }, - format_options, - .. - }) = piece - { - if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { - // If the span is already in the suggestion, we don't need to process it again - return; - } - - // lambda to get the decremented index based on the replaced positions - let decremented_index = |index: usize| -> usize { - let decrement = replaced_position.iter().filter(|&&i| i < index).count(); - index - decrement - }; - - suggestion.push((*span, decremented_index(*index).to_string())); - - // If there are format options, we need to handle them as well - if *format_options != FormatOptions::default() { - // lambda to process width and precision format counts and add them to the suggestion - let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { - if let Some(FormatCount::Argument(FormatArgPosition { - index: Ok(format_arg_index), - kind: FormatArgPositionKind::Number, - span: Some(format_arg_span), - })) = count - { - suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); - } - }; - - process_format_count(&format_options.width, &|index: usize| format!("{index}$")); - process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); - } - } -} - -/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw -/// -/// `r#"a"#` -> (`a`, true) -/// -/// `"b"` -> (`b`, false) -fn extract_str_literal(literal: &str) -> Option<(String, bool)> { - let (literal, raw) = match literal.strip_prefix('r') { - Some(stripped) => (stripped.trim_matches('#'), true), - None => (literal, false), - }; - - Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) -} - -enum UnescapeErr { - /// Should still be linted, can be manually resolved by author, e.g. - /// - /// ```ignore - /// print!(r"{}", '"'); - /// ``` - Lint, - /// Should not be linted, e.g. - /// - /// ```ignore - /// print!(r"{}", '\r'); - /// ``` - Ignore, -} - -/// Unescape a normal string into a raw string -fn conservative_unescape(literal: &str) -> Result { - let mut unescaped = String::with_capacity(literal.len()); - let mut chars = literal.chars(); - let mut err = false; - - while let Some(ch) = chars.next() { - match ch { - '#' => err = true, - '\\' => match chars.next() { - Some('\\') => unescaped.push('\\'), - Some('"') => err = true, - _ => return Err(UnescapeErr::Ignore), - }, - _ => unescaped.push(ch), - } - } - - if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } -} - -/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in -/// `\u{xxxx}` are left unmodified -#[expect(clippy::match_same_arms)] -fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { - #[derive(Clone, Copy)] - enum State { - Normal, - Backslash, - UnicodeEscape, - } - - let mut escaped = String::with_capacity(literal.len()); - let mut state = State::Normal; - - for ch in literal.chars() { - state = match (ch, state) { - // Escape braces outside of unicode escapes by doubling them up - ('{' | '}', State::Normal) => { - escaped.push(ch); - State::Normal - }, - // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: - // - // \u{aaaa} \\ \x01 - // ^ ^ ^ - ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, - // \u{aaaa} - // ^ - ('u', State::Backslash) => State::UnicodeEscape, - // \xAA \\ - // ^ ^ - (_, State::Backslash) => State::Normal, - // \u{aaaa} - // ^ - ('}', State::UnicodeEscape) => State::Normal, - _ => state, - }; - - escaped.push(ch); - } - - escaped -} diff --git a/clippy_lints/src/write/use_debug.rs b/clippy_lints/src/write/use_debug.rs new file mode 100644 index 0000000000000..75dddeb5d2a72 --- /dev/null +++ b/clippy_lints/src/write/use_debug.rs @@ -0,0 +1,18 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::{FormatArgs, FormatArgsPiece, FormatPlaceholder, FormatTrait}; +use rustc_lint::LateContext; + +use super::USE_DEBUG; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs) { + for piece in &format_args.template { + if let &FormatArgsPiece::Placeholder(FormatPlaceholder { + span: Some(span), + format_trait: FormatTrait::Debug, + .. + }) = piece + { + span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); + } + } +} diff --git a/clippy_lints/src/write/with_newline.rs b/clippy_lints/src/write/with_newline.rs new file mode 100644 index 0000000000000..e4b51da3cadcf --- /dev/null +++ b/clippy_lints/src/write/with_newline.rs @@ -0,0 +1,78 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LintContext}; +use rustc_span::BytePos; + +use super::{PRINT_WITH_NEWLINE, WRITE_WITH_NEWLINE}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { + return; + }; + + let count_vertical_whitespace = || { + format_args + .template + .iter() + .filter_map(|piece| match piece { + FormatArgsPiece::Literal(literal) => Some(literal), + FormatArgsPiece::Placeholder(_) => None, + }) + .flat_map(|literal| literal.as_str().chars()) + .filter(|ch| matches!(ch, '\r' | '\n')) + .count() + }; + + if last.as_str().ends_with('\n') + // ignore format strings with other internal vertical whitespace + && count_vertical_whitespace() == 1 + { + let mut format_string_span = format_args.span; + + let lint = if name == "write" { + format_string_span = expand_past_previous_comma(cx, format_string_span); + + WRITE_WITH_NEWLINE + } else { + PRINT_WITH_NEWLINE + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("using `{name}!()` with a format string that ends in a single newline"), + |diag| { + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let Some(format_snippet) = format_string_span.get_source_text(cx) else { + return; + }; + + if format_args.template.len() == 1 && last == sym::LF { + // print!("\n"), write!(f, "\n") + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], + Applicability::MachineApplicable, + ); + } else if format_snippet.ends_with("\\n\"") { + // print!("...\n"), write!(f, "...\n") + + let hi = format_string_span.hi(); + let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (newline_span, String::new())], + Applicability::MachineApplicable, + ); + } + }, + ); + } +} From c71f7b63f8fb3d7f0b2b0e26d0e029646f93fb9e Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 31 Oct 2025 19:15:42 +0100 Subject: [PATCH 32/80] Merge commit 'c936595d17413c1f08e162e117e504fb4ed126e4' into clippy-subtree-update --- CHANGELOG.md | 85 +++++ CODE_OF_CONDUCT.md | 2 +- Cargo.toml | 2 +- book/book.toml | 2 - book/src/development/method_checking.md | 15 +- book/src/lint_configuration.md | 1 + clippy_config/Cargo.toml | 2 +- clippy_config/src/conf.rs | 1 + clippy_dev/src/deprecate_lint.rs | 32 +- clippy_dev/src/fmt.rs | 2 +- clippy_dev/src/lib.rs | 11 +- clippy_dev/src/main.rs | 51 ++- clippy_dev/src/new_lint.rs | 21 +- clippy_dev/src/parse.rs | 285 +++++++++++++++++ clippy_dev/src/parse/cursor.rs | 263 ++++++++++++++++ clippy_dev/src/release.rs | 2 +- clippy_dev/src/rename_lint.rs | 118 ++++--- clippy_dev/src/update_lints.rs | 296 ++---------------- clippy_dev/src/utils.rs | 201 ++---------- clippy_lints/Cargo.toml | 2 +- clippy_lints/src/declared_lints.rs | 12 +- clippy_lints/src/deprecated_lints.rs | 4 + clippy_lints/src/doc/mod.rs | 117 ++++--- clippy_lints/src/doc/needless_doctest_main.rs | 203 ++++-------- clippy_lints/src/doc/test_attr_in_doctest.rs | 34 ++ clippy_lints/src/double_parens.rs | 37 ++- .../src/{empty_enum.rs => empty_enums.rs} | 19 +- .../src/extra_unused_type_parameters.rs | 5 + clippy_lints/src/formatting.rs | 2 +- clippy_lints/src/incompatible_msrv.rs | 53 +++- .../src/integer_division_remainder_used.rs | 50 --- clippy_lints/src/len_zero.rs | 228 ++++++++------ clippy_lints/src/lib.rs | 20 +- clippy_lints/src/lifetimes.rs | 110 +++---- clippy_lints/src/lines_filter_map_ok.rs | 141 --------- clippy_lints/src/loops/needless_range_loop.rs | 5 +- clippy_lints/src/loops/never_loop.rs | 19 +- clippy_lints/src/manual_let_else.rs | 8 +- clippy_lints/src/manual_option_as_slice.rs | 36 ++- clippy_lints/src/map_unit_fn.rs | 9 +- clippy_lints/src/matches/manual_unwrap_or.rs | 32 +- clippy_lints/src/matches/match_as_ref.rs | 104 +++--- clippy_lints/src/matches/match_wild_enum.rs | 2 +- .../src/matches/redundant_pattern_match.rs | 2 +- .../src/methods/lines_filter_map_ok.rs | 85 +++++ clippy_lints/src/methods/mod.rs | 102 +++++- .../src/methods/option_as_ref_cloned.rs | 26 +- clippy_lints/src/methods/search_is_some.rs | 122 ++++---- .../src/methods/unnecessary_filter_map.rs | 96 +++--- .../misc_early/unneeded_wildcard_pattern.rs | 2 +- .../src/needless_borrows_for_generic_args.rs | 2 +- .../src/{needless_if.rs => needless_ifs.rs} | 13 +- clippy_lints/src/only_used_in_recursion.rs | 4 +- .../src/operators/double_comparison.rs | 88 +++--- .../integer_division_remainder_used.rs | 24 ++ .../invalid_upcast_comparisons.rs | 148 ++++----- .../src/{ => operators}/manual_div_ceil.rs | 147 +++------ clippy_lints/src/operators/mod.rs | 87 ++++- clippy_lints/src/precedence.rs | 18 +- clippy_lints/src/single_range_in_vec_init.rs | 3 +- clippy_lints/src/types/option_option.rs | 18 +- clippy_lints/src/unnested_or_patterns.rs | 4 +- clippy_lints/src/write.rs | 25 +- clippy_lints_internal/src/msrv_attr_impl.rs | 2 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/ast_utils/mod.rs | 8 +- clippy_utils/src/higher.rs | 30 +- clippy_utils/src/lib.rs | 2 +- clippy_utils/src/res.rs | 6 +- clippy_utils/src/source.rs | 4 +- clippy_utils/src/sugg.rs | 8 +- declare_clippy_lint/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- .../excessive_nesting/excessive_nesting.rs | 2 +- tests/ui/auxiliary/macro_rules.rs | 12 + tests/ui/auxiliary/proc_macro_derive.rs | 11 + tests/ui/auxiliary/proc_macros.rs | 2 +- tests/ui/blocks_in_conditions.fixed | 2 +- tests/ui/blocks_in_conditions.rs | 2 +- tests/ui/bool_comparison.fixed | 2 +- tests/ui/bool_comparison.rs | 2 +- .../ui/cmp_owned/asymmetric_partial_eq.fixed | 2 +- tests/ui/cmp_owned/asymmetric_partial_eq.rs | 2 +- tests/ui/collapsible_else_if.fixed | 2 +- tests/ui/collapsible_else_if.rs | 2 +- tests/ui/collapsible_if.fixed | 2 +- tests/ui/collapsible_if.rs | 2 +- tests/ui/comparison_to_empty.fixed | 2 +- tests/ui/comparison_to_empty.rs | 2 +- tests/ui/crashes/ice-7169.fixed | 2 +- tests/ui/crashes/ice-7169.rs | 2 +- tests/ui/disallowed_names.rs | 2 +- tests/ui/doc/needless_doctest_main.rs | 28 +- tests/ui/doc/needless_doctest_main.stderr | 33 +- tests/ui/double_comparison.fixed | 2 +- tests/ui/double_comparison.rs | 2 +- tests/ui/double_parens.fixed | 65 ++++ tests/ui/double_parens.rs | 65 ++++ tests/ui/double_parens.stderr | 55 +++- tests/ui/empty_enum.rs | 8 - tests/ui/empty_enums.rs | 25 ++ .../{empty_enum.stderr => empty_enums.stderr} | 6 +- ...e.rs => empty_enums_without_never_type.rs} | 3 +- tests/ui/equatable_if_let.fixed | 2 +- tests/ui/equatable_if_let.rs | 2 +- tests/ui/expect_tool_lint_rfc_2383.rs | 2 +- tests/ui/filetype_is_file.rs | 2 +- tests/ui/if_same_then_else2.rs | 2 +- tests/ui/ifs_same_cond.rs | 2 +- tests/ui/impl.rs | 22 ++ tests/ui/incompatible_msrv.rs | 10 + tests/ui/incompatible_msrv.stderr | 14 +- .../ui/integer_division_remainder_used.stderr | 18 +- tests/ui/len_zero.fixed | 6 +- tests/ui/len_zero.rs | 6 +- tests/ui/len_zero_unstable.fixed | 7 + tests/ui/len_zero_unstable.rs | 7 + tests/ui/len_zero_unstable.stderr | 11 + tests/ui/lines_filter_map_ok.fixed | 18 +- tests/ui/lines_filter_map_ok.rs | 18 +- tests/ui/lines_filter_map_ok.stderr | 20 +- tests/ui/manual_float_methods.rs | 2 +- tests/ui/manual_let_else.rs | 27 +- tests/ui/manual_let_else.stderr | 38 ++- tests/ui/manual_option_as_slice.stderr | 79 ++++- tests/ui/manual_unwrap_or.fixed | 14 + tests/ui/manual_unwrap_or.rs | 14 + tests/ui/manual_unwrap_or.stderr | 14 +- tests/ui/manual_unwrap_or_default.fixed | 14 + tests/ui/manual_unwrap_or_default.rs | 14 + tests/ui/manual_unwrap_or_default.stderr | 14 +- tests/ui/match_as_ref.fixed | 19 ++ tests/ui/match_as_ref.rs | 31 ++ tests/ui/match_as_ref.stderr | 108 ++++++- tests/ui/match_overlapping_arm.rs | 2 +- .../multiple_inherent_impl_cfg.normal.stderr | 31 ++ tests/ui/multiple_inherent_impl_cfg.rs | 46 +++ tests/ui/multiple_inherent_impl_cfg.stderr | 91 ++++++ ...multiple_inherent_impl_cfg.withtest.stderr | 91 ++++++ tests/ui/needless_bool/fixable.fixed | 2 +- tests/ui/needless_bool/fixable.rs | 2 +- tests/ui/needless_borrowed_ref.fixed | 2 +- tests/ui/needless_borrowed_ref.rs | 2 +- tests/ui/needless_collect.fixed | 2 +- tests/ui/needless_collect.rs | 2 +- tests/ui/needless_collect_indirect.rs | 2 +- tests/ui/needless_doc_main.rs | 15 +- tests/ui/needless_doc_main.stderr | 35 +-- .../{needless_if.fixed => needless_ifs.fixed} | 25 +- tests/ui/{needless_if.rs => needless_ifs.rs} | 25 +- ...needless_if.stderr => needless_ifs.stderr} | 32 +- tests/ui/never_loop.rs | 38 ++- tests/ui/never_loop.stderr | 80 +++-- tests/ui/nonminimal_bool.rs | 2 +- tests/ui/nonminimal_bool_methods.fixed | 2 +- tests/ui/nonminimal_bool_methods.rs | 2 +- tests/ui/op_ref.fixed | 2 +- tests/ui/op_ref.rs | 2 +- tests/ui/option_map_unit_fn_fixable.fixed | 5 +- tests/ui/option_map_unit_fn_fixable.rs | 5 +- tests/ui/option_map_unit_fn_fixable.stderr | 253 ++++++++++----- tests/ui/option_map_unit_fn_unfixable.rs | 20 +- tests/ui/option_map_unit_fn_unfixable.stderr | 109 ++++++- tests/ui/option_option.rs | 28 +- tests/ui/option_option.stderr | 54 +++- tests/ui/panicking_overflow_checks.rs | 2 +- tests/ui/partialeq_to_none.fixed | 2 +- tests/ui/partialeq_to_none.rs | 2 +- tests/ui/precedence.fixed | 32 +- tests/ui/precedence.rs | 32 +- tests/ui/precedence.stderr | 38 ++- tests/ui/print.rs | 44 --- tests/ui/print.stderr | 56 ---- tests/ui/print_stdout.rs | 23 ++ tests/ui/print_stdout.stderr | 35 +++ ...edundant_pattern_matching_drop_order.fixed | 2 +- .../redundant_pattern_matching_drop_order.rs | 2 +- ...dundant_pattern_matching_if_let_true.fixed | 2 +- .../redundant_pattern_matching_if_let_true.rs | 2 +- .../redundant_pattern_matching_ipaddr.fixed | 2 +- tests/ui/redundant_pattern_matching_ipaddr.rs | 2 +- .../redundant_pattern_matching_option.fixed | 2 +- tests/ui/redundant_pattern_matching_option.rs | 2 +- .../ui/redundant_pattern_matching_poll.fixed | 2 +- tests/ui/redundant_pattern_matching_poll.rs | 2 +- .../redundant_pattern_matching_result.fixed | 2 +- tests/ui/redundant_pattern_matching_result.rs | 2 +- tests/ui/rename.fixed | 4 + tests/ui/rename.rs | 4 + tests/ui/rename.stderr | 162 +++++----- tests/ui/result_map_unit_fn_fixable.fixed | 4 +- tests/ui/result_map_unit_fn_fixable.rs | 4 +- tests/ui/result_map_unit_fn_fixable.stderr | 217 ++++++++----- tests/ui/result_map_unit_fn_unfixable.rs | 5 +- tests/ui/result_map_unit_fn_unfixable.stderr | 86 +++-- tests/ui/search_is_some.rs | 65 ---- tests/ui/search_is_some.stderr | 102 +----- tests/ui/search_is_some_fixable_none.fixed | 18 ++ tests/ui/search_is_some_fixable_none.rs | 24 ++ tests/ui/search_is_some_fixable_none.stderr | 158 +++++++--- tests/ui/search_is_some_fixable_some.fixed | 31 ++ tests/ui/search_is_some_fixable_some.rs | 35 +++ tests/ui/search_is_some_fixable_some.stderr | 156 ++++++--- tests/ui/shadow.rs | 2 +- tests/ui/single_match.fixed | 2 +- tests/ui/single_match.rs | 2 +- .../ui/single_match_else_deref_patterns.fixed | 2 +- tests/ui/single_match_else_deref_patterns.rs | 2 +- tests/ui/starts_ends_with.fixed | 2 +- tests/ui/starts_ends_with.rs | 2 +- tests/ui/suspicious_else_formatting.rs | 2 +- tests/ui/suspicious_unary_op_formatting.rs | 2 +- tests/ui/test_attr_in_doctest.rs | 1 - tests/ui/test_attr_in_doctest.stderr | 28 +- tests/ui/unit_cmp.rs | 2 +- tests/ui/unnecessary_filter_map.stderr | 24 +- tests/ui/unnecessary_find_map.rs | 2 - tests/ui/unnecessary_find_map.stderr | 20 +- tests/ui/unnecessary_safety_comment.rs | 2 +- tests/ui/unneeded_wildcard_pattern.fixed | 2 +- tests/ui/unneeded_wildcard_pattern.rs | 2 +- tests/ui/unnested_or_patterns.fixed | 2 +- tests/ui/unnested_or_patterns.rs | 2 +- tests/ui/unnested_or_patterns2.fixed | 2 +- tests/ui/unnested_or_patterns2.rs | 2 +- tests/ui/use_debug.rs | 50 +++ tests/ui/use_debug.stderr | 23 ++ tests/ui/useless_conversion.fixed | 2 +- tests/ui/useless_conversion.rs | 2 +- tests/ui/useless_conversion_try.rs | 2 +- ...e_loop.rs => while_immutable_condition.rs} | 2 + ...tderr => while_immutable_condition.stderr} | 25 +- util/gh-pages/script.js | 50 +-- 234 files changed, 4504 insertions(+), 2769 deletions(-) create mode 100644 clippy_dev/src/parse.rs create mode 100644 clippy_dev/src/parse/cursor.rs create mode 100644 clippy_lints/src/doc/test_attr_in_doctest.rs rename clippy_lints/src/{empty_enum.rs => empty_enums.rs} (83%) delete mode 100644 clippy_lints/src/integer_division_remainder_used.rs delete mode 100644 clippy_lints/src/lines_filter_map_ok.rs create mode 100644 clippy_lints/src/methods/lines_filter_map_ok.rs rename clippy_lints/src/{needless_if.rs => needless_ifs.rs} (88%) create mode 100644 clippy_lints/src/operators/integer_division_remainder_used.rs rename clippy_lints/src/{ => operators}/invalid_upcast_comparisons.rs (56%) rename clippy_lints/src/{ => operators}/manual_div_ceil.rs (50%) delete mode 100644 tests/ui/empty_enum.rs create mode 100644 tests/ui/empty_enums.rs rename tests/ui/{empty_enum.stderr => empty_enums.stderr} (75%) rename tests/ui/{empty_enum_without_never_type.rs => empty_enums_without_never_type.rs} (67%) create mode 100644 tests/ui/len_zero_unstable.fixed create mode 100644 tests/ui/len_zero_unstable.rs create mode 100644 tests/ui/len_zero_unstable.stderr create mode 100644 tests/ui/multiple_inherent_impl_cfg.normal.stderr create mode 100644 tests/ui/multiple_inherent_impl_cfg.rs create mode 100644 tests/ui/multiple_inherent_impl_cfg.stderr create mode 100644 tests/ui/multiple_inherent_impl_cfg.withtest.stderr rename tests/ui/{needless_if.fixed => needless_ifs.fixed} (82%) rename tests/ui/{needless_if.rs => needless_ifs.rs} (82%) rename tests/ui/{needless_if.stderr => needless_ifs.stderr} (64%) delete mode 100644 tests/ui/print.rs delete mode 100644 tests/ui/print.stderr create mode 100644 tests/ui/print_stdout.rs create mode 100644 tests/ui/print_stdout.stderr create mode 100644 tests/ui/use_debug.rs create mode 100644 tests/ui/use_debug.stderr rename tests/ui/{infinite_loop.rs => while_immutable_condition.rs} (98%) rename tests/ui/{infinite_loop.stderr => while_immutable_condition.stderr} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d46d3496677..6cb2755be0eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,89 @@ document. [e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master) +## Rust 1.91 + +Current stable, released 2025-10-30 + +[View all 146 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-07-25T21%3A05%3A11Z..2025-09-04T22%3A34%3A27Z+base%3Amaster) + +### New Lints + +* Added [`possible_missing_else`] to `suspicious` + [#15317](https://github.com/rust-lang/rust-clippy/pull/15317) + +### Moves and Deprecations + +* Moved [`cognitive_complexity`] from `nursery` to `restriction` + [#15415](https://github.com/rust-lang/rust-clippy/pull/15415) +* Moved [`declare_interior_mutable_const`] from `style` to `suspicious` + [#15454](https://github.com/rust-lang/rust-clippy/pull/15454) +* Moved [`crosspointer_transmute`] from `complexity` to `suspicious` + [#15403](https://github.com/rust-lang/rust-clippy/pull/15403) + +### Enhancements + +* [`excessive_precision`] added `const_literal_digits_threshold` option to suppress overly precise constants. + [#15193](https://github.com/rust-lang/rust-clippy/pull/15193) +* [`unwrap_in_result`] rewritten for better accuracy; now lints on `.unwrap()` and `.expect()` + directly and no longer mixes `Result` and `Option`. + [#15445](https://github.com/rust-lang/rust-clippy/pull/15445) +* [`panic`] now works in `const` contexts. + [#15565](https://github.com/rust-lang/rust-clippy/pull/15565) +* [`implicit_clone`] now also lints `to_string` calls (merging [`string_to_string`] behavior). + [#14177](https://github.com/rust-lang/rust-clippy/pull/14177) +* [`collapsible_match`] improved suggestions to handle necessary ref/dereferencing. + [#14221](https://github.com/rust-lang/rust-clippy/pull/14221) +* [`map_identity`] now suggests making variables mutable when required; recognizes tuple struct restructuring. + [#15261](https://github.com/rust-lang/rust-clippy/pull/15261) +* [`option_map_unit_fn`] preserves `unsafe` blocks in suggestions. + [#15570](https://github.com/rust-lang/rust-clippy/pull/15570) +* [`unnecessary_mut_passed`] provides structured, clearer fix suggestions. + [#15438](https://github.com/rust-lang/rust-clippy/pull/15438) +* [`float_equality_without_abs`] now checks `f16` and `f128` types. + [#15054](https://github.com/rust-lang/rust-clippy/pull/15054) +* [`doc_markdown`] expanded whitelist (`InfiniBand`, `RoCE`, `PowerPC`) and improved handling of + identifiers like NixOS. + [#15558](https://github.com/rust-lang/rust-clippy/pull/15558) +* [`clone_on_ref_ptr`] now suggests fully qualified paths to avoid resolution errors. + [#15561](https://github.com/rust-lang/rust-clippy/pull/15561) +* [`manual_assert`] simplifies boolean expressions in suggested fixes. + [#15368](https://github.com/rust-lang/rust-clippy/pull/15368) +* [`four_forward_slashes`] warns about bare CR in comments and avoids invalid autofixes. + [#15175](https://github.com/rust-lang/rust-clippy/pull/15175) + +### False Positive Fixes + +* [`alloc_instead_of_core`] fixed FP when `alloc` is an alias + [#15581](https://github.com/rust-lang/rust-clippy/pull/15581) +* [`needless_range_loop`] fixed FP and FN when meeting multidimensional array + [#15486](https://github.com/rust-lang/rust-clippy/pull/15486) +* [`semicolon_inside_block`] fixed FP when attribute over expr is not enabled + [#15476](https://github.com/rust-lang/rust-clippy/pull/15476) +* [`unnested_or_patterns`] fixed FP on structs with only shorthand field patterns + [#15343](https://github.com/rust-lang/rust-clippy/pull/15343) +* [`match_ref_pats`] fixed FP on match scrutinee of never type + [#15474](https://github.com/rust-lang/rust-clippy/pull/15474) +* [`infinite_loop`] fixed FP in async blocks that are not awaited + [#15157](https://github.com/rust-lang/rust-clippy/pull/15157) +* [`iter_on_single_items`] fixed FP on function pointers and let statements + [#15013](https://github.com/rust-lang/rust-clippy/pull/15013) + +### ICE Fixes + +* [`len_zero`] fix ICE when fn len has a return type without generic type params + [#15660](https://github.com/rust-lang/rust-clippy/pull/15660) + +### Documentation Improvements + +* [`cognitive_complexity`] corrected documentation to state lint is in `restriction`, not `nursery` + [#15563](https://github.com/rust-lang/rust-clippy/pull/15563) + +### Performance Improvements + +* [`doc_broken_link`] optimized by 99.77% (12M → 27k instructions) + [#15385](https://github.com/rust-lang/rust-clippy/pull/15385) + ## Rust 1.90 Current stable, released 2025-09-18 @@ -6253,6 +6336,7 @@ Released 2018-09-13 [`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop [`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum [`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets +[`empty_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enums [`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments [`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr [`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop @@ -6583,6 +6667,7 @@ Released 2018-09-13 [`needless_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_else [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each [`needless_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_if +[`needless_ifs`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_ifs [`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes [`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e3708bc485399..133072e0586b4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,3 @@ # The Rust Code of Conduct -The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html). +The Code of Conduct for this repository [can be found online](https://rust-lang.org/policies/code-of-conduct/). diff --git a/Cargo.toml b/Cargo.toml index bedcc300f8567..fee885d8fa7e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.92" +version = "0.1.93" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/book/book.toml b/book/book.toml index c918aadf83c4b..ae5fd07487ced 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,8 +1,6 @@ [book] authors = ["The Rust Clippy Developers"] language = "en" -multilingual = false -src = "src" title = "Clippy Documentation" [rust] diff --git a/book/src/development/method_checking.md b/book/src/development/method_checking.md index cca6d6ae7bf34..f3912f81d859f 100644 --- a/book/src/development/method_checking.md +++ b/book/src/development/method_checking.md @@ -21,14 +21,13 @@ use clippy_utils::sym; impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { // Check our expr is calling a method with pattern matching - if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind + if let hir::ExprKind::MethodCall(path, _, _, _) = &expr.kind // Check if the name of this method is `our_fancy_method` && path.ident.name == sym::our_fancy_method - // We can check the type of the self argument whenever necessary. - // (It's necessary if we want to check that method is specifically belonging to a specific trait, - // for example, a `map` method could belong to user-defined trait instead of to `Iterator`) + // Check if the method belongs to the `sym::OurFancyTrait` trait. + // (for example, a `map` method could belong to user-defined trait instead of to `Iterator`) // See the next section for more information. - && cx.ty_based_def(self_arg).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait) + && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait) { println!("`expr` is a method call for `our_fancy_method`"); } @@ -45,6 +44,12 @@ New symbols such as `our_fancy_method` need to be added to the `clippy_utils::sy This module extends the list of symbols already provided by the compiler crates in `rustc_span::sym`. +If a trait defines only one method (such as the `std::ops::Deref` trait, which only has the `deref()` method), +one might be tempted to omit the method name check. This would work, but is not always advisable because: +- If a new method (possibly with a default implementation) were to be added to the trait, there would be a risk of + matching the wrong method. +- Comparing symbols is very cheap and might prevent a more expensive lookup. + ## Checking if a `impl` block implements a method While sometimes we want to check whether a method is being called or not, other diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index b9ecff1fa3643..6569bdabf115a 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -859,6 +859,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error) * [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map) * [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants) +* [`len_zero`](https://rust-lang.github.io/rust-clippy/master/index.html#len_zero) * [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok) * [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff) * [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index f8c748290e418..3f6b26d3334ed 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.92" +version = "0.1.93" edition = "2024" publish = false diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 9993765b4bdc4..2a042e6c3d853 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -748,6 +748,7 @@ define_Conf! { io_other_error, iter_kv_map, legacy_numeric_constants, + len_zero, lines_filter_map_ok, manual_abs_diff, manual_bits, diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 3bdc5b2772327..0401cfda7080c 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,4 +1,5 @@ -use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{DeprecatedLint, Lint, ParseCx}; +use crate::update_lints::generate_lint_files; use crate::utils::{UpdateMode, Version}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; @@ -13,21 +14,20 @@ use std::{fs, io}; /// # Panics /// /// If a file path could not read from or written to -pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { - if let Some((prefix, _)) = name.split_once("::") { - panic!("`{name}` should not contain the `{prefix}` prefix"); - } - - let mut lints = find_lint_decls(); - let (mut deprecated_lints, renamed_lints) = read_deprecated_lints(); +pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str, reason: &'cx str) { + let mut lints = cx.find_lint_decls(); + let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints(); let Some(lint) = lints.iter().find(|l| l.name == name) else { eprintln!("error: failed to find lint `{name}`"); return; }; - let prefixed_name = String::from_iter(["clippy::", name]); - match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) { + let prefixed_name = cx.str_buf.with(|buf| { + buf.extend(["clippy::", name]); + cx.arena.alloc_str(buf) + }); + match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) { Ok(_) => { println!("`{name}` is already deprecated"); return; @@ -36,8 +36,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { idx, DeprecatedLint { name: prefixed_name, - reason: reason.into(), - version: clippy_version.rust_display().to_string(), + reason, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), }, ), } @@ -61,8 +61,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { } } -fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { - fn remove_lint(name: &str, lints: &mut Vec) { +fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { + fn remove_lint(name: &str, lints: &mut Vec>) { lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); } @@ -135,14 +135,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io ); assert!( - content[lint.declaration_range.clone()].contains(&name.to_uppercase()), + content[lint.declaration_range].contains(&name.to_uppercase()), "error: `{}` does not contain lint `{}`'s declaration", path.display(), lint.name ); // Remove lint declaration (declare_clippy_lint!) - content.replace_range(lint.declaration_range.clone(), ""); + content.replace_range(lint.declaration_range, ""); // Remove the module declaration (mod xyz;) let mod_decl = format!("\nmod {name};"); diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 2b2138d3108df..781e37e6144ef 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) { .expect("invalid rustfmt path"); rustfmt_path.truncate(rustfmt_path.trim_end().len()); - let args: Vec<_> = walk_dir_no_dot_or_target() + let args: Vec<_> = walk_dir_no_dot_or_target(".") .filter_map(|e| { let e = expect_action(e, ErrAction::Read, "."); e.path() diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 16f413e0c862f..dcca08aee7e64 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,9 +1,12 @@ #![feature( - rustc_private, exit_status_error, if_let_guard, + new_range, + new_range_api, os_str_slice, os_string_truncate, + pattern, + rustc_private, slice_split_once )] #![warn( @@ -15,6 +18,7 @@ )] #![allow(clippy::missing_panics_doc)] +extern crate rustc_arena; #[expect(unused_extern_crates, reason = "required to link to rustc crates")] extern crate rustc_driver; extern crate rustc_lexer; @@ -32,5 +36,8 @@ pub mod setup; pub mod sync; pub mod update_lints; +mod parse; mod utils; -pub use utils::{ClippyInfo, UpdateMode}; + +pub use self::parse::{ParseCx, new_parse_cx}; +pub use self::utils::{ClippyInfo, UpdateMode}; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 1b6a590b896f4..392c3aabf1937 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -4,10 +4,9 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ - ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, - update_lints, + ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve, + setup, sync, update_lints, }; -use std::convert::Infallible; use std::env; fn main() { @@ -28,7 +27,7 @@ fn main() { allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)), + DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))), DevCommand::NewLint { pass, name, @@ -36,7 +35,7 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => update_lints::update(UpdateMode::Change), + Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { @@ -79,13 +78,18 @@ fn main() { old_name, new_name, uplift, - } => rename_lint::rename( - clippy.version, - &old_name, - new_name.as_ref().unwrap_or(&old_name), - uplift, - ), - DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason), + } => new_parse_cx(|cx| { + rename_lint::rename( + cx, + clippy.version, + &old_name, + new_name.as_ref().unwrap_or(&old_name), + uplift, + ); + }), + DevCommand::Deprecate { name, reason } => { + new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason)); + }, DevCommand::Sync(SyncCommand { subcommand }) => match subcommand { SyncSubcommand::UpdateNightly => sync::update_nightly(), }, @@ -95,6 +99,20 @@ fn main() { } } +fn lint_name(name: &str) -> Result { + let name = name.replace('-', "_"); + if let Some((pre, _)) = name.split_once("::") { + Err(format!("lint name should not contain the `{pre}` prefix")) + } else if name + .bytes() + .any(|x| !matches!(x, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')) + { + Err("lint name contains invalid characters".to_owned()) + } else { + Ok(name) + } +} + #[derive(Parser)] #[command(name = "dev", about)] struct Dev { @@ -150,7 +168,7 @@ enum DevCommand { #[arg( short, long, - value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")), + value_parser = lint_name, )] /// Name of the new lint in snake case, ex: `fn_too_long` name: String, @@ -223,8 +241,12 @@ enum DevCommand { /// Rename a lint RenameLint { /// The name of the lint to rename + #[arg(value_parser = lint_name)] old_name: String, - #[arg(required_unless_present = "uplift")] + #[arg( + required_unless_present = "uplift", + value_parser = lint_name, + )] /// The new name of the lint new_name: Option, #[arg(long)] @@ -234,6 +256,7 @@ enum DevCommand { /// Deprecate the given lint Deprecate { /// The name of the lint to deprecate + #[arg(value_parser = lint_name)] name: String, #[arg(long, short)] /// The reason for deprecation diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index a14afd8c5f416..a180db6ad0629 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,5 @@ -use crate::utils::{RustSearcher, Token, Version}; +use crate::parse::cursor::{self, Capture, Cursor}; +use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; use std::fmt::{self, Write as _}; @@ -516,22 +517,22 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { #[allow(clippy::enum_glob_use)] - use Token::*; + use cursor::Pat::*; let mut context = None; let mut decl_end = None; - let mut searcher = RustSearcher::new(contents); - while let Some(name) = searcher.find_capture_token(CaptureIdent) { - match name { + let mut cursor = Cursor::new(contents); + let mut captures = [Capture::EMPTY]; + while let Some(name) = cursor.find_any_ident() { + match cursor.get_text(name) { "declare_clippy_lint" => { - if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) { - decl_end = Some(searcher.pos()); + if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) { + decl_end = Some(cursor.pos()); } }, "impl" => { - let mut capture = ""; - if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) { - match capture { + if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) { + match cursor.get_text(captures[0]) { "LateLintPass" => context = Some("LateContext"), "EarlyLintPass" => context = Some("EarlyContext"), _ => {}, diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs new file mode 100644 index 0000000000000..de5caf4e1ef65 --- /dev/null +++ b/clippy_dev/src/parse.rs @@ -0,0 +1,285 @@ +pub mod cursor; + +use self::cursor::{Capture, Cursor}; +use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target}; +use core::fmt::{Display, Write as _}; +use core::range::Range; +use rustc_arena::DroplessArena; +use std::fs; +use std::path::{self, Path, PathBuf}; +use std::str::pattern::Pattern; + +pub struct ParseCxImpl<'cx> { + pub arena: &'cx DroplessArena, + pub str_buf: StrBuf, +} +pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>; + +/// Calls the given function inside a newly created parsing context. +pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T { + let arena = DroplessArena::default(); + f(&mut Scoped::new(ParseCxImpl { + arena: &arena, + str_buf: StrBuf::with_capacity(128), + })) +} + +/// A string used as a temporary buffer used to avoid allocating for short lived strings. +pub struct StrBuf(String); +impl StrBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(String::with_capacity(cap)) + } + + /// Allocates the result of formatting the given value onto the arena. + pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str { + self.0.clear(); + write!(self.0, "{value}").expect("`Display` impl returned an error"); + arena.alloc_str(&self.0) + } + + /// Allocates the string onto the arena with all ascii characters converted to + /// lowercase. + pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str { + self.0.clear(); + self.0.push_str(s); + self.0.make_ascii_lowercase(); + arena.alloc_str(&self.0) + } + + /// Allocates the result of replacing all instances the pattern with the given string + /// onto the arena. + pub fn alloc_replaced<'cx>( + &mut self, + arena: &'cx DroplessArena, + s: &str, + pat: impl Pattern, + replacement: &str, + ) -> &'cx str { + let mut parts = s.split(pat); + let Some(first) = parts.next() else { + return ""; + }; + self.0.clear(); + self.0.push_str(first); + for part in parts { + self.0.push_str(replacement); + self.0.push_str(part); + } + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { + self.0.clear(); + f(&mut self.0) + } +} + +pub struct Lint<'cx> { + pub name: &'cx str, + pub group: &'cx str, + pub module: &'cx str, + pub path: PathBuf, + pub declaration_range: Range, +} + +pub struct DeprecatedLint<'cx> { + pub name: &'cx str, + pub reason: &'cx str, + pub version: &'cx str, +} + +pub struct RenamedLint<'cx> { + pub old_name: &'cx str, + pub new_name: &'cx str, + pub version: &'cx str, +} + +impl<'cx> ParseCxImpl<'cx> { + /// Finds all lint declarations (`declare_clippy_lint!`) + #[must_use] + pub fn find_lint_decls(&mut self) -> Vec> { + let mut lints = Vec::with_capacity(1000); + let mut contents = String::new(); + for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { + let e = expect_action(e, ErrAction::Read, "."); + + // Skip if this isn't a lint crate's directory. + let mut crate_path = if expect_action(e.file_type(), ErrAction::Read, ".").is_dir() + && let Ok(crate_path) = e.file_name().into_string() + && crate_path.starts_with("clippy_lints") + && crate_path != "clippy_lints_internal" + { + crate_path + } else { + continue; + }; + + crate_path.push(path::MAIN_SEPARATOR); + crate_path.push_str("src"); + for e in walk_dir_no_dot_or_target(&crate_path) { + let e = expect_action(e, ErrAction::Read, &crate_path); + if let Some(path) = e.path().to_str() + && let Some(path) = path.strip_suffix(".rs") + && let Some(path) = path.get(crate_path.len() + 1..) + { + let module = if path == "lib" { + "" + } else { + let path = path + .strip_suffix("mod") + .and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR)) + .unwrap_or(path); + self.str_buf + .alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::") + }; + self.parse_clippy_lint_decls( + e.path(), + File::open_read_to_cleared_string(e.path(), &mut contents), + module, + &mut lints, + ); + } + } + } + lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); + lints + } + + /// Parse a source file looking for `declare_clippy_lint` macro invocations. + fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, lints: &mut Vec>) { + #[allow(clippy::enum_glob_use)] + use cursor::Pat::*; + #[rustfmt::skip] + static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ /// docs + Bang, OpenBrace, AnyComment, + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, + // pub NAME, GROUP, + Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, + ]; + + let mut cursor = Cursor::new(contents); + let mut captures = [Capture::EMPTY; 2]; + while let Some(start) = cursor.find_ident("declare_clippy_lint") { + if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) { + lints.push(Lint { + name: self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), + group: self.arena.alloc_str(cursor.get_text(captures[1])), + module, + path: path.into(), + declaration_range: start as usize..cursor.pos() as usize, + }); + } + } + } + + #[must_use] + pub fn read_deprecated_lints(&mut self) -> (Vec>, Vec>) { + #[allow(clippy::enum_glob_use)] + use cursor::Pat::*; + #[rustfmt::skip] + static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, + // ("first", "second"), + OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, + ]; + #[rustfmt::skip] + static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ DEPRECATED(DEPRECATED_VERSION) = [ + Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[ + // !{ RENAMED(RENAMED_VERSION) = [ + Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + + let path = "clippy_lints/src/deprecated_lints.rs"; + let mut deprecated = Vec::with_capacity(30); + let mut renamed = Vec::with_capacity(80); + let mut contents = String::new(); + File::open_read_to_cleared_string(path, &mut contents); + + let mut cursor = Cursor::new(&contents); + let mut captures = [Capture::EMPTY; 3]; + + // First instance is the macro definition. + assert!( + cursor.find_ident("declare_with_version").is_some(), + "error reading deprecated lints" + ); + + if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) { + while cursor.match_all(DECL_TOKENS, &mut captures) { + deprecated.push(DeprecatedLint { + name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])), + reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])), + version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), + }); + } + } else { + panic!("error reading deprecated lints"); + } + + if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) { + while cursor.match_all(DECL_TOKENS, &mut captures) { + renamed.push(RenamedLint { + old_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])), + new_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])), + version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), + }); + } + } else { + panic!("error reading renamed lints"); + } + + deprecated.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); + renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(rhs.old_name)); + (deprecated, renamed) + } + + /// Removes the line splices and surrounding quotes from a string literal + fn parse_str_lit(&mut self, s: &str) -> &'cx str { + let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { + (s.trim_matches('#'), true) + } else { + (s, false) + }; + let s = s + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); + + if is_raw { + if s.is_empty() { "" } else { self.arena.alloc_str(s) } + } else { + self.str_buf.with(|buf| { + rustc_literal_escaper::unescape_str(s, &mut |_, ch| { + if let Ok(ch) = ch { + buf.push(ch); + } + }); + if buf.is_empty() { "" } else { self.arena.alloc_str(buf) } + }) + } + } + + fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str { + let value = self.parse_str_lit(s); + assert!( + !value.contains('\n'), + "error parsing `{}`: `{s}` should be a single line string", + path.display(), + ); + value + } +} diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs new file mode 100644 index 0000000000000..6dc003f326de7 --- /dev/null +++ b/clippy_dev/src/parse/cursor.rs @@ -0,0 +1,263 @@ +use core::slice; +use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind}; + +/// A token pattern used for searching and matching by the [`Cursor`]. +/// +/// In the event that a pattern is a multi-token sequence, earlier tokens will be consumed +/// even if the pattern ultimately isn't matched. e.g. With the sequence `:*` matching +/// `DoubleColon` will consume the first `:` and then fail to match, leaving the cursor at +/// the `*`. +#[derive(Clone, Copy)] +pub enum Pat<'a> { + /// Matches any number of comments and doc comments. + AnyComment, + Ident(&'a str), + CaptureIdent, + LitStr, + CaptureLitStr, + Bang, + CloseBrace, + CloseBracket, + CloseParen, + Comma, + DoubleColon, + Eq, + Lifetime, + Lt, + Gt, + OpenBrace, + OpenBracket, + OpenParen, + Pound, + Semi, +} + +#[derive(Clone, Copy)] +pub struct Capture { + pub pos: u32, + pub len: u32, +} +impl Capture { + pub const EMPTY: Self = Self { pos: 0, len: 0 }; +} + +/// A unidirectional cursor over a token stream that is lexed on demand. +pub struct Cursor<'txt> { + next_token: Token, + pos: u32, + inner: lex::Cursor<'txt>, + text: &'txt str, +} +impl<'txt> Cursor<'txt> { + #[must_use] + pub fn new(text: &'txt str) -> Self { + let mut inner = lex::Cursor::new(text, lex::FrontmatterAllowed::Yes); + Self { + next_token: inner.advance_token(), + pos: 0, + inner, + text, + } + } + + /// Gets the text of the captured token assuming it came from this cursor. + #[must_use] + pub fn get_text(&self, capture: Capture) -> &'txt str { + &self.text[capture.pos as usize..(capture.pos + capture.len) as usize] + } + + /// Gets the text that makes up the next token in the stream, or the empty string if + /// stream is exhausted. + #[must_use] + pub fn peek_text(&self) -> &'txt str { + &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] + } + + /// Gets the length of the next token in bytes, or zero if the stream is exhausted. + #[must_use] + pub fn peek_len(&self) -> u32 { + self.next_token.len + } + + /// Gets the next token in the stream, or [`TokenKind::Eof`] if the stream is + /// exhausted. + #[must_use] + pub fn peek(&self) -> TokenKind { + self.next_token.kind + } + + /// Gets the offset of the next token in the source string, or the string's length if + /// the stream is exhausted. + #[must_use] + pub fn pos(&self) -> u32 { + self.pos + } + + /// Gets whether the cursor has exhausted its input. + #[must_use] + pub fn at_end(&self) -> bool { + self.next_token.kind == TokenKind::Eof + } + + /// Advances the cursor to the next token. If the stream is exhausted this will set + /// the next token to [`TokenKind::Eof`]. + pub fn step(&mut self) { + // `next_token.len` is zero for the eof marker. + self.pos += self.next_token.len; + self.next_token = self.inner.advance_token(); + } + + /// Consumes tokens until the given pattern is either fully matched of fails to match. + /// Returns whether the pattern was fully matched. + /// + /// For each capture made by the pattern one item will be taken from the capture + /// sequence with the result placed inside. + fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { + loop { + match (pat, self.next_token.kind) { + #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/6697 + (_, TokenKind::Whitespace) + | ( + Pat::AnyComment, + TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. }, + ) => self.step(), + (Pat::AnyComment, _) => return true, + (Pat::Bang, TokenKind::Bang) + | (Pat::CloseBrace, TokenKind::CloseBrace) + | (Pat::CloseBracket, TokenKind::CloseBracket) + | (Pat::CloseParen, TokenKind::CloseParen) + | (Pat::Comma, TokenKind::Comma) + | (Pat::Eq, TokenKind::Eq) + | (Pat::Lifetime, TokenKind::Lifetime { .. }) + | (Pat::Lt, TokenKind::Lt) + | (Pat::Gt, TokenKind::Gt) + | (Pat::OpenBrace, TokenKind::OpenBrace) + | (Pat::OpenBracket, TokenKind::OpenBracket) + | (Pat::OpenParen, TokenKind::OpenParen) + | (Pat::Pound, TokenKind::Pound) + | (Pat::Semi, TokenKind::Semi) + | ( + Pat::LitStr, + TokenKind::Literal { + kind: LiteralKind::Str { terminated: true } | LiteralKind::RawStr { .. }, + .. + }, + ) => { + self.step(); + return true; + }, + (Pat::Ident(x), TokenKind::Ident) if x == self.peek_text() => { + self.step(); + return true; + }, + (Pat::DoubleColon, TokenKind::Colon) => { + self.step(); + if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) { + self.step(); + return true; + } + return false; + }, + #[rustfmt::skip] + ( + Pat::CaptureLitStr, + TokenKind::Literal { + kind: + LiteralKind::Str { terminated: true } + | LiteralKind::RawStr { n_hashes: Some(_) }, + .. + }, + ) + | (Pat::CaptureIdent, TokenKind::Ident) => { + *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; + self.step(); + return true; + }, + _ => return false, + } + } + } + + /// Consumes all tokens until the specified identifier is found and returns its + /// position. Returns `None` if the identifier could not be found. + /// + /// The cursor will be positioned immediately after the identifier, or at the end if + /// it is not. + pub fn find_ident(&mut self, ident: &str) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident if self.peek_text() == ident => { + let pos = self.pos; + self.step(); + return Some(pos); + }, + TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + /// Consumes all tokens until the next identifier is found and captures it. Returns + /// `None` if no identifier could be found. + /// + /// The cursor will be positioned immediately after the identifier, or at the end if + /// it is not. + pub fn find_any_ident(&mut self) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident => { + let res = Capture { + pos: self.pos, + len: self.next_token.len, + }; + self.step(); + return Some(res); + }, + TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + /// Continually attempt to match the pattern on subsequent tokens until a match is + /// found. Returns whether the pattern was successfully matched. + /// + /// Not generally suitable for multi-token patterns or patterns that can match + /// nothing. + #[must_use] + pub fn find_pat(&mut self, pat: Pat<'_>) -> bool { + let mut capture = [].iter_mut(); + while !self.match_impl(pat, &mut capture) { + self.step(); + if self.at_end() { + return false; + } + } + true + } + + /// Attempts to match a sequence of patterns at the current position. Returns whether + /// all patterns were successfully matched. + /// + /// Captures will be written to the given slice in the order they're matched. If a + /// capture is matched, but there are no more capture slots this will panic. If the + /// match is completed without filling all the capture slots they will be left + /// unmodified. + /// + /// If the match fails the cursor will be positioned at the first failing token. + #[must_use] + pub fn match_all(&mut self, pats: &[Pat<'_>], captures: &mut [Capture]) -> bool { + let mut captures = captures.iter_mut(); + pats.iter().all(|&p| self.match_impl(p, &mut captures)) + } + + /// Attempts to match a single pattern at the current position. Returns whether the + /// pattern was successfully matched. + /// + /// If the pattern attempts to capture anything this will panic. If the match fails + /// the cursor will be positioned at the first failing token. + #[must_use] + pub fn match_pat(&mut self, pat: Pat<'_>) -> bool { + self.match_impl(pat, &mut [].iter_mut()) + } +} diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 15392dd1d2927..d11070bab85bf 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -23,7 +23,7 @@ pub fn bump_version(mut version: Version) { dst.push_str(&src[..package.version_range.start]); write!(dst, "\"{}\"", version.toml_display()).unwrap(); dst.push_str(&src[package.version_range.end..]); - UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range)) + UpdateStatus::from_changed(src.get(package.version_range) != dst.get(package.version_range)) } }); } diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index d62597428e210..8e30eb7ce95b3 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,7 +1,9 @@ -use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::cursor::{self, Capture, Cursor}; +use crate::parse::{ParseCx, RenamedLint}; +use crate::update_lints::generate_lint_files; use crate::utils::{ - ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, - delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, + ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, + expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, }; use rustc_lexer::TokenKind; use std::ffi::OsString; @@ -24,36 +26,35 @@ use std::path::Path; /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. #[expect(clippy::too_many_lines)] -pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) { - if let Some((prefix, _)) = old_name.split_once("::") { - panic!("`{old_name}` should not contain the `{prefix}` prefix"); - } - if let Some((prefix, _)) = new_name.split_once("::") { - panic!("`{new_name}` should not contain the `{prefix}` prefix"); - } - +pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) { let mut updater = FileUpdater::default(); - let mut lints = find_lint_decls(); - let (deprecated_lints, mut renamed_lints) = read_deprecated_lints(); + let mut lints = cx.find_lint_decls(); + let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); - let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else { + let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else { panic!("could not find lint `{old_name}`"); }; let lint = &lints[lint_idx]; - let old_name_prefixed = String::from_iter(["clippy::", old_name]); + let old_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", old_name]); + cx.arena.alloc_str(buf) + }); let new_name_prefixed = if uplift { - new_name.to_owned() + new_name } else { - String::from_iter(["clippy::", new_name]) + cx.str_buf.with(|buf| { + buf.extend(["clippy::", new_name]); + cx.arena.alloc_str(buf) + }) }; for lint in &mut renamed_lints { if lint.new_name == old_name_prefixed { - lint.new_name.clone_from(&new_name_prefixed); + lint.new_name = new_name_prefixed; } } - match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) { + match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { Ok(_) => { println!("`{old_name}` already has a rename registered"); return; @@ -63,12 +64,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b idx, RenamedLint { old_name: old_name_prefixed, - new_name: if uplift { - new_name.to_owned() - } else { - String::from_iter(["clippy::", new_name]) - }, - version: clippy_version.rust_display().to_string(), + new_name: new_name_prefixed, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), }, ); }, @@ -104,7 +101,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } delete_test_files(old_name, change_prefixed_tests); lints.remove(lint_idx); - } else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() { + } else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { let lint = &mut lints[lint_idx]; if lint.module.ends_with(old_name) && lint @@ -118,13 +115,15 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b mod_edit = ModEdit::Rename; } - let mod_len = lint.module.len(); - lint.module.truncate(mod_len - old_name.len()); - lint.module.push_str(new_name); + lint.module = cx.str_buf.with(|buf| { + buf.push_str(&lint.module[..lint.module.len() - old_name.len()]); + buf.push_str(new_name); + cx.arena.alloc_str(buf) + }); } rename_test_files(old_name, new_name, change_prefixed_tests); - new_name.clone_into(&mut lints[lint_idx].name); - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); + lints[lint_idx].name = new_name; + lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); } else { println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); println!("Since `{new_name}` already exists the existing code has not been changed"); @@ -132,7 +131,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut update_fn = file_update_fn(old_name, new_name, mod_edit); - for e in walk_dir_no_dot_or_target() { + for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { updater.update_file(e.path(), &mut update_fn); @@ -285,47 +284,46 @@ fn file_update_fn<'a, 'b>( move |_, src, dst| { let mut copy_pos = 0u32; let mut changed = false; - let mut searcher = RustSearcher::new(src); - let mut capture = ""; + let mut cursor = Cursor::new(src); + let mut captures = [Capture::EMPTY]; loop { - match searcher.peek() { + match cursor.peek() { TokenKind::Eof => break, TokenKind::Ident => { - let match_start = searcher.pos(); - let text = searcher.peek_text(); - searcher.step(); + let match_start = cursor.pos(); + let text = cursor.peek_text(); + cursor.step(); match text { // clippy::line_name or clippy::lint-name "clippy" => { - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; } }, // mod lint_name "mod" => { if !matches!(mod_edit, ModEdit::None) - && searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + && let Some(pos) = cursor.find_ident(old_name) { match mod_edit { ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, - ModEdit::Delete if searcher.match_tokens(&[Token::Semi], &mut []) => { + ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => { let mut start = &src[copy_pos as usize..match_start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } dst.push_str(start); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); if src[copy_pos as usize..].starts_with("\n\n") { copy_pos += 1; } @@ -337,8 +335,8 @@ fn file_update_fn<'a, 'b>( }, // lint_name:: name if matches!(mod_edit, ModEdit::Rename) && name == old_name => { - let name_end = searcher.pos(); - if searcher.match_tokens(&[Token::DoubleColon], &mut []) { + let name_end = cursor.pos(); + if cursor.match_pat(cursor::Pat::DoubleColon) { dst.push_str(&src[copy_pos as usize..match_start as usize]); dst.push_str(new_name); copy_pos = name_end; @@ -356,36 +354,36 @@ fn file_update_fn<'a, 'b>( }; dst.push_str(&src[copy_pos as usize..match_start as usize]); dst.push_str(replacement); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, } }, // //~ lint_name TokenKind::LineComment { doc_style: None } => { - let text = searcher.peek_text(); + let text = cursor.peek_text(); if text.starts_with("//~") && let Some(text) = text.strip_suffix(old_name) && !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize + text.len()]); + dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]); dst.push_str(new_name); - copy_pos = searcher.pos() + searcher.peek_len(); + copy_pos = cursor.pos() + cursor.peek_len(); changed = true; } - searcher.step(); + cursor.step(); }, // ::lint_name TokenKind::Colon - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name => + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); - copy_pos = searcher.pos(); + copy_pos = cursor.pos(); changed = true; }, - _ => searcher.step(), + _ => cursor.step(), } } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5f6e874ffe254..3d0da68461144 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,10 @@ -use crate::utils::{ - ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn, -}; +use crate::parse::cursor::Cursor; +use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; use std::collections::HashSet; use std::fmt::Write; -use std::fs; -use std::ops::Range; -use std::path::{self, Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use std::path::{self, Path}; const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ @@ -24,18 +21,18 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht /// # Panics /// /// Panics if a file path could not read from or then written to -pub fn update(update_mode: UpdateMode) { - let lints = find_lint_decls(); - let (deprecated, renamed) = read_deprecated_lints(); +pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { + let lints = cx.find_lint_decls(); + let (deprecated, renamed) = cx.read_deprecated_lints(); generate_lint_files(update_mode, &lints, &deprecated, &renamed); } #[expect(clippy::too_many_lines)] pub fn generate_lint_files( update_mode: UpdateMode, - lints: &[Lint], - deprecated: &[DeprecatedLint], - renamed: &[RenamedLint], + lints: &[Lint<'_>], + deprecated: &[DeprecatedLint<'_>], + renamed: &[RenamedLint<'_>], ) { let mut updater = FileUpdater::default(); updater.update_file_checked( @@ -64,7 +61,7 @@ pub fn generate_lint_files( |dst| { for lint in lints .iter() - .map(|l| &*l.name) + .map(|l| l.name) .chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) .chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) .sorted() @@ -79,13 +76,13 @@ pub fn generate_lint_files( update_mode, "clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - let mut searcher = RustSearcher::new(src); + let mut cursor = Cursor::new(src); assert!( - searcher.find_token(Token::Ident("declare_with_version")) - && searcher.find_token(Token::Ident("declare_with_version")), + cursor.find_ident("declare_with_version").is_some() + && cursor.find_ident("declare_with_version").is_some(), "error reading deprecated lints" ); - dst.push_str(&src[..searcher.pos() as usize]); + dst.push_str(&src[..cursor.pos() as usize]); dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); for lint in deprecated { write!( @@ -135,13 +132,13 @@ pub fn generate_lint_files( dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); for lint in renamed { - if seen_lints.insert(&lint.new_name) { + if seen_lints.insert(lint.new_name) { writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); } } seen_lints.clear(); for lint in renamed { - if seen_lints.insert(&lint.old_name) { + if seen_lints.insert(lint.old_name) { writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); } } @@ -167,7 +164,7 @@ pub fn generate_lint_files( for lint_mod in lints .iter() .filter(|l| !l.module.is_empty()) - .map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0)) + .map(|l| l.module.split_once("::").map_or(l.module, |x| x.0)) .sorted() .dedup() { @@ -200,260 +197,3 @@ pub fn generate_lint_files( fn round_to_fifty(count: usize) -> usize { count / 50 * 50 } - -/// Lint data parsed from the Clippy source code. -#[derive(PartialEq, Eq, Debug)] -pub struct Lint { - pub name: String, - pub group: String, - pub module: String, - pub path: PathBuf, - pub declaration_range: Range, -} - -pub struct DeprecatedLint { - pub name: String, - pub reason: String, - pub version: String, -} - -pub struct RenamedLint { - pub old_name: String, - pub new_name: String, - pub version: String, -} - -/// Finds all lint declarations (`declare_clippy_lint!`) -#[must_use] -pub fn find_lint_decls() -> Vec { - let mut lints = Vec::with_capacity(1000); - let mut contents = String::new(); - for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { - let e = expect_action(e, ErrAction::Read, "."); - if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() { - continue; - } - let Ok(mut name) = e.file_name().into_string() else { - continue; - }; - if name.starts_with("clippy_lints") && name != "clippy_lints_internal" { - name.push_str("/src"); - for (file, module) in read_src_with_module(name.as_ref()) { - parse_clippy_lint_decls( - file.path(), - File::open_read_to_cleared_string(file.path(), &mut contents), - &module, - &mut lints, - ); - } - } - } - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - lints -} - -/// Reads the source files from the given root directory -fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator { - WalkDir::new(src_root).into_iter().filter_map(move |e| { - let e = expect_action(e, ErrAction::Read, src_root); - let path = e.path().as_os_str().as_encoded_bytes(); - if let Some(path) = path.strip_suffix(b".rs") - && let Some(path) = path.get(src_root.as_os_str().len() + 1..) - { - if path == b"lib" { - Some((e, String::new())) - } else { - let path = if let Some(path) = path.strip_suffix(b"mod") - && let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\")) - { - path - } else { - path - }; - if let Ok(path) = str::from_utf8(path) { - let path = path.replace(['/', '\\'], "::"); - Some((e, path)) - } else { - None - } - } - } else { - None - } - }) -} - -/// Parse a source file looking for `declare_clippy_lint` macro invocations. -fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // !{ /// docs - Bang, OpenBrace, AnyComment, - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, - // pub NAME, GROUP, - Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, - ]; - - let mut searcher = RustSearcher::new(contents); - while searcher.find_token(Ident("declare_clippy_lint")) { - let start = searcher.pos() as usize - "declare_clippy_lint".len(); - let (mut name, mut group) = ("", ""); - if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { - lints.push(Lint { - name: name.to_lowercase(), - group: group.into(), - module: module.into(), - path: path.into(), - declaration_range: start..searcher.pos() as usize, - }); - } - } -} - -#[must_use] -pub fn read_deprecated_lints() -> (Vec, Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, - // ("first", "second"), - OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, - ]; - #[rustfmt::skip] - static DEPRECATED_TOKENS: &[Token<'_>] = &[ - // !{ DEPRECATED(DEPRECATED_VERSION) = [ - Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - #[rustfmt::skip] - static RENAMED_TOKENS: &[Token<'_>] = &[ - // !{ RENAMED(RENAMED_VERSION) = [ - Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - - let path = "clippy_lints/src/deprecated_lints.rs"; - let mut deprecated = Vec::with_capacity(30); - let mut renamed = Vec::with_capacity(80); - let mut contents = String::new(); - File::open_read_to_cleared_string(path, &mut contents); - - let mut searcher = RustSearcher::new(&contents); - - // First instance is the macro definition. - assert!( - searcher.find_token(Ident("declare_with_version")), - "error reading deprecated lints" - ); - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { - let mut version = ""; - let mut name = ""; - let mut reason = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { - deprecated.push(DeprecatedLint { - name: parse_str_single_line(path.as_ref(), name), - reason: parse_str_single_line(path.as_ref(), reason), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading deprecated lints"); - } - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { - let mut version = ""; - let mut old_name = ""; - let mut new_name = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) { - renamed.push(RenamedLint { - old_name: parse_str_single_line(path.as_ref(), old_name), - new_name: parse_str_single_line(path.as_ref(), new_name), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading renamed lints"); - } - - deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name)); - (deprecated, renamed) -} - -/// Removes the line splices and surrounding quotes from a string literal -fn parse_str_lit(s: &str) -> String { - let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#'); - let s = s - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); - let mut res = String::with_capacity(s.len()); - rustc_literal_escaper::unescape_str(s, &mut |_, ch| { - if let Ok(ch) = ch { - res.push(ch); - } - }); - res -} - -fn parse_str_single_line(path: &Path, s: &str) -> String { - let value = parse_str_lit(s); - assert!( - !value.contains('\n'), - "error parsing `{}`: `{s}` should be a single line string", - path.display(), - ); - value -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_clippy_lint_decls() { - static CONTENTS: &str = r#" - declare_clippy_lint! { - #[clippy::version = "Hello Clippy!"] - pub PTR_ARG, - style, - "really long \ - text" - } - - declare_clippy_lint!{ - #[clippy::version = "Test version"] - pub DOC_MARKDOWN, - pedantic, - "single line" - } - "#; - let mut result = Vec::new(); - parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![ - Lint { - name: "ptr_arg".into(), - group: "style".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - Lint { - name: "doc_markdown".into(), - group: "pedantic".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - ]; - assert_eq!(expected, result); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 057951d0e33bd..52452dd86b490 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,9 +1,9 @@ use core::fmt::{self, Display}; +use core::marker::PhantomData; use core::num::NonZero; -use core::ops::Range; -use core::slice; +use core::ops::{Deref, DerefMut}; +use core::range::Range; use core::str::FromStr; -use rustc_lexer::{self as lexer, FrontmatterAllowed}; use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; @@ -12,6 +12,24 @@ use std::process::{self, Command, Stdio}; use std::{env, thread}; use walkdir::WalkDir; +pub struct Scoped<'inner, 'outer: 'inner, T>(T, PhantomData<&'inner mut T>, PhantomData<&'outer mut ()>); +impl Scoped<'_, '_, T> { + pub fn new(value: T) -> Self { + Self(value, PhantomData, PhantomData) + } +} +impl Deref for Scoped<'_, '_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Scoped<'_, '_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Clone, Copy)] pub enum ErrAction { Open, @@ -410,179 +428,6 @@ pub fn update_text_region_fn( move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert) } -#[derive(Clone, Copy)] -pub enum Token<'a> { - /// Matches any number of comments / doc comments. - AnyComment, - Ident(&'a str), - CaptureIdent, - LitStr, - CaptureLitStr, - Bang, - CloseBrace, - CloseBracket, - CloseParen, - /// This will consume the first colon even if the second doesn't exist. - DoubleColon, - Comma, - Eq, - Lifetime, - Lt, - Gt, - OpenBrace, - OpenBracket, - OpenParen, - Pound, - Semi, -} - -pub struct RustSearcher<'txt> { - text: &'txt str, - cursor: lexer::Cursor<'txt>, - pos: u32, - next_token: lexer::Token, -} -impl<'txt> RustSearcher<'txt> { - #[must_use] - #[expect(clippy::inconsistent_struct_constructor)] - pub fn new(text: &'txt str) -> Self { - let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); - Self { - text, - pos: 0, - next_token: cursor.advance_token(), - cursor, - } - } - - #[must_use] - pub fn peek_text(&self) -> &'txt str { - &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] - } - - #[must_use] - pub fn peek_len(&self) -> u32 { - self.next_token.len - } - - #[must_use] - pub fn peek(&self) -> lexer::TokenKind { - self.next_token.kind - } - - #[must_use] - pub fn pos(&self) -> u32 { - self.pos - } - - #[must_use] - pub fn at_end(&self) -> bool { - self.next_token.kind == lexer::TokenKind::Eof - } - - pub fn step(&mut self) { - // `next_len` is zero for the sentinel value and the eof marker. - self.pos += self.next_token.len; - self.next_token = self.cursor.advance_token(); - } - - /// Consumes the next token if it matches the requested value and captures the value if - /// requested. Returns true if a token was matched. - fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool { - loop { - match (token, self.next_token.kind) { - (_, lexer::TokenKind::Whitespace) - | ( - Token::AnyComment, - lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, - ) => self.step(), - (Token::AnyComment, _) => return true, - (Token::Bang, lexer::TokenKind::Bang) - | (Token::CloseBrace, lexer::TokenKind::CloseBrace) - | (Token::CloseBracket, lexer::TokenKind::CloseBracket) - | (Token::CloseParen, lexer::TokenKind::CloseParen) - | (Token::Comma, lexer::TokenKind::Comma) - | (Token::Eq, lexer::TokenKind::Eq) - | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) - | (Token::Lt, lexer::TokenKind::Lt) - | (Token::Gt, lexer::TokenKind::Gt) - | (Token::OpenBrace, lexer::TokenKind::OpenBrace) - | (Token::OpenBracket, lexer::TokenKind::OpenBracket) - | (Token::OpenParen, lexer::TokenKind::OpenParen) - | (Token::Pound, lexer::TokenKind::Pound) - | (Token::Semi, lexer::TokenKind::Semi) - | ( - Token::LitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) => { - self.step(); - return true; - }, - (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { - self.step(); - return true; - }, - (Token::DoubleColon, lexer::TokenKind::Colon) => { - self.step(); - if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) { - self.step(); - return true; - } - return false; - }, - ( - Token::CaptureLitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) - | (Token::CaptureIdent, lexer::TokenKind::Ident) => { - **captures.next().unwrap() = self.peek_text(); - self.step(); - return true; - }, - _ => return false, - } - } - } - - #[must_use] - pub fn find_token(&mut self, token: Token<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return false; - } - } - true - } - - #[must_use] - pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> { - let mut res = ""; - let mut capture = &mut res; - let mut capture = slice::from_mut(&mut capture).iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return None; - } - } - Some(res) - } - - #[must_use] - pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool { - let mut captures = captures.iter_mut(); - tokens.iter().all(|&t| self.read_token(t, &mut captures)) - } -} - #[track_caller] pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { match OpenOptions::new().create_new(true).write(true).open(new_name) { @@ -748,8 +593,8 @@ pub fn delete_dir_if_exists(path: &Path) { } /// Walks all items excluding top-level dot files/directories and any target directories. -pub fn walk_dir_no_dot_or_target() -> impl Iterator> { - WalkDir::new(".").into_iter().filter_entry(|e| { +pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator> { + WalkDir::new(p).into_iter().filter_entry(|e| { e.path() .file_name() .is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.')) diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 42486e182ee34..bc97746a1cbae 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.92" +version = "0.1.93" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 375d179681da3..a754eea311651 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -135,7 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::duplicate_mod::DUPLICATE_MOD_INFO, crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, - crate::empty_enum::EMPTY_ENUM_INFO, + crate::empty_enums::EMPTY_ENUMS_INFO, crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, @@ -227,8 +227,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO, crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO, - crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, - crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO, crate::item_name_repetitions::MODULE_INCEPTION_INFO, crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO, @@ -258,7 +256,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::lifetimes::ELIDABLE_LIFETIME_NAMES_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, - crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO, crate::literal_representation::LARGE_DIGIT_GROUPS_INFO, @@ -298,7 +295,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, crate::manual_clamp::MANUAL_CLAMP_INFO, - crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO, crate::manual_float_methods::MANUAL_IS_FINITE_INFO, crate::manual_float_methods::MANUAL_IS_INFINITE_INFO, crate::manual_hash_one::MANUAL_HASH_ONE_INFO, @@ -404,6 +400,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::JOIN_ABSOLUTE_PATHS_INFO, + crate::methods::LINES_FILTER_MAP_OK_INFO, crate::methods::MANUAL_CONTAINS_INFO, crate::methods::MANUAL_C_STR_LITERALS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, @@ -545,7 +542,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::needless_continue::NEEDLESS_CONTINUE_INFO, crate::needless_else::NEEDLESS_ELSE_INFO, crate::needless_for_each::NEEDLESS_FOR_EACH_INFO, - crate::needless_if::NEEDLESS_IF_INFO, + crate::needless_ifs::NEEDLESS_IFS_INFO, crate::needless_late_init::NEEDLESS_LATE_INIT_INFO, crate::needless_maybe_sized::NEEDLESS_MAYBE_SIZED_INFO, crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO, @@ -592,6 +589,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::operators::IMPOSSIBLE_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, crate::operators::INTEGER_DIVISION_INFO, + crate::operators::INTEGER_DIVISION_REMAINDER_USED_INFO, + crate::operators::INVALID_UPCAST_COMPARISONS_INFO, + crate::operators::MANUAL_DIV_CEIL_INFO, crate::operators::MANUAL_IS_MULTIPLE_OF_INFO, crate::operators::MANUAL_MIDPOINT_INFO, crate::operators::MISREFACTORED_ASSIGN_OP_INFO, diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index f087a894d76ae..f010d17917f9e 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -85,6 +85,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::drop_copy", "dropping_copy_types"), #[clippy::version = ""] ("clippy::drop_ref", "dropping_references"), + #[clippy::version = "1.92.0"] + ("clippy::empty_enum", "clippy::empty_enums"), #[clippy::version = ""] ("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"), #[clippy::version = "1.53.0"] @@ -137,6 +139,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"), #[clippy::version = "1.80.0"] ("clippy::mismatched_target_os", "unexpected_cfgs"), + #[clippy::version = "1.92.0"] + ("clippy::needless_if", "clippy::needless_ifs"), #[clippy::version = ""] ("clippy::new_without_default_derive", "clippy::new_without_default"), #[clippy::version = ""] diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 2a3fb82946117..1e1d6e69cc91c 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -22,7 +22,6 @@ use rustc_resolve::rustdoc::{ }; use rustc_session::impl_lint_pass; use rustc_span::Span; -use rustc_span::edition::Edition; use std::ops::Range; use url::Url; @@ -36,6 +35,7 @@ mod markdown; mod missing_headers; mod needless_doctest_main; mod suspicious_doc_comments; +mod test_attr_in_doctest; mod too_long_first_doc_paragraph; declare_clippy_lint! { @@ -900,8 +900,6 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ )) } -const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"]; - enum Container { Blockquote, List(usize), @@ -966,6 +964,70 @@ fn check_for_code_clusters<'a, Events: Iterator Self { + Self { + no_run: false, + ignore: false, + compile_fail: false, + + rust: true, + } + } +} + +impl CodeTags { + /// Based on + fn parse(lang: &str) -> Self { + let mut tags = Self::default(); + + let mut seen_rust_tags = false; + let mut seen_other_tags = false; + for item in lang.split([',', ' ', '\t']) { + match item.trim() { + "" => {}, + "rust" => { + tags.rust = true; + seen_rust_tags = true; + }, + "ignore" => { + tags.ignore = true; + seen_rust_tags = !seen_other_tags; + }, + "no_run" => { + tags.no_run = true; + seen_rust_tags = !seen_other_tags; + }, + "should_panic" => seen_rust_tags = !seen_other_tags, + "compile_fail" => { + tags.compile_fail = true; + seen_rust_tags = !seen_other_tags || seen_rust_tags; + }, + "test_harness" | "standalone_crate" => { + seen_rust_tags = !seen_other_tags || seen_rust_tags; + }, + _ if item.starts_with("ignore-") => seen_rust_tags = true, + _ if item.starts_with("edition") => {}, + _ => seen_other_tags = true, + } + } + + tags.rust &= seen_rust_tags || !seen_other_tags; + + tags + } +} + /// Checks parsed documentation. /// This walks the "events" (think sections of markdown) produced by `pulldown_cmark`, /// so lints here will generally access that information. @@ -981,14 +1043,10 @@ fn check_doc<'a, Events: Iterator, Range DocHeaders { // true if a safety header was found let mut headers = DocHeaders::default(); - let mut in_code = false; + let mut code = None; let mut in_link = None; let mut in_heading = false; let mut in_footnote_definition = false; - let mut is_rust = false; - let mut no_test = false; - let mut ignore = false; - let mut edition = None; let mut ticks_unbalanced = false; let mut text_to_check: Vec<(CowStr<'_>, Range, isize)> = Vec::new(); let mut paragraph_range = 0..0; @@ -1048,31 +1106,12 @@ fn check_doc<'a, Events: Iterator, Range { - in_code = true; - if let CodeBlockKind::Fenced(lang) = kind { - for item in lang.split(',') { - if item == "ignore" { - is_rust = false; - break; - } else if item == "no_test" { - no_test = true; - } else if item == "no_run" || item == "compile_fail" { - ignore = true; - } - if let Some(stripped) = item.strip_prefix("edition") { - is_rust = true; - edition = stripped.parse::().ok(); - } else if item.is_empty() || RUST_CODE.contains(&item) { - is_rust = true; - } - } - } - }, - End(TagEnd::CodeBlock) => { - in_code = false; - is_rust = false; - ignore = false; + code = Some(match kind { + CodeBlockKind::Indented => CodeTags::default(), + CodeBlockKind::Fenced(lang) => CodeTags::parse(lang), + }); }, + End(TagEnd::CodeBlock) => code = None, Start(Link { dest_url, .. }) => in_link = Some(dest_url), End(TagEnd::Link) => in_link = None, Start(Heading { .. } | Paragraph | Item) => { @@ -1182,7 +1221,7 @@ fn check_doc<'a, Events: Iterator, Range, Range>) { - test_attr_spans.extend( - item.attrs - .iter() - .find(|attr| attr.has_name(sym::test)) - .map(|attr| attr.span.lo().to_usize()..ident.span.hi().to_usize()), - ); -} - -pub fn check( - cx: &LateContext<'_>, - text: &str, - edition: Edition, - range: Range, - fragments: Fragments<'_>, - ignore: bool, -) { - // return whether the code contains a needless `fn main` plus a vector of byte position ranges - // of all `#[test]` attributes in not ignored code examples - fn check_code_sample(code: String, edition: Edition, ignore: bool) -> (bool, Vec>) { - rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_globals_then(edition, &[], None, || { - let mut test_attr_spans = vec![]; - let filename = FileName::anon_source_code(&code); - - let translator = rustc_driver::default_translator(); - let emitter = HumanEmitter::new(AutoStream::never(Box::new(io::sink())), translator); - let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); - #[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx - let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); - let psess = ParseSess::with_dcx(dcx, sm); - - let mut parser = - match new_parser_from_source_str(&psess, filename, code, StripTokens::ShebangAndFrontmatter) { - Ok(p) => p, - Err(errs) => { - errs.into_iter().for_each(Diag::cancel); - return (false, test_attr_spans); - }, - }; - - let mut relevant_main_found = false; - let mut eligible = true; - loop { - match parser.parse_item(ForceCollect::No) { - Ok(Some(item)) => match &item.kind { - ItemKind::Fn(box Fn { - ident, - sig, - body: Some(block), - .. - }) if ident.name == sym::main => { - if !ignore { - get_test_spans(&item, *ident, &mut test_attr_spans); - } - - let is_async = matches!(sig.header.coroutine_kind, Some(CoroutineKind::Async { .. })); - let returns_nothing = match &sig.decl.output { - FnRetTy::Default(..) => true, - FnRetTy::Ty(ty) if ty.kind.is_unit() => true, - FnRetTy::Ty(_) => false, - }; - - if returns_nothing && !is_async && !block.stmts.is_empty() { - // This main function should be linted, but only if there are no other functions - relevant_main_found = true; - } else { - // This main function should not be linted, we're done - eligible = false; - } - }, - // Another function was found; this case is ignored for needless_doctest_main - ItemKind::Fn(fn_) => { - eligible = false; - if ignore { - // If ignore is active invalidating one lint, - // and we already found another function thus - // invalidating the other one, we have no - // business continuing. - return (false, test_attr_spans); - } - get_test_spans(&item, fn_.ident, &mut test_attr_spans); - }, - // Tests with one of these items are ignored - ItemKind::Static(..) - | ItemKind::Const(..) - | ItemKind::ExternCrate(..) - | ItemKind::ForeignMod(..) => { - eligible = false; - }, - _ => {}, - }, - Ok(None) => break, - Err(e) => { - // See issue #15041. When calling `.cancel()` on the `Diag`, Clippy will unexpectedly panic - // when the `Diag` is unwinded. Meanwhile, we can just call `.emit()`, since the `DiagCtxt` - // is just a sink, nothing will be printed. - e.emit(); - return (false, test_attr_spans); - }, - } - } - - (relevant_main_found & eligible, test_attr_spans) - }) - }) - .ok() - .unwrap_or_default() +use rustc_span::InnerSpan; + +fn returns_unit<'a>(mut tokens: impl Iterator) -> bool { + let mut next = || tokens.next().map_or(TokenKind::Whitespace, |(kind, ..)| kind); + + match next() { + // { + TokenKind::OpenBrace => true, + // - > ( ) { + TokenKind::Minus => { + next() == TokenKind::Gt + && next() == TokenKind::OpenParen + && next() == TokenKind::CloseParen + && next() == TokenKind::OpenBrace + }, + _ => false, } +} - let trailing_whitespace = text.len() - text.trim_end().len(); - - // We currently only test for "fn main". Checking for the real - // entrypoint (with tcx.entry_fn(())) in each block would be unnecessarily - // expensive, as those are probably intended and relevant. Same goes for - // macros and other weird ways of declaring a main function. - // - // Also, as we only check for attribute names and don't do macro expansion, - // we can check only for #[test] - - if !((text.contains("main") && text.contains("fn")) || text.contains("#[test]")) { +pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) { + if !text.contains("main") { return; } - // Because of the global session, we need to create a new session in a different thread with - // the edition we need. - let text = text.to_owned(); - let (has_main, test_attr_spans) = thread::spawn(move || check_code_sample(text, edition, ignore)) - .join() - .expect("thread::spawn failed"); - if has_main && let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace) { - span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); - } - for span in test_attr_spans { - let span = (range.start + span.start)..(range.start + span.end); - if let Some(span) = fragments.span(cx, span) { - span_lint(cx, TEST_ATTR_IN_DOCTEST, span, "unit tests in doctest are not executed"); + let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| { + !matches!( + kind, + TokenKind::Whitespace | TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } + ) + }); + if let Some((TokenKind::Ident, "fn", fn_span)) = tokens.next() + && let Some((TokenKind::Ident, "main", main_span)) = tokens.next() + && let Some((TokenKind::OpenParen, ..)) = tokens.next() + && let Some((TokenKind::CloseParen, ..)) = tokens.next() + && returns_unit(&mut tokens) + { + let mut depth = 1; + for (kind, ..) in &mut tokens { + match kind { + TokenKind::OpenBrace => depth += 1, + TokenKind::CloseBrace => { + depth -= 1; + if depth == 0 { + break; + } + }, + _ => {}, + } + } + + if tokens.next().is_none() + && let Some(span) = fragments.span(cx, fn_span.start + offset..main_span.end + offset) + { + span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); } } } diff --git a/clippy_lints/src/doc/test_attr_in_doctest.rs b/clippy_lints/src/doc/test_attr_in_doctest.rs new file mode 100644 index 0000000000000..65738434ac286 --- /dev/null +++ b/clippy_lints/src/doc/test_attr_in_doctest.rs @@ -0,0 +1,34 @@ +use super::Fragments; +use crate::doc::TEST_ATTR_IN_DOCTEST; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::tokenize_with_text; +use rustc_lexer::TokenKind; +use rustc_lint::LateContext; + +pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) { + if !text.contains("#[test]") { + return; + } + + let mut spans = Vec::new(); + let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| kind != TokenKind::Whitespace); + while let Some(token) = tokens.next() { + if let (TokenKind::Pound, _, pound_span) = token + && let Some((TokenKind::OpenBracket, ..)) = tokens.next() + && let Some((TokenKind::Ident, "test", _)) = tokens.next() + && let Some((TokenKind::CloseBracket, _, close_span)) = tokens.next() + && let Some(span) = fragments.span(cx, pound_span.start + offset..close_span.end + offset) + { + spans.push(span); + } + } + + if !spans.is_empty() { + span_lint( + cx, + TEST_ATTR_IN_DOCTEST, + spans, + "unit tests in doctest are not executed", + ); + } +} diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index bddf4702fb340..8defbeeaa5f22 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{HasSession, snippet_with_applicability, snippet_with_context}; +use clippy_utils::source::{HasSession, SpanRangeExt, snippet_with_applicability, snippet_with_context}; use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -47,8 +47,12 @@ impl EarlyLintPass for DoubleParens { // ^^^^^^ expr // ^^^^ inner ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => { - // suggest removing the outer parens - if expr.span.eq_ctxt(inner.span) { + if expr.span.eq_ctxt(inner.span) + && !expr.span.in_external_macro(cx.sess().source_map()) + && check_source(cx, inner) + { + // suggest removing the outer parens + let mut applicability = Applicability::MachineApplicable; // We don't need to use `snippet_with_context` here, because: // - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above) @@ -74,8 +78,12 @@ impl EarlyLintPass for DoubleParens { if let [arg] = &**args && let ExprKind::Paren(inner) = &arg.kind => { - // suggest removing the inner parens - if expr.span.eq_ctxt(arg.span) { + if expr.span.eq_ctxt(arg.span) + && !arg.span.in_external_macro(cx.sess().source_map()) + && check_source(cx, arg) + { + // suggest removing the inner parens + let mut applicability = Applicability::MachineApplicable; let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0; span_lint_and_sugg( @@ -93,3 +101,22 @@ impl EarlyLintPass for DoubleParens { } } } + +/// Check that the span does indeed look like `( (..) )` +fn check_source(cx: &EarlyContext<'_>, inner: &Expr) -> bool { + if let Some(sfr) = inner.span.get_source_range(cx) + // this is the same as `SourceFileRange::as_str`, but doesn't apply the range right away, because + // we're interested in the source code outside it + && let Some(src) = sfr.sf.src.as_ref().map(|src| src.as_str()) + && let Some((start, outer_after_inner)) = src.split_at_checked(sfr.range.end) + && let Some((outer_before_inner, inner)) = start.split_at_checked(sfr.range.start) + && outer_before_inner.trim_end().ends_with('(') + && inner.starts_with('(') + && inner.ends_with(')') + && outer_after_inner.trim_start().starts_with(')') + { + true + } else { + false + } +} diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enums.rs similarity index 83% rename from clippy_lints/src/empty_enum.rs rename to clippy_lints/src/empty_enums.rs index b0389fd9a2f62..f96854411fe6f 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enums.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::span_contains_cfg; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -25,10 +26,6 @@ declare_clippy_lint! { /// matched to mark code unreachable. If the field is not visible, then the struct /// acts like any other struct with private fields. /// - /// * If the enum has no variants only because all variants happen to be - /// [disabled by conditional compilation][cfg], then it would be appropriate - /// to allow the lint, with `#[allow(empty_enum)]`. - /// /// For further information, visit /// [the never type’s documentation][`!`]. /// @@ -53,24 +50,24 @@ declare_clippy_lint! { /// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction /// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html #[clippy::version = "pre 1.29.0"] - pub EMPTY_ENUM, + pub EMPTY_ENUMS, pedantic, "enum with no variants" } -declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]); +declare_lint_pass!(EmptyEnums => [EMPTY_ENUMS]); -impl LateLintPass<'_> for EmptyEnum { +impl LateLintPass<'_> for EmptyEnums { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if let ItemKind::Enum(..) = item.kind + if let ItemKind::Enum(.., def) = item.kind + && def.variants.is_empty() // Only suggest the `never_type` if the feature is enabled && cx.tcx.features().never_type() - && let Some(adt) = cx.tcx.type_of(item.owner_id).instantiate_identity().ty_adt_def() - && adt.variants().is_empty() + && !span_contains_cfg(cx, item.span) { span_lint_and_help( cx, - EMPTY_ENUM, + EMPTY_ENUMS, item.span, "enum with no variants", None, diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index c0b0fd88d9e16..fb98cb30ae902 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -157,6 +157,11 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { let spans = if explicit_params.len() == extra_params.len() { vec![self.generics.span] // Remove the entire list of generics } else { + // 1. Start from the last extra param + // 2. While the params preceding it are also extra, construct spans going from the current param to + // the comma before it + // 3. Once this chain of extra params stops, switch to constructing spans going from the current + // param to the comma _after_ it let mut end: Option = None; extra_params .iter() diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index 1c751643becb6..3b9c70e23e20d 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -110,7 +110,7 @@ declare_clippy_lint! { /// } if bar { // looks like an `else` is missing here /// } /// ``` - #[clippy::version = "1.90.0"] + #[clippy::version = "1.91.0"] pub POSSIBLE_MISSING_ELSE, suspicious, "possibly missing `else`" diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index bfe7a6355c00d..716334656926d 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -3,10 +3,9 @@ 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_data_structures::fx::FxHashMap; -use rustc_hir::def::DefKind; use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::{ExpnKind, Span, sym}; @@ -83,6 +82,10 @@ pub struct IncompatibleMsrv { availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, core_crate: Option, + + // The most recently called path. Used to skip checking the path after it's + // been checked when visiting the call expression. + called_path: Option, } impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]); @@ -98,6 +101,7 @@ impl IncompatibleMsrv { .iter() .find(|krate| tcx.crate_name(**krate) == sym::core) .copied(), + called_path: None, } } @@ -140,7 +144,14 @@ impl IncompatibleMsrv { } /// 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) { + fn emit_lint_if_under_msrv( + &mut self, + cx: &LateContext<'_>, + needs_const: bool, + 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. return; @@ -158,10 +169,6 @@ 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 Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const) @@ -190,13 +197,33 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { match expr.kind { ExprKind::MethodCall(_, _, _, span) => { if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); + self.emit_lint_if_under_msrv(cx, is_in_const_context(cx), method_did, expr.hir_id, span); } }, - ExprKind::Path(qpath) => { - 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); - } + ExprKind::Call(callee, _) if let ExprKind::Path(qpath) = callee.kind => { + self.called_path = Some(callee.hir_id); + let needs_const = is_in_const_context(cx); + let def_id = if let Some(def_id) = cx.qpath_res(&qpath, callee.hir_id).opt_def_id() { + def_id + } else if needs_const && let ty::FnDef(def_id, _) = *cx.typeck_results().expr_ty(callee).kind() { + // Edge case where a function is first assigned then called. + // We previously would have warned for the non-const MSRV, when + // checking the path, but now that it's called the const MSRV + // must also be met. + def_id + } else { + return; + }; + self.emit_lint_if_under_msrv(cx, needs_const, def_id, expr.hir_id, callee.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) + if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() + && self.called_path != Some(expr.hir_id) => + { + self.emit_lint_if_under_msrv(cx, false, path_def_id, expr.hir_id, expr.span); }, _ => {}, } @@ -208,7 +235,7 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { // `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); + self.emit_lint_if_under_msrv(cx, false, ty_def_id, hir_ty.hir_id, hir_ty.span); } } } diff --git a/clippy_lints/src/integer_division_remainder_used.rs b/clippy_lints/src/integer_division_remainder_used.rs deleted file mode 100644 index a1215491b48c3..0000000000000 --- a/clippy_lints/src/integer_division_remainder_used.rs +++ /dev/null @@ -1,50 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use rustc_ast::BinOpKind; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self}; -use rustc_session::declare_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of division (`/`) and remainder (`%`) operations - /// when performed on any integer types using the default `Div` and `Rem` trait implementations. - /// - /// ### Why restrict this? - /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, - /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). - /// - /// ### Example - /// ```no_run - /// let my_div = 10 / 2; - /// ``` - /// Use instead: - /// ```no_run - /// let my_div = 10 >> 1; - /// ``` - #[clippy::version = "1.79.0"] - pub INTEGER_DIVISION_REMAINDER_USED, - restriction, - "use of disallowed default division and remainder operations" -} - -declare_lint_pass!(IntegerDivisionRemainderUsed => [INTEGER_DIVISION_REMAINDER_USED]); - -impl LateLintPass<'_> for IntegerDivisionRemainderUsed { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary(op, lhs, rhs) = &expr.kind - && let BinOpKind::Div | BinOpKind::Rem = op.node - && let lhs_ty = cx.typeck_results().expr_ty(lhs) - && let rhs_ty = cx.typeck_results().expr_ty(rhs) - && let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind() - && let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind() - { - span_lint( - cx, - INTEGER_DIVISION_REMAINDER_USED, - expr.span.source_callsite(), - format!("use of {} has been disallowed in this context", op.node.as_str()), - ); - } - } -} diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 04a8e4739b853..877bd34a732bc 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,4 +1,6 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; @@ -10,12 +12,12 @@ 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, + Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, RustcVersion, + StabilityLevel, StableSince, TraitItemId, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FnSig, Ty}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; use rustc_span::symbol::kw; use rustc_span::{Ident, Span, Symbol}; @@ -120,7 +122,17 @@ declare_clippy_lint! { "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead" } -declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]); +pub struct LenZero { + msrv: Msrv, +} + +impl_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]); + +impl LenZero { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} impl<'tcx> LateLintPass<'tcx> for LenZero { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { @@ -184,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { _ => false, } && !expr.span.from_expansion() - && has_is_empty(cx, lt.init) + && has_is_empty(cx, lt.init, self.msrv) { let mut applicability = Applicability::MachineApplicable; @@ -206,7 +218,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::PartialEq) && !expr.span.from_expansion() { - check_empty_expr( + self.check_empty_expr( cx, expr.span, lhs_expr, @@ -226,29 +238,110 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { let actual_span = span_without_enclosing_paren(cx, expr.span); match cmp { BinOpKind::Eq => { - check_cmp(cx, actual_span, left, right, "", 0); // len == 0 - check_cmp(cx, actual_span, right, left, "", 0); // 0 == len + self.check_cmp(cx, actual_span, left, right, "", 0); // len == 0 + self.check_cmp(cx, actual_span, right, left, "", 0); // 0 == len }, BinOpKind::Ne => { - check_cmp(cx, actual_span, left, right, "!", 0); // len != 0 - check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len + self.check_cmp(cx, actual_span, left, right, "!", 0); // len != 0 + self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len }, BinOpKind::Gt => { - check_cmp(cx, actual_span, left, right, "!", 0); // len > 0 - check_cmp(cx, actual_span, right, left, "", 1); // 1 > len + self.check_cmp(cx, actual_span, left, right, "!", 0); // len > 0 + self.check_cmp(cx, actual_span, right, left, "", 1); // 1 > len }, BinOpKind::Lt => { - check_cmp(cx, actual_span, left, right, "", 1); // len < 1 - check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len + self.check_cmp(cx, actual_span, left, right, "", 1); // len < 1 + self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len }, - BinOpKind::Ge => check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1 - BinOpKind::Le => check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len + BinOpKind::Ge => self.check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1 + BinOpKind::Le => self.check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len _ => (), } } } } +impl LenZero { + fn check_cmp( + &self, + cx: &LateContext<'_>, + span: Span, + method: &Expr<'_>, + lit: &Expr<'_>, + op: &str, + compare_to: u32, + ) { + if method.span.from_expansion() { + return; + } + + if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) { + // check if we are in an is_empty() method + if parent_item_name(cx, method) == Some(sym::is_empty) { + return; + } + + self.check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to); + } else { + self.check_empty_expr(cx, span, method, lit, op); + } + } + + #[expect(clippy::too_many_arguments)] + fn check_len( + &self, + cx: &LateContext<'_>, + span: Span, + method_name: Symbol, + receiver: &Expr<'_>, + lit: &LitKind, + op: &str, + compare_to: u32, + ) { + if let LitKind::Int(lit, _) = *lit { + // check if length is compared to the specified number + if lit != u128::from(compare_to) { + return; + } + + if method_name == sym::len && has_is_empty(cx, receiver, self.msrv) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + LEN_ZERO, + span, + format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), + format!("using `{op}is_empty` is clearer and more explicit"), + format!( + "{op}{}.is_empty()", + snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0, + ), + applicability, + ); + } + } + } + + fn check_empty_expr(&self, cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { + if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1, self.msrv) { + let mut applicability = Applicability::MachineApplicable; + + let lit1 = peel_ref_operators(cx, lit1); + let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren(); + + span_lint_and_sugg( + cx, + COMPARISON_TO_EMPTY, + span, + "comparison to empty slice", + format!("using `{op}is_empty` is clearer and more explicit"), + format!("{op}{lit_str}.is_empty()"), + applicability, + ); + } + } +} + fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span { let Some(snippet) = span.get_source_text(cx) else { return span; @@ -513,75 +606,6 @@ fn check_for_is_empty( } } -fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { - if method.span.from_expansion() { - return; - } - - if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) { - // check if we are in an is_empty() method - if parent_item_name(cx, method) == Some(sym::is_empty) { - return; - } - - check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to); - } else { - check_empty_expr(cx, span, method, lit, op); - } -} - -fn check_len( - cx: &LateContext<'_>, - span: Span, - method_name: Symbol, - receiver: &Expr<'_>, - lit: &LitKind, - op: &str, - compare_to: u32, -) { - if let LitKind::Int(lit, _) = *lit { - // check if length is compared to the specified number - if lit != u128::from(compare_to) { - return; - } - - if method_name == sym::len && has_is_empty(cx, receiver) { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - LEN_ZERO, - span, - format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), - format!("using `{op}is_empty` is clearer and more explicit"), - format!( - "{op}{}.is_empty()", - snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0, - ), - applicability, - ); - } - } -} - -fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { - if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) { - let mut applicability = Applicability::MachineApplicable; - - let lit1 = peel_ref_operators(cx, lit1); - let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren(); - - span_lint_and_sugg( - cx, - COMPARISON_TO_EMPTY, - span, - "comparison to empty slice", - format!("using `{op}is_empty` is clearer and more explicit"), - format!("{op}{lit_str}.is_empty()"), - applicability, - ); - } -} - fn is_empty_string(expr: &Expr<'_>) -> bool { if let ExprKind::Lit(lit) = expr.kind && let LitKind::Str(lit, _) = lit.node @@ -600,45 +624,59 @@ fn is_empty_array(expr: &Expr<'_>) -> bool { } /// Checks if this type has an `is_empty` method. -fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { +fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) -> bool { /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. - fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool { + fn is_is_empty_and_stable(cx: &LateContext<'_>, item: &ty::AssocItem, msrv: Msrv) -> bool { if item.is_fn() { let sig = cx.tcx.fn_sig(item.def_id).skip_binder(); let ty = sig.skip_binder(); ty.inputs().len() == 1 + && cx.tcx.lookup_stability(item.def_id).is_none_or(|stability| { + if let StabilityLevel::Stable { since, .. } = stability.level { + let version = match since { + StableSince::Version(version) => version, + StableSince::Current => RustcVersion::CURRENT, + StableSince::Err(_) => return false, + }; + + msrv.meets(cx, version) + } else { + // Unstable fn, check if the feature is enabled. + cx.tcx.features().enabled(stability.feature) && msrv.current(cx).is_none() + } + }) } else { false } } /// Checks the inherent impl's items for an `is_empty(self)` method. - fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool { + fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId, msrv: Msrv) -> bool { cx.tcx.inherent_impls(id).iter().any(|imp| { cx.tcx .associated_items(*imp) .filter_by_name_unhygienic(sym::is_empty) - .any(|item| is_is_empty(cx, item)) + .any(|item| is_is_empty_and_stable(cx, item, msrv)) }) } - fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool { + fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize, msrv: Msrv) -> bool { match ty.kind() { ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| { cx.tcx .associated_items(principal.def_id()) .filter_by_name_unhygienic(sym::is_empty) - .any(|item| is_is_empty(cx, item)) + .any(|item| is_is_empty_and_stable(cx, item, msrv)) }), - ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), + ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id, msrv), ty::Adt(id, _) => { - has_is_empty_impl(cx, id.did()) + has_is_empty_impl(cx, id.did(), msrv) || (cx.tcx.recursion_limit().value_within_limit(depth) && cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| { implements_trait(cx, ty, deref_id, &[]) && cx .get_associated_type(ty, deref_id, sym::Target) - .is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1)) + .is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1, msrv)) })) }, ty::Array(..) | ty::Slice(..) | ty::Str => true, @@ -646,5 +684,5 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } } - ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0) + ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0, msrv) } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a4ad9424b3eb1..99cafc7fc6d89 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -32,7 +32,6 @@ extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_data_structures; -extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_hir; extern crate rustc_hir_analysis; @@ -43,7 +42,6 @@ extern crate rustc_infer; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_middle; -extern crate rustc_parse; extern crate rustc_parse_format; extern crate rustc_resolve; extern crate rustc_session; @@ -117,7 +115,7 @@ mod drop_forget_ref; mod duplicate_mod; mod else_if_without_else; mod empty_drop; -mod empty_enum; +mod empty_enums; mod empty_line_after; mod empty_with_brackets; mod endian_bytes; @@ -171,8 +169,6 @@ mod inherent_to_string; mod init_numbered_fields; mod inline_fn_without_body; mod int_plus_one; -mod integer_division_remainder_used; -mod invalid_upcast_comparisons; mod item_name_repetitions; mod items_after_statements; mod items_after_test_module; @@ -191,7 +187,6 @@ mod let_if_seq; mod let_underscore; mod let_with_type_underscore; mod lifetimes; -mod lines_filter_map_ok; mod literal_representation; mod literal_string_with_formatting_args; mod loops; @@ -203,7 +198,6 @@ mod manual_assert; mod manual_async_fn; mod manual_bits; mod manual_clamp; -mod manual_div_ceil; mod manual_float_methods; mod manual_hash_one; mod manual_ignore_case_cmp; @@ -255,7 +249,7 @@ mod needless_borrows_for_generic_args; mod needless_continue; mod needless_else; mod needless_for_each; -mod needless_if; +mod needless_ifs; mod needless_late_init; mod needless_maybe_sized; mod needless_parens_on_range_literals; @@ -481,7 +475,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(mut_mut::MutMut::default())); store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(len_zero::LenZero)); + store.register_late_pass(move |_| Box::new(len_zero::LenZero::new(conf))); store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf))); store.register_late_pass(|_| Box::new(blocks_in_conditions::BlocksInConditions)); store.register_late_pass(|_| Box::new(unicode::Unicode)); @@ -542,8 +536,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(derive::Derive)); store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))); store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef)); - store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum)); - store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons)); + store.register_late_pass(|_| Box::new(empty_enums::EmptyEnums)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); @@ -743,7 +736,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))); - store.register_late_pass(move |_| Box::new(lines_filter_map_ok::LinesFilterMapOk::new(conf))); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))); store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))); @@ -755,7 +747,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes)); store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)); store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)); - store.register_late_pass(|_| Box::new(needless_if::NeedlessIf)); + store.register_late_pass(|_| Box::new(needless_ifs::NeedlessIfs)); store.register_late_pass(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))); store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))); store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)); @@ -798,7 +790,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); - store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); @@ -807,7 +798,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); - store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))); store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)); diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 519ec228c8840..727e9b172a872 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -856,89 +856,49 @@ fn elision_suggestions( .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) .collect::>(); - if !elidable_lts - .iter() - .all(|lt| explicit_params.iter().any(|param| param.def_id == *lt)) - { - return None; - } - - let mut suggestions = if elidable_lts.is_empty() { - vec![] - } else if elidable_lts.len() == explicit_params.len() { + let mut suggestions = if elidable_lts.len() == explicit_params.len() { // if all the params are elided remove the whole generic block // // fn x<'a>() {} // ^^^^ vec![(generics.span, String::new())] } else { - match &explicit_params[..] { - // no params, nothing to elide - [] => unreachable!("handled by `elidable_lts.is_empty()`"), - [param] => { - if elidable_lts.contains(¶m.def_id) { - unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") + // 1. Start from the last elidable lifetime + // 2. While the lifetimes preceding it are also elidable, construct spans going from the current + // lifetime to the comma before it + // 3. Once this chain of elidable lifetimes stops, switch to constructing spans going from the + // current lifetime to the comma _after_ it + let mut end: Option = None; + elidable_lts + .iter() + .rev() + .map(|&id| { + let (idx, param) = explicit_params.iter().find_position(|param| param.def_id == id)?; + + let span = if let Some(next) = explicit_params.get(idx + 1) + && end != Some(next.def_id) + { + // Extend the current span forward, up until the next param in the list. + // fn x<'prev, 'a, 'next>() {} + // ^^^^ + param.span.until(next.span) } else { - unreachable!("handled by `elidable_lts.is_empty()`") - } - }, - [_, _, ..] => { - // Given a list like `<'a, 'b, 'c, 'd, ..>`, - // - // If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should - // suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`: - // <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..> - // ^^ ^^ ^^^^^^^^ - // - // And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go - // over all the lifetimes after it, and for each elidable one, add a suggestion spanning the - // lifetime itself and the comma before, because each individual suggestion is guaranteed to leave - // the list valid: - // <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..> - // ^^ ^^ ^^ ^^^^ ^^^^^^^^ - // - // In case there is no such starting cluster, we only need to do the second part of the algorithm: - // <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..> - // ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^ - - // Split off the starting cluster - // TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811): - // ``` - // let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(¶m.def_id)) else { - // // there were no lifetime param that couldn't be elided - // unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") - // }; - // match split { /* .. */ } - // ``` - let Some(split_pos) = explicit_params - .iter() - .position(|param| !elidable_lts.contains(¶m.def_id)) - else { - // there were no lifetime param that couldn't be elided - unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") + // Extend the current span back to include the comma following the previous + // param. If the span of the next param in the list has already been + // extended, we continue the chain. This is why we're iterating in reverse. + end = Some(param.def_id); + + // `idx` will never be 0, else we'd be removing the entire list of generics + let prev = explicit_params.get(idx - 1)?; + + // fn x<'prev, 'a>() {} + // ^^^^ + param.span.with_lo(prev.span.hi()) }; - let split = explicit_params - .split_at_checked(split_pos) - .expect("got `split_pos` from `position` on the same Vec"); - - match split { - ([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"), - ([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"), - (cluster, rest @ [rest_first, ..]) => { - // the span for the cluster - (cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter()) - // the span for the remaining lifetimes (calculations independent of the cluster) - .chain( - rest.array_windows() - .filter(|[_, curr]| elidable_lts.contains(&curr.def_id)) - .map(|[prev, curr]| curr.span.with_lo(prev.span.hi())), - ) - .map(|sp| (sp, String::new())) - .collect() - }, - } - }, - } + + Some((span, String::new())) + }) + .collect::>>()? }; suggestions.extend(usages.iter().map(|&usage| { diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs deleted file mode 100644 index 65e922ac07d36..0000000000000 --- a/clippy_lints/src/lines_filter_map_ok.rs +++ /dev/null @@ -1,141 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; -use clippy_utils::sym; -use rustc_errors::Applicability; -use rustc_hir::{Body, Closure, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; -use rustc_span::Symbol; - -pub struct LinesFilterMapOk { - msrv: Msrv, -} - -impl LinesFilterMapOk { - pub fn new(conf: &Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` - /// when `lines` has type `std::io::Lines`. - /// - /// ### Why is this bad? - /// `Lines` instances might produce a never-ending stream of `Err`, in which case - /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an - /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, - /// even in the absence of explicit loops in the user code. - /// - /// This situation can arise when working with user-provided paths. On some platforms, - /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, - /// but any later attempt to read from `fs` will return an error. - /// - /// ### Known problems - /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` - /// instance in all cases. There are two cases where the suggestion might not be - /// appropriate or necessary: - /// - /// - If the `Lines` instance can never produce any error, or if an error is produced - /// only once just before terminating the iterator, using `map_while()` is not - /// necessary but will not do any harm. - /// - If the `Lines` instance can produce intermittent errors then recover and produce - /// successful results, using `map_while()` would stop at the first error. - /// - /// ### Example - /// ```no_run - /// # use std::{fs::File, io::{self, BufRead, BufReader}}; - /// # let _ = || -> io::Result<()> { - /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); - /// // If "some-path" points to a directory, the next statement never terminates: - /// let first_line: Option = lines.next(); - /// # Ok(()) }; - /// ``` - /// Use instead: - /// ```no_run - /// # use std::{fs::File, io::{self, BufRead, BufReader}}; - /// # let _ = || -> io::Result<()> { - /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); - /// let first_line: Option = lines.next(); - /// # Ok(()) }; - /// ``` - #[clippy::version = "1.70.0"] - pub LINES_FILTER_MAP_OK, - suspicious, - "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop" -} - -impl_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]); - -impl LateLintPass<'_> for LinesFilterMapOk { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind - && cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) - && let fm_method_name = fm_method.ident.name - && matches!(fm_method_name, sym::filter_map | sym::flat_map | sym::flatten) - && cx - .typeck_results() - .expr_ty_adjusted(fm_receiver) - .is_diag_item(cx, sym::IoLines) - && should_lint(cx, fm_args, fm_method_name) - && self.msrv.meets(cx, msrvs::MAP_WHILE) - { - span_lint_and_then( - cx, - LINES_FILTER_MAP_OK, - fm_span, - format!("`{fm_method_name}()` will run forever if the iterator repeatedly produces an `Err`",), - |diag| { - diag.span_note( - fm_receiver.span, - "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error"); - diag.span_suggestion( - fm_span, - "replace with", - "map_while(Result::ok)", - Applicability::MaybeIncorrect, - ); - }, - ); - } - } -} - -fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_name: Symbol) -> bool { - match args { - [] => method_name == sym::flatten, - [fm_arg] => { - match &fm_arg.kind { - // Detect `Result::ok` - ExprKind::Path(qpath) => cx - .qpath_res(qpath, fm_arg.hir_id) - .opt_def_id() - .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::result_ok_method, did)), - // Detect `|x| x.ok()` - ExprKind::Closure(Closure { body, .. }) => { - if let Body { - params: [param], value, .. - } = cx.tcx.hir_body(*body) - && let ExprKind::MethodCall(method, receiver, [], _) = value.kind - { - method.ident.name == sym::ok - && receiver.res_local_id() == Some(param.pat.hir_id) - && cx - .typeck_results() - .type_dependent_def_id(value.hir_id) - .opt_parent(cx) - .opt_impl_ty(cx) - .is_diag_item(cx, sym::Result) - } else { - false - } - }, - _ => false, - } - }, - _ => false, - } -} diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index c974d9cca7d23..a0de464ff7f81 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -157,10 +157,7 @@ pub(super) fn check<'tcx>( "consider using an iterator and enumerate()", vec![ (pat.span, format!("({}, )", ident.name)), - ( - span, - format!("{indexed}.{method}().enumerate(){method_1}{method_2}"), - ), + (span, format!("{indexed}.{method}().enumerate(){method_1}{method_2}")), ], Applicability::HasPlaceholders, ); diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 528cc64fa7bc5..0d37be17689a5 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -7,7 +7,7 @@ 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, Node, Pat, Stmt, StmtKind, StructTailExpr, + Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, }; use rustc_lint::LateContext; use rustc_span::{BytePos, Span, sym}; @@ -75,12 +75,19 @@ pub(super) fn check<'tcx>( fn contains_any_break_or_continue(block: &Block<'_>) -> bool { for_each_expr_without_closures(block, |e| match e.kind { ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()), + ExprKind::InlineAsm(asm) if contains_label(asm) => ControlFlow::Break(()), ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), _ => ControlFlow::Continue(Descend::Yes), }) .is_some() } +fn contains_label(asm: &InlineAsm<'_>) -> bool { + asm.operands + .iter() + .any(|(op, _span)| matches!(op, InlineAsmOperand::Label { .. })) +} + /// The `never_loop` analysis keeps track of three things: /// /// * Has any (reachable) code path hit a `continue` of the main loop? @@ -378,7 +385,15 @@ fn never_loop_expr<'tcx>( InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => { NeverLoopResult::Normal }, - InlineAsmOperand::Label { block } => never_loop_block(cx, block, local_labels, main_loop_id), + InlineAsmOperand::Label { block } => + // We do not know whether the label will be executed or not, so `Diverging` must be + // downgraded to `Normal`. + { + match never_loop_block(cx, block, local_labels, main_loop_id) { + NeverLoopResult::Diverging { .. } => NeverLoopResult::Normal, + result => result, + } + }, })), ExprKind::OffsetOf(_, _) | ExprKind::Yield(_, _) diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 298bf10754897..0f3d8b3366751 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -183,7 +183,13 @@ fn emit_manual_let_else( format!("{{ {sn_else} }}") }; let sn_bl = replace_in_pattern(cx, span, ident_map, pat, &mut app, true); - let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};"); + let sugg = if sn_expr.ends_with('}') { + // let-else statement expressions are not allowed to end with `}` + // https://rust-lang.github.io/rfcs/3137-let-else.html#let-pattern--if--else--else- + format!("let {sn_bl} = ({sn_expr}) else {else_bl};") + } else { + format!("let {sn_bl} = {sn_expr} else {else_bl};") + }; diag.span_suggestion(span, "consider writing", sugg, app); }, ); diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index 86f0eff9cd869..dce0d105f4c5b 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -1,13 +1,13 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::source::snippet_with_context; use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -124,8 +124,7 @@ fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span, msrv: Msrv) { fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind && seg.ident.name == sym::as_ref - && let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind() - && cx.tcx.is_diagnostic_item(sym::Option, adtdef.did()) + && cx.typeck_results().expr_ty(callee).is_diag_item(cx, sym::Option) && msrv.meets( cx, if clippy_utils::is_in_const_context(cx) { @@ -135,19 +134,22 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { }, ) { - if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) { - span_lint_and_sugg( - cx, - MANUAL_OPTION_AS_SLICE, - span, - "use `Option::as_slice`", - "use", - format!("{snippet}.as_slice()"), - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, MANUAL_OPTION_AS_SLICE, span, "use `Option_as_slice`"); - } + span_lint_and_then( + cx, + MANUAL_OPTION_AS_SLICE, + span, + "manual implementation of `Option::as_slice`", + |diag| { + let mut app = Applicability::MachineApplicable; + let callee = snippet_with_context(cx, callee.span, expr.span.ctxt(), "_", &mut app).0; + diag.span_suggestion_verbose( + span, + "use `Option::as_slice` directly", + format!("{callee}.as_slice()"), + app, + ); + }, + ); } } diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 681dc2ab5bc0c..b07d4fe81f8a9 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -214,6 +214,9 @@ fn lint_map_unit_fn( }; let fn_arg = &map_args.1[0]; + #[expect(clippy::items_after_statements, reason = "the const is only used below")] + const SUGG_MSG: &str = "use `if let` instead"; + if is_unit_function(cx, fn_arg) { let mut applicability = Applicability::MachineApplicable; let msg = suggestion_msg("function", map_type); @@ -226,7 +229,7 @@ fn lint_map_unit_fn( ); span_lint_and_then(cx, lint, expr.span, msg, |diag| { - diag.span_suggestion(stmt.span, "try", suggestion, applicability); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability); }); } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) { let msg = suggestion_msg("closure", map_type); @@ -242,7 +245,7 @@ fn lint_map_unit_fn( snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0, ); - diag.span_suggestion(stmt.span, "try", suggestion, applicability); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability); } else { let suggestion = format!( "if let {0}({1}) = {2} {{ ... }}", @@ -250,7 +253,7 @@ fn lint_map_unit_fn( snippet(cx, binding.pat.span, "_"), snippet(cx, var_arg.span, "_"), ); - diag.span_suggestion(stmt.span, "try", suggestion, Applicability::HasPlaceholders); + diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, Applicability::HasPlaceholders); } }); } diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index e00d0c7f3d6c1..abbc43d8e9b04 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -1,5 +1,5 @@ use clippy_utils::consts::ConstEvalCtxt; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline}; use rustc_ast::{BindingMode, ByRef}; use rustc_errors::Applicability; @@ -11,7 +11,8 @@ use rustc_span::sym; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{expr_type_is_certain, implements_trait}; +use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_copy}; +use clippy_utils::usage::local_used_after_expr; use clippy_utils::{is_default_equivalent, is_lint_allowed, peel_blocks, span_contains_comment}; use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT}; @@ -87,7 +88,9 @@ fn handle( binding_id: HirId, ) { // Only deal with situations where both alternatives return the same non-adjusted type. - if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) { + if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) + || !safe_to_move_scrutinee(cx, expr, condition) + { return; } @@ -185,6 +188,29 @@ fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static } } +/// Checks whether it is safe to move scrutinee. +/// It is not safe to move if: +/// 1. `scrutinee` is a `Result` that doesn't implemenet `Copy`, mainly because the `Err` +/// variant is not copyable. +/// 2. `expr` is a local variable that is used after the if-let-else expression. +/// ```rust,ignore +/// let foo: Result = Ok(0); +/// let v = if let Ok(v) = foo { v } else { 1 }; +/// let bar = foo; +/// ``` +fn safe_to_move_scrutinee(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>) -> bool { + if let Some(hir_id) = scrutinee.res_local_id() + && let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee) + && scrutinee_ty.is_diag_item(cx, sym::Result) + && !is_copy(cx, scrutinee_ty) + && local_used_after_expr(cx, hir_id, expr) + { + false + } else { + true + } +} + pub fn check_match<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 156118be347f9..7ac57fae690cb 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -1,6 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::option_arg_ty; use clippy_utils::{is_none_arm, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; @@ -10,54 +11,77 @@ use rustc_middle::ty; use super::MATCH_AS_REF; pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - let arm_ref_mut = if is_none_arm(cx, &arms[0]) { - is_ref_some_arm(cx, &arms[1]) - } else if is_none_arm(cx, &arms[1]) { - is_ref_some_arm(cx, &arms[0]) + if let [arm1, arm2] = arms + && arm1.guard.is_none() + && arm2.guard.is_none() + && let Some(arm_ref_mutbl) = if is_none_arm(cx, arm1) { + as_ref_some_arm(cx, arm2) + } else if is_none_arm(cx, arm2) { + as_ref_some_arm(cx, arm1) } else { None + } + && let output_ty = cx.typeck_results().expr_ty(expr) + && let input_ty = cx.typeck_results().expr_ty(ex) + && let Some(input_ty) = option_arg_ty(cx, input_ty) + && let Some(output_ty) = option_arg_ty(cx, output_ty) + && let ty::Ref(_, output_ty, output_mutbl) = *output_ty.kind() + { + let method = match arm_ref_mutbl { + Mutability::Not => "as_ref", + Mutability::Mut => "as_mut", }; - if let Some(rb) = arm_ref_mut { - let suggestion = match rb { - Mutability::Not => "as_ref", - Mutability::Mut => "as_mut", - }; - let output_ty = cx.typeck_results().expr_ty(expr); - let input_ty = cx.typeck_results().expr_ty(ex); + // ``` + // let _: Option<&T> = match opt { + // Some(ref mut t) => Some(t), + // None => None, + // }; + // ``` + // We need to suggest `t.as_ref()` in order downcast the reference from `&mut` to `&`. + // We may or may not need to cast the type as well, for which we'd need `.map()`, and that could + // theoretically take care of the reference downcasting as well, but we chose to keep these two + // operations separate + let need_as_ref = arm_ref_mutbl == Mutability::Mut && output_mutbl == Mutability::Not; - let cast = if let ty::Adt(_, args) = input_ty.kind() - && let input_ty = args.type_at(0) - && let ty::Adt(_, args) = output_ty.kind() - && let output_ty = args.type_at(0) - && let ty::Ref(_, output_ty, _) = *output_ty.kind() - && input_ty != output_ty - { - ".map(|x| x as _)" - } else { - "" - }; + let cast = if input_ty == output_ty { "" } else { ".map(|x| x as _)" }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - MATCH_AS_REF, - expr.span, - format!("use `{suggestion}()` instead"), - "try", - format!( - "{}.{suggestion}(){cast}", - snippet_with_applicability(cx, ex.span, "_", &mut applicability), - ), - applicability, - ); - } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then( + cx, + MATCH_AS_REF, + expr.span, + format!("manual implementation of `Option::{method}`"), + |diag| { + if need_as_ref { + diag.note("but the type is coerced to a non-mutable reference, and so `as_ref` can used instead"); + diag.span_suggestion_verbose( + expr.span, + "use `Option::as_ref()`", + format!( + "{}.as_ref(){cast}", + Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + ), + applicability, + ); + } else { + diag.span_suggestion_verbose( + expr.span, + format!("use `Option::{method}()` directly"), + format!( + "{}.{method}(){cast}", + Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + ), + applicability, + ); + } + }, + ); } } // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) -fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { +fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind && cx .qpath_res(qpath, arm.pat.hir_id) diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index ac5da320bdffb..fa44a56af182d 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -108,7 +108,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { }, _, ) => path_prefix.with_prefix(path.segments), - _ => (), + QPath::TypeRelative(..) => (), } }); } diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 2ca76a1fe6945..a0f88cf911d84 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -416,7 +416,7 @@ fn get_ident(path: &QPath<'_>) -> Option { let name = path.segments[0].ident; Some(name) }, - _ => None, + QPath::TypeRelative(..) => None, } } diff --git a/clippy_lints/src/methods/lines_filter_map_ok.rs b/clippy_lints/src/methods/lines_filter_map_ok.rs new file mode 100644 index 0000000000000..baf5b6f93f637 --- /dev/null +++ b/clippy_lints/src/methods/lines_filter_map_ok.rs @@ -0,0 +1,85 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::LINES_FILTER_MAP_OK; + +pub(super) fn check_flatten(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, msrv: Msrv) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx + .typeck_results() + .expr_ty_adjusted(recv) + .is_diag_item(cx, sym::IoLines) + && msrv.meets(cx, msrvs::MAP_WHILE) + { + emit(cx, recv, "flatten", call_span); + } +} + +pub(super) fn check_filter_or_flat_map( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + method_name: &'static str, + method_arg: &Expr<'_>, + call_span: Span, + msrv: Msrv, +) { + if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && cx + .typeck_results() + .expr_ty_adjusted(recv) + .is_diag_item(cx, sym::IoLines) + && match method_arg.kind { + // Detect `Result::ok` + ExprKind::Path(ref qpath) => cx + .qpath_res(qpath, method_arg.hir_id) + .is_diag_item(cx, sym::result_ok_method), + // Detect `|x| x.ok()` + ExprKind::Closure(&Closure { body, .. }) => { + if let Body { + params: [param], value, .. + } = cx.tcx.hir_body(body) + && let ExprKind::MethodCall(method, receiver, [], _) = value.kind + { + method.ident.name == sym::ok + && receiver.res_local_id() == Some(param.pat.hir_id) + && cx.ty_based_def(*value).is_diag_item(cx, sym::result_ok_method) + } else { + false + } + }, + _ => false, + } + && msrv.meets(cx, msrvs::MAP_WHILE) + { + emit(cx, recv, method_name, call_span); + } +} + +fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, method_name: &'static str, call_span: Span) { + span_lint_and_then( + cx, + LINES_FILTER_MAP_OK, + call_span, + format!("`{method_name}()` will run forever if the iterator repeatedly produces an `Err`"), + |diag| { + diag.span_note( + recv.span, + "this expression returning a `std::io::Lines` may produce \ + an infinite number of `Err` in case of a read error", + ); + diag.span_suggestion( + call_span, + "replace with", + "map_while(Result::ok)", + Applicability::MaybeIncorrect, + ); + }, + ); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 48fa5f7bdc6ef..20dfce914838f 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -56,6 +56,7 @@ mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; mod lib; +mod lines_filter_map_ok; mod manual_c_str_literals; mod manual_contains; mod manual_inspect; @@ -4665,6 +4666,55 @@ declare_clippy_lint! { "making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` + /// when `lines` has type `std::io::Lines`. + /// + /// ### Why is this bad? + /// `Lines` instances might produce a never-ending stream of `Err`, in which case + /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an + /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, + /// even in the absence of explicit loops in the user code. + /// + /// This situation can arise when working with user-provided paths. On some platforms, + /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, + /// but any later attempt to read from `fs` will return an error. + /// + /// ### Known problems + /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` + /// instance in all cases. There are two cases where the suggestion might not be + /// appropriate or necessary: + /// + /// - If the `Lines` instance can never produce any error, or if an error is produced + /// only once just before terminating the iterator, using `map_while()` is not + /// necessary but will not do any harm. + /// - If the `Lines` instance can produce intermittent errors then recover and produce + /// successful results, using `map_while()` would stop at the first error. + /// + /// ### Example + /// ```no_run + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); + /// // If "some-path" points to a directory, the next statement never terminates: + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + /// Use instead: + /// ```no_run + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + #[clippy::version = "1.70.0"] + pub LINES_FILTER_MAP_OK, + suspicious, + "filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4847,6 +4897,7 @@ impl_lint_pass!(Methods => [ IP_CONSTANT, REDUNDANT_ITER_CLONED, UNNECESSARY_OPTION_MAP_OR_ELSE, + LINES_FILTER_MAP_OK, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -5050,7 +5101,11 @@ impl Methods { (sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv), (sym::cloned, []) => { cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv); - option_as_ref_cloned::check(cx, recv, span); + if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = + method_call(recv) + { + option_as_ref_cloned::check(cx, span, method, as_ref_recv, as_ref_ident_span); + } }, (sym::collect, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) => { needless_collect::check(cx, span, expr, recv, call_span); @@ -5162,32 +5217,47 @@ impl Methods { }, (sym::filter_map, [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); + unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); + lines_filter_map_ok::check_filter_or_flat_map( + cx, + expr, + recv, + "filter_map", + arg, + call_span, + self.msrv, + ); }, (sym::find_map, [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); + unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap); }, (sym::flat_map, [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); + lines_filter_map_ok::check_filter_or_flat_map( + cx, expr, recv, "flat_map", arg, call_span, self.msrv, + ); }, - (sym::flatten, []) => match method_call(recv) { - Some((sym::map, recv, [map_arg], map_span, _)) => { - map_flatten::check(cx, expr, recv, map_arg, map_span); - }, - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - true, - ), - _ => {}, + (sym::flatten, []) => { + match method_call(recv) { + Some((sym::map, recv, [map_arg], map_span, _)) => { + map_flatten::check(cx, expr, recv, map_arg, map_span); + }, + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + true, + ), + _ => {}, + } + lines_filter_map_ok::check_flatten(cx, expr, recv, call_span, self.msrv); }, (sym::fold, [init, acc]) => { manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints/src/methods/option_as_ref_cloned.rs index 591f6aacaef87..156c624488eba 100644 --- a/clippy_lints/src/methods/option_as_ref_cloned.rs +++ b/clippy_lints/src/methods/option_as_ref_cloned.rs @@ -4,24 +4,28 @@ use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; -use super::{OPTION_AS_REF_CLONED, method_call}; +use super::OPTION_AS_REF_CLONED; -pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) { - if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = - method_call(cloned_recv) - && cx - .typeck_results() - .expr_ty(as_ref_recv) - .peel_refs() - .is_diag_item(cx, sym::Option) +pub(super) fn check( + cx: &LateContext<'_>, + cloned_ident_span: Span, + as_ref_method: Symbol, + as_ref_recv: &Expr<'_>, + as_ref_ident_span: Span, +) { + if cx + .typeck_results() + .expr_ty(as_ref_recv) + .peel_refs() + .is_diag_item(cx, sym::Option) { span_lint_and_sugg( cx, OPTION_AS_REF_CLONED, as_ref_ident_span.to(cloned_ident_span), - format!("cloning an `Option<_>` using `.{method}().cloned()`"), + format!("cloning an `Option<_>` using `.{as_ref_method}().cloned()`"), "this can be written more concisely by cloning the `Option<_>` directly", "clone".into(), Applicability::MachineApplicable, diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs index c9c75f3f38e2d..8732eba6d4e87 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints/src/methods/search_is_some.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::deref_closure_args; @@ -34,79 +34,67 @@ pub(super) fn check<'tcx>( { let msg = format!("called `{option_check_method}()` after searching an `Iterator` with `{search_method}`"); let search_snippet = snippet(cx, search_arg.span, ".."); - if search_snippet.lines().count() <= 1 { - // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` - // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` - let mut applicability = Applicability::MachineApplicable; - let any_search_snippet = if search_method == sym::find - && let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind - && let closure_body = cx.tcx.hir_body(body) - && let Some(closure_arg) = closure_body.params.first() - { - if let PatKind::Ref(..) = closure_arg.pat.kind { - Some(search_snippet.replacen('&', "", 1)) - } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind { - // `find()` provides a reference to the item, but `any` does not, - // so we should fix item usages for suggestion - if let Some(closure_sugg) = deref_closure_args(cx, search_arg) { - applicability = closure_sugg.applicability; - Some(closure_sugg.suggestion) - } else { - Some(search_snippet.to_string()) - } + // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` + // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` + let mut applicability = Applicability::MachineApplicable; + let any_search_snippet = if search_method == sym::find + && let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind + && let closure_body = cx.tcx.hir_body(body) + && let Some(closure_arg) = closure_body.params.first() + { + if let PatKind::Ref(..) = closure_arg.pat.kind { + Some(search_snippet.replacen('&', "", 1)) + } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind { + // `find()` provides a reference to the item, but `any` does not, + // so we should fix item usages for suggestion + if let Some(closure_sugg) = deref_closure_args(cx, search_arg) { + applicability = closure_sugg.applicability; + Some(closure_sugg.suggestion) } else { - None + Some(search_snippet.to_string()) } } else { None - }; - // add note if not multi-line - if is_some { - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - method_span.with_hi(expr.span.hi()), - msg, - "consider using", - format!( - "any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ), - applicability, - ); - } else { - let iter = snippet(cx, search_recv.span, ".."); - let sugg = if is_receiver_of_method_call(cx, expr) { - format!( - "(!{iter}.any({}))", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ) - } else { - format!( - "!{iter}.any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ) - }; - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - expr.span, - msg, - "consider using", - sugg, - applicability, - ); } } else { - let hint = format!( - "this is more succinctly expressed by calling `any()`{}", - if option_check_method == "is_none" { - " with negation" - } else { - "" - } + None + }; + // add note if not multi-line + if is_some { + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + msg, + "consider using", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + applicability, + ); + } else { + let iter = snippet(cx, search_recv.span, ".."); + let sugg = if is_receiver_of_method_call(cx, expr) { + format!( + "(!{iter}.any({}))", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ) + } else { + format!( + "!{iter}.any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ) + }; + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + expr.span, + msg, + "consider using", + sugg, + applicability, ); - span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, msg, None, hint); } } // lint if `find()` is called by `String` or `&str` diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index d9d642015063e..7f729ac7ca94e 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -2,15 +2,15 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; use clippy_utils::sym; -use clippy_utils::ty::is_copy; +use clippy_utils::ty::{is_copy, option_arg_ty}; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use core::ops::ControlFlow; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::Symbol; +use rustc_span::Span; +use std::fmt::Display; use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP}; @@ -18,7 +18,8 @@ pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, - name: Symbol, + call_span: Span, + kind: Kind, ) { if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) { return; @@ -45,61 +46,88 @@ pub(super) fn check<'tcx>( let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { // Check if the closure is .filter_map(|x| Some(x)) - if name == sym::filter_map - && let hir::ExprKind::Call(expr, args) = body.value.kind + if kind.is_filter_map() + && let hir::ExprKind::Call(expr, [arg]) = body.value.kind && expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && let hir::ExprKind::Path(_) = args[0].kind + && let hir::ExprKind::Path(_) = arg.kind { span_lint( cx, UNNECESSARY_FILTER_MAP, - expr.span, + call_span, String::from("this call to `.filter_map(..)` is unnecessary"), ); return; } - if name == sym::filter_map { - "map(..)" - } else { - "map(..).next()" + match kind { + Kind::FilterMap => "map(..)", + Kind::FindMap => "map(..).next()", } } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { - match cx.typeck_results().expr_ty(body.value).kind() { - ty::Adt(adt, subst) - if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => - { - if name == sym::filter_map { - "filter(..)" - } else { - "find(..)" - } - }, - _ => return, + let ty = cx.typeck_results().expr_ty(body.value); + if option_arg_ty(cx, ty).is_some_and(|t| t == in_ty) { + match kind { + Kind::FilterMap => "filter(..)", + Kind::FindMap => "find(..)", + } + } else { + return; } } else { return; }; span_lint( cx, - if name == sym::filter_map { - UNNECESSARY_FILTER_MAP - } else { - UNNECESSARY_FIND_MAP + match kind { + Kind::FilterMap => UNNECESSARY_FILTER_MAP, + Kind::FindMap => UNNECESSARY_FIND_MAP, }, - expr.span, - format!("this `.{name}(..)` can be written more simply using `.{sugg}`"), + call_span, + format!("this `.{kind}(..)` can be written more simply using `.{sugg}`"), ); } } +#[derive(Clone, Copy)] +pub(super) enum Kind { + FilterMap, + FindMap, +} + +impl Kind { + fn is_filter_map(self) -> bool { + matches!(self, Self::FilterMap) + } +} + +impl Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::FilterMap => f.write_str("filter_map"), + Self::FindMap => f.write_str("find_map"), + } + } +} + // returns (found_mapping, found_filtering) fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) { match expr.kind { + hir::ExprKind::Path(ref path) + if cx + .qpath_res(path, expr.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionNone) => + { + // None + (false, true) + }, hir::ExprKind::Call(func, args) => { if func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) { if args[0].res_local_id() == Some(arg_id) { + // Some(arg_id) return (false, false); } + // Some(not arg_id) return (true, false); } (true, true) @@ -109,8 +137,10 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc && cx.typeck_results().expr_ty(recv).is_bool() && arg.res_local_id() == Some(arg_id) { + // bool.then_some(arg_id) (false, true) } else { + // bool.then_some(not arg_id) (true, true) } }, @@ -134,14 +164,6 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc let else_check = check_expression(cx, arg_id, else_arm); (if_check.0 | else_check.0, if_check.1 | else_check.1) }, - hir::ExprKind::Path(ref path) - if cx - .qpath_res(path, expr.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - (false, true) - }, _ => (true, true), } } diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index fffaf40c9d141..0eb5c36a28a2a 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -8,7 +8,7 @@ use super::UNNEEDED_WILDCARD_PATTERN; pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { if let PatKind::TupleStruct(_, _, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind - && let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) + && let Some(rest_index) = patterns.iter().position(Pat::is_rest) { if let Some((left_index, left_pat)) = patterns[..rest_index] .iter() diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index c7c4976aeb7b2..25fcc7ee568e9 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -147,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn path_has_args(p: &QPath<'_>) -> bool { match *p { QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(), - _ => false, + QPath::Resolved(..) => false, } } diff --git a/clippy_lints/src/needless_if.rs b/clippy_lints/src/needless_ifs.rs similarity index 88% rename from clippy_lints/src/needless_if.rs rename to clippy_lints/src/needless_ifs.rs index c90019f6ee161..8ec7e47ccc5bc 100644 --- a/clippy_lints/src/needless_if.rs +++ b/clippy_lints/src/needless_ifs.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::If; use clippy_utils::is_from_proc_macro; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -29,13 +29,13 @@ declare_clippy_lint! { /// really_expensive_condition_with_side_effects(&mut i); /// ``` #[clippy::version = "1.72.0"] - pub NEEDLESS_IF, + pub NEEDLESS_IFS, complexity, "checks for empty if branches" } -declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]); +declare_lint_pass!(NeedlessIfs => [NEEDLESS_IFS]); -impl LateLintPass<'_> for NeedlessIf { +impl LateLintPass<'_> for NeedlessIfs { fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { if let StmtKind::Expr(expr) = stmt.kind && let Some(If { @@ -56,12 +56,13 @@ impl LateLintPass<'_> for NeedlessIf { src.bytes() .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace()) }) - && let Some(cond_snippet) = cond.span.get_source_text(cx) + && let Some(cond_span) = walk_span_to_context(cond.span, expr.span.ctxt()) + && let Some(cond_snippet) = cond_span.get_source_text(cx) && !is_from_proc_macro(cx, expr) { span_lint_and_sugg( cx, - NEEDLESS_IF, + NEEDLESS_IFS, stmt.span, "this `if` branch is empty", "you can remove it", diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 51644f7dce6cf..f756f94d97b39 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -6,7 +6,9 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdMap; -use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemImplKind, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind}; +use rustc_hir::{ + Body, Expr, ExprKind, HirId, ImplItem, ImplItemImplKind, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, ConstKind, GenericArgKind, GenericArgsRef}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs index 54f50f11e0349..71982023779e0 100644 --- a/clippy_lints/src/operators/double_comparison.rs +++ b/clippy_lints/src/operators/double_comparison.rs @@ -8,46 +8,52 @@ use rustc_span::Span; use super::DOUBLE_COMPARISONS; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) { - let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) { - (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => { - (lb.node, llhs, lrhs, rb.node, rlhs, rrhs) - }, - _ => return, - }; - if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) { - return; - } - macro_rules! lint_double_comparison { - ($op:tt) => {{ - let mut applicability = Applicability::MachineApplicable; - let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); - let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); - let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str); - span_lint_and_sugg( - cx, - DOUBLE_COMPARISONS, - span, - "this binary expression can be simplified", - "try", - sugg, - applicability, - ); - }}; - } - match (op, lkind, rkind) { - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { - lint_double_comparison!(<=); - }, - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { - lint_double_comparison!(>=); - }, - (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { - lint_double_comparison!(!=); - }, - (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { - lint_double_comparison!(==); - }, - _ => (), +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) { + if let ExprKind::Binary(lop, llhs, lrhs) = lhs.kind + && let ExprKind::Binary(rop, rlhs, rrhs) = rhs.kind + && eq_expr_value(cx, llhs, rlhs) + && eq_expr_value(cx, lrhs, rrhs) + { + let op = match (op, lop.node, rop.node) { + // x == y || x < y => x <= y + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) + // x < y || x == y => x <= y + | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { + "<=" + }, + // x == y || x > y => x >= y + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) + // x > y || x == y => x >= y + | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { + ">=" + }, + // x < y || x > y => x != y + (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) + // x > y || x < y => x != y + | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { + "!=" + }, + // x <= y && x >= y => x == y + (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) + // x >= y && x <= y => x == y + | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { + "==" + }, + _ => return, + }; + + let mut applicability = Applicability::MachineApplicable; + let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); + let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); + let sugg = format!("{lhs_str} {op} {rhs_str}"); + span_lint_and_sugg( + cx, + DOUBLE_COMPARISONS, + span, + "this binary expression can be simplified", + "try", + sugg, + applicability, + ); } } diff --git a/clippy_lints/src/operators/integer_division_remainder_used.rs b/clippy_lints/src/operators/integer_division_remainder_used.rs new file mode 100644 index 0000000000000..976b2d8b0c63a --- /dev/null +++ b/clippy_lints/src/operators/integer_division_remainder_used.rs @@ -0,0 +1,24 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::BinOpKind; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Span; + +use super::INTEGER_DIVISION_REMAINDER_USED; + +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) { + if let BinOpKind::Div | BinOpKind::Rem = op + && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let rhs_ty = cx.typeck_results().expr_ty(rhs) + && let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind() + && let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind() + { + span_lint( + cx, + INTEGER_DIVISION_REMAINDER_USED, + span.source_callsite(), + format!("use of `{}` has been disallowed in this context", op.as_str()), + ); + } +} diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/operators/invalid_upcast_comparisons.rs similarity index 56% rename from clippy_lints/src/invalid_upcast_comparisons.rs rename to clippy_lints/src/operators/invalid_upcast_comparisons.rs index 885649074ab63..b32848a323375 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/operators/invalid_upcast_comparisons.rs @@ -1,9 +1,8 @@ use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, IntTy, UintTy}; -use rustc_session::declare_lint_pass; use rustc_span::Span; use clippy_utils::comparisons; @@ -12,29 +11,26 @@ use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint; use clippy_utils::source::snippet_with_context; -declare_clippy_lint! { - /// ### What it does - /// Checks for comparisons where the relation is always either - /// true or false, but where one side has been upcast so that the comparison is - /// necessary. Only integer types are checked. - /// - /// ### Why is this bad? - /// An expression like `let x : u8 = ...; (x as u32) > 300` - /// will mistakenly imply that it is possible for `x` to be outside the range of - /// `u8`. - /// - /// ### Example - /// ```no_run - /// let x: u8 = 1; - /// (x as u32) > 300; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INVALID_UPCAST_COMPARISONS, - pedantic, - "a comparison involving an upcast which is always true or false" -} +use super::INVALID_UPCAST_COMPARISONS; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + cmp: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, + span: Span, +) { + let normalized = comparisons::normalize_comparison(cmp, lhs, rhs); + let Some((rel, normalized_lhs, normalized_rhs)) = normalized else { + return; + }; -declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]); + let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); + let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); + + upcast_comparison_bounds_err(cx, span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); + upcast_comparison_bounds_err(cx, span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); +} fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(FullInt, FullInt)> { if let ExprKind::Cast(cast_exp, _) = expr.kind { @@ -68,29 +64,6 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option< } } -fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { - if let ExprKind::Cast(cast_val, _) = expr.kind { - let mut applicability = Applicability::MachineApplicable; - let (cast_val_snip, _) = snippet_with_context( - cx, - cast_val.span, - expr.span.ctxt(), - "the expression", - &mut applicability, - ); - span_lint( - cx, - INVALID_UPCAST_COMPARISONS, - span, - format!( - "because of the numeric bounds on `{}` prior to casting, this expression is always {}", - cast_val_snip, - if always { "true" } else { "false" }, - ), - ); - } -} - fn upcast_comparison_bounds_err<'tcx>( cx: &LateContext<'tcx>, span: Span, @@ -103,63 +76,54 @@ fn upcast_comparison_bounds_err<'tcx>( if let Some((lb, ub)) = lhs_bounds && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt()) { - if rel == Rel::Eq || rel == Rel::Ne { - if norm_rhs_val < lb || norm_rhs_val > ub { - err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); - } - } else if match rel { - Rel::Lt => { - if invert { - norm_rhs_val < lb - } else { - ub < norm_rhs_val + match rel { + Rel::Eq => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, false); } }, - Rel::Le => { - if invert { - norm_rhs_val <= lb - } else { - ub <= norm_rhs_val + Rel::Ne => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, true); } }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, true); - } else if match rel { Rel::Lt => { - if invert { - norm_rhs_val >= ub - } else { - lb >= norm_rhs_val + if (invert && norm_rhs_val < lb) || (!invert && ub < norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, true); + } else if (!invert && norm_rhs_val <= lb) || (invert && ub <= norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, false); } }, Rel::Le => { - if invert { - norm_rhs_val > ub - } else { - lb > norm_rhs_val + if (invert && norm_rhs_val <= lb) || (!invert && ub <= norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, true); + } else if (!invert && norm_rhs_val < lb) || (invert && ub < norm_rhs_val) { + err_upcast_comparison(cx, span, lhs, false); } }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, false); } } } -impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { - let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs); - let Some((rel, normalized_lhs, normalized_rhs)) = normalized else { - return; - }; - - let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); - let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); - - upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); - upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); - } +fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { + if let ExprKind::Cast(cast_val, _) = expr.kind { + let mut applicability = Applicability::MachineApplicable; + let (cast_val_snip, _) = snippet_with_context( + cx, + cast_val.span, + expr.span.ctxt(), + "the expression", + &mut applicability, + ); + span_lint( + cx, + INVALID_UPCAST_COMPARISONS, + span, + format!( + "because of the numeric bounds on `{}` prior to casting, this expression is always {}", + cast_val_snip, + if always { "true" } else { "false" }, + ), + ); } } diff --git a/clippy_lints/src/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs similarity index 50% rename from clippy_lints/src/manual_div_ceil.rs rename to clippy_lints/src/operators/manual_div_ceil.rs index ee531741a5153..98aa474215370 100644 --- a/clippy_lints/src/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -1,4 +1,3 @@ -use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; @@ -7,111 +6,69 @@ use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp}; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::ty::{self}; -use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; -declare_clippy_lint! { - /// ### What it does - /// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation - /// of `x.div_ceil(y)`. - /// - /// ### Why is this bad? - /// It's simpler, clearer and more readable. - /// - /// ### Example - /// ```no_run - /// let x: i32 = 7; - /// let y: i32 = 4; - /// let div = (x + (y - 1)) / y; - /// ``` - /// Use instead: - /// ```no_run - /// #![feature(int_roundings)] - /// let x: i32 = 7; - /// let y: i32 = 4; - /// let div = x.div_ceil(y); - /// ``` - #[clippy::version = "1.83.0"] - pub MANUAL_DIV_CEIL, - complexity, - "manually reimplementing `div_ceil`" -} - -pub struct ManualDivCeil { - msrv: Msrv, -} - -impl ManualDivCeil { - #[must_use] - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } -} +use super::MANUAL_DIV_CEIL; -impl_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]); +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, msrv: Msrv) { + let mut applicability = Applicability::MachineApplicable; -impl<'tcx> LateLintPass<'tcx> for ManualDivCeil { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { - let mut applicability = Applicability::MachineApplicable; - - if let ExprKind::Binary(div_op, div_lhs, div_rhs) = expr.kind - && div_op.node == BinOpKind::Div - && check_int_ty_and_feature(cx, div_lhs) - && check_int_ty_and_feature(cx, div_rhs) - && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = div_lhs.kind - && self.msrv.meets(cx, msrvs::DIV_CEIL) + if op == BinOpKind::Div + && check_int_ty_and_feature(cx, lhs) + && check_int_ty_and_feature(cx, rhs) + && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = lhs.kind + && msrv.meets(cx, msrvs::DIV_CEIL) + { + // (x + (y - 1)) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) { - // (x + (y - 1)) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, div_rhs) - { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - return; - } + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + return; + } - // ((y - 1) + x) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, div_rhs) - { - build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability); - return; - } + // ((y - 1) + x) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) + { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + return; + } - // (x + y - 1) / y - if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Sub - && add_op.node == BinOpKind::Add - && check_literal(inner_rhs) - && check_eq_expr(cx, add_rhs, div_rhs) - { - build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability); - } + // (x + y - 1) / y + if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Sub + && add_op.node == BinOpKind::Add + && check_literal(inner_rhs) + && check_eq_expr(cx, add_rhs, rhs) + { + build_suggestion(cx, expr, add_lhs, rhs, &mut applicability); + } - // (x + (Y - 1)) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, div_rhs) { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - } + // (x + (Y - 1)) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, rhs) { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + } - // ((Y - 1) + x) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, div_rhs) { - build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability); - } + // ((Y - 1) + x) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, rhs) { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + } - // (x - (-Y - 1)) / Y - if inner_op.node == BinOpKind::Sub - && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = div_rhs.kind - && differ_by_one(abs_div_rhs, inner_rhs) - { - build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); - } + // (x - (-Y - 1)) / Y + if inner_op.node == BinOpKind::Sub + && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = rhs.kind + && differ_by_one(abs_div_rhs, inner_rhs) + { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); } } } diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index aaea4ff11fc37..8db2cc1d3f578 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -11,6 +11,9 @@ mod float_cmp; mod float_equality_without_abs; mod identity_op; mod integer_division; +mod integer_division_remainder_used; +mod invalid_upcast_comparisons; +mod manual_div_ceil; mod manual_is_multiple_of; mod manual_midpoint; mod misrefactored_assign_op; @@ -463,7 +466,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for statements of the form `(a - b) < f32::EPSILON` or - /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. + /// `(a - b) < f64::EPSILON`. Note the missing `.abs()`. /// /// ### Why is this bad? /// The code without `.abs()` is more likely to have a bug. @@ -616,7 +619,7 @@ declare_clippy_lint! { /// println!("{within_tolerance}"); // true /// ``` /// - /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is /// a different use of the term that is not suitable for floating point equality comparison. /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. /// @@ -679,7 +682,7 @@ declare_clippy_lint! { /// println!("{within_tolerance}"); // true /// ``` /// - /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is /// a different use of the term that is not suitable for floating point equality comparison. /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. /// @@ -860,6 +863,78 @@ declare_clippy_lint! { "manual implementation of `.is_multiple_of()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation + /// of `x.div_ceil(y)`. + /// + /// ### Why is this bad? + /// It's simpler, clearer and more readable. + /// + /// ### Example + /// ```no_run + /// let x: i32 = 7; + /// let y: i32 = 4; + /// let div = (x + (y - 1)) / y; + /// ``` + /// Use instead: + /// ```no_run + /// #![feature(int_roundings)] + /// let x: i32 = 7; + /// let y: i32 = 4; + /// let div = x.div_ceil(y); + /// ``` + #[clippy::version = "1.83.0"] + pub MANUAL_DIV_CEIL, + complexity, + "manually reimplementing `div_ceil`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for comparisons where the relation is always either + /// true or false, but where one side has been upcast so that the comparison is + /// necessary. Only integer types are checked. + /// + /// ### Why is this bad? + /// An expression like `let x : u8 = ...; (x as u32) > 300` + /// will mistakenly imply that it is possible for `x` to be outside the range of + /// `u8`. + /// + /// ### Example + /// ```no_run + /// let x: u8 = 1; + /// (x as u32) > 300; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INVALID_UPCAST_COMPARISONS, + pedantic, + "a comparison involving an upcast which is always true or false" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of division (`/`) and remainder (`%`) operations + /// when performed on any integer types using the default `Div` and `Rem` trait implementations. + /// + /// ### Why restrict this? + /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, + /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). + /// + /// ### Example + /// ```no_run + /// let my_div = 10 / 2; + /// ``` + /// Use instead: + /// ```no_run + /// let my_div = 10 >> 1; + /// ``` + #[clippy::version = "1.79.0"] + pub INTEGER_DIVISION_REMAINDER_USED, + restriction, + "use of disallowed default division and remainder operations" +} + pub struct Operators { arithmetic_context: numeric_arithmetic::Context, verbose_bit_mask_threshold: u64, @@ -897,6 +972,7 @@ impl_lint_pass!(Operators => [ FLOAT_EQUALITY_WITHOUT_ABS, IDENTITY_OP, INTEGER_DIVISION, + INTEGER_DIVISION_REMAINDER_USED, CMP_OWNED, FLOAT_CMP, FLOAT_CMP_CONST, @@ -906,6 +982,8 @@ impl_lint_pass!(Operators => [ SELF_ASSIGNMENT, MANUAL_MIDPOINT, MANUAL_IS_MULTIPLE_OF, + MANUAL_DIV_CEIL, + INVALID_UPCAST_COMPARISONS, ]); impl<'tcx> LateLintPass<'tcx> for Operators { @@ -921,6 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { } erasing_op::check(cx, e, op.node, lhs, rhs); identity_op::check(cx, e, op.node, lhs, rhs); + invalid_upcast_comparisons::check(cx, op.node, lhs, rhs, e.span); needless_bitwise_bool::check(cx, e, op.node, lhs, rhs); manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv); manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv); @@ -933,6 +1012,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { duration_subsec::check(cx, e, op.node, lhs, rhs); float_equality_without_abs::check(cx, e, op.node, lhs, rhs); integer_division::check(cx, e, op.node, lhs, rhs); + integer_division_remainder_used::check(cx, op.node, lhs, rhs, e.span); cmp_owned::check(cx, op.node, lhs, rhs); float_cmp::check(cx, e, op.node, lhs, rhs); modulo_one::check(cx, e, op.node, rhs); @@ -944,6 +1024,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { rhs, self.modulo_arithmetic_allow_comparison_to_zero, ); + manual_div_ceil::check(cx, e, op.node, lhs, rhs, self.msrv); }, ExprKind::AssignOp(op, lhs, rhs) => { let bin_op = op.node.into(); diff --git a/clippy_lints/src/precedence.rs b/clippy_lints/src/precedence.rs index ec6835db897ed..034fe8edc715d 100644 --- a/clippy_lints/src/precedence.rs +++ b/clippy_lints/src/precedence.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; use rustc_ast::ast::{BinOpKind, Expr, ExprKind}; @@ -10,7 +10,8 @@ use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does /// Checks for operations where precedence may be unclear and suggests to add parentheses. - /// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses + /// It catches a mixed usage of arithmetic and bit shifting/combining operators, + /// as well as method calls applied to closures. /// /// ### Why is this bad? /// Not everyone knows the precedence of those operators by @@ -109,6 +110,19 @@ impl EarlyLintPass for Precedence { }, _ => (), } + } else if let ExprKind::MethodCall(method_call) = &expr.kind + && let ExprKind::Closure(closure) = &method_call.receiver.kind + { + span_lint_and_then(cx, PRECEDENCE, expr.span, "precedence might not be obvious", |diag| { + diag.multipart_suggestion( + "consider parenthesizing the closure", + vec![ + (closure.fn_decl_span.shrink_to_lo(), String::from("(")), + (closure.body.span.shrink_to_hi(), String::from(")")), + ], + Applicability::MachineApplicable, + ); + }); } } } diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index 960edc6b02803..412ca2fa4ed9a 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -86,8 +86,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { return; }; - let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind - else { + let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind else { return; }; diff --git a/clippy_lints/src/types/option_option.rs b/clippy_lints/src/types/option_option.rs index 10df007f2a13d..6d57bb6ef3a0d 100644 --- a/clippy_lints/src/types/option_option.rs +++ b/clippy_lints/src/types/option_option.rs @@ -1,6 +1,7 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::qpath_generic_tys; use clippy_utils::res::MaybeResPath; +use clippy_utils::source::snippet; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, QPath}; use rustc_lint::LateContext; @@ -13,12 +14,21 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ && let Some(arg) = qpath_generic_tys(qpath).next() && arg.basic_res().opt_def_id() == Some(def_id) { - span_lint( + span_lint_and_then( cx, OPTION_OPTION, hir_ty.span, - "consider using `Option` instead of `Option>` or a custom \ - enum if you need to distinguish all 3 cases", + // use just `T` here, as the inner type is not what's problematic + "use of `Option>`", + |diag| { + // but use the specific type here, as: + // - this is kind of a suggestion + // - it's printed right after the linted type + let inner_opt = snippet(cx, arg.span, "_"); + diag.help(format!( + "consider using `{inner_opt}`, or a custom enum if you need to distinguish all 3 cases" + )); + }, ); true } else { diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index ad4f3d450db86..4621c22d6b532 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -454,6 +454,6 @@ fn extend_with_matching( fn eq_pre_post(ps1: &[Pat], ps2: &[Pat], idx: usize) -> bool { ps1.len() == ps2.len() && ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`. - && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r)) - && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r)) + && over(&ps1[..idx], &ps2[..idx], eq_pat) + && over(&ps1[idx + 1..], &ps2[idx + 1..], eq_pat) } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index c55c5ec2f51ae..c39e4a4cc9562 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -9,7 +9,7 @@ use rustc_ast::{ FormatPlaceholder, FormatTrait, }; use rustc_errors::Applicability; -use rustc_hir::{Expr, Impl, Item, ItemKind}; +use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Span}; @@ -240,7 +240,8 @@ declare_clippy_lint! { pub struct Write { format_args: FormatArgsStorage, - in_debug_impl: bool, + // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated + outermost_debug_impl: Option, allow_print_in_tests: bool, } @@ -248,10 +249,14 @@ impl Write { pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { Self { format_args, - in_debug_impl: false, + outermost_debug_impl: None, allow_print_in_tests: conf.allow_print_in_tests, } } + + fn in_debug_impl(&self) -> bool { + self.outermost_debug_impl.is_some() + } } impl_lint_pass!(Write => [ @@ -268,14 +273,16 @@ impl_lint_pass!(Write => [ impl<'tcx> LateLintPass<'tcx> for Write { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_debug_impl(cx, item) { - self.in_debug_impl = true; + // Only check for `impl Debug`s if we're not already in one + if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { + self.outermost_debug_impl = Some(item.owner_id); } } - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_debug_impl(cx, item) { - self.in_debug_impl = false; + fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { + // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl + if self.outermost_debug_impl == Some(item.owner_id) { + self.outermost_debug_impl = None; } } @@ -329,7 +336,7 @@ impl<'tcx> LateLintPass<'tcx> for Write { check_literal(cx, format_args, name); - if !self.in_debug_impl { + if !self.in_debug_impl() { for piece in &format_args.template { if let &FormatArgsPiece::Placeholder(FormatPlaceholder { span: Some(span), diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs index e529bc2d2fa5b..6d5c7b86a0aed 100644 --- a/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/clippy_lints_internal/src/msrv_attr_impl.rs @@ -5,7 +5,7 @@ use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, EarlyBinder, GenericArgKind}; +use rustc_middle::ty::{self, GenericArgKind}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_tool_lint! { diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index d58b47bf6deb3..a1e1b763dccb1 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.92" +version = "0.1.93" edition = "2024" description = "Helpful tools for writing lints, provided as they are used in Clippy" repository = "https://github.com/rust-lang/rust-clippy" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 9d12b46b9546f..45463b4fa1db8 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-10-16 +nightly-2025-10-31 ``` diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 839b46325b5ea..e797c96156045 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -48,12 +48,10 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { (Box(l), Box(r)) | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), - (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, eq_pat), (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { - eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) - && eq_path(lp, rp) - && over(lfs, rfs, |l, r| eq_pat(l, r)) + eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) && eq_path(lp, rp) && over(lfs, rfs, eq_pat) }, (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => { lr == rr @@ -61,7 +59,7 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat) }, - (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), + (Or(ls), Or(rs)) => unordered_over(ls, rs, eq_pat), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), _ => false, } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 3904818cc9b6f..7d6787fec295d 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -209,7 +209,7 @@ pub struct Range<'a> { pub end: Option<&'a Expr<'a>>, /// Whether the interval is open or closed. pub limits: ast::RangeLimits, - pub span: Span + pub span: Span, } impl<'a> Range<'a> { @@ -236,14 +236,12 @@ impl<'a> Range<'a> { limits: ast::RangeLimits::HalfOpen, span, }), - (hir::LangItem::RangeFrom, [field]) if field.ident.name == sym::start => { - Some(Range { - start: Some(field.expr), - end: None, - limits: ast::RangeLimits::HalfOpen, - span, - }) - }, + (hir::LangItem::RangeFrom, [field]) if field.ident.name == sym::start => Some(Range { + start: Some(field.expr), + end: None, + limits: ast::RangeLimits::HalfOpen, + span, + }), (hir::LangItem::Range, [field1, field2]) => { let (start, end) = match (field1.ident.name, field2.ident.name) { (sym::start, sym::end) => (field1.expr, field2.expr), @@ -257,14 +255,12 @@ impl<'a> Range<'a> { span, }) }, - (hir::LangItem::RangeToInclusive, [field]) if field.ident.name == sym::end => { - Some(Range { - start: None, - end: Some(field.expr), - limits: ast::RangeLimits::Closed, - span, - }) - }, + (hir::LangItem::RangeToInclusive, [field]) if field.ident.name == sym::end => Some(Range { + start: None, + end: Some(field.expr), + limits: ast::RangeLimits::Closed, + span, + }), (hir::LangItem::RangeTo, [field]) if field.ident.name == sym::end => Some(Range { start: None, end: Some(field.expr), diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 11220a8c4c017..44abea2f03458 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -342,7 +342,7 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { match *qpath { QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)), QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath), - _ => false, + QPath::TypeRelative(..) => false, } } diff --git a/clippy_utils/src/res.rs b/clippy_utils/src/res.rs index d1f6ebf35ce24..a3efece7d2245 100644 --- a/clippy_utils/src/res.rs +++ b/clippy_utils/src/res.rs @@ -107,9 +107,7 @@ pub trait MaybeQPath<'a>: Copy { fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>) -> Res { match *qpath { QPath::Resolved(_, p) => p.res, - QPath::TypeRelative(..) if let Some((kind, id)) = typeck.ty_based_def(id) => { - Res::Def(kind, id) - }, + QPath::TypeRelative(..) if let Some((kind, id)) = typeck.ty_based_def(id) => Res::Def(kind, id), QPath::TypeRelative(..) => Res::Err, } } @@ -403,7 +401,7 @@ impl<'a> MaybeResPath<'a> for &QPath<'a> { fn opt_res_path(self) -> OptResPath<'a> { match *self { QPath::Resolved(ty, path) => (ty, Some(path)), - _ => (None, None), + QPath::TypeRelative(..) => (None, None), } } } diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 954a71f6c320e..e29d45551d1b0 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -13,7 +13,7 @@ use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::source_map::{SourceMap, original_sp}; use rustc_span::{ - BytePos, DesugaringKind, DUMMY_SP, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, + BytePos, DUMMY_SP, DesugaringKind, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, hygiene, }; use std::borrow::Cow; @@ -675,7 +675,7 @@ fn snippet_with_context_sess<'a>( return ( snippet_with_applicability_sess(sess, span, default, applicability), false, - ) + ); } let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else( diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 5945a0c7b5587..2593df1035277 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -127,7 +127,11 @@ impl<'a> Sugg<'a> { /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*` /// function variants of `Sugg`, since these use different snippet functions. - fn hir_from_snippet(cx: &LateContext<'_>, expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self { + fn hir_from_snippet( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + mut get_snippet: impl FnMut(Span) -> Cow<'a, str>, + ) -> Self { if let Some(range) = higher::Range::hir(cx, expr) { let op = AssocOp::Range(range.limits); let start = range.start.map_or("".into(), |expr| get_snippet(expr.span)); @@ -765,7 +769,7 @@ pub struct DerefClosure { /// such as explicit deref and borrowing cases. /// Returns `None` if no such use cases have been triggered in closure body /// -/// note: this only works on single line immutable closures with exactly one input parameter. +/// note: This only works on immutable closures with exactly one input parameter. pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option { if let ExprKind::Closure(&Closure { fn_decl, def_id, body, .. diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index 4de7b5fb5924d..b73a7c7bb4d98 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.92" +version = "0.1.93" edition = "2024" repository = "https://github.com/rust-lang/rust-clippy" license = "MIT OR Apache-2.0" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d5d96448a97d3..d23fd74d9accd 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-10-16" +channel = "nightly-2025-10-31" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.rs b/tests/ui-toml/excessive_nesting/excessive_nesting.rs index 205cd8ba4ee88..001a6ceb1b17d 100644 --- a/tests/ui-toml/excessive_nesting/excessive_nesting.rs +++ b/tests/ui-toml/excessive_nesting/excessive_nesting.rs @@ -9,7 +9,7 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::never_loop, - clippy::needless_if, + clippy::needless_ifs, clippy::collapsible_if, clippy::blocks_in_conditions, clippy::single_match, diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 9efbb39084976..1f1afe4ea3a53 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -57,3 +57,15 @@ macro_rules! bad_transmute { std::mem::transmute($e) }; } + +#[macro_export] +#[rustfmt::skip] +macro_rules! double_parens { + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (()); + let b = ((5)); + let c = std::convert::identity((5)); + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; +} diff --git a/tests/ui/auxiliary/proc_macro_derive.rs b/tests/ui/auxiliary/proc_macro_derive.rs index 5465092287179..629a500ff6f56 100644 --- a/tests/ui/auxiliary/proc_macro_derive.rs +++ b/tests/ui/auxiliary/proc_macro_derive.rs @@ -230,3 +230,14 @@ pub fn allow_lint_same_span_derive(input: TokenStream) -> TokenStream { span_help(Group::new(Delimiter::Brace, TokenStream::new()).into()), ]) } + +#[proc_macro_derive(DoubleParens)] +pub fn derive_double_parens(_: TokenStream) -> TokenStream { + quote! { + fn foo() { + let a = (()); + let b = ((5)); + let c = std::convert::identity((5)); + } + } +} diff --git a/tests/ui/auxiliary/proc_macros.rs b/tests/ui/auxiliary/proc_macros.rs index bb55539617fc5..a7c20a787519f 100644 --- a/tests/ui/auxiliary/proc_macros.rs +++ b/tests/ui/auxiliary/proc_macros.rs @@ -1,5 +1,5 @@ #![feature(proc_macro_span)] -#![allow(clippy::needless_if, dead_code)] +#![allow(clippy::needless_ifs, dead_code)] extern crate proc_macro; diff --git a/tests/ui/blocks_in_conditions.fixed b/tests/ui/blocks_in_conditions.fixed index 6ae5b0cb2f041..417ed370e7e5b 100644 --- a/tests/ui/blocks_in_conditions.fixed +++ b/tests/ui/blocks_in_conditions.fixed @@ -4,7 +4,7 @@ #![allow( unused, unnecessary_transmutes, - clippy::needless_if, + clippy::needless_ifs, clippy::missing_transmute_annotations )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/blocks_in_conditions.rs b/tests/ui/blocks_in_conditions.rs index 3fd060620728f..fd67ad372114d 100644 --- a/tests/ui/blocks_in_conditions.rs +++ b/tests/ui/blocks_in_conditions.rs @@ -4,7 +4,7 @@ #![allow( unused, unnecessary_transmutes, - clippy::needless_if, + clippy::needless_ifs, clippy::missing_transmute_annotations )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/bool_comparison.fixed b/tests/ui/bool_comparison.fixed index b0b60104c0b91..52564c1cb0922 100644 --- a/tests/ui/bool_comparison.fixed +++ b/tests/ui/bool_comparison.fixed @@ -1,4 +1,4 @@ -#![allow(non_local_definitions, clippy::needless_if)] +#![allow(non_local_definitions, clippy::needless_ifs)] #![warn(clippy::bool_comparison)] #![allow(clippy::non_canonical_partial_ord_impl)] diff --git a/tests/ui/bool_comparison.rs b/tests/ui/bool_comparison.rs index 1b1108d6ce504..a78cf167e84b2 100644 --- a/tests/ui/bool_comparison.rs +++ b/tests/ui/bool_comparison.rs @@ -1,4 +1,4 @@ -#![allow(non_local_definitions, clippy::needless_if)] +#![allow(non_local_definitions, clippy::needless_ifs)] #![warn(clippy::bool_comparison)] #![allow(clippy::non_canonical_partial_ord_impl)] diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed index 8d543b0422009..2e57078d8e169 100644 --- a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed +++ b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_clone, clippy::derive_partial_eq_without_eq )] // See #5700 diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.rs b/tests/ui/cmp_owned/asymmetric_partial_eq.rs index 6da311c50ee7b..7040d8a41030d 100644 --- a/tests/ui/cmp_owned/asymmetric_partial_eq.rs +++ b/tests/ui/cmp_owned/asymmetric_partial_eq.rs @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_clone, clippy::derive_partial_eq_without_eq )] // See #5700 diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index da958f76a5ca0..e7439beef186c 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] #![warn(clippy::collapsible_if, clippy::collapsible_else_if)] #[rustfmt::skip] diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index 06af49f2f6f3b..434ba3654f983 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -1,4 +1,4 @@ -#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)] #![warn(clippy::collapsible_if, clippy::collapsible_else_if)] #[rustfmt::skip] diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index ca9d02ff2d4f8..6e3fd0f78ddfd 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -1,7 +1,7 @@ #![allow( clippy::assertions_on_constants, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::nonminimal_bool, clippy::eq_op, clippy::redundant_pattern_matching diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index 9ac68ecd4cac0..666252a526547 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -1,7 +1,7 @@ #![allow( clippy::assertions_on_constants, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::nonminimal_bool, clippy::eq_op, clippy::redundant_pattern_matching diff --git a/tests/ui/comparison_to_empty.fixed b/tests/ui/comparison_to_empty.fixed index 7a71829dd62cc..4c3b6004e8006 100644 --- a/tests/ui/comparison_to_empty.fixed +++ b/tests/ui/comparison_to_empty.fixed @@ -1,5 +1,5 @@ #![warn(clippy::comparison_to_empty)] -#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)] fn main() { // Disallow comparisons to empty diff --git a/tests/ui/comparison_to_empty.rs b/tests/ui/comparison_to_empty.rs index 5d213a09e8126..0d4bbe4abf8a0 100644 --- a/tests/ui/comparison_to_empty.rs +++ b/tests/ui/comparison_to_empty.rs @@ -1,5 +1,5 @@ #![warn(clippy::comparison_to_empty)] -#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)] fn main() { // Disallow comparisons to empty diff --git a/tests/ui/crashes/ice-7169.fixed b/tests/ui/crashes/ice-7169.fixed index 71a40ad7de71a..4392e5d4c028b 100644 --- a/tests/ui/crashes/ice-7169.fixed +++ b/tests/ui/crashes/ice-7169.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[derive(Default)] struct A { diff --git a/tests/ui/crashes/ice-7169.rs b/tests/ui/crashes/ice-7169.rs index d43e2cc164d75..a2aa1bdd5275e 100644 --- a/tests/ui/crashes/ice-7169.rs +++ b/tests/ui/crashes/ice-7169.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[derive(Default)] struct A { diff --git a/tests/ui/disallowed_names.rs b/tests/ui/disallowed_names.rs index 15bb67349976d..331cccef9f932 100644 --- a/tests/ui/disallowed_names.rs +++ b/tests/ui/disallowed_names.rs @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![allow( dead_code, - clippy::needless_if, + clippy::needless_ifs, clippy::similar_names, clippy::single_match, clippy::toplevel_ref_arg, diff --git a/tests/ui/doc/needless_doctest_main.rs b/tests/ui/doc/needless_doctest_main.rs index 8c3217624d443..0fbf29365ecfe 100644 --- a/tests/ui/doc/needless_doctest_main.rs +++ b/tests/ui/doc/needless_doctest_main.rs @@ -1,33 +1,14 @@ #![warn(clippy::needless_doctest_main)] -//! issue 10491: -//! ```rust,no_test -//! use std::collections::HashMap; -//! -//! fn main() { -//! let mut m = HashMap::new(); -//! m.insert(1u32, 2u32); -//! } -//! ``` -/// some description here -/// ```rust,no_test -/// fn main() { -/// foo() -/// } -/// ``` -fn foo() {} - -#[rustfmt::skip] /// Description /// ```rust /// fn main() { -//~^ error: needless `fn main` in doctest +//~^ needless_doctest_main /// let a = 0; /// } /// ``` fn mulpipulpi() {} -#[rustfmt::skip] /// With a `#[no_main]` /// ```rust /// #[no_main] @@ -45,7 +26,6 @@ fn pulpimulpi() {} /// ``` fn plumilupi() {} -#[rustfmt::skip] /// Additional function, shouldn't trigger /// ```rust /// fn additional_function() { @@ -58,7 +38,6 @@ fn plumilupi() {} /// ``` fn mlupipupi() {} -#[rustfmt::skip] /// Additional function AFTER main, shouldn't trigger /// ```rust /// fn main() { @@ -71,22 +50,19 @@ fn mlupipupi() {} /// ``` fn lumpimupli() {} -#[rustfmt::skip] /// Ignore code block, should not lint at all /// ```rust, ignore /// fn main() { -//~^ error: needless `fn main` in doctest /// // Hi! /// let _ = 0; /// } /// ``` fn mpulpilumi() {} -#[rustfmt::skip] /// Spaces in weird positions (including an \u{A0} after `main`) /// ```rust /// fn main (){ -//~^ error: needless `fn main` in doctest +//~^ needless_doctest_main /// let _ = 0; /// } /// ``` diff --git a/tests/ui/doc/needless_doctest_main.stderr b/tests/ui/doc/needless_doctest_main.stderr index dd5474ccb85af..693cc22fba2a7 100644 --- a/tests/ui/doc/needless_doctest_main.stderr +++ b/tests/ui/doc/needless_doctest_main.stderr @@ -1,36 +1,17 @@ error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:23:5 + --> tests/ui/doc/needless_doctest_main.rs:5:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// let a = 0; -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ | = note: `-D clippy::needless-doctest-main` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]` error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:77:5 + --> tests/ui/doc/needless_doctest_main.rs:64:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// // Hi! -LL | | /// let _ = 0; -LL | | /// } - | |_____^ +LL | /// fn main (){ + | ^^^^^^^^^^^ -error: needless `fn main` in doctest - --> tests/ui/doc/needless_doctest_main.rs:88:5 - | -LL | /// fn main (){ - | _____^ -LL | | -LL | | /// let _ = 0; -LL | | /// } - | |_____^ - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/double_comparison.fixed b/tests/ui/double_comparison.fixed index 685e3319bf9af..0680eb35ef974 100644 --- a/tests/ui/double_comparison.fixed +++ b/tests/ui/double_comparison.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn main() { let x = 1; diff --git a/tests/ui/double_comparison.rs b/tests/ui/double_comparison.rs index 3670a050e88d1..18ab7d2c42546 100644 --- a/tests/ui/double_comparison.rs +++ b/tests/ui/double_comparison.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn main() { let x = 1; diff --git a/tests/ui/double_parens.fixed b/tests/ui/double_parens.fixed index dedc513438d11..024af68401327 100644 --- a/tests/ui/double_parens.fixed +++ b/tests/ui/double_parens.fixed @@ -1,8 +1,13 @@ +//@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs +//@aux-build:macro_rules.rs #![warn(clippy::double_parens)] #![expect(clippy::eq_op, clippy::no_effect)] #![feature(custom_inner_attributes)] #![rustfmt::skip] +use proc_macros::{external, with_span}; + fn dummy_fn(_: T) {} struct DummyStruct; @@ -96,4 +101,64 @@ fn issue9000(x: DummyStruct) { //~^ double_parens } +fn issue15892() { + use macro_rules::double_parens as double_parens_external; + + macro_rules! double_parens{ + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (); + //~^ double_parens + let b = (5); + //~^ double_parens + let c = std::convert::identity(5); + //~^ double_parens + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; + } + + // Don't lint: external macro + (external!((5))); + external!(((5))); + + #[repr(transparent)] + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct InterruptMask(u32); + + impl InterruptMask { + pub const OE: InterruptMask = InterruptMask(1 << 10); + pub const BE: InterruptMask = InterruptMask(1 << 9); + pub const PE: InterruptMask = InterruptMask(1 << 8); + pub const FE: InterruptMask = InterruptMask(1 << 7); + // Lint: internal macro + pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + // Don't lint: external macro + pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE); + #[allow(clippy::unnecessary_cast)] + pub const G: InterruptMask = external!( + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + #[allow(clippy::unnecessary_cast)] + // Don't lint: external proc-macro + pub const H: InterruptMask = with_span!(span + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + pub const fn into_bits(self) -> u32 { + self.0 + } + #[must_use] + pub const fn union(self, rhs: Self) -> Self { + InterruptMask(self.0 | rhs.0) + } + } +} + +fn issue15940() { + use proc_macro_derive::DoubleParens; + + #[derive(DoubleParens)] + // Don't lint: external derive macro + pub struct Person; +} + fn main() {} diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 27f252485b714..8a76f2837f353 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -1,8 +1,13 @@ +//@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs +//@aux-build:macro_rules.rs #![warn(clippy::double_parens)] #![expect(clippy::eq_op, clippy::no_effect)] #![feature(custom_inner_attributes)] #![rustfmt::skip] +use proc_macros::{external, with_span}; + fn dummy_fn(_: T) {} struct DummyStruct; @@ -96,4 +101,64 @@ fn issue9000(x: DummyStruct) { //~^ double_parens } +fn issue15892() { + use macro_rules::double_parens as double_parens_external; + + macro_rules! double_parens{ + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a); + let a = (()); + //~^ double_parens + let b = ((5)); + //~^ double_parens + let c = std::convert::identity((5)); + //~^ double_parens + InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32) + }}; + } + + // Don't lint: external macro + (external!((5))); + external!(((5))); + + #[repr(transparent)] + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct InterruptMask(u32); + + impl InterruptMask { + pub const OE: InterruptMask = InterruptMask(1 << 10); + pub const BE: InterruptMask = InterruptMask(1 << 9); + pub const PE: InterruptMask = InterruptMask(1 << 8); + pub const FE: InterruptMask = InterruptMask(1 << 7); + // Lint: internal macro + pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + // Don't lint: external macro + pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE); + #[allow(clippy::unnecessary_cast)] + pub const G: InterruptMask = external!( + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + #[allow(clippy::unnecessary_cast)] + // Don't lint: external proc-macro + pub const H: InterruptMask = with_span!(span + InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32) + ); + pub const fn into_bits(self) -> u32 { + self.0 + } + #[must_use] + pub const fn union(self, rhs: Self) -> Self { + InterruptMask(self.0 | rhs.0) + } + } +} + +fn issue15940() { + use proc_macro_derive::DoubleParens; + + #[derive(DoubleParens)] + // Don't lint: external derive macro + pub struct Person; +} + fn main() {} diff --git a/tests/ui/double_parens.stderr b/tests/ui/double_parens.stderr index 3a740e44cacf3..51b5c6279b211 100644 --- a/tests/ui/double_parens.stderr +++ b/tests/ui/double_parens.stderr @@ -1,5 +1,5 @@ error: unnecessary parentheses - --> tests/ui/double_parens.rs:15:5 + --> tests/ui/double_parens.rs:20:5 | LL | ((0)) | ^^^^^ help: remove them: `(0)` @@ -8,37 +8,37 @@ LL | ((0)) = help: to override `-D warnings` add `#[allow(clippy::double_parens)]` error: unnecessary parentheses - --> tests/ui/double_parens.rs:20:14 + --> tests/ui/double_parens.rs:25:14 | LL | dummy_fn((0)); | ^^^ help: remove them: `0` error: unnecessary parentheses - --> tests/ui/double_parens.rs:25:20 + --> tests/ui/double_parens.rs:30:20 | LL | x.dummy_method((0)); | ^^^ help: remove them: `0` error: unnecessary parentheses - --> tests/ui/double_parens.rs:30:5 + --> tests/ui/double_parens.rs:35:5 | LL | ((1, 2)) | ^^^^^^^^ help: remove them: `(1, 2)` error: unnecessary parentheses - --> tests/ui/double_parens.rs:36:5 + --> tests/ui/double_parens.rs:41:5 | LL | (()) | ^^^^ help: remove them: `()` error: unnecessary parentheses - --> tests/ui/double_parens.rs:59:16 + --> tests/ui/double_parens.rs:64:16 | LL | assert_eq!(((1, 2)), (1, 2), "Error"); | ^^^^^^^^ help: remove them: `(1, 2)` error: unnecessary parentheses - --> tests/ui/double_parens.rs:84:16 + --> tests/ui/double_parens.rs:89:16 | LL | () => {((100))} | ^^^^^^^ help: remove them: `(100)` @@ -49,22 +49,55 @@ LL | bar!(); = note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info) error: unnecessary parentheses - --> tests/ui/double_parens.rs:91:5 + --> tests/ui/double_parens.rs:96:5 | LL | ((vec![1, 2])); | ^^^^^^^^^^^^^^ help: remove them: `(vec![1, 2])` error: unnecessary parentheses - --> tests/ui/double_parens.rs:93:14 + --> tests/ui/double_parens.rs:98:14 | LL | dummy_fn((vec![1, 2])); | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` error: unnecessary parentheses - --> tests/ui/double_parens.rs:95:20 + --> tests/ui/double_parens.rs:100:20 | LL | x.dummy_method((vec![1, 2])); | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` -error: aborting due to 10 previous errors +error: unnecessary parentheses + --> tests/ui/double_parens.rs:110:21 + | +LL | let a = (()); + | ^^^^ help: remove them: `()` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:112:21 + | +LL | let b = ((5)); + | ^^^^^ help: remove them: `(5)` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:114:44 + | +LL | let c = std::convert::identity((5)); + | ^^^ help: remove them: `5` +... +LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE); + | -------------------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 13 previous errors diff --git a/tests/ui/empty_enum.rs b/tests/ui/empty_enum.rs deleted file mode 100644 index 439fd0974f5f8..0000000000000 --- a/tests/ui/empty_enum.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(dead_code)] -#![warn(clippy::empty_enum)] -// Enable never type to test empty enum lint -#![feature(never_type)] -enum Empty {} -//~^ empty_enum - -fn main() {} diff --git a/tests/ui/empty_enums.rs b/tests/ui/empty_enums.rs new file mode 100644 index 0000000000000..0deb0f57b0e4a --- /dev/null +++ b/tests/ui/empty_enums.rs @@ -0,0 +1,25 @@ +#![warn(clippy::empty_enums)] +// Enable never type to test empty enum lint +#![feature(never_type)] + +enum Empty {} +//~^ empty_enums + +mod issue15910 { + enum NotReallyEmpty { + #[cfg(false)] + Hidden, + } + + enum OneVisibleVariant { + #[cfg(false)] + Hidden, + Visible, + } + + enum CfgInsideVariant { + Variant(#[cfg(false)] String), + } +} + +fn main() {} diff --git a/tests/ui/empty_enum.stderr b/tests/ui/empty_enums.stderr similarity index 75% rename from tests/ui/empty_enum.stderr rename to tests/ui/empty_enums.stderr index 6a1ded9298ed7..5aa2347b4ae08 100644 --- a/tests/ui/empty_enum.stderr +++ b/tests/ui/empty_enums.stderr @@ -1,12 +1,12 @@ error: enum with no variants - --> tests/ui/empty_enum.rs:5:1 + --> tests/ui/empty_enums.rs:5:1 | LL | enum Empty {} | ^^^^^^^^^^^^^ | = help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated - = note: `-D clippy::empty-enum` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::empty_enum)]` + = note: `-D clippy::empty-enums` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::empty_enums)]` error: aborting due to 1 previous error diff --git a/tests/ui/empty_enum_without_never_type.rs b/tests/ui/empty_enums_without_never_type.rs similarity index 67% rename from tests/ui/empty_enum_without_never_type.rs rename to tests/ui/empty_enums_without_never_type.rs index 3661a15372086..17ccac83ce98e 100644 --- a/tests/ui/empty_enum_without_never_type.rs +++ b/tests/ui/empty_enums_without_never_type.rs @@ -1,7 +1,6 @@ //@ check-pass -#![allow(dead_code)] -#![warn(clippy::empty_enum)] +#![warn(clippy::empty_enums)] // `never_type` is not enabled; this test has no stderr file enum Empty {} diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index ce8b67f9ca7b0..58fbad64a78da 100644 --- a/tests/ui/equatable_if_let.fixed +++ b/tests/ui/equatable_if_let.fixed @@ -4,7 +4,7 @@ unused_variables, dead_code, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #![warn(clippy::equatable_if_let)] diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs index ff09533f26519..cca97c76b5094 100644 --- a/tests/ui/equatable_if_let.rs +++ b/tests/ui/equatable_if_let.rs @@ -4,7 +4,7 @@ unused_variables, dead_code, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #![warn(clippy::equatable_if_let)] diff --git a/tests/ui/expect_tool_lint_rfc_2383.rs b/tests/ui/expect_tool_lint_rfc_2383.rs index 2295691c81272..82ac4db172d88 100644 --- a/tests/ui/expect_tool_lint_rfc_2383.rs +++ b/tests/ui/expect_tool_lint_rfc_2383.rs @@ -10,7 +10,7 @@ //! This test can't cover every lint from Clippy, rustdoc and potentially other //! tools that will be developed. This therefore only tests a small subset of lints #![expect(rustdoc::missing_crate_level_docs)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] mod rustc_ok { //! See diff --git a/tests/ui/filetype_is_file.rs b/tests/ui/filetype_is_file.rs index 8ca01b91210fb..2ec5b3b81446d 100644 --- a/tests/ui/filetype_is_file.rs +++ b/tests/ui/filetype_is_file.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #![warn(clippy::filetype_is_file)] fn main() -> std::io::Result<()> { diff --git a/tests/ui/if_same_then_else2.rs b/tests/ui/if_same_then_else2.rs index 5b74aecdacbe5..6ac5fe6e7b543 100644 --- a/tests/ui/if_same_then_else2.rs +++ b/tests/ui/if_same_then_else2.rs @@ -5,7 +5,7 @@ clippy::equatable_if_let, clippy::collapsible_if, clippy::ifs_same_cond, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::single_element_loop, clippy::branches_sharing_code diff --git a/tests/ui/ifs_same_cond.rs b/tests/ui/ifs_same_cond.rs index 7067434953d6e..486903f653de6 100644 --- a/tests/ui/ifs_same_cond.rs +++ b/tests/ui/ifs_same_cond.rs @@ -1,5 +1,5 @@ #![warn(clippy::ifs_same_cond)] -#![allow(clippy::if_same_then_else, clippy::needless_if, clippy::needless_else)] // all empty blocks +#![allow(clippy::if_same_then_else, clippy::needless_ifs, clippy::needless_else)] // all empty blocks fn ifs_same_cond() { let a = 0; diff --git a/tests/ui/impl.rs b/tests/ui/impl.rs index 15cb61c6eebd9..e6044cc507812 100644 --- a/tests/ui/impl.rs +++ b/tests/ui/impl.rs @@ -84,4 +84,26 @@ impl OneExpected {} impl OneExpected {} //~^ multiple_inherent_impl +// issue #8714 +struct Lifetime<'s> { + s: &'s str, +} + +impl Lifetime<'_> {} +impl Lifetime<'_> {} // false negative + +impl<'a> Lifetime<'a> {} +impl<'a> Lifetime<'a> {} // false negative + +impl<'b> Lifetime<'b> {} // false negative? + +impl Lifetime<'static> {} + +struct Generic { + g: Vec, +} + +impl Generic {} +impl Generic {} // false negative + fn main() {} diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs index f7f21e1850d0a..3069c8139abe3 100644 --- a/tests/ui/incompatible_msrv.rs +++ b/tests/ui/incompatible_msrv.rs @@ -168,4 +168,14 @@ fn enum_variant_ok() { let _ = const { std::io::ErrorKind::InvalidFilename }; } +#[clippy::msrv = "1.38.0"] +const fn uncalled_len() { + let _ = Vec::::len; + let x = str::len; + let _ = x(""); + //~^ incompatible_msrv + let _ = "".len(); + //~^ incompatible_msrv +} + fn main() {} diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr index e42360d296f58..3c0bb595bd5b0 100644 --- a/tests/ui/incompatible_msrv.stderr +++ b/tests/ui/incompatible_msrv.stderr @@ -110,5 +110,17 @@ error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item i LL | let _ = const { std::io::ErrorKind::InvalidFilename }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 17 previous errors +error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0` + --> tests/ui/incompatible_msrv.rs:175:13 + | +LL | let _ = x(""); + | ^ + +error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0` + --> tests/ui/incompatible_msrv.rs:177:16 + | +LL | let _ = "".len(); + | ^^^^^ + +error: aborting due to 19 previous errors diff --git a/tests/ui/integer_division_remainder_used.stderr b/tests/ui/integer_division_remainder_used.stderr index ea9f0e716c7d4..32a61a8585f03 100644 --- a/tests/ui/integer_division_remainder_used.stderr +++ b/tests/ui/integer_division_remainder_used.stderr @@ -1,4 +1,4 @@ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:10:14 | LL | Self(self.0 / rhs.0) @@ -7,49 +7,49 @@ LL | Self(self.0 / rhs.0) = note: `-D clippy::integer-division-remainder-used` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::integer_division_remainder_used)]` -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:18:14 | LL | Self(self.0 % rhs.0) | ^^^^^^^^^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:27:13 | LL | let c = a / b; | ^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:29:13 | LL | let d = a % b; | ^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:31:13 | LL | let e = &a / b; | ^^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:33:13 | LL | let f = a % &b; | ^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:35:13 | LL | let g = &a / &b; | ^^^^^^^ -error: use of % has been disallowed in this context +error: use of `%` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:37:13 | LL | let h = &10 % b; | ^^^^^^^ -error: use of / has been disallowed in this context +error: use of `/` has been disallowed in this context --> tests/ui/integer_division_remainder_used.rs:39:13 | LL | let i = a / &4; diff --git a/tests/ui/len_zero.fixed b/tests/ui/len_zero.fixed index b8573ef13b0e8..50b96f7712d71 100644 --- a/tests/ui/len_zero.fixed +++ b/tests/ui/len_zero.fixed @@ -2,7 +2,7 @@ #![allow( dead_code, unused, - clippy::needless_if, + clippy::needless_ifs, clippy::len_without_is_empty, clippy::const_is_empty )] @@ -275,3 +275,7 @@ fn no_infinite_recursion() -> bool { // Do not crash while checking if S implements `.is_empty()` S == "" } + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 +} diff --git a/tests/ui/len_zero.rs b/tests/ui/len_zero.rs index ef3c49c1ab309..a6acc2be714bc 100644 --- a/tests/ui/len_zero.rs +++ b/tests/ui/len_zero.rs @@ -2,7 +2,7 @@ #![allow( dead_code, unused, - clippy::needless_if, + clippy::needless_ifs, clippy::len_without_is_empty, clippy::const_is_empty )] @@ -275,3 +275,7 @@ fn no_infinite_recursion() -> bool { // Do not crash while checking if S implements `.is_empty()` S == "" } + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 +} diff --git a/tests/ui/len_zero_unstable.fixed b/tests/ui/len_zero_unstable.fixed new file mode 100644 index 0000000000000..8d4e6c2cc006f --- /dev/null +++ b/tests/ui/len_zero_unstable.fixed @@ -0,0 +1,7 @@ +#![warn(clippy::len_zero)] +#![feature(exact_size_is_empty)] + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.is_empty() + //~^ len_zero +} diff --git a/tests/ui/len_zero_unstable.rs b/tests/ui/len_zero_unstable.rs new file mode 100644 index 0000000000000..f59056c5c55b1 --- /dev/null +++ b/tests/ui/len_zero_unstable.rs @@ -0,0 +1,7 @@ +#![warn(clippy::len_zero)] +#![feature(exact_size_is_empty)] + +fn issue15890(vertices: &mut dyn ExactSizeIterator) -> bool { + vertices.len() == 0 + //~^ len_zero +} diff --git a/tests/ui/len_zero_unstable.stderr b/tests/ui/len_zero_unstable.stderr new file mode 100644 index 0000000000000..103ccf3dcbf5a --- /dev/null +++ b/tests/ui/len_zero_unstable.stderr @@ -0,0 +1,11 @@ +error: length comparison to zero + --> tests/ui/len_zero_unstable.rs:5:5 + | +LL | vertices.len() == 0 + | ^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `vertices.is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::len_zero)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/lines_filter_map_ok.fixed b/tests/ui/lines_filter_map_ok.fixed index 977e31c744a2c..0617f06fafb7c 100644 --- a/tests/ui/lines_filter_map_ok.fixed +++ b/tests/ui/lines_filter_map_ok.fixed @@ -1,39 +1,37 @@ -#![allow(unused, clippy::map_identity)] +#![allow(clippy::map_identity)] #![warn(clippy::lines_filter_map_ok)] use std::io::{self, BufRead, BufReader}; fn main() -> io::Result<()> { + // Lint: + let f = std::fs::File::open("/")?; - // Lint BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - let s = "foo\nbar\nbaz\n"; - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().map_while(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Do not lint (not a `Lines` iterator) + + // Do not lint: + + // not a `Lines` iterator io::stdin() .lines() .map(std::convert::identity) .filter_map(|x| x.ok()) .for_each(|_| ()); - // Do not lint (not a `Result::ok()` extractor) + // not a `Result::ok()` extractor io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); Ok(()) } diff --git a/tests/ui/lines_filter_map_ok.rs b/tests/ui/lines_filter_map_ok.rs index 2196075bc4455..dfd1ec431a52f 100644 --- a/tests/ui/lines_filter_map_ok.rs +++ b/tests/ui/lines_filter_map_ok.rs @@ -1,39 +1,37 @@ -#![allow(unused, clippy::map_identity)] +#![allow(clippy::map_identity)] #![warn(clippy::lines_filter_map_ok)] use std::io::{self, BufRead, BufReader}; fn main() -> io::Result<()> { + // Lint: + let f = std::fs::File::open("/")?; - // Lint BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint let f = std::fs::File::open("/")?; BufReader::new(f).lines().flatten().for_each(|_| ()); //~^ lines_filter_map_ok - let s = "foo\nbar\nbaz\n"; - // Lint io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); //~^ lines_filter_map_ok - // Lint io::stdin().lines().flatten().for_each(|_| ()); //~^ lines_filter_map_ok - // Do not lint (not a `Lines` iterator) + + // Do not lint: + + // not a `Lines` iterator io::stdin() .lines() .map(std::convert::identity) .filter_map(|x| x.ok()) .for_each(|_| ()); - // Do not lint (not a `Result::ok()` extractor) + // not a `Result::ok()` extractor io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); Ok(()) } diff --git a/tests/ui/lines_filter_map_ok.stderr b/tests/ui/lines_filter_map_ok.stderr index f9038eec9fb21..c05dadd1b5f29 100644 --- a/tests/ui/lines_filter_map_ok.stderr +++ b/tests/ui/lines_filter_map_ok.stderr @@ -1,11 +1,11 @@ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:9:31 + --> tests/ui/lines_filter_map_ok.rs:10:31 | LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:9:5 + --> tests/ui/lines_filter_map_ok.rs:10:5 | LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,49 +25,49 @@ LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `flatten()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:17:31 + --> tests/ui/lines_filter_map_ok.rs:16:31 | LL | BufReader::new(f).lines().flatten().for_each(|_| ()); | ^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:17:5 + --> tests/ui/lines_filter_map_ok.rs:16:5 | LL | BufReader::new(f).lines().flatten().for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:22:25 + --> tests/ui/lines_filter_map_ok.rs:19:25 | LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:22:5 + --> tests/ui/lines_filter_map_ok.rs:19:5 | LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:25:25 + --> tests/ui/lines_filter_map_ok.rs:21:25 | LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:25:5 + --> tests/ui/lines_filter_map_ok.rs:21:5 | LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ error: `flatten()` will run forever if the iterator repeatedly produces an `Err` - --> tests/ui/lines_filter_map_ok.rs:28:25 + --> tests/ui/lines_filter_map_ok.rs:23:25 | LL | io::stdin().lines().flatten().for_each(|_| ()); | ^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error - --> tests/ui/lines_filter_map_ok.rs:28:5 + --> tests/ui/lines_filter_map_ok.rs:23:5 | LL | io::stdin().lines().flatten().for_each(|_| ()); | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/manual_float_methods.rs b/tests/ui/manual_float_methods.rs index 4b496a4932833..3cd469fd43a0d 100644 --- a/tests/ui/manual_float_methods.rs +++ b/tests/ui/manual_float_methods.rs @@ -1,6 +1,6 @@ //@no-rustfix: overlapping suggestions //@aux-build:proc_macros.rs -#![allow(clippy::needless_if, unused)] +#![allow(clippy::needless_ifs, unused)] #![warn(clippy::manual_is_infinite, clippy::manual_is_finite)] // FIXME(f16_f128): add tests for these types once constants are available diff --git a/tests/ui/manual_let_else.rs b/tests/ui/manual_let_else.rs index 3781ba1676f5e..4523edec3c764 100644 --- a/tests/ui/manual_let_else.rs +++ b/tests/ui/manual_let_else.rs @@ -6,7 +6,7 @@ clippy::let_unit_value, clippy::match_single_binding, clippy::never_loop, - clippy::needless_if, + clippy::needless_ifs, clippy::diverging_sub_expression, clippy::single_match, clippy::manual_unwrap_or_default @@ -546,3 +546,28 @@ mod issue14598 { todo!() } } + +mod issue15914 { + // https://github.com/rust-lang/rust-clippy/issues/15914 + unsafe fn something_unsafe() -> Option { + None + } + + fn foo() { + let value = if let Some(value) = unsafe { something_unsafe() } { + //~^ manual_let_else + value + } else { + return; + }; + + let some_flag = true; + + let value = if let Some(value) = if some_flag { None } else { Some(3) } { + //~^ manual_let_else + value + } else { + return; + }; + } +} diff --git a/tests/ui/manual_let_else.stderr b/tests/ui/manual_let_else.stderr index a1eea04192919..f4b1644c44baa 100644 --- a/tests/ui/manual_let_else.stderr +++ b/tests/ui/manual_let_else.stderr @@ -549,5 +549,41 @@ LL | | Some(x) => x, LL | | }; | |__________^ help: consider writing: `let Some(v) = w else { return Err("abc") };` -error: aborting due to 35 previous errors +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else.rs:557:9 + | +LL | / let value = if let Some(value) = unsafe { something_unsafe() } { +LL | | +LL | | value +LL | | } else { +LL | | return; +LL | | }; + | |__________^ + | +help: consider writing + | +LL ~ let Some(value) = (unsafe { something_unsafe() }) else { +LL + return; +LL + }; + | + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else.rs:566:9 + | +LL | / let value = if let Some(value) = if some_flag { None } else { Some(3) } { +LL | | +LL | | value +LL | | } else { +LL | | return; +LL | | }; + | |__________^ + | +help: consider writing + | +LL ~ let Some(value) = (if some_flag { None } else { Some(3) }) else { +LL + return; +LL + }; + | + +error: aborting due to 37 previous errors diff --git a/tests/ui/manual_option_as_slice.stderr b/tests/ui/manual_option_as_slice.stderr index e240ae8eb7d90..37113e9eafe1b 100644 --- a/tests/ui/manual_option_as_slice.stderr +++ b/tests/ui/manual_option_as_slice.stderr @@ -1,4 +1,4 @@ -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:5:9 | LL | _ = match x.as_ref() { @@ -7,12 +7,21 @@ LL | | LL | | Some(f) => std::slice::from_ref(f), LL | | None => &[], LL | | }; - | |_____^ help: use: `x.as_slice()` + | |_____^ | = note: `-D clippy::manual-option-as-slice` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_option_as_slice)]` +help: use `Option::as_slice` directly + | +LL - _ = match x.as_ref() { +LL - +LL - Some(f) => std::slice::from_ref(f), +LL - None => &[], +LL - }; +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:11:9 | LL | _ = if let Some(f) = x.as_ref() { @@ -23,37 +32,79 @@ LL | | std::slice::from_ref(f) LL | | } else { LL | | &[] LL | | }; - | |_____^ help: use: `x.as_slice()` + | |_____^ + | +help: use `Option::as_slice` directly + | +LL - _ = if let Some(f) = x.as_ref() { +LL - +LL - +LL - std::slice::from_ref(f) +LL - } else { +LL - &[] +LL - }; +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:19:9 | LL | _ = x.as_ref().map_or(&[][..], std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:22:9 | LL | _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:25:9 | LL | _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:28:9 | LL | _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); +LL + _ = x.as_slice(); + | -error: use `Option::as_slice` +error: manual implementation of `Option::as_slice` --> tests/ui/manual_option_as_slice.rs:33:13 | LL | _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `Option::as_slice` directly + | +LL - _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); +LL + _ = x.as_slice(); + | error: aborting due to 7 previous errors diff --git a/tests/ui/manual_unwrap_or.fixed b/tests/ui/manual_unwrap_or.fixed index e12287a709395..8dd9e7a8a6fae 100644 --- a/tests/ui/manual_unwrap_or.fixed +++ b/tests/ui/manual_unwrap_or.fixed @@ -250,4 +250,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 { Some(42).unwrap_or(0) } +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 2 }; + + let x = uncopyable_res; + let _ = x.unwrap_or(2); + //~^ manual_unwrap_or + + let copyable_res: Result = Ok(1); + let _ = copyable_res.unwrap_or(2); + //~^ manual_unwrap_or + let _ = copyable_res; +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.rs b/tests/ui/manual_unwrap_or.rs index 53cffcab5b56c..f8e2c5f43745a 100644 --- a/tests/ui/manual_unwrap_or.rs +++ b/tests/ui/manual_unwrap_or.rs @@ -329,4 +329,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 { } } +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 2 }; + + let x = uncopyable_res; + let _ = if let Ok(v) = x { v } else { 2 }; + //~^ manual_unwrap_or + + let copyable_res: Result = Ok(1); + let _ = if let Ok(v) = copyable_res { v } else { 2 }; + //~^ manual_unwrap_or + let _ = copyable_res; +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.stderr b/tests/ui/manual_unwrap_or.stderr index 320e895fb8237..18764d1501d89 100644 --- a/tests/ui/manual_unwrap_or.stderr +++ b/tests/ui/manual_unwrap_or.stderr @@ -201,5 +201,17 @@ LL | | 0 LL | | } | |_____^ help: replace with: `Some(42).unwrap_or(0)` -error: aborting due to 18 previous errors +error: this pattern reimplements `Result::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:337:13 + | +LL | let _ = if let Ok(v) = x { v } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `x.unwrap_or(2)` + +error: this pattern reimplements `Result::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:341:13 + | +LL | let _ = if let Ok(v) = copyable_res { v } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `copyable_res.unwrap_or(2)` + +error: aborting due to 20 previous errors diff --git a/tests/ui/manual_unwrap_or_default.fixed b/tests/ui/manual_unwrap_or_default.fixed index 11023ac1142a7..0c64ed1826b26 100644 --- a/tests/ui/manual_unwrap_or_default.fixed +++ b/tests/ui/manual_unwrap_or_default.fixed @@ -128,3 +128,17 @@ mod issue14716 { }; } } + +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 0 }; + + let x = uncopyable_res; + let _ = x.unwrap_or_default(); + //~^ manual_unwrap_or_default + + let copyable_res: Result = Ok(1); + let _ = copyable_res.unwrap_or_default(); + //~^ manual_unwrap_or_default + let _ = copyable_res; +} diff --git a/tests/ui/manual_unwrap_or_default.rs b/tests/ui/manual_unwrap_or_default.rs index bf06d9af7d219..e99f7d44dde19 100644 --- a/tests/ui/manual_unwrap_or_default.rs +++ b/tests/ui/manual_unwrap_or_default.rs @@ -169,3 +169,17 @@ mod issue14716 { }; } } + +fn issue_15807() { + let uncopyable_res: Result = Ok(1); + let _ = if let Ok(v) = uncopyable_res { v } else { 0 }; + + let x = uncopyable_res; + let _ = if let Ok(v) = x { v } else { 0 }; + //~^ manual_unwrap_or_default + + let copyable_res: Result = Ok(1); + let _ = if let Ok(v) = copyable_res { v } else { 0 }; + //~^ manual_unwrap_or_default + let _ = copyable_res; +} diff --git a/tests/ui/manual_unwrap_or_default.stderr b/tests/ui/manual_unwrap_or_default.stderr index 031100832b16f..a9e78afe6f065 100644 --- a/tests/ui/manual_unwrap_or_default.stderr +++ b/tests/ui/manual_unwrap_or_default.stderr @@ -97,5 +97,17 @@ LL | | 0 LL | | } | |_____^ help: replace it with: `Some(42).unwrap_or_default()` -error: aborting due to 9 previous errors +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default.rs:178:13 + | +LL | let _ = if let Ok(v) = x { v } else { 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x.unwrap_or_default()` + +error: if let can be simplified with `.unwrap_or_default()` + --> tests/ui/manual_unwrap_or_default.rs:182:13 + | +LL | let _ = if let Ok(v) = copyable_res { v } else { 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `copyable_res.unwrap_or_default()` + +error: aborting due to 11 previous errors diff --git a/tests/ui/match_as_ref.fixed b/tests/ui/match_as_ref.fixed index a39f0c9299bd5..09a6ed1693905 100644 --- a/tests/ui/match_as_ref.fixed +++ b/tests/ui/match_as_ref.fixed @@ -71,3 +71,22 @@ mod issue15691 { }; } } + +fn recv_requiring_parens() { + struct S; + + impl std::ops::Not for S { + type Output = Option; + fn not(self) -> Self::Output { + None + } + } + + let _ = (!S).as_ref(); +} + +fn issue15932() { + let _: Option<&u32> = Some(0).as_ref(); + + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); +} diff --git a/tests/ui/match_as_ref.rs b/tests/ui/match_as_ref.rs index 049928167901a..347b6d1868879 100644 --- a/tests/ui/match_as_ref.rs +++ b/tests/ui/match_as_ref.rs @@ -83,3 +83,34 @@ mod issue15691 { }; } } + +fn recv_requiring_parens() { + struct S; + + impl std::ops::Not for S { + type Output = Option; + fn not(self) -> Self::Output { + None + } + } + + let _ = match !S { + //~^ match_as_ref + None => None, + Some(ref v) => Some(v), + }; +} + +fn issue15932() { + let _: Option<&u32> = match Some(0) { + //~^ match_as_ref + None => None, + Some(ref mut v) => Some(v), + }; + + let _: Option<&dyn std::fmt::Debug> = match Some(0) { + //~^ match_as_ref + None => None, + Some(ref mut v) => Some(v), + }; +} diff --git a/tests/ui/match_as_ref.stderr b/tests/ui/match_as_ref.stderr index 7b40cb80dc6ed..df06e358f296e 100644 --- a/tests/ui/match_as_ref.stderr +++ b/tests/ui/match_as_ref.stderr @@ -1,4 +1,4 @@ -error: use `as_ref()` instead +error: manual implementation of `Option::as_ref` --> tests/ui/match_as_ref.rs:6:33 | LL | let borrowed: Option<&()> = match owned { @@ -7,12 +7,21 @@ LL | | LL | | None => None, LL | | Some(ref v) => Some(v), LL | | }; - | |_____^ help: try: `owned.as_ref()` + | |_____^ | = note: `-D clippy::match-as-ref` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::match_as_ref)]` +help: use `Option::as_ref()` directly + | +LL - let borrowed: Option<&()> = match owned { +LL - +LL - None => None, +LL - Some(ref v) => Some(v), +LL - }; +LL + let borrowed: Option<&()> = owned.as_ref(); + | -error: use `as_mut()` instead +error: manual implementation of `Option::as_mut` --> tests/ui/match_as_ref.rs:13:39 | LL | let borrow_mut: Option<&mut ()> = match mut_owned { @@ -21,9 +30,19 @@ LL | | LL | | None => None, LL | | Some(ref mut v) => Some(v), LL | | }; - | |_____^ help: try: `mut_owned.as_mut()` + | |_____^ + | +help: use `Option::as_mut()` directly + | +LL - let borrow_mut: Option<&mut ()> = match mut_owned { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let borrow_mut: Option<&mut ()> = mut_owned.as_mut(); + | -error: use `as_ref()` instead +error: manual implementation of `Option::as_ref` --> tests/ui/match_as_ref.rs:32:13 | LL | / match self.source { @@ -31,7 +50,82 @@ LL | | LL | | Some(ref s) => Some(s), LL | | None => None, LL | | } - | |_____________^ help: try: `self.source.as_ref().map(|x| x as _)` + | |_____________^ + | +help: use `Option::as_ref()` directly + | +LL - match self.source { +LL - +LL - Some(ref s) => Some(s), +LL - None => None, +LL - } +LL + self.source.as_ref().map(|x| x as _) + | + +error: manual implementation of `Option::as_ref` + --> tests/ui/match_as_ref.rs:97:13 + | +LL | let _ = match !S { + | _____________^ +LL | | +LL | | None => None, +LL | | Some(ref v) => Some(v), +LL | | }; + | |_____^ + | +help: use `Option::as_ref()` directly + | +LL - let _ = match !S { +LL - +LL - None => None, +LL - Some(ref v) => Some(v), +LL - }; +LL + let _ = (!S).as_ref(); + | + +error: manual implementation of `Option::as_mut` + --> tests/ui/match_as_ref.rs:105:27 + | +LL | let _: Option<&u32> = match Some(0) { + | ___________________________^ +LL | | +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ + | + = note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead +help: use `Option::as_ref()` + | +LL - let _: Option<&u32> = match Some(0) { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let _: Option<&u32> = Some(0).as_ref(); + | + +error: manual implementation of `Option::as_mut` + --> tests/ui/match_as_ref.rs:111:43 + | +LL | let _: Option<&dyn std::fmt::Debug> = match Some(0) { + | ___________________________________________^ +LL | | +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ + | + = note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead +help: use `Option::as_ref()` + | +LL - let _: Option<&dyn std::fmt::Debug> = match Some(0) { +LL - +LL - None => None, +LL - Some(ref mut v) => Some(v), +LL - }; +LL + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); + | -error: aborting due to 3 previous errors +error: aborting due to 6 previous errors diff --git a/tests/ui/match_overlapping_arm.rs b/tests/ui/match_overlapping_arm.rs index 176287e3d2e0c..224cac055f251 100644 --- a/tests/ui/match_overlapping_arm.rs +++ b/tests/ui/match_overlapping_arm.rs @@ -1,6 +1,6 @@ #![warn(clippy::match_overlapping_arm)] #![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::if_same_then_else, clippy::equatable_if_let, clippy::needless_if)] +#![allow(clippy::if_same_then_else, clippy::equatable_if_let, clippy::needless_ifs)] fn overlapping() { const FOO: u64 = 2; diff --git a/tests/ui/multiple_inherent_impl_cfg.normal.stderr b/tests/ui/multiple_inherent_impl_cfg.normal.stderr new file mode 100644 index 0000000000000..b7d95fbfa8f58 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.normal.stderr @@ -0,0 +1,31 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:11:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:3:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/multiple_inherent_impl_cfg.rs b/tests/ui/multiple_inherent_impl_cfg.rs new file mode 100644 index 0000000000000..15c8b7c508781 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.rs @@ -0,0 +1,46 @@ +//@compile-flags: --cfg test +#![deny(clippy::multiple_inherent_impl)] + +// issue #13040 + +fn main() {} + +struct A; + +impl A {} + +impl A {} +//~^ multiple_inherent_impl + +#[cfg(test)] +impl A {} // false positive +//~^ multiple_inherent_impl + +#[cfg(test)] +impl A {} +//~^ multiple_inherent_impl + +struct B; + +impl B {} + +#[cfg(test)] +impl B {} // false positive +//~^ multiple_inherent_impl + +impl B {} +//~^ multiple_inherent_impl + +#[cfg(test)] +impl B {} +//~^ multiple_inherent_impl + +#[cfg(test)] +struct C; + +#[cfg(test)] +impl C {} + +#[cfg(test)] +impl C {} +//~^ multiple_inherent_impl diff --git a/tests/ui/multiple_inherent_impl_cfg.stderr b/tests/ui/multiple_inherent_impl_cfg.stderr new file mode 100644 index 0000000000000..9d408ce3dec38 --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.stderr @@ -0,0 +1,91 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:12:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:2:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 + | +LL | impl A {} // false positive + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:20:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 + | +LL | impl B {} // false positive + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:31:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:35:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:45:1 + | +LL | impl C {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:42:1 + | +LL | impl C {} + | ^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/multiple_inherent_impl_cfg.withtest.stderr b/tests/ui/multiple_inherent_impl_cfg.withtest.stderr new file mode 100644 index 0000000000000..1e98b1f18801e --- /dev/null +++ b/tests/ui/multiple_inherent_impl_cfg.withtest.stderr @@ -0,0 +1,91 @@ +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:11:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/multiple_inherent_impl_cfg.rs:3:9 + | +LL | #![deny(clippy::multiple_inherent_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:14:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:17:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + | +LL | impl A {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:23:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 + | +LL | impl B {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:21:1 + | +LL | impl B {} + | ^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:36:1 + | +LL | impl C {} + | ^^^^^^^^^ + | +note: first implementation here + --> tests/ui/multiple_inherent_impl_cfg.rs:34:1 + | +LL | impl C {} + | ^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/needless_bool/fixable.fixed b/tests/ui/needless_bool/fixable.fixed index 0664abf0944dc..2589819f019b8 100644 --- a/tests/ui/needless_bool/fixable.fixed +++ b/tests/ui/needless_bool/fixable.fixed @@ -5,7 +5,7 @@ clippy::no_effect, clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::self_named_constructors, clippy::struct_field_names diff --git a/tests/ui/needless_bool/fixable.rs b/tests/ui/needless_bool/fixable.rs index 7507a6af408bb..f9cc0122f5e7b 100644 --- a/tests/ui/needless_bool/fixable.rs +++ b/tests/ui/needless_bool/fixable.rs @@ -5,7 +5,7 @@ clippy::no_effect, clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_return, clippy::self_named_constructors, clippy::struct_field_names diff --git a/tests/ui/needless_borrowed_ref.fixed b/tests/ui/needless_borrowed_ref.fixed index 84924cac62d52..6d74899218124 100644 --- a/tests/ui/needless_borrowed_ref.fixed +++ b/tests/ui/needless_borrowed_ref.fixed @@ -4,7 +4,7 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_if + clippy::needless_ifs )] fn main() {} diff --git a/tests/ui/needless_borrowed_ref.rs b/tests/ui/needless_borrowed_ref.rs index 280cef43340cc..a4cb899231647 100644 --- a/tests/ui/needless_borrowed_ref.rs +++ b/tests/ui/needless_borrowed_ref.rs @@ -4,7 +4,7 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_if + clippy::needless_ifs )] fn main() {} diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index b09efe9888f50..842d77dbc8c50 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::suspicious_map, clippy::iter_count, clippy::manual_contains diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index da4182966bb17..98d8d27321d24 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -1,6 +1,6 @@ #![allow( unused, - clippy::needless_if, + clippy::needless_ifs, clippy::suspicious_map, clippy::iter_count, clippy::manual_contains diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index fff6d2f34b8e3..69764becfe666 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -1,4 +1,4 @@ -#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_if)] +#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_ifs)] #![warn(clippy::needless_collect)] //@no-rustfix use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; diff --git a/tests/ui/needless_doc_main.rs b/tests/ui/needless_doc_main.rs index cf01cae2e8fd4..1b6fa45be0fe5 100644 --- a/tests/ui/needless_doc_main.rs +++ b/tests/ui/needless_doc_main.rs @@ -5,7 +5,7 @@ /// This should lint /// ``` /// fn main() { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -13,7 +13,7 @@ /// With an explicit return type it should lint too /// ```edition2015 /// fn main() -> () { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -21,7 +21,7 @@ /// This should, too. /// ```rust /// fn main() { -//~^ ERROR: needless `fn main` in doctest +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -29,8 +29,8 @@ /// This one too. /// ```no_run /// // the fn is not always the first line -//~^ ERROR: needless `fn main` in doctest /// fn main() { +//~^ needless_doctest_main /// unimplemented!(); /// } /// ``` @@ -38,12 +38,7 @@ fn bad_doctests() {} /// # Examples /// -/// This shouldn't lint, because the `main` is empty: -/// ``` -/// fn main(){} -/// ``` -/// -/// This shouldn't lint either, because main is async: +/// This shouldn't lint because main is async: /// ```edition2018 /// async fn main() { /// assert_eq!(42, ANSWER); diff --git a/tests/ui/needless_doc_main.stderr b/tests/ui/needless_doc_main.stderr index 9ba2ad306dac2..79763b0f22487 100644 --- a/tests/ui/needless_doc_main.stderr +++ b/tests/ui/needless_doc_main.stderr @@ -1,12 +1,8 @@ error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:7:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ | = note: `-D clippy::needless-doctest-main` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]` @@ -14,33 +10,20 @@ LL | | /// } error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:15:5 | -LL | /// fn main() -> () { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() -> () { + | ^^^^^^^ error: needless `fn main` in doctest --> tests/ui/needless_doc_main.rs:23:5 | -LL | /// fn main() { - | _____^ -LL | | -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ error: needless `fn main` in doctest - --> tests/ui/needless_doc_main.rs:31:5 + --> tests/ui/needless_doc_main.rs:32:5 | -LL | /// // the fn is not always the first line - | _____^ -LL | | -LL | | /// fn main() { -LL | | /// unimplemented!(); -LL | | /// } - | |_____^ +LL | /// fn main() { + | ^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/ui/needless_if.fixed b/tests/ui/needless_ifs.fixed similarity index 82% rename from tests/ui/needless_if.fixed rename to tests/ui/needless_ifs.fixed index c839156bed9bd..0e0b0fa39c9b7 100644 --- a/tests/ui/needless_if.fixed +++ b/tests/ui/needless_ifs.fixed @@ -12,7 +12,7 @@ clippy::redundant_pattern_matching, unused )] -#![warn(clippy::needless_if)] +#![warn(clippy::needless_ifs)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -24,16 +24,16 @@ fn maybe_side_effect() -> bool { fn main() { // Lint - //~^ needless_if + //~^ needless_ifs // Do not remove the condition maybe_side_effect(); - //~^ needless_if + //~^ needless_ifs // Do not lint if (true) { } else { } ({ - //~^ needless_if + //~^ needless_ifs return; }); // Do not lint if `else if` is present @@ -48,7 +48,7 @@ fn main() { if true && let true = true {} // Can lint nested `if let`s ({ - //~^ needless_if + //~^ needless_ifs if let true = true && true { @@ -92,15 +92,24 @@ fn main() { // Must be placed into an expression context to not be interpreted as a block ({ maybe_side_effect() }); - //~^ needless_if + //~^ needless_ifs // Would be a block followed by `&&true` - a double reference to `true` ({ maybe_side_effect() } && true); - //~^ needless_if + //~^ needless_ifs // Don't leave trailing attributes #[allow(unused)] true; - //~^ needless_if + //~^ needless_ifs let () = if maybe_side_effect() {}; } + +fn issue15960() -> i32 { + matches!(2, 3); + //~^ needless_ifs + matches!(2, 3) == (2 * 2 == 5); + //~^ needless_ifs + + 1 // put something here so that `if` is a statement not an expression +} diff --git a/tests/ui/needless_if.rs b/tests/ui/needless_ifs.rs similarity index 82% rename from tests/ui/needless_if.rs rename to tests/ui/needless_ifs.rs index 11103af5c5598..fb0ee5c9cc832 100644 --- a/tests/ui/needless_if.rs +++ b/tests/ui/needless_ifs.rs @@ -12,7 +12,7 @@ clippy::redundant_pattern_matching, unused )] -#![warn(clippy::needless_if)] +#![warn(clippy::needless_ifs)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -24,16 +24,16 @@ fn maybe_side_effect() -> bool { fn main() { // Lint if (true) {} - //~^ needless_if + //~^ needless_ifs // Do not remove the condition if maybe_side_effect() {} - //~^ needless_if + //~^ needless_ifs // Do not lint if (true) { } else { } if { - //~^ needless_if + //~^ needless_ifs return; } {} // Do not lint if `else if` is present @@ -48,7 +48,7 @@ fn main() { if true && let true = true {} // Can lint nested `if let`s if { - //~^ needless_if + //~^ needless_ifs if let true = true && true { @@ -93,15 +93,24 @@ fn main() { // Must be placed into an expression context to not be interpreted as a block if { maybe_side_effect() } {} - //~^ needless_if + //~^ needless_ifs // Would be a block followed by `&&true` - a double reference to `true` if { maybe_side_effect() } && true {} - //~^ needless_if + //~^ needless_ifs // Don't leave trailing attributes #[allow(unused)] if true {} - //~^ needless_if + //~^ needless_ifs let () = if maybe_side_effect() {}; } + +fn issue15960() -> i32 { + if matches!(2, 3) {} + //~^ needless_ifs + if matches!(2, 3) == (2 * 2 == 5) {} + //~^ needless_ifs + + 1 // put something here so that `if` is a statement not an expression +} diff --git a/tests/ui/needless_if.stderr b/tests/ui/needless_ifs.stderr similarity index 64% rename from tests/ui/needless_if.stderr rename to tests/ui/needless_ifs.stderr index 4b56843bd5229..8684ec217a0a4 100644 --- a/tests/ui/needless_if.stderr +++ b/tests/ui/needless_ifs.stderr @@ -1,20 +1,20 @@ error: this `if` branch is empty - --> tests/ui/needless_if.rs:26:5 + --> tests/ui/needless_ifs.rs:26:5 | LL | if (true) {} | ^^^^^^^^^^^^ help: you can remove it | - = note: `-D clippy::needless-if` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::needless_if)]` + = note: `-D clippy::needless-ifs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_ifs)]` error: this `if` branch is empty - --> tests/ui/needless_if.rs:29:5 + --> tests/ui/needless_ifs.rs:29:5 | LL | if maybe_side_effect() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();` error: this `if` branch is empty - --> tests/ui/needless_if.rs:35:5 + --> tests/ui/needless_ifs.rs:35:5 | LL | / if { LL | | @@ -31,7 +31,7 @@ LL + }); | error: this `if` branch is empty - --> tests/ui/needless_if.rs:50:5 + --> tests/ui/needless_ifs.rs:50:5 | LL | / if { LL | | @@ -57,22 +57,34 @@ LL + } && true); | error: this `if` branch is empty - --> tests/ui/needless_if.rs:95:5 + --> tests/ui/needless_ifs.rs:95:5 | LL | if { maybe_side_effect() } {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });` error: this `if` branch is empty - --> tests/ui/needless_if.rs:98:5 + --> tests/ui/needless_ifs.rs:98:5 | LL | if { maybe_side_effect() } && true {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);` error: this `if` branch is empty - --> tests/ui/needless_if.rs:103:5 + --> tests/ui/needless_ifs.rs:103:5 | LL | if true {} | ^^^^^^^^^^ help: you can remove it: `true;` -error: aborting due to 7 previous errors +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:110:5 + | +LL | if matches!(2, 3) {} + | ^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3);` + +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:112:5 + | +LL | if matches!(2, 3) == (2 * 2 == 5) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3) == (2 * 2 == 5);` + +error: aborting due to 9 previous errors diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 01db64a446c0f..9769ee0c3a37d 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -1,12 +1,9 @@ #![feature(try_blocks)] -#![allow( - clippy::eq_op, - clippy::single_match, - unused_assignments, - unused_variables, - clippy::while_immutable_condition -)] +#![expect(clippy::eq_op, clippy::single_match, clippy::while_immutable_condition)] //@no-rustfix + +use std::arch::asm; + fn test1() { let mut x = 0; loop { @@ -522,3 +519,30 @@ fn issue15350() { } } } + +fn issue15673() { + loop { + unsafe { + // No lint since we don't analyze the inside of the asm + asm! { + "/* {} */", + label { + break; + } + } + } + } + + //~v never_loop + loop { + unsafe { + asm! { + "/* {} */", + label { + break; + } + } + } + return; + } +} diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index 4fda06cff4ab9..49392d971ee32 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -1,5 +1,5 @@ error: this loop never actually loops - --> tests/ui/never_loop.rs:12:5 + --> tests/ui/never_loop.rs:9:5 | LL | / loop { ... | @@ -10,7 +10,7 @@ LL | | } = note: `#[deny(clippy::never_loop)]` on by default error: this loop never actually loops - --> tests/ui/never_loop.rs:36:5 + --> tests/ui/never_loop.rs:33:5 | LL | / loop { ... | @@ -19,7 +19,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:58:5 + --> tests/ui/never_loop.rs:55:5 | LL | / loop { ... | @@ -28,7 +28,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:62:9 + --> tests/ui/never_loop.rs:59:9 | LL | / while i == 0 { ... | @@ -36,7 +36,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:76:9 + --> tests/ui/never_loop.rs:73:9 | LL | / loop { ... | @@ -45,7 +45,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:114:5 + --> tests/ui/never_loop.rs:111:5 | LL | / while let Some(y) = x { ... | @@ -53,7 +53,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:123:5 + --> tests/ui/never_loop.rs:120:5 | LL | / for x in 0..10 { ... | @@ -67,7 +67,7 @@ LL + if let Some(x) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:173:5 + --> tests/ui/never_loop.rs:170:5 | LL | / 'outer: while a { ... | @@ -76,7 +76,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:190:9 + --> tests/ui/never_loop.rs:187:9 | LL | / while false { LL | | @@ -86,7 +86,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:243:13 + --> tests/ui/never_loop.rs:240:13 | LL | let _ = loop { | _____________^ @@ -99,7 +99,7 @@ LL | | }; | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:266:5 + --> tests/ui/never_loop.rs:263:5 | LL | / 'a: loop { LL | | @@ -110,7 +110,7 @@ LL | | } | |_____^ error: sub-expression diverges - --> tests/ui/never_loop.rs:271:17 + --> tests/ui/never_loop.rs:268:17 | LL | break 'a; | ^^^^^^^^ @@ -119,7 +119,7 @@ LL | break 'a; = help: to override `-D warnings` add `#[allow(clippy::diverging_sub_expression)]` error: this loop never actually loops - --> tests/ui/never_loop.rs:303:13 + --> tests/ui/never_loop.rs:300:13 | LL | / for _ in 0..20 { LL | | @@ -135,7 +135,7 @@ LL + if let Some(_) = (0..20).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:388:13 + --> tests/ui/never_loop.rs:385:13 | LL | / 'c: loop { LL | | @@ -145,7 +145,7 @@ LL | | } | |_____________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:400:5 + --> tests/ui/never_loop.rs:397:5 | LL | / loop { LL | | @@ -155,7 +155,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:405:5 + --> tests/ui/never_loop.rs:402:5 | LL | / loop { LL | | @@ -165,7 +165,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> tests/ui/never_loop.rs:426:5 + --> tests/ui/never_loop.rs:423:5 | LL | / for v in 0..10 { LL | | @@ -183,7 +183,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:434:5 + --> tests/ui/never_loop.rs:431:5 | LL | / 'outer: for v in 0..10 { LL | | @@ -194,7 +194,7 @@ LL | | } | |_____^ | help: this code is unreachable. Consider moving the reachable parts out - --> tests/ui/never_loop.rs:436:9 + --> tests/ui/never_loop.rs:433:9 | LL | / loop { LL | | @@ -202,7 +202,7 @@ LL | | break 'outer; LL | | } | |_________^ help: this code is unreachable. Consider moving the reachable parts out - --> tests/ui/never_loop.rs:440:9 + --> tests/ui/never_loop.rs:437:9 | LL | return; | ^^^^^^^ @@ -213,7 +213,7 @@ LL + if let Some(v) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:436:9 + --> tests/ui/never_loop.rs:433:9 | LL | / loop { LL | | @@ -222,7 +222,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:443:5 + --> tests/ui/never_loop.rs:440:5 | LL | / for v in 0..10 { LL | | @@ -239,7 +239,7 @@ LL + if let Some(v) = (0..10).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:445:9 + --> tests/ui/never_loop.rs:442:9 | LL | / 'inner: loop { LL | | @@ -248,7 +248,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:471:5 + --> tests/ui/never_loop.rs:468:5 | LL | / 'a: for _ in 0..1 { LL | | @@ -264,7 +264,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:477:5 + --> tests/ui/never_loop.rs:474:5 | LL | / 'a: for i in 0..1 { LL | | @@ -288,7 +288,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:492:5 + --> tests/ui/never_loop.rs:489:5 | LL | / for v in 0..10 { LL | | @@ -311,7 +311,7 @@ LL ~ | error: this loop never actually loops - --> tests/ui/never_loop.rs:503:5 + --> tests/ui/never_loop.rs:500:5 | LL | / 'bar: for _ in 0..100 { LL | | @@ -321,7 +321,7 @@ LL | | } | |_____^ | help: this code is unreachable. Consider moving the reachable parts out - --> tests/ui/never_loop.rs:505:9 + --> tests/ui/never_loop.rs:502:9 | LL | / loop { LL | | @@ -336,7 +336,7 @@ LL + if let Some(_) = (0..100).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:505:9 + --> tests/ui/never_loop.rs:502:9 | LL | / loop { LL | | @@ -346,7 +346,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:512:5 + --> tests/ui/never_loop.rs:509:5 | LL | / 'foo: for _ in 0..100 { LL | | @@ -356,7 +356,7 @@ LL | | } | |_____^ | help: this code is unreachable. Consider moving the reachable parts out - --> tests/ui/never_loop.rs:514:9 + --> tests/ui/never_loop.rs:511:9 | LL | / loop { LL | | @@ -372,7 +372,7 @@ LL + if let Some(_) = (0..100).next() { | error: this loop never actually loops - --> tests/ui/never_loop.rs:514:9 + --> tests/ui/never_loop.rs:511:9 | LL | / loop { LL | | @@ -383,7 +383,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> tests/ui/never_loop.rs:517:13 + --> tests/ui/never_loop.rs:514:13 | LL | / loop { LL | | @@ -392,5 +392,17 @@ LL | | break 'foo; LL | | } | |_____________^ -error: aborting due to 29 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:537:5 + | +LL | / loop { +LL | | unsafe { +LL | | asm! { +LL | | "/* {} */", +... | +LL | | return; +LL | | } + | |_____^ + +error: aborting due to 30 previous errors diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index f03f74dfafe21..d040ba6ee83cb 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -2,7 +2,7 @@ #![allow( unused, clippy::diverging_sub_expression, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_pattern_matching )] #![warn(clippy::nonminimal_bool)] diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index c2377491f25b1..f50af147c60cf 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -1,4 +1,4 @@ -#![allow(unused, clippy::diverging_sub_expression, clippy::needless_if)] +#![allow(unused, clippy::diverging_sub_expression, clippy::needless_ifs)] #![warn(clippy::nonminimal_bool)] fn methods_with_negation() { diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 1ae0f0064c6b6..0ecd4775035bf 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -1,4 +1,4 @@ -#![allow(unused, clippy::diverging_sub_expression, clippy::needless_if)] +#![allow(unused, clippy::diverging_sub_expression, clippy::needless_ifs)] #![warn(clippy::nonminimal_bool)] fn methods_with_negation() { diff --git a/tests/ui/op_ref.fixed b/tests/ui/op_ref.fixed index 4bf4b91888c8c..fe4a48989b0fb 100644 --- a/tests/ui/op_ref.fixed +++ b/tests/ui/op_ref.fixed @@ -111,7 +111,7 @@ mod issue_2597 { } } -#[allow(clippy::needless_if)] +#[allow(clippy::needless_ifs)] fn issue15063() { use std::ops::BitAnd; diff --git a/tests/ui/op_ref.rs b/tests/ui/op_ref.rs index 9a192661aafcd..cd0d231497abc 100644 --- a/tests/ui/op_ref.rs +++ b/tests/ui/op_ref.rs @@ -111,7 +111,7 @@ mod issue_2597 { } } -#[allow(clippy::needless_if)] +#[allow(clippy::needless_ifs)] fn issue15063() { use std::ops::BitAnd; diff --git a/tests/ui/option_map_unit_fn_fixable.fixed b/tests/ui/option_map_unit_fn_fixable.fixed index 340be7c7e932a..ddba1cd1291fa 100644 --- a/tests/ui/option_map_unit_fn_fixable.fixed +++ b/tests/ui/option_map_unit_fn_fixable.fixed @@ -1,6 +1,5 @@ #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)] +#![expect(clippy::unnecessary_wraps)] fn do_nothing(_: T) {} @@ -98,7 +97,7 @@ fn option_map_unit_fn() { if let Some(a) = option() { do_nothing(a) } //~^ option_map_unit_fn - if let Some(value) = option() { println!("{:?}", value) } + if let Some(value) = option() { println!("{value:?}") } //~^ option_map_unit_fn } diff --git a/tests/ui/option_map_unit_fn_fixable.rs b/tests/ui/option_map_unit_fn_fixable.rs index d902c87379b77..7bd1fe92bdb6e 100644 --- a/tests/ui/option_map_unit_fn_fixable.rs +++ b/tests/ui/option_map_unit_fn_fixable.rs @@ -1,6 +1,5 @@ #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)] +#![expect(clippy::unnecessary_wraps)] fn do_nothing(_: T) {} @@ -98,7 +97,7 @@ fn option_map_unit_fn() { option().map(do_nothing); //~^ option_map_unit_fn - option().map(|value| println!("{:?}", value)); + option().map(|value| println!("{value:?}")); //~^ option_map_unit_fn } diff --git a/tests/ui/option_map_unit_fn_fixable.stderr b/tests/ui/option_map_unit_fn_fixable.stderr index 2405aa9a7ccfa..70b6985406f44 100644 --- a/tests/ui/option_map_unit_fn_fixable.stderr +++ b/tests/ui/option_map_unit_fn_fixable.stderr @@ -1,173 +1,256 @@ error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:37:5 + --> tests/ui/option_map_unit_fn_fixable.rs:36:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::option_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Some(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:40:5 + --> tests/ui/option_map_unit_fn_fixable.rs:39:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Some(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:43:5 + --> tests/ui/option_map_unit_fn_fixable.rs:42:5 | LL | x.field.map(diverge); - | ^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x_field) = x.field { diverge(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(diverge); +LL + if let Some(x_field) = x.field { diverge(x_field) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:50:5 + --> tests/ui/option_map_unit_fn_fixable.rs:49:5 | LL | x.field.map(|value| x.do_option_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { x.do_option_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| x.do_option_nothing(value + captured)); +LL + if let Some(value) = x.field { x.do_option_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:53:5 + --> tests/ui/option_map_unit_fn_fixable.rs:52:5 | LL | x.field.map(|value| { x.do_option_plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { x.do_option_plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { x.do_option_plus_one(value + captured); }); +LL + if let Some(value) = x.field { x.do_option_plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:57:5 + --> tests/ui/option_map_unit_fn_fixable.rs:56:5 | LL | x.field.map(|value| do_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| do_nothing(value + captured)); +LL + if let Some(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:60:5 + --> tests/ui/option_map_unit_fn_fixable.rs:59:5 | LL | x.field.map(|value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured) }); +LL + if let Some(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:63:5 + --> tests/ui/option_map_unit_fn_fixable.rs:62:5 | LL | x.field.map(|value| { do_nothing(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured); }); +LL + if let Some(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:66:5 + --> tests/ui/option_map_unit_fn_fixable.rs:65:5 | LL | x.field.map(|value| { { do_nothing(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { do_nothing(value + captured); } }); +LL + if let Some(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:70:5 + --> tests/ui/option_map_unit_fn_fixable.rs:69:5 | LL | x.field.map(|value| diverge(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| diverge(value + captured)); +LL + if let Some(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:73:5 + --> tests/ui/option_map_unit_fn_fixable.rs:72:5 | LL | x.field.map(|value| { diverge(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured) }); +LL + if let Some(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:76:5 + --> tests/ui/option_map_unit_fn_fixable.rs:75:5 | LL | x.field.map(|value| { diverge(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured); }); +LL + if let Some(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:79:5 + --> tests/ui/option_map_unit_fn_fixable.rs:78:5 | LL | x.field.map(|value| { { diverge(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { diverge(value + captured); } }); +LL + if let Some(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:85:5 + --> tests/ui/option_map_unit_fn_fixable.rs:84:5 | LL | x.field.map(|value| { let y = plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { let y = plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { let y = plus_one(value + captured); }); +LL + if let Some(value) = x.field { let y = plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:88:5 + --> tests/ui/option_map_unit_fn_fixable.rs:87:5 | LL | x.field.map(|value| { plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { plus_one(value + captured); }); +LL + if let Some(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:91:5 + --> tests/ui/option_map_unit_fn_fixable.rs:90:5 | LL | x.field.map(|value| { { plus_one(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { plus_one(value + captured); } }); +LL + if let Some(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:95:5 + --> tests/ui/option_map_unit_fn_fixable.rs:94:5 | LL | x.field.map(|ref value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(ref value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|ref value| { do_nothing(value + captured) }); +LL + if let Some(ref value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:98:5 + --> tests/ui/option_map_unit_fn_fixable.rs:97:5 | LL | option().map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(a) = option() { do_nothing(a) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - option().map(do_nothing); +LL + if let Some(a) = option() { do_nothing(a) } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:101:5 + --> tests/ui/option_map_unit_fn_fixable.rs:100:5 + | +LL | option().map(|value| println!("{value:?}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - option().map(|value| println!("{value:?}")); +LL + if let Some(value) = option() { println!("{value:?}") } | -LL | option().map(|value| println!("{:?}", value)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(value) = option() { println!("{:?}", value) }` error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:108:5 + --> tests/ui/option_map_unit_fn_fixable.rs:107:5 | LL | x.map(|x| unsafe { f(x) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x) = x { unsafe { f(x) } }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.map(|x| unsafe { f(x) }); +LL + if let Some(x) = x { unsafe { f(x) } } + | error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` - --> tests/ui/option_map_unit_fn_fixable.rs:110:5 + --> tests/ui/option_map_unit_fn_fixable.rs:109:5 | LL | x.map(|x| unsafe { { f(x) } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Some(x) = x { unsafe { f(x) } }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.map(|x| unsafe { { f(x) } }); +LL + if let Some(x) = x { unsafe { f(x) } } + | error: aborting due to 21 previous errors diff --git a/tests/ui/option_map_unit_fn_unfixable.rs b/tests/ui/option_map_unit_fn_unfixable.rs index dd2f80fefbeec..a6d7ea4a4c8d9 100644 --- a/tests/ui/option_map_unit_fn_unfixable.rs +++ b/tests/ui/option_map_unit_fn_unfixable.rs @@ -1,5 +1,6 @@ +//@no-rustfix #![warn(clippy::option_map_unit_fn)] -#![allow(unused)] +#![allow(clippy::unnecessary_wraps, clippy::unnecessary_map_on_constructor)] // only fires before the fix fn do_nothing(_: T) {} @@ -11,14 +12,19 @@ fn plus_one(value: usize) -> usize { value + 1 } +struct HasOption { + field: Option, +} + #[rustfmt::skip] fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; x.field.map(|value| { do_nothing(value); do_nothing(value) }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn // Suggestion for the let block should be `{ ... }` as it's too difficult to build a // proper suggestion for these cases @@ -26,18 +32,22 @@ fn option_map_unit_fn() { do_nothing(value); do_nothing(value) }); - //~^^^^ ERROR: cannot find value + //~^^^^ option_map_unit_fn x.field.map(|value| { do_nothing(value); do_nothing(value); }); - //~^ ERROR: cannot find value + //~^ option_map_unit_fn // The following should suggest `if let Some(_X) ...` as it's difficult to generate a proper let variable name for them Some(42).map(diverge); + //~^ option_map_unit_fn "12".parse::().ok().map(diverge); + //~^ option_map_unit_fn Some(plus_one(1)).map(do_nothing); + //~^ option_map_unit_fn // Should suggest `if let Some(_y) ...` to not override the existing foo variable let y = Some(42); y.map(do_nothing); + //~^ option_map_unit_fn } fn main() {} diff --git a/tests/ui/option_map_unit_fn_unfixable.stderr b/tests/ui/option_map_unit_fn_unfixable.stderr index 8527850143295..8cc246a38d37b 100644 --- a/tests/ui/option_map_unit_fn_unfixable.stderr +++ b/tests/ui/option_map_unit_fn_unfixable.stderr @@ -1,27 +1,106 @@ -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:17:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:23:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::option_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value) }); +LL + if let Some(value) = x.field { ... } + | -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:20:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:26:5 | LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); +LL + if let Some(value) = x.field { ... } + | -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:25:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:31:5 + | +LL | / x.field.map(|value| { +LL | | do_nothing(value); +LL | | do_nothing(value) +LL | | }); + | |______^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { +LL - do_nothing(value); +LL - do_nothing(value) +LL - }); +LL + if let Some(value) = x.field { ... } | -LL | x.field.map(|value| { - | ^ not found in this scope -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:30:5 +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:36:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); - | ^ not found in this scope + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value); }); +LL + if let Some(value) = x.field { ... } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:40:5 + | +LL | Some(42).map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - Some(42).map(diverge); +LL + if let Some(a) = Some(42) { diverge(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:42:5 + | +LL | "12".parse::().ok().map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - "12".parse::().ok().map(diverge); +LL + if let Some(a) = "12".parse::().ok() { diverge(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:44:5 + | +LL | Some(plus_one(1)).map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - Some(plus_one(1)).map(do_nothing); +LL + if let Some(a) = Some(plus_one(1)) { do_nothing(a) } + | + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()` + --> tests/ui/option_map_unit_fn_unfixable.rs:49:5 + | +LL | y.map(do_nothing); + | ^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - y.map(do_nothing); +LL + if let Some(_y) = y { do_nothing(_y) } + | -error: aborting due to 4 previous errors +error: aborting due to 8 previous errors -For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/option_option.rs b/tests/ui/option_option.rs index 42f03aae7bb82..136db902a9758 100644 --- a/tests/ui/option_option.rs +++ b/tests/ui/option_option.rs @@ -1,52 +1,52 @@ -#![deny(clippy::option_option)] -#![allow(clippy::unnecessary_wraps, clippy::manual_unwrap_or_default)] +#![warn(clippy::option_option)] +#![expect(clippy::unnecessary_wraps)] const C: Option> = None; -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option static S: Option> = None; -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option fn input(_: Option>) {} -//~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if +//~^ option_option fn output() -> Option> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option None } fn output_nested() -> Vec>> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option vec![None] } // The lint only generates one warning for this fn output_nested_nested() -> Option>> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum if + //~^ option_option None } struct Struct { x: Option>, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } impl Struct { fn struct_fn() -> Option> { - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option None } } trait Trait { fn trait_fn() -> Option>; - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } enum Enum { Tuple(Option>), - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option Struct { x: Option> }, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom enum + //~^ option_option } // The lint allows this @@ -88,7 +88,7 @@ mod issue_4298 { #[serde(default)] #[serde(borrow)] foo: Option>>, - //~^ ERROR: consider using `Option` instead of `Option>` or a custom + //~^ option_option } #[allow(clippy::option_option)] diff --git a/tests/ui/option_option.stderr b/tests/ui/option_option.stderr index 0cd048e400e40..1c39f9acae8e3 100644 --- a/tests/ui/option_option.stderr +++ b/tests/ui/option_option.stderr @@ -1,80 +1,100 @@ -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:4:10 | LL | const C: Option> = None; | ^^^^^^^^^^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/option_option.rs:1:9 - | -LL | #![deny(clippy::option_option)] - | ^^^^^^^^^^^^^^^^^^^^^ + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases + = note: `-D clippy::option-option` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::option_option)]` -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:6:11 | LL | static S: Option> = None; | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:9:13 | LL | fn input(_: Option>) {} | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:12:16 | LL | fn output() -> Option> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:17:27 | LL | fn output_nested() -> Vec>> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:23:30 | LL | fn output_nested_nested() -> Option>> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option>`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:29:8 | LL | x: Option>, | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:34:23 | LL | fn struct_fn() -> Option> { | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:41:22 | LL | fn trait_fn() -> Option>; | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:46:11 | LL | Tuple(Option>), | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:48:17 | LL | Struct { x: Option> }, | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option`, or a custom enum if you need to distinguish all 3 cases -error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases +error: use of `Option>` --> tests/ui/option_option.rs:90:14 | LL | foo: Option>>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `Option>`, or a custom enum if you need to distinguish all 3 cases error: aborting due to 12 previous errors diff --git a/tests/ui/panicking_overflow_checks.rs b/tests/ui/panicking_overflow_checks.rs index 29789c9497567..61dfca8b37286 100644 --- a/tests/ui/panicking_overflow_checks.rs +++ b/tests/ui/panicking_overflow_checks.rs @@ -1,5 +1,5 @@ #![warn(clippy::panicking_overflow_checks)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] fn test(a: u32, b: u32, c: u32) { if a + b < a {} diff --git a/tests/ui/partialeq_to_none.fixed b/tests/ui/partialeq_to_none.fixed index e700cc56349f2..9288529423dd9 100644 --- a/tests/ui/partialeq_to_none.fixed +++ b/tests/ui/partialeq_to_none.fixed @@ -1,5 +1,5 @@ #![warn(clippy::partialeq_to_none)] -#![allow(clippy::eq_op, clippy::needless_if)] +#![allow(clippy::eq_op, clippy::needless_ifs)] struct Foobar; diff --git a/tests/ui/partialeq_to_none.rs b/tests/ui/partialeq_to_none.rs index 1bae076dd3377..7940251c18a2b 100644 --- a/tests/ui/partialeq_to_none.rs +++ b/tests/ui/partialeq_to_none.rs @@ -1,5 +1,5 @@ #![warn(clippy::partialeq_to_none)] -#![allow(clippy::eq_op, clippy::needless_if)] +#![allow(clippy::eq_op, clippy::needless_ifs)] struct Foobar; diff --git a/tests/ui/precedence.fixed b/tests/ui/precedence.fixed index cbb78bff68bff..f0252c4484a5a 100644 --- a/tests/ui/precedence.fixed +++ b/tests/ui/precedence.fixed @@ -1,7 +1,12 @@ #![warn(clippy::precedence)] -#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] -#![allow(clippy::identity_op)] -#![allow(clippy::eq_op)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::clone_on_copy, + clippy::identity_op, + clippy::eq_op +)] macro_rules! trip { ($a:expr) => { @@ -35,3 +40,24 @@ fn main() { let b = 3; trip!(b * 8); } + +struct W(u8); +impl Clone for W { + fn clone(&self) -> Self { + W(1) + } +} + +fn closure_method_call() { + // Do not lint when the method call is applied to the block, both inside the closure + let f = |x: W| { x }.clone(); + assert!(matches!(f(W(0)), W(1))); + + let f = (|x: W| -> _ { x }).clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence + + let f = (move |x: W| -> _ { x }).clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence +} diff --git a/tests/ui/precedence.rs b/tests/ui/precedence.rs index c73a4020e2e51..5d47462114b85 100644 --- a/tests/ui/precedence.rs +++ b/tests/ui/precedence.rs @@ -1,7 +1,12 @@ #![warn(clippy::precedence)] -#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] -#![allow(clippy::identity_op)] -#![allow(clippy::eq_op)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::clone_on_copy, + clippy::identity_op, + clippy::eq_op +)] macro_rules! trip { ($a:expr) => { @@ -35,3 +40,24 @@ fn main() { let b = 3; trip!(b * 8); } + +struct W(u8); +impl Clone for W { + fn clone(&self) -> Self { + W(1) + } +} + +fn closure_method_call() { + // Do not lint when the method call is applied to the block, both inside the closure + let f = |x: W| { x }.clone(); + assert!(matches!(f(W(0)), W(1))); + + let f = |x: W| -> _ { x }.clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence + + let f = move |x: W| -> _ { x }.clone(); + assert!(matches!(f(W(0)), W(0))); + //~^^ precedence +} diff --git a/tests/ui/precedence.stderr b/tests/ui/precedence.stderr index 50cd8f4b81464..f086cfe028904 100644 --- a/tests/ui/precedence.stderr +++ b/tests/ui/precedence.stderr @@ -1,5 +1,5 @@ error: operator precedence might not be obvious - --> tests/ui/precedence.rs:16:5 + --> tests/ui/precedence.rs:21:5 | LL | 1 << 2 + 3; | ^^^^^^^^^^ help: consider parenthesizing your expression: `1 << (2 + 3)` @@ -8,40 +8,62 @@ LL | 1 << 2 + 3; = help: to override `-D warnings` add `#[allow(clippy::precedence)]` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:18:5 + --> tests/ui/precedence.rs:23:5 | LL | 1 + 2 << 3; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 2) << 3` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:20:5 + --> tests/ui/precedence.rs:25:5 | LL | 4 >> 1 + 1; | ^^^^^^^^^^ help: consider parenthesizing your expression: `4 >> (1 + 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:22:5 + --> tests/ui/precedence.rs:27:5 | LL | 1 + 3 >> 2; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 3) >> 2` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:24:5 + --> tests/ui/precedence.rs:29:5 | LL | 1 ^ 1 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `1 ^ (1 - 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:26:5 + --> tests/ui/precedence.rs:31:5 | LL | 3 | 2 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 | (2 - 1)` error: operator precedence might not be obvious - --> tests/ui/precedence.rs:28:5 + --> tests/ui/precedence.rs:33:5 | LL | 3 & 5 - 2; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` -error: aborting due to 7 previous errors +error: precedence might not be obvious + --> tests/ui/precedence.rs:56:13 + | +LL | let f = |x: W| -> _ { x }.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider parenthesizing the closure + | +LL | let f = (|x: W| -> _ { x }).clone(); + | + + + +error: precedence might not be obvious + --> tests/ui/precedence.rs:60:13 + | +LL | let f = move |x: W| -> _ { x }.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider parenthesizing the closure + | +LL | let f = (move |x: W| -> _ { x }).clone(); + | + + + +error: aborting due to 9 previous errors diff --git a/tests/ui/print.rs b/tests/ui/print.rs deleted file mode 100644 index ee3d9dc0de037..0000000000000 --- a/tests/ui/print.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![allow(clippy::print_literal, clippy::write_literal)] -#![warn(clippy::print_stdout, clippy::use_debug)] - -use std::fmt::{Debug, Display, Formatter, Result}; - -#[allow(dead_code)] -struct Foo; - -impl Display for Foo { - fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{:?}", 43.1415) - //~^ use_debug - } -} - -impl Debug for Foo { - fn fmt(&self, f: &mut Formatter) -> Result { - // ok, we can use `Debug` formatting in `Debug` implementations - write!(f, "{:?}", 42.718) - } -} - -fn main() { - println!("Hello"); - //~^ print_stdout - - print!("Hello"); - //~^ print_stdout - - print!("Hello {}", "World"); - //~^ print_stdout - - print!("Hello {:?}", "World"); - //~^ print_stdout - //~| use_debug - - print!("Hello {:#?}", "#orld"); - //~^ print_stdout - //~| use_debug - - assert_eq!(42, 1337); - - vec![1, 2]; -} diff --git a/tests/ui/print.stderr b/tests/ui/print.stderr deleted file mode 100644 index 9dd216bd1449d..0000000000000 --- a/tests/ui/print.stderr +++ /dev/null @@ -1,56 +0,0 @@ -error: use of `Debug`-based formatting - --> tests/ui/print.rs:11:20 - | -LL | write!(f, "{:?}", 43.1415) - | ^^^^ - | - = note: `-D clippy::use-debug` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::use_debug)]` - -error: use of `println!` - --> tests/ui/print.rs:24:5 - | -LL | println!("Hello"); - | ^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::print-stdout` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::print_stdout)]` - -error: use of `print!` - --> tests/ui/print.rs:27:5 - | -LL | print!("Hello"); - | ^^^^^^^^^^^^^^^ - -error: use of `print!` - --> tests/ui/print.rs:30:5 - | -LL | print!("Hello {}", "World"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `print!` - --> tests/ui/print.rs:33:5 - | -LL | print!("Hello {:?}", "World"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `Debug`-based formatting - --> tests/ui/print.rs:33:19 - | -LL | print!("Hello {:?}", "World"); - | ^^^^ - -error: use of `print!` - --> tests/ui/print.rs:37:5 - | -LL | print!("Hello {:#?}", "#orld"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: use of `Debug`-based formatting - --> tests/ui/print.rs:37:19 - | -LL | print!("Hello {:#?}", "#orld"); - | ^^^^^ - -error: aborting due to 8 previous errors - diff --git a/tests/ui/print_stdout.rs b/tests/ui/print_stdout.rs new file mode 100644 index 0000000000000..a379457f18696 --- /dev/null +++ b/tests/ui/print_stdout.rs @@ -0,0 +1,23 @@ +#![expect(clippy::print_literal)] +#![warn(clippy::print_stdout)] + +fn main() { + println!("Hello"); + //~^ print_stdout + + print!("Hello"); + //~^ print_stdout + + print!("Hello {}", "World"); + //~^ print_stdout + + print!("Hello {:?}", "World"); + //~^ print_stdout + + print!("Hello {:#?}", "#orld"); + //~^ print_stdout + + assert_eq!(42, 1337); + + vec![1, 2]; +} diff --git a/tests/ui/print_stdout.stderr b/tests/ui/print_stdout.stderr new file mode 100644 index 0000000000000..e1360e59b4126 --- /dev/null +++ b/tests/ui/print_stdout.stderr @@ -0,0 +1,35 @@ +error: use of `println!` + --> tests/ui/print_stdout.rs:5:5 + | +LL | println!("Hello"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-stdout` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::print_stdout)]` + +error: use of `print!` + --> tests/ui/print_stdout.rs:8:5 + | +LL | print!("Hello"); + | ^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:11:5 + | +LL | print!("Hello {}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:14:5 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> tests/ui/print_stdout.rs:17:5 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/redundant_pattern_matching_drop_order.fixed b/tests/ui/redundant_pattern_matching_drop_order.fixed index 1141b5db3ebf4..490948442e11f 100644 --- a/tests/ui/redundant_pattern_matching_drop_order.fixed +++ b/tests/ui/redundant_pattern_matching_drop_order.fixed @@ -3,7 +3,7 @@ #![allow( clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] use std::task::Poll::{Pending, Ready}; diff --git a/tests/ui/redundant_pattern_matching_drop_order.rs b/tests/ui/redundant_pattern_matching_drop_order.rs index f60ddf4683096..d40fe84693b00 100644 --- a/tests/ui/redundant_pattern_matching_drop_order.rs +++ b/tests/ui/redundant_pattern_matching_drop_order.rs @@ -3,7 +3,7 @@ #![allow( clippy::if_same_then_else, clippy::equatable_if_let, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] use std::task::Poll::{Pending, Ready}; diff --git a/tests/ui/redundant_pattern_matching_if_let_true.fixed b/tests/ui/redundant_pattern_matching_if_let_true.fixed index 980455da2ee7e..b746bfd1c3584 100644 --- a/tests/ui/redundant_pattern_matching_if_let_true.fixed +++ b/tests/ui/redundant_pattern_matching_if_let_true.fixed @@ -1,5 +1,5 @@ #![warn(clippy::redundant_pattern_matching)] -#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] +#![allow(clippy::needless_ifs, clippy::no_effect, clippy::nonminimal_bool)] macro_rules! condition { () => { diff --git a/tests/ui/redundant_pattern_matching_if_let_true.rs b/tests/ui/redundant_pattern_matching_if_let_true.rs index 9cb0fe2e65f63..7be9f31e56bd6 100644 --- a/tests/ui/redundant_pattern_matching_if_let_true.rs +++ b/tests/ui/redundant_pattern_matching_if_let_true.rs @@ -1,5 +1,5 @@ #![warn(clippy::redundant_pattern_matching)] -#![allow(clippy::needless_if, clippy::no_effect, clippy::nonminimal_bool)] +#![allow(clippy::needless_ifs, clippy::no_effect, clippy::nonminimal_bool)] macro_rules! condition { () => { diff --git a/tests/ui/redundant_pattern_matching_ipaddr.fixed b/tests/ui/redundant_pattern_matching_ipaddr.fixed index 1cec19ab8c99d..7c05eca1b70e5 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.fixed +++ b/tests/ui/redundant_pattern_matching_ipaddr.fixed @@ -2,7 +2,7 @@ #![allow( clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args )] diff --git a/tests/ui/redundant_pattern_matching_ipaddr.rs b/tests/ui/redundant_pattern_matching_ipaddr.rs index 123573a8602b6..1d4abca492813 100644 --- a/tests/ui/redundant_pattern_matching_ipaddr.rs +++ b/tests/ui/redundant_pattern_matching_ipaddr.rs @@ -2,7 +2,7 @@ #![allow( clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args )] diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index dc9d6491691f3..a54a4ce13d53f 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -2,7 +2,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 2e9714ad8e75f..7252fce8cce6d 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -2,7 +2,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_poll.fixed b/tests/ui/redundant_pattern_matching_poll.fixed index 800889b5fda0a..735c444bb45b5 100644 --- a/tests/ui/redundant_pattern_matching_poll.fixed +++ b/tests/ui/redundant_pattern_matching_poll.fixed @@ -1,7 +1,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_poll.rs b/tests/ui/redundant_pattern_matching_poll.rs index 1668c2ff2bbac..eeca51e8be760 100644 --- a/tests/ui/redundant_pattern_matching_poll.rs +++ b/tests/ui/redundant_pattern_matching_poll.rs @@ -1,7 +1,7 @@ #![warn(clippy::redundant_pattern_matching)] #![allow( clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::match_like_matches_macro, clippy::equatable_if_let, clippy::if_same_then_else diff --git a/tests/ui/redundant_pattern_matching_result.fixed b/tests/ui/redundant_pattern_matching_result.fixed index dab816716d598..261d82fc35c87 100644 --- a/tests/ui/redundant_pattern_matching_result.fixed +++ b/tests/ui/redundant_pattern_matching_result.fixed @@ -4,7 +4,7 @@ clippy::if_same_then_else, clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args, clippy::unnecessary_wraps )] diff --git a/tests/ui/redundant_pattern_matching_result.rs b/tests/ui/redundant_pattern_matching_result.rs index 3fd70515d0847..6cae4cc4b6b07 100644 --- a/tests/ui/redundant_pattern_matching_result.rs +++ b/tests/ui/redundant_pattern_matching_result.rs @@ -4,7 +4,7 @@ clippy::if_same_then_else, clippy::match_like_matches_macro, clippy::needless_bool, - clippy::needless_if, + clippy::needless_ifs, clippy::uninlined_format_args, clippy::unnecessary_wraps )] diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index fdd851414746b..c08819b21ff01 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -19,6 +19,7 @@ #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] +#![allow(clippy::empty_enums)] #![allow(clippy::mixed_read_write_in_expression)] #![allow(clippy::manual_filter_map)] #![allow(clippy::manual_find_map)] @@ -42,6 +43,7 @@ #![allow(clippy::overly_complex_bool_expr)] #![allow(unexpected_cfgs)] #![allow(enum_intrinsics_non_enums)] +#![allow(clippy::needless_ifs)] #![allow(clippy::new_without_default)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::expect_used)] @@ -83,6 +85,7 @@ #![warn(drop_bounds)] //~ ERROR: lint `clippy::drop_bounds` #![warn(dropping_copy_types)] //~ ERROR: lint `clippy::drop_copy` #![warn(dropping_references)] //~ ERROR: lint `clippy::drop_ref` +#![warn(clippy::empty_enums)] //~ ERROR: lint `clippy::empty_enum` #![warn(clippy::mixed_read_write_in_expression)] //~ ERROR: lint `clippy::eval_order_dependence` #![warn(clippy::manual_filter_map)] //~ ERROR: lint `clippy::filter_map` #![warn(clippy::manual_find_map)] //~ ERROR: lint `clippy::find_map` @@ -109,6 +112,7 @@ #![warn(unexpected_cfgs)] //~ ERROR: lint `clippy::maybe_misused_cfg` #![warn(enum_intrinsics_non_enums)] //~ ERROR: lint `clippy::mem_discriminant_non_enum` #![warn(unexpected_cfgs)] //~ ERROR: lint `clippy::mismatched_target_os` +#![warn(clippy::needless_ifs)] //~ ERROR: lint `clippy::needless_if` #![warn(clippy::new_without_default)] //~ ERROR: lint `clippy::new_without_default_derive` #![warn(clippy::bind_instead_of_map)] //~ ERROR: lint `clippy::option_and_then_some` #![warn(clippy::expect_used)] //~ ERROR: lint `clippy::option_expect_used` diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index 591c8ca53ac22..0f6b58e144d95 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -19,6 +19,7 @@ #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] +#![allow(clippy::empty_enums)] #![allow(clippy::mixed_read_write_in_expression)] #![allow(clippy::manual_filter_map)] #![allow(clippy::manual_find_map)] @@ -42,6 +43,7 @@ #![allow(clippy::overly_complex_bool_expr)] #![allow(unexpected_cfgs)] #![allow(enum_intrinsics_non_enums)] +#![allow(clippy::needless_ifs)] #![allow(clippy::new_without_default)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::expect_used)] @@ -83,6 +85,7 @@ #![warn(clippy::drop_bounds)] //~ ERROR: lint `clippy::drop_bounds` #![warn(clippy::drop_copy)] //~ ERROR: lint `clippy::drop_copy` #![warn(clippy::drop_ref)] //~ ERROR: lint `clippy::drop_ref` +#![warn(clippy::empty_enum)] //~ ERROR: lint `clippy::empty_enum` #![warn(clippy::eval_order_dependence)] //~ ERROR: lint `clippy::eval_order_dependence` #![warn(clippy::filter_map)] //~ ERROR: lint `clippy::filter_map` #![warn(clippy::find_map)] //~ ERROR: lint `clippy::find_map` @@ -109,6 +112,7 @@ #![warn(clippy::maybe_misused_cfg)] //~ ERROR: lint `clippy::maybe_misused_cfg` #![warn(clippy::mem_discriminant_non_enum)] //~ ERROR: lint `clippy::mem_discriminant_non_enum` #![warn(clippy::mismatched_target_os)] //~ ERROR: lint `clippy::mismatched_target_os` +#![warn(clippy::needless_if)] //~ ERROR: lint `clippy::needless_if` #![warn(clippy::new_without_default_derive)] //~ ERROR: lint `clippy::new_without_default_derive` #![warn(clippy::option_and_then_some)] //~ ERROR: lint `clippy::option_and_then_some` #![warn(clippy::option_expect_used)] //~ ERROR: lint `clippy::option_expect_used` diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index b54fec8c57949..4a7799be37d71 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,442 +8,454 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:79:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:80:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:81:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:82:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:83:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::double_neg` has been renamed to `double_negations` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:84:9 | LL | #![warn(clippy::double_neg)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `double_negations` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:85:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:86:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:87:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` +error: lint `clippy::empty_enum` has been renamed to `clippy::empty_enums` + --> tests/ui/rename.rs:88:9 + | +LL | #![warn(clippy::empty_enum)] + | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::empty_enums` + error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:89:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::filter_map` has been renamed to `clippy::manual_filter_map` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:90:9 | LL | #![warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_filter_map` error: lint `clippy::find_map` has been renamed to `clippy::manual_find_map` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:91:9 | LL | #![warn(clippy::find_map)] | ^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_find_map` error: lint `clippy::fn_address_comparisons` has been renamed to `unpredictable_function_pointer_comparisons` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:92:9 | LL | #![warn(clippy::fn_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unpredictable_function_pointer_comparisons` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:93:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:94:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:95:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:96:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:97:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:98:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:99:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_redundant_pattern_matching` has been renamed to `clippy::redundant_pattern_matching` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:100:9 | LL | #![warn(clippy::if_let_redundant_pattern_matching)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_pattern_matching` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:101:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:102:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:103:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:104:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:105:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:106:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_null_ptr_usage` has been renamed to `invalid_null_arguments` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:107:9 | LL | #![warn(clippy::invalid_null_ptr_usage)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_null_arguments` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:108:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:109:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:110:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:111:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::maybe_misused_cfg` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:112:9 | LL | #![warn(clippy::maybe_misused_cfg)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:113:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::mismatched_target_os` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:114:9 | LL | #![warn(clippy::mismatched_target_os)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` +error: lint `clippy::needless_if` has been renamed to `clippy::needless_ifs` + --> tests/ui/rename.rs:115:9 + | +LL | #![warn(clippy::needless_if)] + | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_ifs` + error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:116:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:117:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:118:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:119:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:116:9 + --> tests/ui/rename.rs:120:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:117:9 + --> tests/ui/rename.rs:121:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` - --> tests/ui/rename.rs:118:9 + --> tests/ui/rename.rs:122:9 | LL | #![warn(clippy::overflow_check_conditional)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:119:9 + --> tests/ui/rename.rs:123:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:120:9 + --> tests/ui/rename.rs:124:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:121:9 + --> tests/ui/rename.rs:125:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:122:9 + --> tests/ui/rename.rs:126:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:123:9 + --> tests/ui/rename.rs:127:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:124:9 + --> tests/ui/rename.rs:128:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_empty_ranges` - --> tests/ui/rename.rs:125:9 + --> tests/ui/rename.rs:129:9 | LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:126:9 + --> tests/ui/rename.rs:130:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:127:9 + --> tests/ui/rename.rs:131:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:128:9 + --> tests/ui/rename.rs:132:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:129:9 + --> tests/ui/rename.rs:133:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:130:9 + --> tests/ui/rename.rs:134:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::transmute_float_to_int` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:131:9 + --> tests/ui/rename.rs:135:9 | LL | #![warn(clippy::transmute_float_to_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_char` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:132:9 + --> tests/ui/rename.rs:136:9 | LL | #![warn(clippy::transmute_int_to_char)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_float` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:133:9 + --> tests/ui/rename.rs:137:9 | LL | #![warn(clippy::transmute_int_to_float)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_num_to_bytes` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:134:9 + --> tests/ui/rename.rs:138:9 | LL | #![warn(clippy::transmute_num_to_bytes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::unchecked_duration_subtraction` has been renamed to `clippy::unchecked_time_subtraction` - --> tests/ui/rename.rs:135:9 + --> tests/ui/rename.rs:139:9 | LL | #![warn(clippy::unchecked_duration_subtraction)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unchecked_time_subtraction` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:136:9 + --> tests/ui/rename.rs:140:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:137:9 + --> tests/ui/rename.rs:141:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:138:9 + --> tests/ui/rename.rs:142:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:139:9 + --> tests/ui/rename.rs:143:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:140:9 + --> tests/ui/rename.rs:144:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:141:9 + --> tests/ui/rename.rs:145:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` -error: aborting due to 74 previous errors +error: aborting due to 76 previous errors diff --git a/tests/ui/result_map_unit_fn_fixable.fixed b/tests/ui/result_map_unit_fn_fixable.fixed index 6bee013c3f470..b810e85a45fac 100644 --- a/tests/ui/result_map_unit_fn_fixable.fixed +++ b/tests/ui/result_map_unit_fn_fixable.fixed @@ -1,6 +1,4 @@ #![warn(clippy::result_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args)] fn do_nothing(_: T) {} @@ -92,7 +90,7 @@ fn result_map_unit_fn() { if let Ok(ref value) = x.field { do_nothing(value + captured) } //~^ result_map_unit_fn - if let Ok(value) = x.field { println!("{:?}", value) } + if let Ok(value) = x.field { println!("{value:?}") } //~^ result_map_unit_fn } diff --git a/tests/ui/result_map_unit_fn_fixable.rs b/tests/ui/result_map_unit_fn_fixable.rs index a206cfe6842ff..18cd557f04544 100644 --- a/tests/ui/result_map_unit_fn_fixable.rs +++ b/tests/ui/result_map_unit_fn_fixable.rs @@ -1,6 +1,4 @@ #![warn(clippy::result_map_unit_fn)] -#![allow(unused)] -#![allow(clippy::uninlined_format_args)] fn do_nothing(_: T) {} @@ -92,7 +90,7 @@ fn result_map_unit_fn() { x.field.map(|ref value| { do_nothing(value + captured) }); //~^ result_map_unit_fn - x.field.map(|value| println!("{:?}", value)); + x.field.map(|value| println!("{value:?}")); //~^ result_map_unit_fn } diff --git a/tests/ui/result_map_unit_fn_fixable.stderr b/tests/ui/result_map_unit_fn_fixable.stderr index eca844e06cc06..d8f6239764d13 100644 --- a/tests/ui/result_map_unit_fn_fixable.stderr +++ b/tests/ui/result_map_unit_fn_fixable.stderr @@ -1,149 +1,220 @@ error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:34:5 + --> tests/ui/result_map_unit_fn_fixable.rs:32:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::result_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Ok(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:37:5 + --> tests/ui/result_map_unit_fn_fixable.rs:35:5 | LL | x.field.map(do_nothing); - | ^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(do_nothing); +LL + if let Ok(x_field) = x.field { do_nothing(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:40:5 + --> tests/ui/result_map_unit_fn_fixable.rs:38:5 | LL | x.field.map(diverge); - | ^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(x_field) = x.field { diverge(x_field) }` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(diverge); +LL + if let Ok(x_field) = x.field { diverge(x_field) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:47:5 + --> tests/ui/result_map_unit_fn_fixable.rs:45:5 | LL | x.field.map(|value| x.do_result_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { x.do_result_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| x.do_result_nothing(value + captured)); +LL + if let Ok(value) = x.field { x.do_result_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:50:5 + --> tests/ui/result_map_unit_fn_fixable.rs:48:5 | LL | x.field.map(|value| { x.do_result_plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { x.do_result_plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { x.do_result_plus_one(value + captured); }); +LL + if let Ok(value) = x.field { x.do_result_plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:54:5 + --> tests/ui/result_map_unit_fn_fixable.rs:52:5 | LL | x.field.map(|value| do_nothing(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| do_nothing(value + captured)); +LL + if let Ok(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:57:5 + --> tests/ui/result_map_unit_fn_fixable.rs:55:5 | LL | x.field.map(|value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured) }); +LL + if let Ok(value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:60:5 + --> tests/ui/result_map_unit_fn_fixable.rs:58:5 | LL | x.field.map(|value| { do_nothing(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value + captured); }); +LL + if let Ok(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:63:5 + --> tests/ui/result_map_unit_fn_fixable.rs:61:5 | LL | x.field.map(|value| { { do_nothing(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { do_nothing(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { do_nothing(value + captured); } }); +LL + if let Ok(value) = x.field { do_nothing(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:67:5 + --> tests/ui/result_map_unit_fn_fixable.rs:65:5 | LL | x.field.map(|value| diverge(value + captured)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| diverge(value + captured)); +LL + if let Ok(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:70:5 + --> tests/ui/result_map_unit_fn_fixable.rs:68:5 | LL | x.field.map(|value| { diverge(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured) }); +LL + if let Ok(value) = x.field { diverge(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:73:5 + --> tests/ui/result_map_unit_fn_fixable.rs:71:5 | LL | x.field.map(|value| { diverge(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { diverge(value + captured); }); +LL + if let Ok(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:76:5 + --> tests/ui/result_map_unit_fn_fixable.rs:74:5 | LL | x.field.map(|value| { { diverge(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { diverge(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { diverge(value + captured); } }); +LL + if let Ok(value) = x.field { diverge(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:82:5 + --> tests/ui/result_map_unit_fn_fixable.rs:80:5 | LL | x.field.map(|value| { let y = plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { let y = plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { let y = plus_one(value + captured); }); +LL + if let Ok(value) = x.field { let y = plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:85:5 + --> tests/ui/result_map_unit_fn_fixable.rs:83:5 | LL | x.field.map(|value| { plus_one(value + captured); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { plus_one(value + captured); }); +LL + if let Ok(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:88:5 + --> tests/ui/result_map_unit_fn_fixable.rs:86:5 | LL | x.field.map(|value| { { plus_one(value + captured); } }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { plus_one(value + captured); }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { { plus_one(value + captured); } }); +LL + if let Ok(value) = x.field { plus_one(value + captured); } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:92:5 + --> tests/ui/result_map_unit_fn_fixable.rs:90:5 | LL | x.field.map(|ref value| { do_nothing(value + captured) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(ref value) = x.field { do_nothing(value + captured) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|ref value| { do_nothing(value + captured) }); +LL + if let Ok(ref value) = x.field { do_nothing(value + captured) } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_fixable.rs:95:5 + --> tests/ui/result_map_unit_fn_fixable.rs:93:5 + | +LL | x.field.map(|value| println!("{value:?}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| println!("{value:?}")); +LL + if let Ok(value) = x.field { println!("{value:?}") } | -LL | x.field.map(|value| println!("{:?}", value)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { println!("{:?}", value) }` error: aborting due to 18 previous errors diff --git a/tests/ui/result_map_unit_fn_unfixable.rs b/tests/ui/result_map_unit_fn_unfixable.rs index fe3d8ece39f4c..e3f5c7f996dad 100644 --- a/tests/ui/result_map_unit_fn_unfixable.rs +++ b/tests/ui/result_map_unit_fn_unfixable.rs @@ -1,7 +1,8 @@ +//@no-rustfix #![warn(clippy::result_map_unit_fn)] #![feature(never_type)] -#![allow(unused, clippy::unnecessary_map_on_constructor)] -//@no-rustfix +#![allow(clippy::unnecessary_map_on_constructor)] + struct HasResult { field: Result, } diff --git a/tests/ui/result_map_unit_fn_unfixable.stderr b/tests/ui/result_map_unit_fn_unfixable.stderr index a6e38d808afa7..9f80ec1bbbd07 100644 --- a/tests/ui/result_map_unit_fn_unfixable.stderr +++ b/tests/ui/result_map_unit_fn_unfixable.stderr @@ -1,58 +1,86 @@ error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:23:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:24:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::result_map_unit_fn)]` +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value) }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:28:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:29:5 | LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:34:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:35:5 + | +LL | / x.field.map(|value| { +LL | | +LL | | +LL | | do_nothing(value); +LL | | do_nothing(value) +LL | | }); + | |______^ | -LL | // x.field.map(|value| { -LL | || -LL | || -LL | || do_nothing(value); -LL | || do_nothing(value) -LL | || }); - | ||______^- help: try: `if let Ok(value) = x.field { ... }` - | |______| +help: use `if let` instead + | +LL - x.field.map(|value| { +LL - +LL - +LL - do_nothing(value); +LL - do_nothing(value) +LL - }); +LL + if let Ok(value) = x.field { ... } | error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:40:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:41:5 | LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(value) = x.field { ... }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - x.field.map(|value| { do_nothing(value); do_nothing(value); }); +LL + if let Ok(value) = x.field { ... } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:46:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:47:5 | LL | "12".parse::().map(diverge); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(a) = "12".parse::() { diverge(a) }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - "12".parse::().map(diverge); +LL + if let Ok(a) = "12".parse::() { diverge(a) } + | error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type `()` - --> tests/ui/result_map_unit_fn_unfixable.rs:54:5 + --> tests/ui/result_map_unit_fn_unfixable.rs:55:5 | LL | y.map(do_nothing); - | ^^^^^^^^^^^^^^^^^- - | | - | help: try: `if let Ok(_y) = y { do_nothing(_y) }` + | ^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - y.map(do_nothing); +LL + if let Ok(_y) = y { do_nothing(_y) } + | error: aborting due to 6 previous errors diff --git a/tests/ui/search_is_some.rs b/tests/ui/search_is_some.rs index 802d27449abf6..0a1e0b07d1eb6 100644 --- a/tests/ui/search_is_some.rs +++ b/tests/ui/search_is_some.rs @@ -8,31 +8,6 @@ use option_helpers::IteratorFalsePositives; //@no-rustfix #[rustfmt::skip] fn main() { - let v = vec![3, 2, 1, 0, -1, -2, -3]; - let y = &&42; - - - // Check `find().is_some()`, multi-line case. - let _ = v.iter().find(|&x| { - //~^ search_is_some - *x < 0 - } - ).is_some(); - - // Check `position().is_some()`, multi-line case. - let _ = v.iter().position(|&x| { - //~^ search_is_some - x < 0 - } - ).is_some(); - - // Check `rposition().is_some()`, multi-line case. - let _ = v.iter().rposition(|&x| { - //~^ search_is_some - x < 0 - } - ).is_some(); - // Check that we don't lint if the caller is not an `Iterator` or string let falsepos = IteratorFalsePositives { foo: 0 }; let _ = falsepos.find().is_some(); @@ -49,31 +24,6 @@ fn main() { #[rustfmt::skip] fn is_none() { - let v = vec![3, 2, 1, 0, -1, -2, -3]; - let y = &&42; - - - // Check `find().is_none()`, multi-line case. - let _ = v.iter().find(|&x| { - //~^ search_is_some - *x < 0 - } - ).is_none(); - - // Check `position().is_none()`, multi-line case. - let _ = v.iter().position(|&x| { - //~^ search_is_some - x < 0 - } - ).is_none(); - - // Check `rposition().is_none()`, multi-line case. - let _ = v.iter().rposition(|&x| { - //~^ search_is_some - x < 0 - } - ).is_none(); - // Check that we don't lint if the caller is not an `Iterator` or string let falsepos = IteratorFalsePositives { foo: 0 }; let _ = falsepos.find().is_none(); @@ -87,18 +37,3 @@ fn is_none() { let _ = (0..1).find(some_closure).is_none(); //~^ search_is_some } - -#[allow(clippy::match_like_matches_macro)] -fn issue15102() { - let values = [None, Some(3)]; - let has_even = values - //~^ search_is_some - .iter() - .find(|v| match v { - Some(x) if x % 2 == 0 => true, - _ => false, - }) - .is_some(); - - println!("{has_even}"); -} diff --git a/tests/ui/search_is_some.stderr b/tests/ui/search_is_some.stderr index d5412f9011107..ccbab5320ee24 100644 --- a/tests/ui/search_is_some.stderr +++ b/tests/ui/search_is_some.stderr @@ -1,109 +1,17 @@ error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:16:13 - | -LL | let _ = v.iter().find(|&x| { - | _____________^ -LL | | -LL | | *x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - = note: `-D clippy::search-is-some` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::search_is_some)]` - -error: called `is_some()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:23:13 - | -LL | let _ = v.iter().position(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: called `is_some()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:30:13 - | -LL | let _ = v.iter().rposition(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_some(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:46:20 + --> tests/ui/search_is_some.rs:21:20 | LL | let _ = (0..1).find(some_closure).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(some_closure)` - -error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:57:13 | -LL | let _ = v.iter().find(|&x| { - | _____________^ -LL | | -LL | | *x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation - -error: called `is_none()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:64:13 - | -LL | let _ = v.iter().position(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation - -error: called `is_none()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:71:13 - | -LL | let _ = v.iter().rposition(|&x| { - | _____________^ -LL | | -LL | | x < 0 -LL | | } -LL | | ).is_none(); - | |______________________________^ - | - = help: this is more succinctly expressed by calling `any()` with negation + = note: `-D clippy::search-is-some` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::search_is_some)]` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:87:13 + --> tests/ui/search_is_some.rs:37:13 | LL | let _ = (0..1).find(some_closure).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!(0..1).any(some_closure)` -error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:94:20 - | -LL | let has_even = values - | ____________________^ -LL | | -LL | | .iter() -LL | | .find(|v| match v { -... | -LL | | }) -LL | | .is_some(); - | |__________________^ - | - = help: this is more succinctly expressed by calling `any()` - -error: aborting due to 9 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/search_is_some_fixable_none.fixed b/tests/ui/search_is_some_fixable_none.fixed index cc4dbc919d81d..720ed385fb480 100644 --- a/tests/ui/search_is_some_fixable_none.fixed +++ b/tests/ui/search_is_some_fixable_none.fixed @@ -24,14 +24,32 @@ fn main() { let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0); //~^ search_is_some let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1); + // Check `find().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|x| { + *x < 0 // + }); // Check `position().is_none()`, single-line case. let _ = !v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `position().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|&x| { + x < 0 // + }); // Check `rposition().is_none()`, single-line case. let _ = !v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `rposition().is_none()`, multi-line case. + let _ = !v + //~^ search_is_some + .iter().any(|&x| { + x < 0 // + }); let s1 = String::from("hello world"); let s2 = String::from("world"); diff --git a/tests/ui/search_is_some_fixable_none.rs b/tests/ui/search_is_some_fixable_none.rs index fa31a9ddedc66..1cb5f9c399590 100644 --- a/tests/ui/search_is_some_fixable_none.rs +++ b/tests/ui/search_is_some_fixable_none.rs @@ -27,14 +27,38 @@ fn main() { //~^ search_is_some .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1) .is_none(); + // Check `find().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .find(|&x| { + *x < 0 // + }) + .is_none(); // Check `position().is_none()`, single-line case. let _ = v.iter().position(|&x| x < 0).is_none(); //~^ search_is_some + // Check `position().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .position(|&x| { + x < 0 // + }) + .is_none(); // Check `rposition().is_none()`, single-line case. let _ = v.iter().rposition(|&x| x < 0).is_none(); //~^ search_is_some + // Check `rposition().is_none()`, multi-line case. + let _ = v + //~^ search_is_some + .iter() + .rposition(|&x| { + x < 0 // + }) + .is_none(); let s1 = String::from("hello world"); let s2 = String::from("world"); diff --git a/tests/ui/search_is_some_fixable_none.stderr b/tests/ui/search_is_some_fixable_none.stderr index b079cf7ea361b..909248357169f 100644 --- a/tests/ui/search_is_some_fixable_none.stderr +++ b/tests/ui/search_is_some_fixable_none.stderr @@ -59,92 +59,158 @@ LL | | .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains( LL | | .is_none(); | |__________________^ help: consider using: `!(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)` +error: called `is_none()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_none.rs:31:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .find(|&x| { +LL | | *x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|x| { +LL + *x < 0 // +LL ~ }); + | + error: called `is_none()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some_fixable_none.rs:32:13 + --> tests/ui/search_is_some_fixable_none.rs:40:13 | LL | let _ = v.iter().position(|&x| x < 0).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|&x| x < 0)` +error: called `is_none()` after searching an `Iterator` with `position` + --> tests/ui/search_is_some_fixable_none.rs:43:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .position(|&x| { +LL | | x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|&x| { +LL + x < 0 // +LL ~ }); + | + error: called `is_none()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some_fixable_none.rs:36:13 + --> tests/ui/search_is_some_fixable_none.rs:52:13 | LL | let _ = v.iter().rposition(|&x| x < 0).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|&x| x < 0)` +error: called `is_none()` after searching an `Iterator` with `rposition` + --> tests/ui/search_is_some_fixable_none.rs:55:13 + | +LL | let _ = v + | _____________^ +LL | | +LL | | .iter() +LL | | .rposition(|&x| { +LL | | x < 0 // +LL | | }) +LL | | .is_none(); + | |__________________^ + | +help: consider using + | +LL ~ let _ = !v +LL + +LL + .iter().any(|&x| { +LL + x < 0 // +LL ~ }); + | + error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:43:13 + --> tests/ui/search_is_some_fixable_none.rs:67:13 | LL | let _ = "hello world".find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:45:13 + --> tests/ui/search_is_some_fixable_none.rs:69:13 | LL | let _ = "hello world".find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:47:13 + --> tests/ui/search_is_some_fixable_none.rs:71:13 | LL | let _ = "hello world".find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!"hello world".contains(&s2[2..])` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:50:13 + --> tests/ui/search_is_some_fixable_none.rs:74:13 | LL | let _ = s1.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:52:13 + --> tests/ui/search_is_some_fixable_none.rs:76:13 | LL | let _ = s1.find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:54:13 + --> tests/ui/search_is_some_fixable_none.rs:78:13 | LL | let _ = s1.find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1.contains(&s2[2..])` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:57:13 + --> tests/ui/search_is_some_fixable_none.rs:81:13 | LL | let _ = s1[2..].find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:59:13 + --> tests/ui/search_is_some_fixable_none.rs:83:13 | LL | let _ = s1[2..].find(&s2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains(&s2)` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:61:13 + --> tests/ui/search_is_some_fixable_none.rs:85:13 | LL | let _ = s1[2..].find(&s2[2..]).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s1[2..].contains(&s2[2..])` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:78:25 + --> tests/ui/search_is_some_fixable_none.rs:102:25 | LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_none()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!filter_hand.iter().any(|cc| c == &cc)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:95:30 + --> tests/ui/search_is_some_fixable_none.rs:119:30 | LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_none()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!filter_hand.iter().any(|cc| c == cc)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:107:17 + --> tests/ui/search_is_some_fixable_none.rs:131:17 | LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.foo == 1 && v.bar == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:111:17 + --> tests/ui/search_is_some_fixable_none.rs:135:17 | LL | let _ = vfoo | _________________^ @@ -162,55 +228,55 @@ LL ~ .iter().any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2); | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:120:17 + --> tests/ui/search_is_some_fixable_none.rs:144:17 | LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|a| a[0] == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:127:17 + --> tests/ui/search_is_some_fixable_none.rs:151:17 | LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|sub| sub[1..4].len() == 3)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:146:17 + --> tests/ui/search_is_some_fixable_none.rs:170:17 | LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `![ppx].iter().any(|ppp_x: &&u32| please(ppp_x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:148:17 + --> tests/ui/search_is_some_fixable_none.rs:172:17 | LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `![String::from("Hey hey")].iter().any(|s| s.len() == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:152:17 + --> tests/ui/search_is_some_fixable_none.rs:176:17 | LL | let _ = v.iter().find(|x| deref_enough(**x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| deref_enough(*x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:154:17 + --> tests/ui/search_is_some_fixable_none.rs:178:17 | LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x: &u32| deref_enough(*x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:158:17 + --> tests/ui/search_is_some_fixable_none.rs:182:17 | LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| arg_no_deref(&x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:161:17 + --> tests/ui/search_is_some_fixable_none.rs:185:17 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x: &u32| arg_no_deref(&x))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:182:17 + --> tests/ui/search_is_some_fixable_none.rs:206:17 | LL | let _ = vfoo | _________________^ @@ -228,25 +294,25 @@ LL ~ .iter().any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:199:17 + --> tests/ui/search_is_some_fixable_none.rs:223:17 | LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.inner[0].bar == 2)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:205:17 + --> tests/ui/search_is_some_fixable_none.rs:229:17 | LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|x| (**x)[0] == 9)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:219:17 + --> tests/ui/search_is_some_fixable_none.rs:243:17 | LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!vfoo.iter().any(|v| v.by_ref(&v.bar))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:224:17 + --> tests/ui/search_is_some_fixable_none.rs:248:17 | LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)] | _________________^ @@ -264,106 +330,106 @@ LL ~ .iter().any(|&&(&x, ref y)| x == *y); | error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:247:17 + --> tests/ui/search_is_some_fixable_none.rs:271:17 | LL | let _ = v.iter().find(|s| s[0].is_empty()).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|s| s[0].is_empty())` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:249:17 + --> tests/ui/search_is_some_fixable_none.rs:273:17 | LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|s| test_string_1(&s[0]))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:259:17 + --> tests/ui/search_is_some_fixable_none.rs:283:17 | LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| fp.field.is_power_of_two())` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:261:17 + --> tests/ui/search_is_some_fixable_none.rs:285:17 | LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| test_u32_1(fp.field))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:263:17 + --> tests/ui/search_is_some_fixable_none.rs:287:17 | LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| test_u32_2(*fp.field))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:280:17 + --> tests/ui/search_is_some_fixable_none.rs:304:17 | LL | let _ = v.iter().find(|x| **x == 42).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:282:17 + --> tests/ui/search_is_some_fixable_none.rs:306:17 | LL | Foo.bar(v.iter().find(|x| **x == 42).is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:288:9 + --> tests/ui/search_is_some_fixable_none.rs:312:9 | LL | v.iter().find(|x| **x == 42).is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_none.rs:294:9 + --> tests/ui/search_is_some_fixable_none.rs:318:9 | LL | v.iter().find(|x| **x == 42).is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:300:17 + --> tests/ui/search_is_some_fixable_none.rs:324:17 | LL | let _ = s.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:302:17 + --> tests/ui/search_is_some_fixable_none.rs:326:17 | LL | Foo.bar(s.find("world").is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:305:17 + --> tests/ui/search_is_some_fixable_none.rs:329:17 | LL | let _ = s.find("world").is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:307:17 + --> tests/ui/search_is_some_fixable_none.rs:331:17 | LL | Foo.bar(s.find("world").is_none()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:313:17 + --> tests/ui/search_is_some_fixable_none.rs:337:17 | LL | let _ = s.find("world").is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:316:17 + --> tests/ui/search_is_some_fixable_none.rs:340:17 | LL | let _ = s.find("world").is_none().then(computations); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:322:17 + --> tests/ui/search_is_some_fixable_none.rs:346:17 | LL | let _ = s.find("world").is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` error: called `is_none()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_none.rs:325:17 + --> tests/ui/search_is_some_fixable_none.rs:349:17 | LL | let _ = s.find("world").is_none().then_some(0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))` -error: aborting due to 54 previous errors +error: aborting due to 57 previous errors diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index c7a4422f37378..1213fdcf61197 100644 --- a/tests/ui/search_is_some_fixable_some.fixed +++ b/tests/ui/search_is_some_fixable_some.fixed @@ -25,14 +25,35 @@ fn main() { //~^ search_is_some let _ = (1..3) .any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1); + // Check `find().is_some()`, multi-line case. + let _ = v + .iter() + .any(|x| { + //~^ search_is_some + *x < 0 + }); // Check `position().is_some()`, single-line case. let _ = v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `position().is_some()`, multi-line case. + let _ = v + .iter() + .any(|&x| { + //~^ search_is_some + x < 0 + }); // Check `rposition().is_some()`, single-line case. let _ = v.iter().any(|&x| x < 0); //~^ search_is_some + // Check `rposition().is_some()`, multi-line case. + let _ = v + .iter() + .any(|&x| { + //~^ search_is_some + x < 0 + }); let s1 = String::from("hello world"); let s2 = String::from("world"); @@ -290,9 +311,19 @@ mod issue9120 { } } +#[allow(clippy::match_like_matches_macro)] fn issue15102() { let values = [None, Some(3)]; let has_even = values.iter().any(|v| matches!(&v, Some(x) if x % 2 == 0)); //~^ search_is_some println!("{has_even}"); + + let has_even = values + .iter() + .any(|v| match &v { + //~^ search_is_some + Some(x) if x % 2 == 0 => true, + _ => false, + }); + println!("{has_even}"); } diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index d6b1c67c9718c..4294a39333f20 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -27,14 +27,38 @@ fn main() { .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1) //~^ search_is_some .is_some(); + // Check `find().is_some()`, multi-line case. + let _ = v + .iter() + .find(|&x| { + //~^ search_is_some + *x < 0 + }) + .is_some(); // Check `position().is_some()`, single-line case. let _ = v.iter().position(|&x| x < 0).is_some(); //~^ search_is_some + // Check `position().is_some()`, multi-line case. + let _ = v + .iter() + .position(|&x| { + //~^ search_is_some + x < 0 + }) + .is_some(); // Check `rposition().is_some()`, single-line case. let _ = v.iter().rposition(|&x| x < 0).is_some(); //~^ search_is_some + // Check `rposition().is_some()`, multi-line case. + let _ = v + .iter() + .rposition(|&x| { + //~^ search_is_some + x < 0 + }) + .is_some(); let s1 = String::from("hello world"); let s2 = String::from("world"); @@ -298,9 +322,20 @@ mod issue9120 { } } +#[allow(clippy::match_like_matches_macro)] fn issue15102() { let values = [None, Some(3)]; let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); //~^ search_is_some println!("{has_even}"); + + let has_even = values + .iter() + .find(|v| match v { + //~^ search_is_some + Some(x) if x % 2 == 0 => true, + _ => false, + }) + .is_some(); + println!("{has_even}"); } diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr index 551a670d937f2..cee1eb08876ba 100644 --- a/tests/ui/search_is_some_fixable_some.stderr +++ b/tests/ui/search_is_some_fixable_some.stderr @@ -58,92 +58,149 @@ LL | | LL | | .is_some(); | |__________________^ help: consider using: `any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)` +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_some.rs:33:10 + | +LL | .find(|&x| { + | __________^ +LL | | +LL | | *x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|x| { +LL + +LL + *x < 0 +LL ~ }); + | + error: called `is_some()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some_fixable_some.rs:32:22 + --> tests/ui/search_is_some_fixable_some.rs:40:22 | LL | let _ = v.iter().position(|&x| x < 0).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|&x| x < 0)` +error: called `is_some()` after searching an `Iterator` with `position` + --> tests/ui/search_is_some_fixable_some.rs:45:10 + | +LL | .position(|&x| { + | __________^ +LL | | +LL | | x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|&x| { +LL + +LL + x < 0 +LL ~ }); + | + error: called `is_some()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some_fixable_some.rs:36:22 + --> tests/ui/search_is_some_fixable_some.rs:52:22 | LL | let _ = v.iter().rposition(|&x| x < 0).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|&x| x < 0)` +error: called `is_some()` after searching an `Iterator` with `rposition` + --> tests/ui/search_is_some_fixable_some.rs:57:10 + | +LL | .rposition(|&x| { + | __________^ +LL | | +LL | | x < 0 +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|&x| { +LL + +LL + x < 0 +LL ~ }); + | + error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:42:27 + --> tests/ui/search_is_some_fixable_some.rs:66:27 | LL | let _ = "hello world".find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:44:27 + --> tests/ui/search_is_some_fixable_some.rs:68:27 | LL | let _ = "hello world".find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:46:27 + --> tests/ui/search_is_some_fixable_some.rs:70:27 | LL | let _ = "hello world".find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:49:16 + --> tests/ui/search_is_some_fixable_some.rs:73:16 | LL | let _ = s1.find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:51:16 + --> tests/ui/search_is_some_fixable_some.rs:75:16 | LL | let _ = s1.find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:53:16 + --> tests/ui/search_is_some_fixable_some.rs:77:16 | LL | let _ = s1.find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:56:21 + --> tests/ui/search_is_some_fixable_some.rs:80:21 | LL | let _ = s1[2..].find("world").is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains("world")` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:58:21 + --> tests/ui/search_is_some_fixable_some.rs:82:21 | LL | let _ = s1[2..].find(&s2).is_some(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2)` error: called `is_some()` after calling `find()` on a string - --> tests/ui/search_is_some_fixable_some.rs:60:21 + --> tests/ui/search_is_some_fixable_some.rs:84:21 | LL | let _ = s1[2..].find(&s2[2..]).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `contains(&s2[2..])` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:77:44 + --> tests/ui/search_is_some_fixable_some.rs:101:44 | LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_some()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|cc| c == &cc)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:94:49 + --> tests/ui/search_is_some_fixable_some.rs:118:49 | LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_some()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|cc| c == cc)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:106:29 + --> tests/ui/search_is_some_fixable_some.rs:130:29 | LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.foo == 1 && v.bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:112:14 + --> tests/ui/search_is_some_fixable_some.rs:136:14 | LL | .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2) | ______________^ @@ -152,55 +209,55 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:119:29 + --> tests/ui/search_is_some_fixable_some.rs:143:29 | LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|a| a[0] == 42)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:126:29 + --> tests/ui/search_is_some_fixable_some.rs:150:29 | LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|sub| sub[1..4].len() == 3)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:145:30 + --> tests/ui/search_is_some_fixable_some.rs:169:30 | LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|ppp_x: &&u32| please(ppp_x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:147:50 + --> tests/ui/search_is_some_fixable_some.rs:171:50 | LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| s.len() == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:151:26 + --> tests/ui/search_is_some_fixable_some.rs:175:26 | LL | let _ = v.iter().find(|x| deref_enough(**x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| deref_enough(*x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:153:26 + --> tests/ui/search_is_some_fixable_some.rs:177:26 | LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| deref_enough(*x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:157:26 + --> tests/ui/search_is_some_fixable_some.rs:181:26 | LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| arg_no_deref(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:160:26 + --> tests/ui/search_is_some_fixable_some.rs:184:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:183:14 + --> tests/ui/search_is_some_fixable_some.rs:207:14 | LL | .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2) | ______________^ @@ -209,25 +266,25 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:198:29 + --> tests/ui/search_is_some_fixable_some.rs:222:29 | LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.inner[0].bar == 2)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:204:29 + --> tests/ui/search_is_some_fixable_some.rs:228:29 | LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x| (**x)[0] == 9)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:218:29 + --> tests/ui/search_is_some_fixable_some.rs:242:29 | LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| v.by_ref(&v.bar))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:225:14 + --> tests/ui/search_is_some_fixable_some.rs:249:14 | LL | .find(|&&&(&x, ref y)| x == *y) | ______________^ @@ -236,64 +293,85 @@ LL | | .is_some(); | |______________________^ help: consider using: `any(|&&(&x, ref y)| x == *y)` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:246:26 + --> tests/ui/search_is_some_fixable_some.rs:270:26 | LL | let _ = v.iter().find(|s| s[0].is_empty()).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| s[0].is_empty())` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:248:26 + --> tests/ui/search_is_some_fixable_some.rs:272:26 | LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|s| test_string_1(&s[0]))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:258:26 + --> tests/ui/search_is_some_fixable_some.rs:282:26 | LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| fp.field.is_power_of_two())` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:260:26 + --> tests/ui/search_is_some_fixable_some.rs:284:26 | LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| test_u32_1(fp.field))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:262:26 + --> tests/ui/search_is_some_fixable_some.rs:286:26 | LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|fp| test_u32_2(*fp.field))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:278:18 + --> tests/ui/search_is_some_fixable_some.rs:302:18 | LL | v.iter().find(|x: &&u32| func(x)).is_some() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| func(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:288:26 + --> tests/ui/search_is_some_fixable_some.rs:312:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_impl(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref_impl(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:292:26 + --> tests/ui/search_is_some_fixable_some.rs:316:26 | LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_dyn(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| arg_no_deref_dyn(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:296:26 + --> tests/ui/search_is_some_fixable_some.rs:320:26 | LL | let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| (*arg_no_deref_dyn)(&x))` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some_fixable_some.rs:303:34 + --> tests/ui/search_is_some_fixable_some.rs:328:34 | LL | let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| matches!(&v, Some(x) if x % 2 == 0))` -error: aborting due to 47 previous errors +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_some.rs:334:10 + | +LL | .find(|v| match v { + | __________^ +LL | | +LL | | Some(x) if x % 2 == 0 => true, +LL | | _ => false, +LL | | }) +LL | | .is_some(); + | |__________________^ + | +help: consider using + | +LL ~ .any(|v| match &v { +LL + +LL + Some(x) if x % 2 == 0 => true, +LL + _ => false, +LL ~ }); + | + +error: aborting due to 51 previous errors diff --git a/tests/ui/shadow.rs b/tests/ui/shadow.rs index 05009b2ddd416..d589bbc4affde 100644 --- a/tests/ui/shadow.rs +++ b/tests/ui/shadow.rs @@ -3,7 +3,7 @@ #![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)] #![allow( clippy::let_unit_value, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_locals )] diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed index 03982b069e675..fe28674ec18e5 100644 --- a/tests/ui/single_match.fixed +++ b/tests/ui/single_match.fixed @@ -3,7 +3,7 @@ #![allow( unused, clippy::uninlined_format_args, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_pattern_matching, clippy::manual_unwrap_or_default diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index e28128e35adab..0f558cb5f786c 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -3,7 +3,7 @@ #![allow( unused, clippy::uninlined_format_args, - clippy::needless_if, + clippy::needless_ifs, clippy::redundant_guards, clippy::redundant_pattern_matching, clippy::manual_unwrap_or_default diff --git a/tests/ui/single_match_else_deref_patterns.fixed b/tests/ui/single_match_else_deref_patterns.fixed index 7a9f806309645..1743ae6bf5a7d 100644 --- a/tests/ui/single_match_else_deref_patterns.fixed +++ b/tests/ui/single_match_else_deref_patterns.fixed @@ -5,7 +5,7 @@ clippy::op_ref, clippy::deref_addrof, clippy::borrow_deref_ref, - clippy::needless_if + clippy::needless_ifs )] #![deny(clippy::single_match_else)] diff --git a/tests/ui/single_match_else_deref_patterns.rs b/tests/ui/single_match_else_deref_patterns.rs index ef19c7cbde2ba..d5cb8a24a609c 100644 --- a/tests/ui/single_match_else_deref_patterns.rs +++ b/tests/ui/single_match_else_deref_patterns.rs @@ -5,7 +5,7 @@ clippy::op_ref, clippy::deref_addrof, clippy::borrow_deref_ref, - clippy::needless_if + clippy::needless_ifs )] #![deny(clippy::single_match_else)] diff --git a/tests/ui/starts_ends_with.fixed b/tests/ui/starts_ends_with.fixed index b4f9d5867703a..f2fab9217604b 100644 --- a/tests/ui/starts_ends_with.fixed +++ b/tests/ui/starts_ends_with.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if, dead_code, unused_must_use, clippy::double_ended_iterator_last)] +#![allow(clippy::needless_ifs, dead_code, unused_must_use, clippy::double_ended_iterator_last)] fn main() {} diff --git a/tests/ui/starts_ends_with.rs b/tests/ui/starts_ends_with.rs index dae04ac4a62d5..1460d77a094dd 100644 --- a/tests/ui/starts_ends_with.rs +++ b/tests/ui/starts_ends_with.rs @@ -1,4 +1,4 @@ -#![allow(clippy::needless_if, dead_code, unused_must_use, clippy::double_ended_iterator_last)] +#![allow(clippy::needless_ifs, dead_code, unused_must_use, clippy::double_ended_iterator_last)] fn main() {} diff --git a/tests/ui/suspicious_else_formatting.rs b/tests/ui/suspicious_else_formatting.rs index 072e7b27b0d0a..8574cf1c20a1a 100644 --- a/tests/ui/suspicious_else_formatting.rs +++ b/tests/ui/suspicious_else_formatting.rs @@ -4,7 +4,7 @@ #![allow( clippy::if_same_then_else, clippy::let_unit_value, - clippy::needless_if, + clippy::needless_ifs, clippy::needless_else )] diff --git a/tests/ui/suspicious_unary_op_formatting.rs b/tests/ui/suspicious_unary_op_formatting.rs index ab9bdde6cf902..19f8b231925b7 100644 --- a/tests/ui/suspicious_unary_op_formatting.rs +++ b/tests/ui/suspicious_unary_op_formatting.rs @@ -1,5 +1,5 @@ #![warn(clippy::suspicious_unary_op_formatting)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[rustfmt::skip] fn main() { diff --git a/tests/ui/test_attr_in_doctest.rs b/tests/ui/test_attr_in_doctest.rs index 6cffd813b8358..7d1a09024895b 100644 --- a/tests/ui/test_attr_in_doctest.rs +++ b/tests/ui/test_attr_in_doctest.rs @@ -21,7 +21,6 @@ /// } /// /// #[test] -//~^ test_attr_in_doctest /// fn should_be_linted_too() { /// assert_eq!("#[test]", " /// #[test] diff --git a/tests/ui/test_attr_in_doctest.stderr b/tests/ui/test_attr_in_doctest.stderr index 166b9b417b626..a8fa53034036b 100644 --- a/tests/ui/test_attr_in_doctest.stderr +++ b/tests/ui/test_attr_in_doctest.stderr @@ -1,11 +1,8 @@ error: unit tests in doctest are not executed --> tests/ui/test_attr_in_doctest.rs:6:5 | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_be_linted() { - | |_______________________^ +LL | /// #[test] + | ^^^^^^^ | = note: `-D clippy::test-attr-in-doctest` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::test_attr_in_doctest)]` @@ -13,20 +10,11 @@ LL | | /// fn should_be_linted() { error: unit tests in doctest are not executed --> tests/ui/test_attr_in_doctest.rs:16:5 | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_also_be_linted() { - | |____________________________^ +LL | /// #[test] + | ^^^^^^^ +... +LL | /// #[test] + | ^^^^^^^ -error: unit tests in doctest are not executed - --> tests/ui/test_attr_in_doctest.rs:23:5 - | -LL | /// #[test] - | _____^ -LL | | -LL | | /// fn should_be_linted_too() { - | |___________________________^ - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/unit_cmp.rs b/tests/ui/unit_cmp.rs index 0a0fe3a1990b0..839987a474cd0 100644 --- a/tests/ui/unit_cmp.rs +++ b/tests/ui/unit_cmp.rs @@ -3,7 +3,7 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::derive_partial_eq_without_eq, - clippy::needless_if + clippy::needless_ifs )] #[derive(PartialEq)] diff --git a/tests/ui/unnecessary_filter_map.stderr b/tests/ui/unnecessary_filter_map.stderr index a879633e10f2a..8c33c08c267d5 100644 --- a/tests/ui/unnecessary_filter_map.stderr +++ b/tests/ui/unnecessary_filter_map.stderr @@ -1,17 +1,17 @@ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:4:13 + --> tests/ui/unnecessary_filter_map.rs:4:20 | LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_filter_map)]` error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:7:13 + --> tests/ui/unnecessary_filter_map.rs:7:20 | LL | let _ = (0..4).filter_map(|x| { - | _____________^ + | ____________________^ LL | | LL | | LL | | if x > 1 { @@ -21,10 +21,10 @@ LL | | }); | |______^ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:15:13 + --> tests/ui/unnecessary_filter_map.rs:15:20 | LL | let _ = (0..4).filter_map(|x| match x { - | _____________^ + | ____________________^ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), @@ -32,22 +32,22 @@ LL | | }); | |______^ error: this `.filter_map(..)` can be written more simply using `.map(..)` - --> tests/ui/unnecessary_filter_map.rs:21:13 + --> tests/ui/unnecessary_filter_map.rs:21:20 | LL | let _ = (0..4).filter_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `.filter_map(..)` is unnecessary - --> tests/ui/unnecessary_filter_map.rs:28:61 + --> tests/ui/unnecessary_filter_map.rs:28:46 | LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ error: this `.filter_map(..)` can be written more simply using `.filter(..)` - --> tests/ui/unnecessary_filter_map.rs:166:14 + --> tests/ui/unnecessary_filter_map.rs:166:33 | LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 6 previous errors diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs index 33ba7074d623b..c5a8937488d98 100644 --- a/tests/ui/unnecessary_find_map.rs +++ b/tests/ui/unnecessary_find_map.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - fn main() { let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); //~^ unnecessary_find_map diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr index 3754a3d99538e..8a269119df220 100644 --- a/tests/ui/unnecessary_find_map.stderr +++ b/tests/ui/unnecessary_find_map.stderr @@ -1,17 +1,17 @@ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:4:13 + --> tests/ui/unnecessary_find_map.rs:2:20 | LL | let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-find-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_find_map)]` error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:7:13 + --> tests/ui/unnecessary_find_map.rs:5:20 | LL | let _ = (0..4).find_map(|x| { - | _____________^ + | ____________________^ LL | | LL | | LL | | if x > 1 { @@ -21,10 +21,10 @@ LL | | }); | |______^ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:15:13 + --> tests/ui/unnecessary_find_map.rs:13:20 | LL | let _ = (0..4).find_map(|x| match x { - | _____________^ + | ____________________^ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), @@ -32,16 +32,16 @@ LL | | }); | |______^ error: this `.find_map(..)` can be written more simply using `.map(..).next()` - --> tests/ui/unnecessary_find_map.rs:21:13 + --> tests/ui/unnecessary_find_map.rs:19:20 | LL | let _ = (0..4).find_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: this `.find_map(..)` can be written more simply using `.find(..)` - --> tests/ui/unnecessary_find_map.rs:33:14 + --> tests/ui/unnecessary_find_map.rs:31:33 | LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_safety_comment.rs b/tests/ui/unnecessary_safety_comment.rs index 4440089b3633b..d82a7b969080b 100644 --- a/tests/ui/unnecessary_safety_comment.rs +++ b/tests/ui/unnecessary_safety_comment.rs @@ -1,5 +1,5 @@ #![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)] -#![allow(clippy::let_unit_value, clippy::missing_safety_doc, clippy::needless_if)] +#![allow(clippy::let_unit_value, clippy::missing_safety_doc, clippy::needless_ifs)] mod unsafe_items_invalid_comment { // SAFETY: diff --git a/tests/ui/unneeded_wildcard_pattern.fixed b/tests/ui/unneeded_wildcard_pattern.fixed index 7e6a493b86c12..32494435fff5e 100644 --- a/tests/ui/unneeded_wildcard_pattern.fixed +++ b/tests/ui/unneeded_wildcard_pattern.fixed @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![feature(stmt_expr_attributes)] #![deny(clippy::unneeded_wildcard_pattern)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unneeded_wildcard_pattern.rs b/tests/ui/unneeded_wildcard_pattern.rs index c91383e2cca5f..b3a0fb6098d61 100644 --- a/tests/ui/unneeded_wildcard_pattern.rs +++ b/tests/ui/unneeded_wildcard_pattern.rs @@ -1,7 +1,7 @@ //@aux-build:proc_macros.rs #![feature(stmt_expr_attributes)] #![deny(clippy::unneeded_wildcard_pattern)] -#![allow(clippy::needless_if)] +#![allow(clippy::needless_ifs)] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnested_or_patterns.fixed b/tests/ui/unnested_or_patterns.fixed index 339d4a95084ae..0391fb19b1f6f 100644 --- a/tests/ui/unnested_or_patterns.fixed +++ b/tests/ui/unnested_or_patterns.fixed @@ -4,7 +4,7 @@ clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] diff --git a/tests/ui/unnested_or_patterns.rs b/tests/ui/unnested_or_patterns.rs index f5c99183b0c51..f0702668fa917 100644 --- a/tests/ui/unnested_or_patterns.rs +++ b/tests/ui/unnested_or_patterns.rs @@ -4,7 +4,7 @@ clippy::cognitive_complexity, clippy::match_ref_pats, clippy::upper_case_acronyms, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] diff --git a/tests/ui/unnested_or_patterns2.fixed b/tests/ui/unnested_or_patterns2.fixed index 6d601ea5e57fe..2c794463457fb 100644 --- a/tests/ui/unnested_or_patterns2.fixed +++ b/tests/ui/unnested_or_patterns2.fixed @@ -3,7 +3,7 @@ #![allow( clippy::cognitive_complexity, clippy::match_ref_pats, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] diff --git a/tests/ui/unnested_or_patterns2.rs b/tests/ui/unnested_or_patterns2.rs index 7e5ea0161bffd..12f3d3fca464e 100644 --- a/tests/ui/unnested_or_patterns2.rs +++ b/tests/ui/unnested_or_patterns2.rs @@ -3,7 +3,7 @@ #![allow( clippy::cognitive_complexity, clippy::match_ref_pats, - clippy::needless_if, + clippy::needless_ifs, clippy::manual_range_patterns )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)] diff --git a/tests/ui/use_debug.rs b/tests/ui/use_debug.rs new file mode 100644 index 0000000000000..89c127a12faf9 --- /dev/null +++ b/tests/ui/use_debug.rs @@ -0,0 +1,50 @@ +#![warn(clippy::use_debug)] + +use std::fmt::{Debug, Display, Formatter, Result}; + +struct Foo; + +impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{:?}", 43.1415) + //~^ use_debug + } +} + +impl Debug for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } +} + +fn main() { + print!("Hello {:?}", "World"); + //~^ use_debug + + print!("Hello {:#?}", "#orld"); + //~^ use_debug + + assert_eq!(42, 1337); + + vec![1, 2]; +} + +// don't get confused by nested impls +fn issue15942() { + struct Bar; + impl Debug for Bar { + fn fmt(&self, f: &mut Formatter) -> Result { + struct Baz; + impl Debug for Baz { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } + } + + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } + } +} diff --git a/tests/ui/use_debug.stderr b/tests/ui/use_debug.stderr new file mode 100644 index 0000000000000..85168d3bc3416 --- /dev/null +++ b/tests/ui/use_debug.stderr @@ -0,0 +1,23 @@ +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:9:20 + | +LL | write!(f, "{:?}", 43.1415) + | ^^^^ + | + = note: `-D clippy::use-debug` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::use_debug)]` + +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:22:19 + | +LL | print!("Hello {:?}", "World"); + | ^^^^ + +error: use of `Debug`-based formatting + --> tests/ui/use_debug.rs:25:19 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index 2942f64741e91..9de7d2c67149d 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index f2da414e9f652..38cd1175aa485 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs index d16506d94aa3c..157c236a214cd 100644 --- a/tests/ui/useless_conversion_try.rs +++ b/tests/ui/useless_conversion_try.rs @@ -1,6 +1,6 @@ #![deny(clippy::useless_conversion)] #![allow( - clippy::needless_if, + clippy::needless_ifs, clippy::unnecessary_fallible_conversions, clippy::manual_unwrap_or_default )] diff --git a/tests/ui/infinite_loop.rs b/tests/ui/while_immutable_condition.rs similarity index 98% rename from tests/ui/infinite_loop.rs rename to tests/ui/while_immutable_condition.rs index 8ff7f3b0c18d8..5c18cd41ff792 100644 --- a/tests/ui/infinite_loop.rs +++ b/tests/ui/while_immutable_condition.rs @@ -1,3 +1,5 @@ +#![warn(clippy::while_immutable_condition)] + fn fn_val(i: i32) -> i32 { unimplemented!() } diff --git a/tests/ui/infinite_loop.stderr b/tests/ui/while_immutable_condition.stderr similarity index 76% rename from tests/ui/infinite_loop.stderr rename to tests/ui/while_immutable_condition.stderr index 04da9776c3025..278b473d23cee 100644 --- a/tests/ui/infinite_loop.stderr +++ b/tests/ui/while_immutable_condition.stderr @@ -1,14 +1,15 @@ error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:20:11 + --> tests/ui/while_immutable_condition.rs:22:11 | LL | while y < 10 { | ^^^^^^ | = note: this may lead to an infinite or to a never running loop - = note: `#[deny(clippy::while_immutable_condition)]` on by default + = note: `-D clippy::while-immutable-condition` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::while_immutable_condition)]` error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:27:11 + --> tests/ui/while_immutable_condition.rs:29:11 | LL | while y < 10 && x < 3 { | ^^^^^^^^^^^^^^^ @@ -16,7 +17,7 @@ LL | while y < 10 && x < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:36:11 + --> tests/ui/while_immutable_condition.rs:38:11 | LL | while !cond { | ^^^^^ @@ -24,7 +25,7 @@ LL | while !cond { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:82:11 + --> tests/ui/while_immutable_condition.rs:84:11 | LL | while i < 3 { | ^^^^^ @@ -32,7 +33,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:89:11 + --> tests/ui/while_immutable_condition.rs:91:11 | LL | while i < 3 && j > 0 { | ^^^^^^^^^^^^^^ @@ -40,7 +41,7 @@ LL | while i < 3 && j > 0 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:95:11 + --> tests/ui/while_immutable_condition.rs:97:11 | LL | while i < 3 { | ^^^^^ @@ -48,7 +49,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:112:11 + --> tests/ui/while_immutable_condition.rs:114:11 | LL | while i < 3 { | ^^^^^ @@ -56,7 +57,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:119:11 + --> tests/ui/while_immutable_condition.rs:121:11 | LL | while i < 3 { | ^^^^^ @@ -64,7 +65,7 @@ LL | while i < 3 { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:187:15 + --> tests/ui/while_immutable_condition.rs:189:15 | LL | while self.count < n { | ^^^^^^^^^^^^^^ @@ -72,7 +73,7 @@ LL | while self.count < n { = note: this may lead to an infinite or to a never running loop error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:197:11 + --> tests/ui/while_immutable_condition.rs:199:11 | LL | while y < 10 { | ^^^^^^ @@ -82,7 +83,7 @@ LL | while y < 10 { = help: rewrite it as `if cond { loop { } }` error: variables in the condition are not mutated in the loop body - --> tests/ui/infinite_loop.rs:206:11 + --> tests/ui/while_immutable_condition.rs:208:11 | LL | while y < 10 { | ^^^^^^ diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 2b6ee67c37dc7..b468b52aea5bb 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,23 +1,12 @@ "use strict"; window.searchState = { - timeout: null, inputElem: document.getElementById("search-input"), lastSearch: '', clearInput: () => { searchState.inputElem.value = ""; searchState.filterLints(); }, - clearInputTimeout: () => { - if (searchState.timeout !== null) { - clearTimeout(searchState.timeout); - searchState.timeout = null - } - }, - resetInputTimeout: () => { - searchState.clearInputTimeout(); - setTimeout(searchState.filterLints, 50); - }, filterLints: () => { function matchesSearch(lint, terms, searchStr) { // Search by id @@ -42,8 +31,6 @@ window.searchState = { return true; } - searchState.clearInputTimeout(); - let searchStr = searchState.inputElem.value.trim().toLowerCase(); if (searchStr.startsWith("clippy::")) { searchStr = searchStr.slice(8); @@ -79,7 +66,7 @@ function handleInputChanged(event) { if (event.target !== document.activeElement) { return; } - searchState.resetInputTimeout(); + searchState.filterLints(); } function handleShortcut(ev) { @@ -149,27 +136,25 @@ function lintAnchor(event) { expandLint(id); } +const clipboardTimeouts = new Map(); function copyToClipboard(event) { event.preventDefault(); event.stopPropagation(); const clipboard = event.target; - let resetClipboardTimeout = null; - const resetClipboardIcon = clipboard.innerHTML; - - function resetClipboard() { - resetClipboardTimeout = null; - clipboard.innerHTML = resetClipboardIcon; - } - navigator.clipboard.writeText("clippy::" + clipboard.parentElement.id.slice(5)); - clipboard.innerHTML = "✓"; - if (resetClipboardTimeout !== null) { - clearTimeout(resetClipboardTimeout); - } - resetClipboardTimeout = setTimeout(resetClipboard, 1000); + clipboard.textContent = "✓"; + + clearTimeout(clipboardTimeouts.get(clipboard)); + clipboardTimeouts.set( + clipboard, + setTimeout(() => { + clipboard.textContent = "📋"; + clipboardTimeouts.delete(clipboard); + }, 1000) + ); } function handleBlur(event, elementId) { @@ -487,14 +472,6 @@ function generateSettings() { setupDropdown("version-filter"); } -function generateSearch() { - searchState.inputElem.addEventListener("change", handleInputChanged); - searchState.inputElem.addEventListener("input", handleInputChanged); - searchState.inputElem.addEventListener("keydown", handleInputChanged); - searchState.inputElem.addEventListener("keyup", handleInputChanged); - searchState.inputElem.addEventListener("paste", handleInputChanged); -} - function scrollToLint(lintId) { const target = document.getElementById(lintId); if (!target) { @@ -540,6 +517,8 @@ function parseURLFilters() { } function addListeners() { + searchState.inputElem.addEventListener("input", handleInputChanged); + disableShortcutsButton.addEventListener("change", () => { disableShortcuts = disableShortcutsButton.checked; storeValue("disable-shortcuts", disableShortcuts); @@ -594,7 +573,6 @@ disableShortcutsButton.checked = disableShortcuts; addListeners(); highlightLazily(); generateSettings(); -generateSearch(); parseURLFilters(); scrollToLintByURL(); filters.filterLints(); From 34abe4ef70ad933315eefb230f55763d450b567a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 25 Jul 2025 09:12:15 +0200 Subject: [PATCH 33/80] use `ExtractIf` --- clippy_lints/src/unnested_or_patterns.rs | 27 +++++++----------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 4621c22d6b532..f49ed6fdaf5e7 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -10,6 +10,7 @@ use rustc_ast::mut_visit::*; use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind}; use rustc_ast_pretty::pprust; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_data_structures::thinvec::ExtractIf; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::impl_lint_pass; @@ -415,26 +416,14 @@ fn drain_matching( let mut tail_or = ThinVec::new(); let mut idx = 0; - // If `ThinVec` had the `drain_filter` method, this loop could be rewritten - // like so: - // - // for pat in alternatives.drain_filter(|p| { - // // Check if we should extract, but only if `idx >= start`. - // idx += 1; - // idx > start && predicate(&p.kind) - // }) { - // tail_or.push(extract(pat.into_inner().kind)); - // } - let mut i = 0; - while i < alternatives.len() { - idx += 1; + // FIXME: once `thin-vec` releases a new version, change this to `alternatives.extract_if()` + // See https://github.com/mozilla/thin-vec/issues/77 + for pat in ExtractIf::new(alternatives, |p| { // Check if we should extract, but only if `idx >= start`. - if idx > start && predicate(&alternatives[i].kind) { - let pat = alternatives.remove(i); - tail_or.push(extract(pat.kind)); - } else { - i += 1; - } + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.kind)); } tail_or From adf38c0b6265b7d5cb167761612707ce0a6e990c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 25 Jul 2025 13:36:03 +0200 Subject: [PATCH 34/80] match on `kind` directly this destructuring is.. a bit weird --- clippy_lints/src/unnested_or_patterns.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index f49ed6fdaf5e7..32418e8616166 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -385,11 +385,11 @@ fn take_pat(from: &mut Pat) -> Pat { /// in `tail_or` if there are any and return if there were. fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec) -> bool { fn extend(target: &mut Pat, mut tail_or: ThinVec) { - match target { + match &mut target.kind { // On an existing or-pattern in the target, append to it. - Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), + Or(ps) => ps.append(&mut tail_or), // Otherwise convert the target to an or-pattern. - target => { + _ => { let mut init_or = thin_vec![take_pat(target)]; init_or.append(&mut tail_or); target.kind = Or(init_or); From 31b7ddc93935b4d6fe675e0fb748840d8917be70 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 25 Jul 2025 18:25:04 +0200 Subject: [PATCH 35/80] use `if let` --- clippy_lints/src/unnested_or_patterns.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 32418e8616166..eb35ed173d835 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -385,15 +385,14 @@ fn take_pat(from: &mut Pat) -> Pat { /// in `tail_or` if there are any and return if there were. fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec) -> bool { fn extend(target: &mut Pat, mut tail_or: ThinVec) { - match &mut target.kind { - // On an existing or-pattern in the target, append to it. - Or(ps) => ps.append(&mut tail_or), - // Otherwise convert the target to an or-pattern. - _ => { - let mut init_or = thin_vec![take_pat(target)]; - init_or.append(&mut tail_or); - target.kind = Or(init_or); - }, + // On an existing or-pattern in the target, append to it, + // otherwise convert the target to an or-pattern. + if let Or(ps) = &mut target.kind { + ps.append(&mut tail_or); + } else { + let mut init_or = thin_vec![take_pat(target)]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); } } From 44e3b16e7cae2df7c945e565238ec4fe551465e2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 28 Jul 2025 16:02:56 +0200 Subject: [PATCH 36/80] rm some `Box`es --- clippy_lints/src/unnested_or_patterns.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index eb35ed173d835..66a1dfc1f486e 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -99,7 +99,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { return; } - let mut pat = Box::new(pat.clone()); + let mut pat = pat.clone(); // Nix all the paren patterns everywhere so that they aren't in our way. remove_all_parens(&mut pat); @@ -121,7 +121,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { } /// Remove all `(p)` patterns in `pat`. -fn remove_all_parens(pat: &mut Box) { +fn remove_all_parens(pat: &mut Pat) { #[derive(Default)] struct Visitor { /// If is not in the outer most pattern. This is needed to avoid removing the outermost @@ -144,7 +144,7 @@ fn remove_all_parens(pat: &mut Box) { } /// Insert parens where necessary according to Rust's precedence rules for patterns. -fn insert_necessary_parens(pat: &mut Box) { +fn insert_necessary_parens(pat: &mut Pat) { struct Visitor; impl MutVisitor for Visitor { fn visit_pat(&mut self, pat: &mut Pat) { @@ -164,7 +164,7 @@ fn insert_necessary_parens(pat: &mut Box) { /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. -fn unnest_or_patterns(pat: &mut Box) -> bool { +fn unnest_or_patterns(pat: &mut Pat) -> bool { struct Visitor { changed: bool, } From e7b4ae869efb17ac817ed0eb3bd15aeebf5242c3 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 12 Oct 2025 14:50:02 +0200 Subject: [PATCH 37/80] clean-up --- clippy_lints/src/ptr.rs | 7 ++----- tests/ui/cmp_null.fixed | 3 +-- tests/ui/cmp_null.rs | 3 +-- tests/ui/cmp_null.stderr | 12 ++++++------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 8446b6fbbea57..134034abf6cc2 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { if let TraitItemKind::Fn(sig, trait_method) = &item.kind { if matches!(trait_method, TraitFn::Provided(_)) { - // Handled by check body. + // Handled by `check_body`. return; } @@ -393,10 +393,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( hir_tys: &'tcx [hir::Ty<'tcx>], params: &'tcx [Param<'tcx>], ) -> impl Iterator> + 'cx { - fn_sig - .inputs() - .iter() - .zip(hir_tys.iter()) + iter::zip(fn_sig.inputs(), hir_tys) .enumerate() .filter_map(move |(i, (ty, hir_ty))| { if let ty::Ref(_, ty, mutability) = *ty.kind() diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 04b8ec50160b3..c12279cf12e63 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m.is_null() { //~^ cmp_null diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 6f7762e6ae831..2771a16e00c52 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m == ptr::null_mut() { //~^ cmp_null diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 8a75b05011193..381747cb3c657 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:9:8 + --> tests/ui/cmp_null.rs:8:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,31 +8,31 @@ LL | if p == ptr::null() { = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:14:8 + --> tests/ui/cmp_null.rs:13:8 | LL | if ptr::null() == p { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:22:8 + --> tests/ui/cmp_null.rs:21:8 | LL | if m == ptr::null_mut() { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:27:8 + --> tests/ui/cmp_null.rs:26:8 | LL | if ptr::null_mut() == m { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:33:13 + --> tests/ui/cmp_null.rs:32:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:39:19 + --> tests/ui/cmp_null.rs:38:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` From 54a84c1d769a3d01edfaa44f86e895a857982c32 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 31 Oct 2025 22:21:19 +0100 Subject: [PATCH 38/80] move `ptr.rs` to `ptr/mod.rs` --- clippy_lints/src/{ptr.rs => ptr/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clippy_lints/src/{ptr.rs => ptr/mod.rs} (100%) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr/mod.rs similarity index 100% rename from clippy_lints/src/ptr.rs rename to clippy_lints/src/ptr/mod.rs From ccd51307df285419226ec9e434887ee431489f82 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 31 Oct 2025 22:22:46 +0100 Subject: [PATCH 39/80] extract each lint into its own module --- clippy_lints/src/ptr/cmp_null.rs | 49 ++ clippy_lints/src/ptr/mod.rs | 655 +-------------------------- clippy_lints/src/ptr/mut_from_ref.rs | 75 +++ clippy_lints/src/ptr/ptr_arg.rs | 475 +++++++++++++++++++ clippy_lints/src/ptr/ptr_eq.rs | 87 ++++ 5 files changed, 701 insertions(+), 640 deletions(-) create mode 100644 clippy_lints/src/ptr/cmp_null.rs create mode 100644 clippy_lints/src/ptr/mut_from_ref.rs create mode 100644 clippy_lints/src/ptr/ptr_arg.rs create mode 100644 clippy_lints/src/ptr/ptr_eq.rs diff --git a/clippy_lints/src/ptr/cmp_null.rs b/clippy_lints/src/ptr/cmp_null.rs new file mode 100644 index 0000000000000..905b48e6d1d49 --- /dev/null +++ b/clippy_lints/src/ptr/cmp_null.rs @@ -0,0 +1,49 @@ +use super::CMP_NULL; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + l: &Expr<'_>, + r: &Expr<'_>, +) -> bool { + let non_null_path_snippet = match ( + is_lint_allowed(cx, CMP_NULL, expr.hir_id), + is_null_path(cx, l), + is_null_path(cx, r), + ) { + (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), + (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), + _ => return false, + }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + + span_lint_and_sugg( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + "try", + format!("{invert}{non_null_path_snippet}.is_null()",), + Applicability::MachineApplicable, + ); + true +} + +fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(pathexp, []) = expr.kind { + matches!( + pathexp.basic_res().opt_diag_name(cx), + Some(sym::ptr_null | sym::ptr_null_mut) + ) + } else { + false + } +} diff --git a/clippy_lints/src/ptr/mod.rs b/clippy_lints/src/ptr/mod.rs index 134034abf6cc2..6b2647e7b0a2f 100644 --- a/clippy_lints/src/ptr/mod.rs +++ b/clippy_lints/src/ptr/mod.rs @@ -1,31 +1,11 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::res::{MaybeDef, MaybeResPath}; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::sugg::Sugg; -use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, std_or_core, sym}; -use hir::LifetimeKind; -use rustc_abi::ExternAbi; -use rustc_errors::{Applicability, MultiSpan}; -use rustc_hir::hir_id::{HirId, HirIdMap}; -use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{ - self as hir, AnonConst, BinOpKind, BindingMode, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg, ImplItemKind, - ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, TyKind, -}; -use rustc_infer::infer::TyCtxtInferExt; -use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, ImplItemKind, ItemKind, Node, TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::symbol::Symbol; -use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; -use std::{fmt, iter}; -use crate::vec::is_allowed_vec_method; +mod cmp_null; +mod mut_from_ref; +mod ptr_arg; +mod ptr_eq; declare_clippy_lint! { /// ### What it does @@ -165,30 +145,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { return; } - check_mut_from_ref(cx, sig, None); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - for arg in check_fn_args( - cx, - cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(), - sig.decl.inputs, - &[], - ) - .filter(|arg| arg.mutability() == Mutability::Not) - { - span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { - diag.span_suggestion( - arg.span, - "change this to", - format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), - Applicability::Unspecified, - ); - }); - } + mut_from_ref::check(cx, sig, None); + ptr_arg::check_trait_item(cx, item.owner_id, sig); } } @@ -224,604 +182,21 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { _ => return, }; - check_mut_from_ref(cx, sig, Some(body)); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); - let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) - .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) - .collect(); - let results = check_ptr_arg_usage(cx, body, &lint_args); - - for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { - span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { - diag.multipart_suggestion( - "change this to", - iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) - .chain(result.replacements.iter().map(|r| { - ( - r.expr_span, - format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), - ) - })) - .collect(), - Applicability::Unspecified, - ); - }); - } + mut_from_ref::check(cx, sig, Some(body)); + ptr_arg::check_body(cx, body, item_id, sig, is_trait_item); } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(op, l, r) = expr.kind && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) { - let non_null_path_snippet = match ( - is_lint_allowed(cx, CMP_NULL, expr.hir_id), - is_null_path(cx, l), - is_null_path(cx, r), - ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), - _ => return check_ptr_eq(cx, expr, op.node, l, r), - }; - let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; - - span_lint_and_sugg( - cx, - CMP_NULL, - expr.span, - "comparing with null is better expressed by the `.is_null()` method", - "try", - format!("{invert}{non_null_path_snippet}.is_null()",), - Applicability::MachineApplicable, - ); - } - } -} - -#[derive(Default)] -struct PtrArgResult { - skip: bool, - replacements: Vec, -} - -struct PtrArgReplacement { - expr_span: Span, - self_span: Span, - replacement: &'static str, -} - -struct PtrArg<'tcx> { - idx: usize, - emission_id: HirId, - span: Span, - ty_name: Symbol, - method_renames: &'static [(Symbol, &'static str)], - ref_prefix: RefPrefix, - deref_ty: DerefTy<'tcx>, -} -impl PtrArg<'_> { - fn build_msg(&self) -> String { - format!( - "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do", - self.ref_prefix.mutability.prefix_str(), - self.ty_name, - self.ref_prefix.mutability.prefix_str(), - self.deref_ty.argless_str(), - ) - } - - fn mutability(&self) -> Mutability { - self.ref_prefix.mutability - } -} - -struct RefPrefix { - lt: Lifetime, - mutability: Mutability, -} -impl fmt::Display for RefPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; - f.write_char('&')?; - if !self.lt.is_anonymous() { - self.lt.ident.fmt(f)?; - f.write_char(' ')?; - } - f.write_str(self.mutability.prefix_str()) - } -} - -struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>); -impl fmt::Display for DerefTyDisplay<'_, '_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::fmt::Write; - match self.1 { - DerefTy::Str => f.write_str("str"), - DerefTy::Path => f.write_str("Path"), - DerefTy::Slice(hir_ty, ty) => { - f.write_char('[')?; - match hir_ty.and_then(|s| s.get_source_text(self.0)) { - Some(s) => f.write_str(&s)?, - None => ty.fmt(f)?, - } - f.write_char(']') - }, - } - } -} - -enum DerefTy<'tcx> { - Str, - Path, - Slice(Option, Ty<'tcx>), -} -impl<'tcx> DerefTy<'tcx> { - fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> { - match *self { - Self::Str => cx.tcx.types.str_, - Self::Path => Ty::new_adt( - cx.tcx, - cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()), - List::empty(), - ), - Self::Slice(_, ty) => Ty::new_slice(cx.tcx, ty), - } - } - - fn argless_str(&self) -> &'static str { - match *self { - Self::Str => "str", - Self::Path => "Path", - Self::Slice(..) => "[_]", - } - } - - fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> { - DerefTyDisplay(cx, self) - } -} - -fn check_fn_args<'cx, 'tcx: 'cx>( - cx: &'cx LateContext<'tcx>, - fn_sig: ty::FnSig<'tcx>, - hir_tys: &'tcx [hir::Ty<'tcx>], - params: &'tcx [Param<'tcx>], -) -> impl Iterator> + 'cx { - iter::zip(fn_sig.inputs(), hir_tys) - .enumerate() - .filter_map(move |(i, (ty, hir_ty))| { - if let ty::Ref(_, ty, mutability) = *ty.kind() - && let ty::Adt(adt, args) = *ty.kind() - && let TyKind::Ref(lt, ref ty) = hir_ty.kind - && let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind - // Check that the name as typed matches the actual name of the type. - // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec` - && let [.., name] = path.segments - && cx.tcx.item_name(adt.did()) == name.ident.name - { - let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); - let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Vec) => ( - [(sym::clone, ".to_owned()")].as_slice(), - DerefTy::Slice( - if let Some(name_args) = name.args - && let [GenericArg::Type(ty), ..] = name_args.args - { - Some(ty.span) - } else { - None - }, - args.type_at(0), - ), - ), - _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( - [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), - DerefTy::Str, - ), - Some(sym::PathBuf) => ( - [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), - DerefTy::Path, - ), - Some(sym::Cow) if mutability == Mutability::Not => { - if let Some(name_args) = name.args - && let [GenericArg::Lifetime(lifetime), ty] = name_args.args - { - if let LifetimeKind::Param(param_def_id) = lifetime.kind - && !lifetime.is_anonymous() - && fn_sig - .output() - .walk() - .filter_map(ty::GenericArg::as_region) - .filter_map(|lifetime| match lifetime.kind() { - ty::ReEarlyParam(r) => Some( - cx.tcx - .generics_of(cx.tcx.parent(param_def_id.to_def_id())) - .region_param(r, cx.tcx) - .def_id, - ), - ty::ReBound(_, r) => r.kind.get_id(), - ty::ReLateParam(r) => r.kind.get_id(), - ty::ReStatic - | ty::ReVar(_) - | ty::RePlaceholder(_) - | ty::ReErased - | ty::ReError(_) => None, - }) - .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) - { - // `&Cow<'a, T>` when the return type uses 'a is okay - return None; - } - - span_lint_hir_and_then( - cx, - PTR_ARG, - emission_id, - hir_ty.span, - "using a reference to `Cow` is not recommended", - |diag| { - diag.span_suggestion( - hir_ty.span, - "change this to", - match ty.span().get_source_text(cx) { - Some(s) => format!("&{}{s}", mutability.prefix_str()), - None => format!("&{}{}", mutability.prefix_str(), args.type_at(1)), - }, - Applicability::Unspecified, - ); - }, - ); - } - return None; - }, - _ => return None, - }; - return Some(PtrArg { - idx: i, - emission_id, - span: hir_ty.span, - ty_name: name.ident.name, - method_renames, - ref_prefix: RefPrefix { lt: *lt, mutability }, - deref_ty, - }); - } - None - }) -} - -fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { - let FnRetTy::Return(ty) = sig.decl.output else { return }; - for (out, mutability, out_span) in get_lifetimes(ty) { - if mutability != Some(Mutability::Mut) { - continue; - } - let out_region = cx.tcx.named_bound_var(out.hir_id); - // `None` if one of the types contains `&'a mut T` or `T<'a>`. - // Else, contains all the locations of `&'a T` types. - let args_immut_refs: Option> = sig - .decl - .inputs - .iter() - .flat_map(get_lifetimes) - .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) - .collect(); - if let Some(args_immut_refs) = args_immut_refs - && !args_immut_refs.is_empty() - && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) - { - span_lint_and_then( - cx, - MUT_FROM_REF, - out_span, - "mutable borrow from immutable input(s)", - |diag| { - let ms = MultiSpan::from_spans(args_immut_refs); - diag.span_note(ms, "immutable borrow here"); - }, - ); - } - } -} - -#[expect(clippy::too_many_lines)] -fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { - struct V<'cx, 'tcx> { - cx: &'cx LateContext<'tcx>, - /// Map from a local id to which argument it came from (index into `Self::args` and - /// `Self::results`) - bindings: HirIdMap, - /// The arguments being checked. - args: &'cx [PtrArg<'tcx>], - /// The results for each argument (len should match args.len) - results: Vec, - /// The number of arguments which can't be linted. Used to return early. - skip_count: usize, - } - impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } - - fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} - - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.skip_count == self.args.len() { - return; + #[expect( + clippy::collapsible_if, + reason = "the outer `if`s check the HIR, the inner ones run lints" + )] + if !cmp_null::check(cx, expr, op.node, l, r) { + ptr_eq::check(cx, op.node, l, r, expr.span); } - - // Check if this is local we care about - let Some(&args_idx) = e.res_local_id().and_then(|id| self.bindings.get(&id)) else { - return walk_expr(self, e); - }; - let args = &self.args[args_idx]; - let result = &mut self.results[args_idx]; - - // Helper function to handle early returns. - let mut set_skip_flag = || { - if !result.skip { - self.skip_count += 1; - } - result.skip = true; - }; - - match get_expr_use_or_unification_node(self.cx.tcx, e) { - Some((Node::Stmt(_), _)) => (), - Some((Node::LetStmt(l), _)) => { - // Only trace simple bindings. e.g `let x = y;` - 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(); - } - }, - Some((Node::Expr(use_expr), child_id)) => { - if let ExprKind::Index(e, ..) = use_expr.kind - && e.hir_id == child_id - { - // Indexing works with both owned and its dereferenced type - return; - } - - if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind - && receiver.hir_id == child_id - { - let name = name.ident.name; - - // Check if the method can be renamed. - if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { - result.replacements.push(PtrArgReplacement { - expr_span: use_expr.span, - self_span: receiver.span, - replacement, - }); - return; - } - - // Some methods exist on both `[T]` and `Vec`, such as `len`, where the receiver type - // doesn't coerce to a slice and our adjusted type check below isn't enough, - // but it would still be valid to call with a slice - if is_allowed_vec_method(use_expr) { - return; - } - } - - // If the expression's type gets adjusted down to the deref type, we might as - // well have started with that deref type -- the lint should fire - let deref_ty = args.deref_ty.ty(self.cx); - let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs(); - if adjusted_ty == deref_ty { - return; - } - - // If the expression's type is constrained by `dyn Trait`, see if the deref - // type implements the trait(s) as well, and if so, the lint should fire - if let ty::Dynamic(preds, ..) = adjusted_ty.kind() - && matches_preds(self.cx, deref_ty, preds) - { - return; - } - - set_skip_flag(); - }, - _ => set_skip_flag(), - } - } - } - - let mut skip_count = 0; - let mut results = args.iter().map(|_| PtrArgResult::default()).collect::>(); - let mut v = V { - cx, - bindings: args - .iter() - .enumerate() - .filter_map(|(i, arg)| { - let param = &body.params[arg.idx]; - match param.pat.kind { - 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)) - }, - _ => { - skip_count += 1; - results[i].skip = true; - None - }, - } - }) - .collect(), - args, - results, - skip_count, - }; - v.visit_expr(body.value); - v.results -} - -fn matches_preds<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - preds: &'tcx [ty::PolyExistentialPredicate<'tcx>], -) -> bool { - let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); - preds - .iter() - .all(|&p| match cx.tcx.instantiate_bound_regions_with_erased(p) { - ExistentialPredicate::Trait(p) => infcx - .type_implements_trait(p.def_id, [ty.into()].into_iter().chain(p.args.iter()), cx.param_env) - .must_apply_modulo_regions(), - ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new( - cx.tcx, - ObligationCause::dummy(), - cx.param_env, - cx.tcx - .mk_predicate(Binder::dummy(PredicateKind::Clause(ClauseKind::Projection( - p.with_self_ty(cx.tcx, ty), - )))), - )), - ExistentialPredicate::AutoTrait(p) => infcx - .type_implements_trait(p, [ty], cx.param_env) - .must_apply_modulo_regions(), - }) -} - -struct LifetimeVisitor<'tcx> { - result: Vec<(&'tcx Lifetime, Option, Span)>, -} - -impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { - fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { - if let TyKind::Ref(lt, ref m) = ty.kind { - self.result.push((lt, Some(m.mutbl), ty.span)); - } - hir::intravisit::walk_ty(self, ty); - } - - fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { - if let GenericArg::Lifetime(lt) = generic_arg { - self.result.push((lt, None, generic_arg.span())); } - hir::intravisit::walk_generic_arg(self, generic_arg); - } -} - -/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. -/// -/// The second field of the vector's elements indicate if the lifetime is attached to a -/// shared reference, a mutable reference, or neither. -fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { - use hir::intravisit::VisitorExt as _; - - let mut visitor = LifetimeVisitor { result: Vec::new() }; - visitor.visit_ty_unambig(ty); - visitor.result -} - -fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(pathexp, []) = expr.kind { - matches!( - pathexp.basic_res().opt_diag_name(cx), - Some(sym::ptr_null | sym::ptr_null_mut) - ) - } else { - false - } -} - -fn check_ptr_eq<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - op: BinOpKind, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, -) { - if expr.span.from_expansion() { - return; - } - - // Remove one level of usize conversion if any - let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { - (Some(lhs), Some(rhs)) => (lhs, rhs, true), - _ => (left, right, false), - }; - - // This lint concerns raw pointers - let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); - if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { - return; - } - - let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = - (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); - - if !(usize_peeled || left_casts_peeled || right_casts_peeled) { - return; - } - - let mut app = Applicability::MachineApplicable; - let left_snip = Sugg::hir_with_context(cx, left_var, expr.span.ctxt(), "_", &mut app); - let right_snip = Sugg::hir_with_context(cx, right_var, expr.span.ctxt(), "_", &mut app); - { - let Some(top_crate) = std_or_core(cx) else { return }; - let invert = if op == BinOpKind::Eq { "" } else { "!" }; - span_lint_and_sugg( - cx, - PTR_EQ, - expr.span, - format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), - "try", - format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), - app, - ); - } -} - -// If the given expression is a cast to a usize, return the lhs of the cast -// E.g., `foo as *const _ as usize` returns `foo as *const _`. -fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if !cast_expr.span.from_expansion() - && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize - && let ExprKind::Cast(expr, _) = cast_expr.kind - { - Some(expr) - } else { - None - } -} - -// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been -// peeled or not. -fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { - if !expr.span.from_expansion() - && let ExprKind::Cast(inner, _) = expr.kind - && let ty::RawPtr(target_ty, _) = expr_ty.kind() - && let inner_ty = cx.typeck_results().expr_ty(inner) - && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() - && target_ty == inner_target_ty - { - (peel_raw_casts(cx, inner, inner_ty).0, true) - } else { - (expr, false) } } diff --git a/clippy_lints/src/ptr/mut_from_ref.rs b/clippy_lints/src/ptr/mut_from_ref.rs new file mode 100644 index 0000000000000..30d708f436b40 --- /dev/null +++ b/clippy_lints/src/ptr/mut_from_ref.rs @@ -0,0 +1,75 @@ +use super::MUT_FROM_REF; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::visitors::contains_unsafe_block; +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Body, FnRetTy, FnSig, GenericArg, Lifetime, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { + let FnRetTy::Return(ty) = sig.decl.output else { return }; + for (out, mutability, out_span) in get_lifetimes(ty) { + if mutability != Some(Mutability::Mut) { + continue; + } + let out_region = cx.tcx.named_bound_var(out.hir_id); + // `None` if one of the types contains `&'a mut T` or `T<'a>`. + // Else, contains all the locations of `&'a T` types. + let args_immut_refs: Option> = sig + .decl + .inputs + .iter() + .flat_map(get_lifetimes) + .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) + .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) + .collect(); + if let Some(args_immut_refs) = args_immut_refs + && !args_immut_refs.is_empty() + && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) + { + span_lint_and_then( + cx, + MUT_FROM_REF, + out_span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(args_immut_refs); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +struct LifetimeVisitor<'tcx> { + result: Vec<(&'tcx Lifetime, Option, Span)>, +} + +impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let TyKind::Ref(lt, ref m) = ty.kind { + self.result.push((lt, Some(m.mutbl), ty.span)); + } + hir::intravisit::walk_ty(self, ty); + } + + fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { + if let GenericArg::Lifetime(lt) = generic_arg { + self.result.push((lt, None, generic_arg.span())); + } + hir::intravisit::walk_generic_arg(self, generic_arg); + } +} + +/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. +/// +/// The second field of the vector's elements indicate if the lifetime is attached to a +/// shared reference, a mutable reference, or neither. +fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { + use hir::intravisit::VisitorExt as _; + + let mut visitor = LifetimeVisitor { result: Vec::new() }; + visitor.visit_ty_unambig(ty); + visitor.result +} diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs new file mode 100644 index 0000000000000..fd9230f00a8bd --- /dev/null +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -0,0 +1,475 @@ +use super::PTR_ARG; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::MaybeResPath; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, sym}; +use hir::LifetimeKind; +use rustc_abi::ExternAbi; +use rustc_errors::Applicability; +use rustc_hir::hir_id::{HirId, HirIdMap}; +use rustc_hir::intravisit::{Visitor, walk_expr}; +use rustc_hir::{ + self as hir, AnonConst, BindingMode, Body, Expr, ExprKind, FnSig, GenericArg, Lifetime, Mutability, Node, OwnerId, + Param, PatKind, QPath, TyKind, +}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; +use rustc_span::Span; +use rustc_span::symbol::Symbol; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; +use std::{fmt, iter}; + +use crate::vec::is_allowed_vec_method; + +pub(super) fn check_body<'tcx>( + cx: &LateContext<'tcx>, + body: &Body<'tcx>, + item_id: OwnerId, + sig: &FnSig<'tcx>, + is_trait_item: bool, +) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + + let decl = sig.decl; + let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); + let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) + .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) + .collect(); + let results = check_ptr_arg_usage(cx, body, &lint_args); + + for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { + span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { + diag.multipart_suggestion( + "change this to", + iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) + .chain(result.replacements.iter().map(|r| { + ( + r.expr_span, + format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), + ) + })) + .collect(), + Applicability::Unspecified, + ); + }); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, sig: &FnSig<'tcx>) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + + for arg in check_fn_args( + cx, + cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(), + sig.decl.inputs, + &[], + ) + .filter(|arg| arg.mutability() == Mutability::Not) + { + span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { + diag.span_suggestion( + arg.span, + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, + ); + }); + } +} + +#[derive(Default)] +struct PtrArgResult { + skip: bool, + replacements: Vec, +} + +struct PtrArgReplacement { + expr_span: Span, + self_span: Span, + replacement: &'static str, +} + +struct PtrArg<'tcx> { + idx: usize, + emission_id: HirId, + span: Span, + ty_name: Symbol, + method_renames: &'static [(Symbol, &'static str)], + ref_prefix: RefPrefix, + deref_ty: DerefTy<'tcx>, +} +impl PtrArg<'_> { + fn build_msg(&self) -> String { + format!( + "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do", + self.ref_prefix.mutability.prefix_str(), + self.ty_name, + self.ref_prefix.mutability.prefix_str(), + self.deref_ty.argless_str(), + ) + } + + fn mutability(&self) -> Mutability { + self.ref_prefix.mutability + } +} + +struct RefPrefix { + lt: Lifetime, + mutability: Mutability, +} +impl fmt::Display for RefPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; + f.write_char('&')?; + if !self.lt.is_anonymous() { + self.lt.ident.fmt(f)?; + f.write_char(' ')?; + } + f.write_str(self.mutability.prefix_str()) + } +} + +struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>); +impl fmt::Display for DerefTyDisplay<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write; + match self.1 { + DerefTy::Str => f.write_str("str"), + DerefTy::Path => f.write_str("Path"), + DerefTy::Slice(hir_ty, ty) => { + f.write_char('[')?; + match hir_ty.and_then(|s| s.get_source_text(self.0)) { + Some(s) => f.write_str(&s)?, + None => ty.fmt(f)?, + } + f.write_char(']') + }, + } + } +} + +enum DerefTy<'tcx> { + Str, + Path, + Slice(Option, Ty<'tcx>), +} +impl<'tcx> DerefTy<'tcx> { + fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> { + match *self { + Self::Str => cx.tcx.types.str_, + Self::Path => Ty::new_adt( + cx.tcx, + cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()), + List::empty(), + ), + Self::Slice(_, ty) => Ty::new_slice(cx.tcx, ty), + } + } + + fn argless_str(&self) -> &'static str { + match *self { + Self::Str => "str", + Self::Path => "Path", + Self::Slice(..) => "[_]", + } + } + + fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> { + DerefTyDisplay(cx, self) + } +} + +fn check_fn_args<'cx, 'tcx: 'cx>( + cx: &'cx LateContext<'tcx>, + fn_sig: ty::FnSig<'tcx>, + hir_tys: &'tcx [hir::Ty<'tcx>], + params: &'tcx [Param<'tcx>], +) -> impl Iterator> + 'cx { + iter::zip(fn_sig.inputs(), hir_tys) + .enumerate() + .filter_map(move |(i, (ty, hir_ty))| { + if let ty::Ref(_, ty, mutability) = *ty.kind() + && let ty::Adt(adt, args) = *ty.kind() + && let TyKind::Ref(lt, ref ty) = hir_ty.kind + && let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind + // Check that the name as typed matches the actual name of the type. + // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec` + && let [.., name] = path.segments + && cx.tcx.item_name(adt.did()) == name.ident.name + { + let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); + let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Vec) => ( + [(sym::clone, ".to_owned()")].as_slice(), + DerefTy::Slice( + if let Some(name_args) = name.args + && let [GenericArg::Type(ty), ..] = name_args.args + { + Some(ty.span) + } else { + None + }, + args.type_at(0), + ), + ), + _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( + [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), + DerefTy::Str, + ), + Some(sym::PathBuf) => ( + [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), + DerefTy::Path, + ), + Some(sym::Cow) if mutability == Mutability::Not => { + if let Some(name_args) = name.args + && let [GenericArg::Lifetime(lifetime), ty] = name_args.args + { + if let LifetimeKind::Param(param_def_id) = lifetime.kind + && !lifetime.is_anonymous() + && fn_sig + .output() + .walk() + .filter_map(ty::GenericArg::as_region) + .filter_map(|lifetime| match lifetime.kind() { + ty::ReEarlyParam(r) => Some( + cx.tcx + .generics_of(cx.tcx.parent(param_def_id.to_def_id())) + .region_param(r, cx.tcx) + .def_id, + ), + ty::ReBound(_, r) => r.kind.get_id(), + ty::ReLateParam(r) => r.kind.get_id(), + ty::ReStatic + | ty::ReVar(_) + | ty::RePlaceholder(_) + | ty::ReErased + | ty::ReError(_) => None, + }) + .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) + { + // `&Cow<'a, T>` when the return type uses 'a is okay + return None; + } + + span_lint_hir_and_then( + cx, + PTR_ARG, + emission_id, + hir_ty.span, + "using a reference to `Cow` is not recommended", + |diag| { + diag.span_suggestion( + hir_ty.span, + "change this to", + match ty.span().get_source_text(cx) { + Some(s) => format!("&{}{s}", mutability.prefix_str()), + None => format!("&{}{}", mutability.prefix_str(), args.type_at(1)), + }, + Applicability::Unspecified, + ); + }, + ); + } + return None; + }, + _ => return None, + }; + return Some(PtrArg { + idx: i, + emission_id, + span: hir_ty.span, + ty_name: name.ident.name, + method_renames, + ref_prefix: RefPrefix { lt: *lt, mutability }, + deref_ty, + }); + } + None + }) +} + +#[expect(clippy::too_many_lines)] +fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + /// Map from a local id to which argument it came from (index into `Self::args` and + /// `Self::results`) + bindings: HirIdMap, + /// The arguments being checked. + args: &'cx [PtrArg<'tcx>], + /// The results for each argument (len should match args.len) + results: Vec, + /// The number of arguments which can't be linted. Used to return early. + skip_count: usize, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } + + fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.skip_count == self.args.len() { + return; + } + + // Check if this is local we care about + let Some(&args_idx) = e.res_local_id().and_then(|id| self.bindings.get(&id)) else { + return walk_expr(self, e); + }; + let args = &self.args[args_idx]; + let result = &mut self.results[args_idx]; + + // Helper function to handle early returns. + let mut set_skip_flag = || { + if !result.skip { + self.skip_count += 1; + } + result.skip = true; + }; + + match get_expr_use_or_unification_node(self.cx.tcx, e) { + Some((Node::Stmt(_), _)) => (), + Some((Node::LetStmt(l), _)) => { + // Only trace simple bindings. e.g `let x = y;` + 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(); + } + }, + Some((Node::Expr(use_expr), child_id)) => { + if let ExprKind::Index(e, ..) = use_expr.kind + && e.hir_id == child_id + { + // Indexing works with both owned and its dereferenced type + return; + } + + if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind + && receiver.hir_id == child_id + { + let name = name.ident.name; + + // Check if the method can be renamed. + if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { + result.replacements.push(PtrArgReplacement { + expr_span: use_expr.span, + self_span: receiver.span, + replacement, + }); + return; + } + + // Some methods exist on both `[T]` and `Vec`, such as `len`, where the receiver type + // doesn't coerce to a slice and our adjusted type check below isn't enough, + // but it would still be valid to call with a slice + if is_allowed_vec_method(use_expr) { + return; + } + } + + // If the expression's type gets adjusted down to the deref type, we might as + // well have started with that deref type -- the lint should fire + let deref_ty = args.deref_ty.ty(self.cx); + let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs(); + if adjusted_ty == deref_ty { + return; + } + + // If the expression's type is constrained by `dyn Trait`, see if the deref + // type implements the trait(s) as well, and if so, the lint should fire + if let ty::Dynamic(preds, ..) = adjusted_ty.kind() + && matches_preds(self.cx, deref_ty, preds) + { + return; + } + + set_skip_flag(); + }, + _ => set_skip_flag(), + } + } + } + + let mut skip_count = 0; + let mut results = args.iter().map(|_| PtrArgResult::default()).collect::>(); + let mut v = V { + cx, + bindings: args + .iter() + .enumerate() + .filter_map(|(i, arg)| { + let param = &body.params[arg.idx]; + match param.pat.kind { + 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)) + }, + _ => { + skip_count += 1; + results[i].skip = true; + None + }, + } + }) + .collect(), + args, + results, + skip_count, + }; + v.visit_expr(body.value); + v.results +} + +fn matches_preds<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + preds: &'tcx [ty::PolyExistentialPredicate<'tcx>], +) -> bool { + let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); + preds + .iter() + .all(|&p| match cx.tcx.instantiate_bound_regions_with_erased(p) { + ExistentialPredicate::Trait(p) => infcx + .type_implements_trait(p.def_id, [ty.into()].into_iter().chain(p.args.iter()), cx.param_env) + .must_apply_modulo_regions(), + ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new( + cx.tcx, + ObligationCause::dummy(), + cx.param_env, + cx.tcx + .mk_predicate(Binder::dummy(PredicateKind::Clause(ClauseKind::Projection( + p.with_self_ty(cx.tcx, ty), + )))), + )), + ExistentialPredicate::AutoTrait(p) => infcx + .type_implements_trait(p, [ty], cx.param_env) + .must_apply_modulo_regions(), + }) +} diff --git a/clippy_lints/src/ptr/ptr_eq.rs b/clippy_lints/src/ptr/ptr_eq.rs new file mode 100644 index 0000000000000..c982bb1ffbc5f --- /dev/null +++ b/clippy_lints/src/ptr/ptr_eq.rs @@ -0,0 +1,87 @@ +use super::PTR_EQ; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::std_or_core; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + span: Span, +) { + if span.from_expansion() { + return; + } + + // Remove one level of usize conversion if any + let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs, true), + _ => (left, right, false), + }; + + // This lint concerns raw pointers + let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); + if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { + return; + } + + let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = + (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); + + if !(usize_peeled || left_casts_peeled || right_casts_peeled) { + return; + } + + let mut app = Applicability::MachineApplicable; + let ctxt = span.ctxt(); + let left_snip = Sugg::hir_with_context(cx, left_var, ctxt, "_", &mut app); + let right_snip = Sugg::hir_with_context(cx, right_var, ctxt, "_", &mut app); + { + let Some(top_crate) = std_or_core(cx) else { return }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + span_lint_and_sugg( + cx, + PTR_EQ, + span, + format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), + "try", + format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), + app, + ); + } +} + +// If the given expression is a cast to a usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if !cast_expr.span.from_expansion() + && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize + && let ExprKind::Cast(expr, _) = cast_expr.kind + { + Some(expr) + } else { + None + } +} + +// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been +// peeled or not. +fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { + if !expr.span.from_expansion() + && let ExprKind::Cast(inner, _) = expr.kind + && let ty::RawPtr(target_ty, _) = expr_ty.kind() + && let inner_ty = cx.typeck_results().expr_ty(inner) + && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() + && target_ty == inner_target_ty + { + (peel_raw_casts(cx, inner, inner_ty).0, true) + } else { + (expr, false) + } +} From 6bc8d214b4955a738a9bf378e7c9113571cdcc14 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 1 Nov 2025 18:01:28 +0100 Subject: [PATCH 40/80] fix(let_and_return): disallow _any_ text between let and return --- clippy_lints/src/returns/let_and_return.rs | 4 ++-- tests/ui/let_and_return.edition2021.fixed | 10 ++++++++++ tests/ui/let_and_return.edition2024.fixed | 10 ++++++++++ tests/ui/let_and_return.rs | 10 ++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs index f54a26a77620a..0a00981e15bed 100644 --- a/clippy_lints/src/returns/let_and_return.rs +++ b/clippy_lints/src/returns/let_and_return.rs @@ -3,7 +3,7 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_cfg}; +use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, PatKind, StmtKind}; @@ -27,7 +27,7 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) && !initexpr.span.in_external_macro(cx.sess().source_map()) && !retexpr.span.in_external_macro(cx.sess().source_map()) && !local.span.from_expansion() - && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) + && !span_contains_non_whitespace(cx, stmt.span.between(retexpr.span), true) { span_lint_hir_and_then( cx, diff --git a/tests/ui/let_and_return.edition2021.fixed b/tests/ui/let_and_return.edition2021.fixed index 70d503018e0fe..e89e4476bf820 100644 --- a/tests/ui/let_and_return.edition2021.fixed +++ b/tests/ui/let_and_return.edition2021.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2024.fixed b/tests/ui/let_and_return.edition2024.fixed index 9990c3b712055..d2c76673ca03c 100644 --- a/tests/ui/let_and_return.edition2024.fixed +++ b/tests/ui/let_and_return.edition2024.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 48c20cdd60dbe..1af5f8ba5c165 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} From eefeff552eb24fbe8f86a145c4af2731b844cd91 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Tue, 14 Oct 2025 02:52:59 +0100 Subject: [PATCH 41/80] mgca: Add ConstArg representation for const items --- clippy_utils/src/ast_utils/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 839b46325b5ea..f5e61e365c577 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -358,7 +358,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ident: li, generics: lg, ty: lt, - expr: le, + body: lb, define_opaque: _, }), Const(box ConstItem { @@ -366,7 +366,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ident: ri, generics: rg, ty: rt, - expr: re, + body: rb, define_opaque: _, }), ) => { @@ -374,7 +374,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && eq_expr_opt(le.as_deref(), re.as_deref()) + && both(lb.as_deref(), rb.as_deref(), |l, r| eq_anon_const(l, r)) }, ( Fn(box ast::Fn { @@ -612,7 +612,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { ident: li, generics: lg, ty: lt, - expr: le, + body: lb, define_opaque: _, }), Const(box ConstItem { @@ -620,7 +620,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { ident: ri, generics: rg, ty: rt, - expr: re, + body: rb, define_opaque: _, }), ) => { @@ -628,7 +628,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && eq_expr_opt(le.as_deref(), re.as_deref()) + && both(lb.as_deref(), rb.as_deref(), |l, r| eq_anon_const(l, r)) }, ( Fn(box ast::Fn { From 098ded31605c193ff35e97ff6c1e03973e21daa3 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Sat, 1 Nov 2025 00:26:34 +0000 Subject: [PATCH 42/80] fix: `replace_box` FP when the box is moved --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/replace_box.rs | 99 +++++++++++++++++++++++++++++++-- tests/ui/replace_box.fixed | 71 +++++++++++++++++++++++ tests/ui/replace_box.rs | 71 +++++++++++++++++++++++ tests/ui/replace_box.stderr | 50 ++++++++++++++++- 5 files changed, 286 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8e35883ae9d46..49e6fb07ca773 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -820,6 +820,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); - store.register_late_pass(|_| Box::new(replace_box::ReplaceBox)); + store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default())); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/replace_box.rs b/clippy_lints/src/replace_box.rs index 4bbd1803a78d2..638f6dc1532bb 100644 --- a/clippy_lints/src/replace_box.rs +++ b/clippy_lints/src/replace_box.rs @@ -3,11 +3,17 @@ use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{is_default_equivalent_call, local_is_initialized}; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::smallvec::SmallVec; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, QPath}; +use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, LangItem, QPath}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::sym; +use rustc_middle::hir::place::ProjectionKind; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -33,17 +39,57 @@ declare_clippy_lint! { perf, "assigning a newly created box to `Box` is inefficient" } -declare_lint_pass!(ReplaceBox => [REPLACE_BOX]); + +#[derive(Default)] +pub struct ReplaceBox { + consumed_locals: FxHashSet, + loaded_bodies: SmallVec<[BodyId; 2]>, +} + +impl ReplaceBox { + fn get_consumed_locals(&mut self, cx: &LateContext<'_>) -> &FxHashSet { + if let Some(body_id) = cx.enclosing_body + && !self.loaded_bodies.contains(&body_id) + { + self.loaded_bodies.push(body_id); + ExprUseVisitor::for_clippy( + cx, + cx.tcx.hir_body_owner_def_id(body_id), + MovedVariablesCtxt { + consumed_locals: &mut self.consumed_locals, + }, + ) + .consume_body(cx.tcx.hir_body(body_id)) + .into_ok(); + } + + &self.consumed_locals + } +} + +impl_lint_pass!(ReplaceBox => [REPLACE_BOX]); impl LateLintPass<'_> for ReplaceBox { + fn check_body_post(&mut self, _: &LateContext<'_>, body: &Body<'_>) { + if self.loaded_bodies.first().is_some_and(|&x| x == body.id()) { + self.consumed_locals.clear(); + self.loaded_bodies.clear(); + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = &expr.kind && !lhs.span.from_expansion() && !rhs.span.from_expansion() && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let Some(inner_ty) = lhs_ty.boxed_ty() // No diagnostic for late-initialized locals && lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local)) - && let Some(inner_ty) = lhs_ty.boxed_ty() + // No diagnostic if this is a local that has been moved, or the field + // of a local that has been moved, or several chained field accesses of a local + && local_base(lhs).is_none_or(|(base_id, _)| { + !self.get_consumed_locals(cx).contains(&base_id) + }) { if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) && implements_trait(cx, inner_ty, default_trait_id, &[]) @@ -109,3 +155,46 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option< None } } + +struct MovedVariablesCtxt<'a> { + consumed_locals: &'a mut FxHashSet, +} + +impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_> { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(id) = cmt.place.base + && let mut projections = cmt + .place + .projections + .iter() + .filter(|x| matches!(x.kind, ProjectionKind::Deref)) + // Either no deref or multiple derefs + && (projections.next().is_none() || projections.next().is_some()) + { + self.consumed_locals.insert(id); + } + } + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +/// A local place followed by optional fields +type IdFields = (HirId, Vec); + +/// If `expr` is a local variable with optional field accesses, return it. +fn local_base(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Path(qpath) => qpath.res_local_id().map(|id| (id, Vec::new())), + ExprKind::Field(expr, field) => local_base(expr).map(|(id, mut fields)| { + fields.push(field.name); + (id, fields) + }), + _ => None, + } +} diff --git a/tests/ui/replace_box.fixed b/tests/ui/replace_box.fixed index 58c8ed1691d77..e3fc7190a9c9c 100644 --- a/tests/ui/replace_box.fixed +++ b/tests/ui/replace_box.fixed @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + *x = Foo { inner: String::new() }; + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + *x = Foo { inner: String::new() }; + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| **x = String::new(); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + *s.b = T::new(); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + *s.b = T::new(); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + *q.0 = T::new(); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.rs b/tests/ui/replace_box.rs index e1fb223e4f211..1d5ca1b24994f 100644 --- a/tests/ui/replace_box.rs +++ b/tests/ui/replace_box.rs @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| *x = Box::new(String::new()); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + s.b = Box::new(T::new()); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + s.b = Box::new(T::new()); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + q.0 = Box::new(T::new()); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.stderr b/tests/ui/replace_box.stderr index 7d9c85da17314..4b7bd4a0eeaeb 100644 --- a/tests/ui/replace_box.stderr +++ b/tests/ui/replace_box.stderr @@ -48,5 +48,53 @@ LL | b = Box::new(mac!(three)); | = note: this creates a needless allocation -error: aborting due to 6 previous errors +error: creating a new box + --> tests/ui/replace_box.rs:86:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:92:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:97:38 + | +LL | static R: fn(&mut Box) = |x| *x = Box::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**x = String::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:122:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:126:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:136:5 + | +LL | q.0 = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*q.0 = T::new()` + | + = note: this creates a needless allocation + +error: aborting due to 12 previous errors From ce0c449237574b932ce13ab62b2afd3f9af93518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sun, 2 Nov 2025 11:03:31 +0100 Subject: [PATCH 43/80] Generalize branch references to HEAD --- book/src/development/infrastructure/changelog_update.md | 2 +- clippy_lints/src/disallowed_script_idents.rs | 2 +- src/driver.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/development/infrastructure/changelog_update.md b/book/src/development/infrastructure/changelog_update.md index c96ff228b01af..9eb7e7cc9c9eb 100644 --- a/book/src/development/infrastructure/changelog_update.md +++ b/book/src/development/infrastructure/changelog_update.md @@ -112,7 +112,7 @@ written for. If not, update the version to the changelog version. [changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md [forge]: https://forge.rust-lang.org/ -[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy +[rust_master_tools]: https://github.com/rust-lang/rust/tree/HEAD/src/tools/clippy [rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy [rust_stable_tools]: https://github.com/rust-lang/rust/releases [`beta-accepted`]: https://github.com/rust-lang/rust-clippy/issues?q=label%3Abeta-accepted+ diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index cf964d4b58044..5b4fe2a6b8036 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -67,7 +67,7 @@ impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]); impl EarlyLintPass for DisallowedScriptIdents { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { // Implementation is heavily inspired by the implementation of [`non_ascii_idents`] lint: - // https://github.com/rust-lang/rust/blob/master/compiler/rustc_lint/src/non_ascii_idents.rs + // https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_lint/src/non_ascii_idents.rs let check_disallowed_script_idents = cx.builder.lint_level(DISALLOWED_SCRIPT_IDENTS).level != Level::Allow; if !check_disallowed_script_idents { diff --git a/src/driver.rs b/src/driver.rs index 102ca3fa69f7e..abc706b7772f1 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -13,7 +13,7 @@ extern crate rustc_interface; extern crate rustc_session; extern crate rustc_span; -// See docs in https://github.com/rust-lang/rust/blob/master/compiler/rustc/src/main.rs +// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs // about jemalloc. #[cfg(feature = "jemalloc")] extern crate tikv_jemalloc_sys as jemalloc_sys; @@ -189,7 +189,7 @@ const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/ne #[expect(clippy::too_many_lines)] pub fn main() { - // See docs in https://github.com/rust-lang/rust/blob/master/compiler/rustc/src/main.rs + // See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs // about jemalloc. #[cfg(feature = "jemalloc")] { From 8f9ef1cb8f6446acb56fb6a62f781c11e4f53c33 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 3 Nov 2025 20:19:29 +0100 Subject: [PATCH 44/80] Fix `missing_transmute_annotations` example --- clippy_lints/src/transmute/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 5fda388259a61..048c32ac15450 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -437,8 +437,9 @@ declare_clippy_lint! { /// ### Example /// ```no_run /// # unsafe { + /// let mut x: i32 = 0; /// // Avoid "naked" calls to `transmute()`! - /// let x: i32 = std::mem::transmute([1u16, 2u16]); + /// x = std::mem::transmute([1u16, 2u16]); /// /// // `first_answers` is intended to transmute a slice of bool to a slice of u8. /// // But the programmer forgot to index the first element of the outer slice, From 0afbb65bacdd68e4abcaaec8b56c5e049a6e2645 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 3 Nov 2025 20:28:00 +0100 Subject: [PATCH 45/80] Remove `no_run` tags on `missing_transmute_annotations` code examples --- clippy_lints/src/transmute/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 048c32ac15450..d643f7aea497f 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -435,7 +435,7 @@ declare_clippy_lint! { /// to infer a technically correct yet unexpected type. /// /// ### Example - /// ```no_run + /// ``` /// # unsafe { /// let mut x: i32 = 0; /// // Avoid "naked" calls to `transmute()`! @@ -450,7 +450,7 @@ declare_clippy_lint! { /// # } /// ``` /// Use instead: - /// ```no_run + /// ``` /// # unsafe { /// let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); /// From 0d6fdc089a898897137ab14f839bdd1b27d98edb Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 5 Nov 2025 00:58:18 +0100 Subject: [PATCH 46/80] Use macOS 14 instead of GitHub-deprecated macOS 13 The "macos 13" runners are being phased out (https://github.com/actions/runner-images/issues/13046). --- .github/workflows/clippy_mq.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 9d099137449ef..abbdace0230ae 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -25,7 +25,7 @@ jobs: host: i686-unknown-linux-gnu - os: windows-latest host: x86_64-pc-windows-msvc - - os: macos-13 + - os: macos-14 host: x86_64-apple-darwin - os: macos-latest host: aarch64-apple-darwin From 3dab2738248261fe4a7344f66742df59796a7a3a Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 5 Nov 2025 09:31:29 +0100 Subject: [PATCH 47/80] Do not run merge queue tests on tier 2 x86_64-apple-darwin x86_64-apple-darwin has been demoted to tier 2 since Rust 1.90. Moreover, Github runners for macOS 14 on x86_64 are much slower than the others. --- .github/workflows/clippy_mq.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index abbdace0230ae..ce15a861bb072 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -25,8 +25,6 @@ jobs: host: i686-unknown-linux-gnu - os: windows-latest host: x86_64-pc-windows-msvc - - os: macos-14 - host: x86_64-apple-darwin - os: macos-latest host: aarch64-apple-darwin From 5db73006f281d58b051e69b34a76893d44112cfc Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 14 Oct 2025 19:19:46 -0600 Subject: [PATCH 48/80] feat: Use annotate-snippets by default on nightly --- tests/ui/bool_assert_comparison.stderr | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/ui/bool_assert_comparison.stderr b/tests/ui/bool_assert_comparison.stderr index f823f08f31dca..72aa6303a2026 100644 --- a/tests/ui/bool_assert_comparison.stderr +++ b/tests/ui/bool_assert_comparison.stderr @@ -272,10 +272,8 @@ LL | assert_eq!(a!(), true); | help: replace it with `assert!(..)` | -LL | true -... -LL | -LL ~ assert!(a!()); +LL - assert_eq!(a!(), true); +LL + assert!(a!()); | error: used `assert_eq!` with a literal bool @@ -286,10 +284,8 @@ LL | assert_eq!(true, b!()); | help: replace it with `assert!(..)` | -LL | true -... -LL | -LL ~ assert!(b!()); +LL - assert_eq!(true, b!()); +LL + assert!(b!()); | error: used `debug_assert_eq!` with a literal bool From c100a0808bd67e7bebadbbb734b465afc39f0915 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Sun, 2 Nov 2025 22:49:11 +0000 Subject: [PATCH 49/80] fix: `nonminimal_bool` wrongly unmangled terms --- clippy_lints/src/booleans.rs | 13 +++++++------ tests/ui/nonminimal_bool_methods.fixed | 5 +++++ tests/ui/nonminimal_bool_methods.rs | 5 +++++ tests/ui/nonminimal_bool_methods.stderr | 8 +++++++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index f3985603c4d2e..902ba70577b91 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher::has_let_expr; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{eq_expr_value, sym}; @@ -415,19 +415,20 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio BinOpKind::Ge => Some(" < "), _ => None, } - .and_then(|op| { - let lhs_snippet = lhs.span.get_source_text(cx)?; - let rhs_snippet = rhs.span.get_source_text(cx)?; + .map(|op| { + let mut app = Applicability::MachineApplicable; + let (lhs_snippet, _) = snippet_with_context(cx, lhs.span, SyntaxContext::root(), "", &mut app); + let (rhs_snippet, _) = snippet_with_context(cx, rhs.span, SyntaxContext::root(), "", &mut app); if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) && let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) { // e.g. `(a as u64) < b`. Without the parens the `<` is // interpreted as a start of generic arguments for `u64` - return Some(format!("({lhs_snippet}){op}{rhs_snippet}")); + return format!("({lhs_snippet}){op}{rhs_snippet}"); } - Some(format!("{lhs_snippet}{op}{rhs_snippet}")) + format!("{lhs_snippet}{op}{rhs_snippet}") }) }, ExprKind::MethodCall(path, receiver, args, _) => { diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index f50af147c60cf..0de944f9edcfe 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -242,4 +242,9 @@ fn issue_13436() { } } +fn issue16014() { + (vec![1, 2, 3] > vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 0ecd4775035bf..ac0bd6d8a4910 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -242,4 +242,9 @@ fn issue_13436() { } } +fn issue16014() { + !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/tests/ui/nonminimal_bool_methods.stderr b/tests/ui/nonminimal_bool_methods.stderr index b5155b3b16968..568e880077279 100644 --- a/tests/ui/nonminimal_bool_methods.stderr +++ b/tests/ui/nonminimal_bool_methods.stderr @@ -247,5 +247,11 @@ error: this boolean expression can be simplified LL | _ = !opt.is_none_or(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_ok())` -error: aborting due to 41 previous errors +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:246:5 + | +LL | !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(vec![1, 2, 3] > vec![1, 2, 3, 3])` + +error: aborting due to 42 previous errors From 0ab135f9d0542095b4de54b5fcedd9128b9fbd8e Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Wed, 5 Nov 2025 20:34:33 +0000 Subject: [PATCH 50/80] fix: `missing_asserts_for_indexing` changes `assert_eq` to `assert` --- .../src/missing_asserts_for_indexing.rs | 57 +++++++++++++------ tests/ui/missing_asserts_for_indexing.fixed | 18 +++++- tests/ui/missing_asserts_for_indexing.rs | 14 +++++ tests/ui/missing_asserts_for_indexing.stderr | 56 +++++++++++++++++- 4 files changed, 124 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index 35d06780bcb82..808adb7e71cee 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -3,10 +3,11 @@ use std::ops::ControlFlow; use clippy_utils::comparisons::{Rel, normalize_comparison}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; +use clippy_utils::higher::{If, Range}; +use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{eq_expr_value, hash_expr, higher}; +use clippy_utils::{eq_expr_value, hash_expr}; use rustc_ast::{BinOpKind, LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; @@ -15,7 +16,7 @@ use rustc_hir::{Block, Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -134,15 +135,15 @@ fn len_comparison<'hir>( fn assert_len_expr<'hir>( cx: &LateContext<'_>, expr: &'hir Expr<'hir>, -) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { - let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) +) -> Option<(LengthComparison, usize, &'hir Expr<'hir>, Symbol)> { + let ((cmp, asserted_len, slice_len), macro_call) = if let Some(If { cond, then, .. }) = If::hir(expr) && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind && let ExprKind::Binary(bin_op, left, right) = &condition.kind // check if `then` block has a never type expression && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind && cx.typeck_results().expr_ty(then_expr).is_never() { - len_comparison(bin_op.node, left, right)? + (len_comparison(bin_op.node, left, right)?, sym::assert_macro) } else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| { match cx.tcx.get_diagnostic_name(macro_call.def_id) { Some(sym::assert_eq_macro) => Some((macro_call, BinOpKind::Eq)), @@ -151,7 +152,12 @@ fn assert_len_expr<'hir>( } }) && let Some((left, right, _)) = find_assert_eq_args(cx, expr, macro_call.expn) { - len_comparison(bin_op, left, right)? + ( + len_comparison(bin_op, left, right)?, + root_macro_call(expr.span) + .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id)) + .unwrap_or(sym::assert_macro), + ) } else { return None; }; @@ -160,7 +166,7 @@ fn assert_len_expr<'hir>( && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() && method.ident.name == sym::len { - Some((cmp, asserted_len, recv)) + Some((cmp, asserted_len, recv, macro_call)) } else { None } @@ -174,6 +180,7 @@ enum IndexEntry<'hir> { comparison: LengthComparison, assert_span: Span, slice: &'hir Expr<'hir>, + macro_call: Symbol, }, /// `assert!` with indexing /// @@ -187,6 +194,7 @@ enum IndexEntry<'hir> { slice: &'hir Expr<'hir>, indexes: Vec, comparison: LengthComparison, + macro_call: Symbol, }, /// Indexing without an `assert!` IndexWithoutAssert { @@ -225,9 +233,9 @@ fn upper_index_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { && let LitKind::Int(Pu128(index), _) = lit.node { Some(index as usize) - } else if let Some(higher::Range { + } else if let Some(Range { end: Some(end), limits, .. - }) = higher::Range::hir(cx, expr) + }) = Range::hir(cx, expr) && let ExprKind::Lit(lit) = &end.kind && let LitKind::Int(Pu128(index @ 1..), _) = lit.node { @@ -258,6 +266,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni comparison, assert_span, slice, + macro_call, } => { if slice.span.lo() > assert_span.lo() { *entry = IndexEntry::AssertWithIndex { @@ -268,6 +277,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni slice, indexes: vec![expr.span], comparison: *comparison, + macro_call: *macro_call, }; } }, @@ -303,7 +313,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni /// Checks if the expression is an `assert!` expression and adds it to `asserts` fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap>>) { - if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) { + if let Some((comparison, asserted_len, slice, macro_call)) = assert_len_expr(cx, expr) { let hash = hash_expr(cx, slice); let indexes = map.entry(hash).or_default(); @@ -326,6 +336,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un assert_span: expr.span.source_callsite(), comparison, asserted_len, + macro_call, }; } } else { @@ -334,6 +345,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un comparison, assert_span: expr.span.source_callsite(), slice, + macro_call, }); } } @@ -362,6 +374,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap comparison, assert_span, slice, + macro_call, } if indexes.len() > 1 && !is_first_highest => { // if we have found an `assert!`, let's also check that it's actually right // and if it covers the highest index and if not, suggest the correct length @@ -382,11 +395,23 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap snippet(cx, slice.span, "..") )), // `highest_index` here is rather a length, so we need to add 1 to it - LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => Some(format!( - "assert!({}.len() == {})", - snippet(cx, slice.span, ".."), - highest_index + 1 - )), + LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call { + sym::assert_eq_macro => Some(format!( + "assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + sym::debug_assert_eq_macro => Some(format!( + "debug_assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + _ => Some(format!( + "assert!({}.len() == {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + }, _ => None, }; diff --git a/tests/ui/missing_asserts_for_indexing.fixed b/tests/ui/missing_asserts_for_indexing.fixed index 9018f38100efd..50bc576dd1e23 100644 --- a/tests/ui/missing_asserts_for_indexing.fixed +++ b/tests/ui/missing_asserts_for_indexing.fixed @@ -150,9 +150,9 @@ fn highest_index_first(v1: &[u8]) { } fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { - assert!(v1.len() == 3); + assert_eq!(v1.len(), 3); assert_eq!(v2.len(), 4); - assert!(v3.len() == 3); + assert_eq!(v3.len(), 3); assert_eq!(4, v4.len()); let _ = v1[0] + v1[1] + v1[2]; @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.rs b/tests/ui/missing_asserts_for_indexing.rs index 44c5eddf3d8b9..9e219a2af0732 100644 --- a/tests/ui/missing_asserts_for_indexing.rs +++ b/tests/ui/missing_asserts_for_indexing.rs @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.stderr b/tests/ui/missing_asserts_for_indexing.stderr index b610de94b5308..b686eda7530a0 100644 --- a/tests/ui/missing_asserts_for_indexing.stderr +++ b/tests/ui/missing_asserts_for_indexing.stderr @@ -305,7 +305,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:158:13 | LL | assert_eq!(v1.len(), 2); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v1.len(), 3)` ... LL | let _ = v1[0] + v1[1] + v1[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -331,7 +331,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:163:13 | LL | assert_eq!(2, v3.len()); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v3.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v3.len(), 3)` ... LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -353,5 +353,55 @@ LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^ = note: asserting the length before indexing will elide bounds checks -error: aborting due to 13 previous errors +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | assert_eq!(v.len(), 2); + | ---------------------- help: provide the highest index that is indexed with: `assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | debug_assert_eq!(v.len(), 2); + | ---------------------------- help: provide the highest index that is indexed with: `debug_assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: aborting due to 15 previous errors From a86dd638181512ec4b485493e1dd2c37850ddc64 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Thu, 21 Aug 2025 23:40:27 +0800 Subject: [PATCH 51/80] Make `missing_inline_in_public_items` warn on executables --- clippy_lints/src/missing_inline.rs | 27 +++++++++++------------ tests/ui/missing_inline_executable.rs | 3 +-- tests/ui/missing_inline_executable.stderr | 11 +++++++++ tests/ui/missing_inline_test_crate.rs | 2 ++ 4 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 tests/ui/missing_inline_executable.stderr diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 5e584e85a3c90..29399132a2c92 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::{span_lint, span_lint_hir}; -use clippy_utils::fulfill_or_allowed; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocContainer; +use rustc_session::config::CrateType; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -82,15 +82,6 @@ fn check_missing_inline_attrs( } } -fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool { - use rustc_session::config::CrateType; - - cx.tcx - .crate_types() - .iter() - .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) -} - declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); impl<'tcx> LateLintPass<'tcx> for MissingInline { @@ -99,9 +90,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { return; } - if is_executable_or_proc_macro(cx) - // Allow the lint if it is expected, when building with `--test` - && !(cx.sess().is_test_crate() && fulfill_or_allowed(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, [it.hir_id()])) + if cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) { return; } @@ -157,7 +150,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if impl_item.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if impl_item.span.in_external_macro(cx.sess().source_map()) + || cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) + { return; } diff --git a/tests/ui/missing_inline_executable.rs b/tests/ui/missing_inline_executable.rs index 444a7f1c964f0..2ab3e3b7825d5 100644 --- a/tests/ui/missing_inline_executable.rs +++ b/tests/ui/missing_inline_executable.rs @@ -1,7 +1,6 @@ -//@ check-pass - #![warn(clippy::missing_inline_in_public_items)] pub fn foo() {} +//~^ missing_inline_in_public_items fn main() {} diff --git a/tests/ui/missing_inline_executable.stderr b/tests/ui/missing_inline_executable.stderr new file mode 100644 index 0000000000000..3108e4e490659 --- /dev/null +++ b/tests/ui/missing_inline_executable.stderr @@ -0,0 +1,11 @@ +error: missing `#[inline]` for a function + --> tests/ui/missing_inline_executable.rs:3:1 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-inline-in-public-items` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_inline_in_public_items)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/missing_inline_test_crate.rs b/tests/ui/missing_inline_test_crate.rs index d7fb5ae2283f3..728292a0ee2b8 100644 --- a/tests/ui/missing_inline_test_crate.rs +++ b/tests/ui/missing_inline_test_crate.rs @@ -6,3 +6,5 @@ pub fn foo() -> u32 { 0 } + +fn private_function() {} From 8539de9ccf966a1a5b80406a86dc2725f9aed8f6 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Nov 2025 15:55:14 -0600 Subject: [PATCH 52/80] Don't flag cfg(test) as multiple inherent impl --- clippy_lints/src/inherent_impl.rs | 5 +-- tests/ui/multiple_inherent_impl_cfg.rs | 6 ++-- tests/ui/multiple_inherent_impl_cfg.stderr | 42 +++++----------------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index a08efbc52d457..f59c7615d7458 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_config::types::InherentImplLintScope; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::fulfill_or_allowed; +use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_in_cfg_test}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::{Item, ItemKind, Node}; @@ -100,7 +100,8 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { }, InherentImplLintScope::Crate => Criterion::Crate, }; - match type_map.entry((impl_ty, criterion)) { + let is_test = is_cfg_test(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id); + match type_map.entry((impl_ty, criterion, is_test)) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); diff --git a/tests/ui/multiple_inherent_impl_cfg.rs b/tests/ui/multiple_inherent_impl_cfg.rs index 15c8b7c508781..4b973d762ed92 100644 --- a/tests/ui/multiple_inherent_impl_cfg.rs +++ b/tests/ui/multiple_inherent_impl_cfg.rs @@ -13,8 +13,7 @@ impl A {} //~^ multiple_inherent_impl #[cfg(test)] -impl A {} // false positive -//~^ multiple_inherent_impl +impl A {} #[cfg(test)] impl A {} @@ -25,8 +24,7 @@ struct B; impl B {} #[cfg(test)] -impl B {} // false positive -//~^ multiple_inherent_impl +impl B {} impl B {} //~^ multiple_inherent_impl diff --git a/tests/ui/multiple_inherent_impl_cfg.stderr b/tests/ui/multiple_inherent_impl_cfg.stderr index 9d408ce3dec38..991ceb0ff9677 100644 --- a/tests/ui/multiple_inherent_impl_cfg.stderr +++ b/tests/ui/multiple_inherent_impl_cfg.stderr @@ -16,76 +16,52 @@ LL | #![deny(clippy::multiple_inherent_impl)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 - | -LL | impl A {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 - | -LL | impl A {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:20:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:19:1 | LL | impl A {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 | LL | impl A {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 - | -LL | impl B {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 - | -LL | impl B {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:31:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:29:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:24:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:35:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:33:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:27:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:45:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:43:1 | LL | impl C {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:42:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:40:1 | LL | impl C {} | ^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors From e6a3dde4960e1a5f32d8fd89de70a357c20e7d52 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 7 Nov 2025 08:22:08 +0100 Subject: [PATCH 53/80] Make bug report issue template more similar to that of `rust-lang/rust` It's always a bit jarring to be required to provide the expected output first, as usually one starts with having the existing output, and modifies that. This commit reorders the two steps, and also gives them the same names as `rust-lang/rust` for some extra consistency --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b6f70a7f18300..91aaf1f3644e0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,15 +20,15 @@ body: label: Reproducer description: Please provide the code and steps to reproduce the bug value: | - I tried this code: + Code: ```rust ``` - I expected to see this happen: + Current output: - Instead, this happened: + Desired output: - type: textarea id: version attributes: From da7bde794c620e8539b2ba8db670d7ab352d8a5b Mon Sep 17 00:00:00 2001 From: Tom Fryers Date: Fri, 7 Nov 2025 12:46:30 +0000 Subject: [PATCH 54/80] Fix mod_module_files FP for tests in workspaces Workspaces don't have their integration tests in tests/ at the root, so this check missed them. --- clippy_lints/src/module_style.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index f132b90ac4f2e..9096d6f1c7b32 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -4,7 +4,7 @@ use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; declare_clippy_lint! { @@ -150,7 +150,13 @@ fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile /// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test) /// for code-sharing between tests. fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { - if path.ends_with("mod.rs") && !path.starts_with("tests") { + if path.ends_with("mod.rs") + && !path + .components() + .filter_map(|c| if let Component::Normal(d) = c { Some(d) } else { None }) + .take_while(|&c| c != "src") + .any(|c| c == "tests") + { span_lint_and_then( cx, MOD_MODULE_FILES, From 9147a36ba6332fd5db02569ef560ed84ca874a2d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 7 Nov 2025 21:37:51 +0100 Subject: [PATCH 55/80] perf(manual_is_power_of_two): perform the `is_integer_literal` check first .. as it's much cheaper than `count_ones_receiver` --- clippy_lints/src/manual_is_power_of_two.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs index 4439a28763a2f..25db719c8214e 100644 --- a/clippy_lints/src/manual_is_power_of_two.rs +++ b/clippy_lints/src/manual_is_power_of_two.rs @@ -70,12 +70,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo { if !expr.span.from_expansion() && let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq) { - if let Some(a) = count_ones_receiver(cx, lhs) - && is_integer_literal(rhs, 1) + if is_integer_literal(rhs, 1) + && let Some(a) = count_ones_receiver(cx, lhs) { self.build_sugg(cx, expr, a); - } else if let Some(a) = count_ones_receiver(cx, rhs) - && is_integer_literal(lhs, 1) + } else if is_integer_literal(lhs, 1) + && let Some(a) = count_ones_receiver(cx, rhs) { self.build_sugg(cx, expr, a); } else if is_integer_literal(rhs, 0) From 21ddc50eec995332e45c457250539c74bc55015c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 7 Nov 2025 22:13:12 +0100 Subject: [PATCH 56/80] chore(unnecessary_map_on_constructor): clean-up - reduce indentation - print constructor/method name in backticks --- .../src/unnecessary_map_on_constructor.rs | 52 ++++++++----------- .../ui/unnecessary_map_on_constructor.stderr | 16 +++--- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/unnecessary_map_on_constructor.rs b/clippy_lints/src/unnecessary_map_on_constructor.rs index af9f291f5deb7..fba530d0dfcae 100644 --- a/clippy_lints/src/unnecessary_map_on_constructor.rs +++ b/clippy_lints/src/unnecessary_map_on_constructor.rs @@ -35,18 +35,17 @@ declare_lint_pass!(UnnecessaryMapOnConstructor => [UNNECESSARY_MAP_ON_CONSTRUCTO impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if expr.span.from_expansion() { - return; - } - if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + if !expr.span.from_expansion() + && let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + && !map_arg.span.from_expansion() + && let hir::ExprKind::Path(fun) = map_arg.kind && let Some(sym::Option | sym::Result) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx) { let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind && let hir::ExprKind::Path(constructor_path) = constructor.kind + && !constructor.span.from_expansion() + && !arg.span.from_expansion() { - if constructor.span.from_expansion() || arg.span.from_expansion() { - return; - } (constructor_path, arg) } else { return; @@ -67,29 +66,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { _ => return, } - if let hir::ExprKind::Path(fun) = map_arg.kind { - if map_arg.span.from_expansion() { - return; - } - let mut applicability = Applicability::MachineApplicable; - let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability); - let constructor_snippet = - snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability); - let constructor_arg_snippet = - snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability); - span_lint_and_sugg( - cx, - UNNECESSARY_MAP_ON_CONSTRUCTOR, - expr.span, - format!( - "unnecessary {} on constructor {constructor_snippet}(_)", - path.ident.name - ), - "try", - format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), - applicability, - ); - } + let mut app = Applicability::MachineApplicable; + let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut app); + let constructor_snippet = snippet_with_applicability(cx, constructor_path.span(), "_", &mut app); + let constructor_arg_snippet = snippet_with_applicability(cx, constructor_item.span, "_", &mut app); + span_lint_and_sugg( + cx, + UNNECESSARY_MAP_ON_CONSTRUCTOR, + expr.span, + format!( + "unnecessary `{}` on constructor `{constructor_snippet}(_)`", + path.ident.name + ), + "try", + format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), + app, + ); } } } diff --git a/tests/ui/unnecessary_map_on_constructor.stderr b/tests/ui/unnecessary_map_on_constructor.stderr index f29bfec60f726..a191168208088 100644 --- a/tests/ui/unnecessary_map_on_constructor.stderr +++ b/tests/ui/unnecessary_map_on_constructor.stderr @@ -1,4 +1,4 @@ -error: unnecessary map on constructor Some(_) +error: unnecessary `map` on constructor `Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:32:13 | LL | let a = Some(x).map(fun); @@ -7,43 +7,43 @@ LL | let a = Some(x).map(fun); = note: `-D clippy::unnecessary-map-on-constructor` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_map_on_constructor)]` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:34:27 | LL | let b: SimpleResult = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:36:27 | LL | let c: SimpleResult = Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Err(notfun(err))` -error: unnecessary map on constructor Option::Some(_) +error: unnecessary `map` on constructor `Option::Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:39:13 | LL | let a = Option::Some(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Option::Some(fun(x))` -error: unnecessary map on constructor SimpleResult::Ok(_) +error: unnecessary `map` on constructor `SimpleResult::Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:41:27 | LL | let b: SimpleResult = SimpleResult::Ok(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Ok(fun(x))` -error: unnecessary map_err on constructor SimpleResult::Err(_) +error: unnecessary `map_err` on constructor `SimpleResult::Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:43:27 | LL | let c: SimpleResult = SimpleResult::Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Err(notfun(err))` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:45:52 | LL | let b: std::result::Result = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:47:52 | LL | let c: std::result::Result = Err(err).map_err(notfun); From b59b2fdddb6f44e739e3995aacc951e9422f7d96 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 8 Nov 2025 10:14:33 +0100 Subject: [PATCH 57/80] chore(unnecessary_mut_passed): show the intention not to lint `&raw` references --- tests/ui/unnecessary_mut_passed.fixed | 2 +- tests/ui/unnecessary_mut_passed.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/unnecessary_mut_passed.fixed b/tests/ui/unnecessary_mut_passed.fixed index 63bbadb01dcb2..876b61d29519b 100644 --- a/tests/ui/unnecessary_mut_passed.fixed +++ b/tests/ui/unnecessary_mut_passed.fixed @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; diff --git a/tests/ui/unnecessary_mut_passed.rs b/tests/ui/unnecessary_mut_passed.rs index b719ca1871b29..e92368bfffeb6 100644 --- a/tests/ui/unnecessary_mut_passed.rs +++ b/tests/ui/unnecessary_mut_passed.rs @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; From a4a88ea592a1a43ab7d2ece3928702f3c8d7e803 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 1 Nov 2025 17:11:32 +0100 Subject: [PATCH 58/80] refactor(needless_arbitrary_self_type): give suggestions with finer diffs --- .../src/needless_arbitrary_self_type.rs | 108 ++++++++---------- tests/ui/needless_arbitrary_self_type.stderr | 63 ++++++++-- ...dless_arbitrary_self_type_unfixable.stderr | 7 +- 3 files changed, 109 insertions(+), 69 deletions(-) diff --git a/clippy_lints/src/needless_arbitrary_self_type.rs b/clippy_lints/src/needless_arbitrary_self_type.rs index 5f7fde30f03f6..691d9035d02c5 100644 --- a/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/clippy_lints/src/needless_arbitrary_self_type.rs @@ -1,10 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Mutability, Param, PatKind, Path, TyKind}; +use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Param, PatKind, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; use rustc_span::symbol::kw; declare_clippy_lint! { @@ -65,52 +64,6 @@ enum Mode { Value, } -fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mode: &Mode, mutbl: Mutability) { - if let [segment] = &path.segments[..] - && segment.ident.name == kw::SelfUpper - { - // In case we have a named lifetime, we check if the name comes from expansion. - // If it does, at this point we know the rest of the parameter was written by the user, - // so let them decide what the name of the lifetime should be. - // See #6089 for more details. - let mut applicability = Applicability::MachineApplicable; - let self_param = match (binding_mode, mutbl) { - (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Mut) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ mut self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} mut self") - } - }, - (Mode::Ref(None), Mutability::Not) => "&self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Not) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} self") - } - }, - (Mode::Value, Mutability::Mut) => "mut self".to_string(), - (Mode::Value, Mutability::Not) => "self".to_string(), - }; - - span_lint_and_sugg( - cx, - NEEDLESS_ARBITRARY_SELF_TYPE, - span, - "the type of the `self` parameter does not need to be arbitrary", - "consider to change this parameter to", - self_param, - applicability, - ); - } -} - impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { // Bail out if the parameter it's not a receiver or was not written by the user @@ -118,20 +71,55 @@ impl EarlyLintPass for NeedlessArbitrarySelfType { return; } - match &p.ty.kind { - TyKind::Path(None, path) => { - if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Value, mutbl); - } + let (path, binding_mode, mutbl) = match &p.ty.kind { + TyKind::Path(None, path) if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind => { + (path, Mode::Value, mutbl) }, - TyKind::Ref(lifetime, mut_ty) => { + TyKind::Ref(lifetime, mut_ty) if let TyKind::Path(None, path) = &mut_ty.ty.kind - && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind - { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Ref(*lifetime), mut_ty.mutbl); - } + && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind => + { + (path, Mode::Ref(*lifetime), mut_ty.mutbl) }, - _ => {}, + _ => return, + }; + + let span = p.span.to(p.ty.span); + if let [segment] = &path.segments[..] + && segment.ident.name == kw::SelfUpper + { + span_lint_and_then( + cx, + NEEDLESS_ARBITRARY_SELF_TYPE, + span, + "the type of the `self` parameter does not need to be arbitrary", + |diag| { + let mut applicability = Applicability::MachineApplicable; + let add = match binding_mode { + Mode::Value => String::new(), + Mode::Ref(None) => mutbl.ref_prefix_str().to_string(), + Mode::Ref(Some(lifetime)) => { + // In case we have a named lifetime, we check if the name comes from expansion. + // If it does, at this point we know the rest of the parameter was written by the user, + // so let them decide what the name of the lifetime should be. + // See #6089 for more details. + let lt_name = if lifetime.ident.span.from_expansion() { + applicability = Applicability::HasPlaceholders; + "'_".into() + } else { + snippet_with_applicability(cx, lifetime.ident.span, "'_", &mut applicability) + }; + format!("&{lt_name} {mut_}", mut_ = mutbl.prefix_str()) + }, + }; + + let mut sugg = vec![(p.ty.span.with_lo(p.span.hi()), String::new())]; + if !add.is_empty() { + sugg.push((p.span.shrink_to_lo(), add)); + } + diag.multipart_suggestion_verbose("remove the type", sugg, applicability); + }, + ); } } } diff --git a/tests/ui/needless_arbitrary_self_type.stderr b/tests/ui/needless_arbitrary_self_type.stderr index b5c0aae8310f3..bb42e5ea63f51 100644 --- a/tests/ui/needless_arbitrary_self_type.stderr +++ b/tests/ui/needless_arbitrary_self_type.stderr @@ -2,52 +2,99 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:10:16 | LL | pub fn bad(self: Self) { - | ^^^^^^^^^^ help: consider to change this parameter to: `self` + | ^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - pub fn bad(self: Self) { +LL + pub fn bad(self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:19:20 | LL | pub fn mut_bad(mut self: Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `mut self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_bad(mut self: Self) { +LL + pub fn mut_bad(mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:28:20 | LL | pub fn ref_bad(self: &Self) { - | ^^^^^^^^^^^ help: consider to change this parameter to: `&self` + | ^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad(self: &Self) { +LL + pub fn ref_bad(&self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:37:38 | LL | pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { +LL + pub fn ref_bad_with_lifetime<'a>(&'a self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:46:24 | LL | pub fn mut_ref_bad(self: &mut Self) { - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad(self: &mut Self) { +LL + pub fn mut_ref_bad(&mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:55:42 | LL | pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { - | ^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a mut self` + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { +LL + pub fn mut_ref_bad_with_lifetime<'a>(&'a mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:74:11 | LL | fn f1(self: &'r#struct Self) {} - | ^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct self` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f1(self: &'r#struct Self) {} +LL + fn f1(&'r#struct self) {} + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:76:11 | LL | fn f2(self: &'r#struct mut Self) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct mut self` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f2(self: &'r#struct mut Self) {} +LL + fn f2(&'r#struct mut self) {} + | error: aborting due to 8 previous errors diff --git a/tests/ui/needless_arbitrary_self_type_unfixable.stderr b/tests/ui/needless_arbitrary_self_type_unfixable.stderr index b50e005756290..4f8f001fc5e4d 100644 --- a/tests/ui/needless_arbitrary_self_type_unfixable.stderr +++ b/tests/ui/needless_arbitrary_self_type_unfixable.stderr @@ -2,10 +2,15 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type_unfixable.rs:42:31 | LL | fn call_with_mut_self(self: &mut Self) {} - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - fn call_with_mut_self(self: &mut Self) {} +LL + fn call_with_mut_self(&mut self) {} + | error: aborting due to 1 previous error From 8cb6e1bfcfd9f31051937708c52143a53137c09b Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Sat, 1 Nov 2025 13:52:11 -0400 Subject: [PATCH 59/80] Fix clippy When mgca is enabled, const rhs's that are paths may have false negatives with the lints in non_copy_const.rs. But these should probably be using the trait solver anyway, and it only happens under mgca. --- clippy_lints/src/non_copy_const.rs | 73 +++++++++++-------- .../src/undocumented_unsafe_blocks.rs | 29 +++++++- clippy_lints/src/utils/author.rs | 1 + clippy_utils/src/ast_utils/mod.rs | 13 +++- clippy_utils/src/consts.rs | 16 +++- clippy_utils/src/hir_utils.rs | 6 +- clippy_utils/src/msrvs.rs | 16 ++-- tests/ui/needless_doc_main.rs | 6 +- tests/ui/trait_duplication_in_bounds.fixed | 11 +-- tests/ui/trait_duplication_in_bounds.rs | 11 +-- tests/ui/trait_duplication_in_bounds.stderr | 8 +- ...duplication_in_bounds_assoc_const_eq.fixed | 14 ++++ ...it_duplication_in_bounds_assoc_const_eq.rs | 14 ++++ ...uplication_in_bounds_assoc_const_eq.stderr | 14 ++++ 14 files changed, 157 insertions(+), 75 deletions(-) create mode 100644 tests/ui/trait_duplication_in_bounds_assoc_const_eq.fixed create mode 100644 tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs create mode 100644 tests/ui/trait_duplication_in_bounds_assoc_const_eq.stderr diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 2fffc4244a736..3c3e5fea49703 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -18,7 +18,7 @@ // definitely contains interior mutability. use clippy_config::Conf; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, const_item_rhs_to_expr}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::is_in_const_context; use clippy_utils::macros::macro_backtrace; @@ -28,7 +28,8 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ - Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Node, StructTailExpr, TraitItem, TraitItemKind, UnOp, + ConstArgKind, ConstItemRhs, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Node, StructTailExpr, + TraitItem, TraitItemKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::mir::{ConstValue, UnevaluatedConst}; @@ -272,6 +273,7 @@ impl<'tcx> NonCopyConst<'tcx> { /// Checks if a value of the given type is `Freeze`, or may be depending on the value. fn is_ty_freeze(&mut self, tcx: TyCtxt<'tcx>, typing_env: TypingEnv<'tcx>, ty: Ty<'tcx>) -> IsFreeze { + // FIXME: this should probably be using the trait solver let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); match self.freeze_tys.entry(ty) { Entry::Occupied(e) => *e.get(), @@ -695,7 +697,7 @@ impl<'tcx> NonCopyConst<'tcx> { impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Const(ident, .., body_id) = item.kind + if let ItemKind::Const(ident, .., ct_rhs) = item.kind && !ident.is_special() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { @@ -703,13 +705,14 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { IsFreeze::Yes => false, IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, - _ => !self.is_init_expr_freeze( + // FIXME: we just assume mgca rhs's are freeze + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), cx.tcx.typeck(item.owner_id), GenericArgs::identity_for_item(cx.tcx, item.owner_id), - cx.tcx.hir_body(body_id).value, - ), + e + )), }, } && !item.span.in_external_macro(cx.sess().source_map()) @@ -736,22 +739,25 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Const(_, body_id_opt) = item.kind + if let TraitItemKind::Const(_, ct_rhs_opt) = item.kind && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { IsFreeze::No => true, - IsFreeze::Maybe if let Some(body_id) = body_id_opt => { + IsFreeze::Maybe if let Some(ct_rhs) = ct_rhs_opt => { match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => { !is_freeze }, - _ => !self.is_init_expr_freeze( - cx.tcx, - cx.typing_env(), - cx.tcx.typeck(item.owner_id), - GenericArgs::identity_for_item(cx.tcx, item.owner_id), - cx.tcx.hir_body(body_id).value, - ), + // FIXME: we just assume mgca rhs's are freeze + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + !self.is_init_expr_freeze( + cx.tcx, + cx.typing_env(), + cx.tcx.typeck(item.owner_id), + GenericArgs::identity_for_item(cx.tcx, item.owner_id), + e, + ) + }), } }, IsFreeze::Yes | IsFreeze::Maybe => false, @@ -768,7 +774,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { - if let ImplItemKind::Const(_, body_id) = item.kind + if let ImplItemKind::Const(_, ct_rhs) = item.kind && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { IsFreeze::Yes => false, @@ -799,13 +805,16 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { // interior mutability. IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, - _ => !self.is_init_expr_freeze( - cx.tcx, - cx.typing_env(), - cx.tcx.typeck(item.owner_id), - GenericArgs::identity_for_item(cx.tcx, item.owner_id), - cx.tcx.hir_body(body_id).value, - ), + // FIXME: we just assume mgca rhs's are freeze + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + !self.is_init_expr_freeze( + cx.tcx, + cx.typing_env(), + cx.tcx.typeck(item.owner_id), + GenericArgs::identity_for_item(cx.tcx, item.owner_id), + e, + ) + }), }, } && !item.span.in_external_macro(cx.sess().source_map()) @@ -913,20 +922,26 @@ fn get_const_hir_value<'tcx>( args: GenericArgsRef<'tcx>, ) -> Option<(&'tcx TypeckResults<'tcx>, &'tcx Expr<'tcx>)> { let did = did.as_local()?; - let (did, body_id) = match tcx.hir_node(tcx.local_def_id_to_hir_id(did)) { - Node::Item(item) if let ItemKind::Const(.., body_id) = item.kind => (did, body_id), - Node::ImplItem(item) if let ImplItemKind::Const(.., body_id) = item.kind => (did, body_id), + let (did, ct_rhs) = match tcx.hir_node(tcx.local_def_id_to_hir_id(did)) { + Node::Item(item) if let ItemKind::Const(.., ct_rhs) = item.kind => (did, ct_rhs), + Node::ImplItem(item) if let ImplItemKind::Const(.., ct_rhs) = item.kind => (did, ct_rhs), Node::TraitItem(_) if let Ok(Some(inst)) = Instance::try_resolve(tcx, typing_env, did.into(), args) && let Some(did) = inst.def_id().as_local() => { match tcx.hir_node(tcx.local_def_id_to_hir_id(did)) { - Node::ImplItem(item) if let ImplItemKind::Const(.., body_id) = item.kind => (did, body_id), - Node::TraitItem(item) if let TraitItemKind::Const(.., Some(body_id)) = item.kind => (did, body_id), + Node::ImplItem(item) if let ImplItemKind::Const(.., ct_rhs) = item.kind => (did, ct_rhs), + Node::TraitItem(item) if let TraitItemKind::Const(.., Some(ct_rhs)) = item.kind => (did, ct_rhs), _ => return None, } }, _ => return None, }; - Some((tcx.typeck(did), tcx.hir_body(body_id).value)) + match ct_rhs { + ConstItemRhs::Body(body_id) => Some((tcx.typeck(did), tcx.hir_body(body_id).value)), + ConstItemRhs::TypeConst(ct_arg) => match ct_arg.kind { + ConstArgKind::Anon(anon_const) => Some((tcx.typeck(did), tcx.hir_body(anon_const.body).value)), + _ => None, + }, + } } diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 9935dc309611f..11d3f33331cb8 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -2,6 +2,7 @@ use std::ops::ControlFlow; use std::sync::Arc; use clippy_config::Conf; +use clippy_utils::consts::const_item_rhs_to_expr; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_lint_allowed; use clippy_utils::source::walk_span_to_context; @@ -184,7 +185,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { } } - fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &hir::Item<'tcx>) { if item.span.in_external_macro(cx.tcx.sess.source_map()) { return; } @@ -214,7 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { } } -fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span), is_doc: bool) { +fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, (span, help_span): (Span, Span), is_doc: bool) { match &item.kind { ItemKind::Impl(Impl { of_trait: Some(of_trait), @@ -234,7 +235,29 @@ fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, h }, ItemKind::Impl(_) => {}, // const and static items only need a safety comment if their body is an unsafe block, lint otherwise - &ItemKind::Const(.., body) | &ItemKind::Static(.., body) => { + &ItemKind::Const(.., ct_rhs) => { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, ct_rhs.hir_id()) { + let expr = const_item_rhs_to_expr(cx.tcx, ct_rhs); + if let Some(expr) = expr && !matches!( + expr.kind, hir::ExprKind::Block(block, _) + if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + ) { + span_lint_and_then( + cx, + UNNECESSARY_SAFETY_COMMENT, + span, + format!( + "{} has unnecessary safety comment", + cx.tcx.def_descr(item.owner_id.to_def_id()), + ), + |diag| { + diag.span_help(help_span, "consider removing the safety comment"); + }, + ); + } + } + } + &ItemKind::Static(.., body) => { if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) { let body = cx.tcx.hir_body(body); if !matches!( diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 222427cd3075f..d7180f4908d0a 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -320,6 +320,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.body(field!(anon_const.body)); }, ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), + ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), } } diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index f5e61e365c577..68fa3d191d280 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -374,7 +374,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_deref(), rb.as_deref(), |l, r| eq_anon_const(l, r)) + && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) }, ( Fn(box ast::Fn { @@ -628,7 +628,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_deref(), rb.as_deref(), |l, r| eq_anon_const(l, r)) + && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) }, ( Fn(box ast::Fn { @@ -786,6 +786,15 @@ pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool { eq_expr(&l.value, &r.value) } +pub fn eq_const_item_rhs(l: &ConstItemRhs, r: &ConstItemRhs) -> bool { + use ConstItemRhs::*; + match (l, r) { + (TypeConst(l), TypeConst(r)) => eq_anon_const(l, r), + (Body(l), Body(r)) => eq_expr(l, r), + (TypeConst(..), Body(..)) | (Body(..), TypeConst(..)) => false, + } +} + pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool { use UseTreeKind::*; match (l, r) { diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 7b8c7f3f0d6b4..ac408a1b59e5a 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -13,7 +13,10 @@ use rustc_apfloat::Float; use rustc_apfloat::ieee::{Half, Quad}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, PatExpr, PatExprKind, QPath, TyKind, UnOp}; +use rustc_hir::{ + BinOpKind, Block, ConstArgKind, ConstBlock, ConstItemRhs, Expr, ExprKind, HirId, PatExpr, PatExprKind, QPath, + TyKind, UnOp, +}; use rustc_lexer::{FrontmatterAllowed, tokenize}; use rustc_lint::LateContext; use rustc_middle::mir::ConstValue; @@ -1130,3 +1133,14 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { integer_const(cx, expr, ctxt) == Some(0) } + +pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match ct_rhs { + ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), + ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { + ConstArgKind::Path(_) => None, + ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), + ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, + }, + } +} diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 710b88e92154e..dd411fe21bdd3 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -481,7 +481,9 @@ impl HirEqInterExpr<'_, '_, '_> { (ConstArgKind::Path(..), ConstArgKind::Anon(..)) | (ConstArgKind::Anon(..), ConstArgKind::Path(..)) | (ConstArgKind::Infer(..), _) - | (_, ConstArgKind::Infer(..)) => false, + | (_, ConstArgKind::Infer(..)) + | (ConstArgKind::Error(..), _) + | (_, ConstArgKind::Error(..)) => false, } } @@ -1330,7 +1332,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { match &const_arg.kind { ConstArgKind::Path(path) => self.hash_qpath(path), ConstArgKind::Anon(anon) => self.hash_body(anon.body), - ConstArgKind::Infer(..) => {}, + ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, } } diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index f7a0c3e39afd0..86d17a8231d53 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -192,12 +192,12 @@ fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option Option () { @@ -17,7 +17,7 @@ /// unimplemented!(); /// } /// ``` -/// +/// /// This should, too. /// ```rust /// fn main() { @@ -25,7 +25,7 @@ /// unimplemented!(); /// } /// ``` -/// +/// /// This one too. /// ```no_run /// // the fn is not always the first line diff --git a/tests/ui/trait_duplication_in_bounds.fixed b/tests/ui/trait_duplication_in_bounds.fixed index 88ba5f810b4eb..959f5db112655 100644 --- a/tests/ui/trait_duplication_in_bounds.fixed +++ b/tests/ui/trait_duplication_in_bounds.fixed @@ -1,6 +1,6 @@ #![deny(clippy::trait_duplication_in_bounds)] #![allow(unused)] -#![feature(associated_const_equality, const_trait_impl)] +#![feature(const_trait_impl)] use std::any::Any; @@ -194,12 +194,3 @@ where T: Iterator + Iterator, { } -trait AssocConstTrait { - const ASSOC: usize; -} -fn assoc_const_args() -where - T: AssocConstTrait, - //~^ trait_duplication_in_bounds -{ -} diff --git a/tests/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index 19a4e70e294ef..9bfecf40fc03d 100644 --- a/tests/ui/trait_duplication_in_bounds.rs +++ b/tests/ui/trait_duplication_in_bounds.rs @@ -1,6 +1,6 @@ #![deny(clippy::trait_duplication_in_bounds)] #![allow(unused)] -#![feature(associated_const_equality, const_trait_impl)] +#![feature(const_trait_impl)] use std::any::Any; @@ -194,12 +194,3 @@ where T: Iterator + Iterator, { } -trait AssocConstTrait { - const ASSOC: usize; -} -fn assoc_const_args() -where - T: AssocConstTrait + AssocConstTrait, - //~^ trait_duplication_in_bounds -{ -} diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index a56a683de9738..93488ea8e74db 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -70,11 +70,5 @@ error: these where clauses contain repeated elements LL | T: IntoIterator + IntoIterator, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `IntoIterator` -error: these where clauses contain repeated elements - --> tests/ui/trait_duplication_in_bounds.rs:202:8 - | -LL | T: AssocConstTrait + AssocConstTrait, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `AssocConstTrait` - -error: aborting due to 12 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/trait_duplication_in_bounds_assoc_const_eq.fixed b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.fixed new file mode 100644 index 0000000000000..f2bd306a99e49 --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.fixed @@ -0,0 +1,14 @@ +#![deny(clippy::trait_duplication_in_bounds)] +#![expect(incomplete_features)] +#![feature(associated_const_equality, min_generic_const_args)] + +trait AssocConstTrait { + #[type_const] + const ASSOC: usize; +} +fn assoc_const_args() +where + T: AssocConstTrait, + //~^ trait_duplication_in_bounds +{ +} diff --git a/tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs new file mode 100644 index 0000000000000..8e7d843a44c0f --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs @@ -0,0 +1,14 @@ +#![deny(clippy::trait_duplication_in_bounds)] +#![expect(incomplete_features)] +#![feature(associated_const_equality, min_generic_const_args)] + +trait AssocConstTrait { + #[type_const] + const ASSOC: usize; +} +fn assoc_const_args() +where + T: AssocConstTrait + AssocConstTrait, + //~^ trait_duplication_in_bounds +{ +} diff --git a/tests/ui/trait_duplication_in_bounds_assoc_const_eq.stderr b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.stderr new file mode 100644 index 0000000000000..accc4d7b5bb8a --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_assoc_const_eq.stderr @@ -0,0 +1,14 @@ +error: these where clauses contain repeated elements + --> tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs:11:8 + | +LL | T: AssocConstTrait + AssocConstTrait, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `AssocConstTrait` + | +note: the lint level is defined here + --> tests/ui/trait_duplication_in_bounds_assoc_const_eq.rs:1:9 + | +LL | #![deny(clippy::trait_duplication_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 7a745ff987e17c9899e1a3106751487031ab2133 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Sat, 1 Nov 2025 15:11:26 -0400 Subject: [PATCH 60/80] Use "rhs" terminology instead of "body" --- clippy_utils/src/ast_utils/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 68fa3d191d280..f0b86ee3ea717 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -358,7 +358,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ident: li, generics: lg, ty: lt, - body: lb, + rhs: lb, define_opaque: _, }), Const(box ConstItem { @@ -366,7 +366,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ident: ri, generics: rg, ty: rt, - body: rb, + rhs: rb, define_opaque: _, }), ) => { @@ -612,7 +612,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { ident: li, generics: lg, ty: lt, - body: lb, + rhs: lb, define_opaque: _, }), Const(box ConstItem { @@ -620,7 +620,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { ident: ri, generics: rg, ty: rt, - body: rb, + rhs: rb, define_opaque: _, }), ) => { From 276053175ddd93fb80bae7ef37f2a292bc08b776 Mon Sep 17 00:00:00 2001 From: Frank King Date: Sun, 2 Nov 2025 19:23:36 +0800 Subject: [PATCH 61/80] Implement `&pin` patterns and `ref pin` bindings --- clippy_lints/src/equatable_if_let.rs | 2 +- clippy_lints/src/loops/manual_find.rs | 2 +- clippy_lints/src/manual_retain.rs | 4 ++-- clippy_lints/src/matches/manual_ok_err.rs | 2 +- clippy_lints/src/matches/manual_utils.rs | 2 +- clippy_lints/src/matches/match_ref_pats.rs | 2 +- clippy_lints/src/matches/match_same_arms.rs | 2 +- clippy_lints/src/matches/redundant_guards.rs | 4 ++-- .../src/matches/redundant_pattern_match.rs | 2 +- clippy_lints/src/matches/single_match.rs | 2 +- clippy_lints/src/methods/filter_map.rs | 2 +- clippy_lints/src/methods/iter_filter.rs | 2 +- clippy_lints/src/methods/iter_overeager_cloned.rs | 2 +- clippy_lints/src/methods/map_clone.rs | 4 ++-- clippy_lints/src/needless_borrowed_ref.rs | 4 ++-- clippy_lints/src/unnested_or_patterns.rs | 15 ++++++++------- clippy_lints/src/utils/author.rs | 4 ++-- clippy_utils/src/ast_utils/mod.rs | 5 ++--- clippy_utils/src/hir_utils.rs | 15 ++++++++++----- clippy_utils/src/lib.rs | 6 +++--- 20 files changed, 44 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index c3fc09343dbfe..9b5cd7e1731f3 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -55,7 +55,7 @@ fn unary_pattern(pat: &Pat<'_>) -> bool { | PatKind::Err(_) => false, PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| unary_pattern(x.pat)), PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a), - PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), + PatKind::Ref(x, _, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), PatKind::Expr(_) => true, } } diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index c38cf83f44100..3455a47ba078d 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -43,7 +43,7 @@ pub(super) fn check<'tcx>( let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); // Checks if `pat` is a single reference to a binding (`&x`) let is_ref_to_binding = - matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); + matches!(pat.kind, PatKind::Ref(inner, _, _) if matches!(inner.kind, PatKind::Binding(..))); // If `pat` is not a binding or a reference to a binding (`x` or `&x`) // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`) if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) { diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 674f0da818f5f..6870c9819fc06 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -157,7 +157,7 @@ fn check_iter( ), ); }, - hir::PatKind::Ref(pat, _) => make_span_lint_and_sugg( + hir::PatKind::Ref(pat, _, _) => make_span_lint_and_sugg( cx, parent_expr_span, format!( @@ -196,7 +196,7 @@ fn check_to_owned( && let filter_body = cx.tcx.hir_body(closure.body) && let [filter_params] = filter_body.params && msrv.meets(cx, msrvs::STRING_RETAIN) - && let hir::PatKind::Ref(pat, _) = filter_params.pat.kind + && let hir::PatKind::Ref(pat, _, _) = filter_params.pat.kind { make_span_lint_and_sugg( cx, diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index c9293412fba82..9ced6c9d452ba 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -78,7 +78,7 @@ fn is_variant_or_wildcard(cx: &LateContext<'_>, pat: &Pat<'_>, can_be_wild: bool .is_lang_item(cx, ResultErr) == must_match_err }, - PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) => { + PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _, _) => { is_variant_or_wildcard(cx, pat, can_be_wild, must_match_err) }, _ => false, diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 235cb9e4ecce8..19b3572bd3ae7 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -254,7 +254,7 @@ pub(super) fn try_parse_pattern<'tcx>( ) -> Option> { match pat.kind { PatKind::Wild => Some(OptionPat::Wild), - PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), + PatKind::Ref(pat, _, _) => f(cx, pat, ref_count + 1, ctxt), PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, diff --git a/clippy_lints/src/matches/match_ref_pats.rs b/clippy_lints/src/matches/match_ref_pats.rs index 5934ec409935a..042817f5cdb82 100644 --- a/clippy_lints/src/matches/match_ref_pats.rs +++ b/clippy_lints/src/matches/match_ref_pats.rs @@ -50,7 +50,7 @@ where } let remaining_suggs = pats.filter_map(|pat| { - if let PatKind::Ref(refp, _) = pat.kind { + if let PatKind::Ref(refp, _, _) = pat.kind { Some((pat.span, snippet(cx, refp.span, "..").to_string())) } else { None diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index be914429edb44..c20217563d62b 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -264,7 +264,7 @@ impl<'a> NormalizedPat<'a> { PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Deref(pat) - | PatKind::Ref(pat, _) + | PatKind::Ref(pat, _, _) | PatKind::Guard(pat, _) => Self::from_pat(cx, arena, pat), PatKind::Never => Self::Never, PatKind::Struct(ref path, fields, _) => { diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 7a1dd94567b14..757ecf75ed45e 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: && !pat_contains_disallowed_or(cx, arm.pat, msrv) { let pat_span = match (arm.pat.kind, binding.byref_ident) { - (PatKind::Ref(pat, _), Some(_)) => pat.span, + (PatKind::Ref(pat, _, _), Some(_)) => pat.span, (PatKind::Ref(..), None) | (_, Some(_)) => continue, _ => arm.pat.span, }; @@ -49,7 +49,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: && !pat_contains_disallowed_or(cx, let_expr.pat, msrv) { let pat_span = match (let_expr.pat.kind, binding.byref_ident) { - (PatKind::Ref(pat, _), Some(_)) => pat.span, + (PatKind::Ref(pat, _, _), Some(_)) => pat.span, (PatKind::Ref(..), None) | (_, Some(_)) => continue, _ => let_expr.pat.span, }; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index a0f88cf911d84..bc3783750e5cc 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -186,7 +186,7 @@ fn find_method_sugg_for_if_let<'tcx>( // also look inside refs // if we have &None for example, peel it so we can detect "if let None = x" let check_pat = match let_pat.kind { - PatKind::Ref(inner, _mutability) => inner, + PatKind::Ref(inner, _pinnedness, _mutability) => inner, _ => let_pat, }; let op_ty = cx.typeck_results().expr_ty(let_expr); diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 44c4d7a31ff31..57a91cf846b83 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -373,7 +373,7 @@ impl<'a> PatState<'a> { }, // Patterns for things which can only contain a single sub-pattern. - PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) | PatKind::Box(pat) | PatKind::Deref(pat) => { + PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _, _) | PatKind::Box(pat) | PatKind::Deref(pat) => { self.add_pat(cx, pat) }, PatKind::Tuple([sub_pat], pos) diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 26b19848fe1b6..7b10c37de42df 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -404,7 +404,7 @@ fn is_find_or_filter<'a>( && let filter_body = cx.tcx.hir_body(filter_body_id) && let [filter_param] = filter_body.params // optional ref pattern: `filter(|&x| ..)` - && let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + && let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _, _) = filter_param.pat.kind { (ref_pat, true) } else { (filter_param.pat, false) diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs index 8d95b70c6a4bc..aaface3aa971d 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints/src/methods/iter_filter.rs @@ -50,7 +50,7 @@ fn is_method( fn pat_is_recv(ident: Ident, param: &hir::Pat<'_>) -> bool { match param.kind { hir::PatKind::Binding(_, _, other, _) => ident == other, - hir::PatKind::Ref(pat, _) => pat_is_recv(ident, pat), + hir::PatKind::Ref(pat, _, _) => pat_is_recv(ident, pat), _ => false, } } diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index d43dc23a86b23..1c26648e26eb1 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -81,7 +81,7 @@ pub(super) fn check<'tcx>( } match it.kind { - PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) | PatKind::Ref(_, Mutability::Mut) => { + PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) | PatKind::Ref(_, _, Mutability::Mut) => { to_be_discarded = true; false }, diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index 1bc29c9c1dd1c..a1aac96ccf86a 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -8,7 +8,7 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem}; use rustc_lint::LateContext; -use rustc_middle::mir::Mutability; +use rustc_middle::mir::{Mutability, Pinnedness}; use rustc_middle::ty; use rustc_middle::ty::adjustment::Adjust; use rustc_span::symbol::Ident; @@ -50,7 +50,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ let closure_body = cx.tcx.hir_body(body); let closure_expr = peel_blocks(closure_body.value); match closure_body.params[0].pat.kind { - hir::PatKind::Ref(inner, Mutability::Not) => { + hir::PatKind::Ref(inner, Pinnedness::Not, Mutability::Not) => { if let hir::PatKind::Binding(hir::BindingMode::NONE, .., name, None) = inner.kind && ident_eq(name, closure_expr) { diff --git a/clippy_lints/src/needless_borrowed_ref.rs b/clippy_lints/src/needless_borrowed_ref.rs index 1b6896827fed8..29b4da93b7fb2 100644 --- a/clippy_lints/src/needless_borrowed_ref.rs +++ b/clippy_lints/src/needless_borrowed_ref.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; -use rustc_hir::{BindingMode, Mutability, Node, Pat, PatKind}; +use rustc_hir::{BindingMode, Mutability, Node, Pat, PatKind, Pinnedness}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -37,7 +37,7 @@ declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]); impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef { fn check_pat(&mut self, cx: &LateContext<'tcx>, ref_pat: &'tcx Pat<'_>) { - if let PatKind::Ref(pat, Mutability::Not) = ref_pat.kind + if let PatKind::Ref(pat, Pinnedness::Not, Mutability::Not) = ref_pat.kind && !ref_pat.span.from_expansion() && cx .tcx diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 4621c22d6b532..bbb1b831888fd 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -7,7 +7,7 @@ use clippy_utils::msrvs::{self, MsrvStack}; use clippy_utils::over; use rustc_ast::PatKind::*; use rustc_ast::mut_visit::*; -use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind}; +use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind, Pinnedness}; use rustc_ast_pretty::pprust; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; use rustc_errors::Applicability; @@ -151,8 +151,8 @@ fn insert_necessary_parens(pat: &mut Box) { walk_pat(self, pat); let target = match &mut pat.kind { // `i @ a | b`, `box a | b`, and `& mut? a | b`. - Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, - Ref(p, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, // `&(mut x)` + Ident(.., Some(p)) | Box(p) | Ref(p, _, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, + Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, // `&(mut x)` _ => return, }; target.kind = Paren(Box::new(take_pat(target))); @@ -241,7 +241,8 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec, focus_idx: usize // Skip immutable refs, as grouping them saves few characters, // and almost always requires adding parens (increasing noisiness). // In the case of only two patterns, replacement adds net characters. - | Ref(_, Mutability::Not) + // FIXME(pin_ergonomics): handle pinned patterns + | Ref(_, _, Mutability::Not) // Dealt with elsewhere. | Or(_) | Paren(_) | Deref(_) | Guard(..) => false, // Transform `box x | ... | box y` into `box (x | y)`. @@ -254,10 +255,10 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec, focus_idx: usize |k| always_pat!(k, Box(p) => *p), ), // Transform `&mut x | ... | &mut y` into `&mut (x | y)`. - Ref(target, Mutability::Mut) => extend_with_matching( + Ref(target, _, Mutability::Mut) => extend_with_matching( target, start, alternatives, - |k| matches!(k, Ref(_, Mutability::Mut)), - |k| always_pat!(k, Ref(p, _) => *p), + |k| matches!(k, Ref(_, _, Mutability::Mut)), + |k| always_pat!(k, Ref(p, _, _) => *p), ), // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`. Ident(b1, i1, Some(target)) => extend_with_matching( diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 7f9dbdd885a10..57f889818be37 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -793,9 +793,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { kind!("Deref({pat})"); self.pat(pat); }, - PatKind::Ref(pat, muta) => { + PatKind::Ref(pat, pinn, muta) => { bind!(self, pat); - kind!("Ref({pat}, Mutability::{muta:?})"); + kind!("Ref({pat}, Pinning::{pinn:?}, Mutability::{muta:?})"); self.pat(pat); }, PatKind::Guard(pat, cond) => { diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 771862ee5b964..208aa98f12f22 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -45,9 +45,8 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { && eq_expr_opt(lt.as_deref(), rt.as_deref()) && eq_range_end(&le.node, &re.node) }, - (Box(l), Box(r)) - | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) - | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), + (Box(l), Box(r)) => eq_pat(l, r), + (Ref(l, l_pin, l_mut), Ref(r, r_pin, r_mut)) => l_pin == r_pin && l_mut == r_mut && eq_pat(l, r), (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, eq_pat), (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index dd411fe21bdd3..2433ca8b97f27 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -8,9 +8,9 @@ use rustc_data_structures::fx::FxHasher; use rustc_hir::MatchSource::TryDesugar; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ - AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField, - ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeKind, - Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, + AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, ByRef, Closure, ConstArg, ConstArgKind, Expr, + ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, + LifetimeKind, Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; @@ -538,7 +538,7 @@ impl HirEqInterExpr<'_, '_, '_> { && both(le.as_ref(), re.as_ref(), |a, b| self.eq_pat_expr(a, b)) && (li == ri) }, - (PatKind::Ref(le, lm), PatKind::Ref(re, rm)) => lm == rm && self.eq_pat(le, re), + (PatKind::Ref(le, lp, lm), PatKind::Ref(re, rp, rm)) => lp == rp && lm == rm && self.eq_pat(le, re), (PatKind::Slice(ls, li, le), PatKind::Slice(rs, ri, re)) => { over(ls, rs, |l, r| self.eq_pat(l, r)) && over(le, re, |l, r| self.eq_pat(l, r)) @@ -1131,6 +1131,10 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { PatKind::Missing => unreachable!(), PatKind::Binding(BindingMode(by_ref, mutability), _, _, pat) => { std::mem::discriminant(by_ref).hash(&mut self.s); + if let ByRef::Yes(pi, mu) = by_ref { + std::mem::discriminant(pi).hash(&mut self.s); + std::mem::discriminant(mu).hash(&mut self.s); + } std::mem::discriminant(mutability).hash(&mut self.s); if let Some(pat) = pat { self.hash_pat(pat); @@ -1152,8 +1156,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } std::mem::discriminant(i).hash(&mut self.s); }, - PatKind::Ref(pat, mu) => { + PatKind::Ref(pat, pi, mu) => { self.hash_pat(pat); + std::mem::discriminant(pi).hash(&mut self.s); std::mem::discriminant(mu).hash(&mut self.s); }, PatKind::Guard(pat, guard) => { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 4c0d70d320b9f..d98e3073b41d1 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1462,7 +1462,7 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { PatKind::Missing => unreachable!(), PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable. PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)), - PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat), + PatKind::Box(pat) | PatKind::Ref(pat, _, _) => is_refutable(cx, pat), PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, @@ -1612,7 +1612,7 @@ pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> } pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> { - while let PatKind::Ref(subpat, _) = pat.kind { + while let PatKind::Ref(subpat, _, _) = pat.kind { pat = subpat; } pat @@ -2157,7 +2157,7 @@ where /// references removed. pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) { - if let PatKind::Ref(pat, _) = pat.kind { + if let PatKind::Ref(pat, _, _) = pat.kind { peel(pat, count + 1) } else { (pat, count) From d16f26b9442871416ee035160bedd32834d89482 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 25 May 2025 00:47:17 +0200 Subject: [PATCH 62/80] Allow `function_casts_as_integer` in non-related clippy ui tests --- tests/ui/cast_enum_constructor.rs | 2 +- .../ui/confusing_method_to_numeric_cast.fixed | 1 + tests/ui/confusing_method_to_numeric_cast.rs | 1 + .../confusing_method_to_numeric_cast.stderr | 16 +++---- tests/ui/derive.rs | 1 - tests/ui/derive.stderr | 10 ++-- tests/ui/fn_to_numeric_cast.64bit.stderr | 46 +++++++++---------- tests/ui/fn_to_numeric_cast.rs | 1 + tests/ui/fn_to_numeric_cast_any.rs | 1 + tests/ui/fn_to_numeric_cast_any.stderr | 34 +++++++------- tests/ui/ptr_eq.fixed | 1 + tests/ui/ptr_eq.rs | 1 + tests/ui/ptr_eq.stderr | 12 ++--- .../transmutes_expressible_as_ptr_casts.fixed | 1 + .../ui/transmutes_expressible_as_ptr_casts.rs | 1 + ...transmutes_expressible_as_ptr_casts.stderr | 18 ++++---- 16 files changed, 77 insertions(+), 70 deletions(-) diff --git a/tests/ui/cast_enum_constructor.rs b/tests/ui/cast_enum_constructor.rs index eecf56f71a334..6d70387b6ce29 100644 --- a/tests/ui/cast_enum_constructor.rs +++ b/tests/ui/cast_enum_constructor.rs @@ -1,5 +1,5 @@ #![warn(clippy::cast_enum_constructor)] -#![allow(clippy::fn_to_numeric_cast)] +#![allow(clippy::fn_to_numeric_cast, function_casts_as_integer)] fn main() { enum Foo { diff --git a/tests/ui/confusing_method_to_numeric_cast.fixed b/tests/ui/confusing_method_to_numeric_cast.fixed index e698b99edd5c0..b9691a3284ac0 100644 --- a/tests/ui/confusing_method_to_numeric_cast.fixed +++ b/tests/ui/confusing_method_to_numeric_cast.fixed @@ -1,5 +1,6 @@ #![feature(float_minimum_maximum)] #![warn(clippy::confusing_method_to_numeric_cast)] +#![allow(function_casts_as_integer)] fn main() { let _ = u16::MAX as usize; //~ confusing_method_to_numeric_cast diff --git a/tests/ui/confusing_method_to_numeric_cast.rs b/tests/ui/confusing_method_to_numeric_cast.rs index ef65c21563d98..b402cbf92cb2b 100644 --- a/tests/ui/confusing_method_to_numeric_cast.rs +++ b/tests/ui/confusing_method_to_numeric_cast.rs @@ -1,5 +1,6 @@ #![feature(float_minimum_maximum)] #![warn(clippy::confusing_method_to_numeric_cast)] +#![allow(function_casts_as_integer)] fn main() { let _ = u16::max as usize; //~ confusing_method_to_numeric_cast diff --git a/tests/ui/confusing_method_to_numeric_cast.stderr b/tests/ui/confusing_method_to_numeric_cast.stderr index ba90df2059af6..0d5e08f880773 100644 --- a/tests/ui/confusing_method_to_numeric_cast.stderr +++ b/tests/ui/confusing_method_to_numeric_cast.stderr @@ -1,5 +1,5 @@ error: casting function pointer `u16::max` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:5:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:6:13 | LL | let _ = u16::max as usize; | ^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let _ = u16::MAX as usize; | error: casting function pointer `u16::min` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:6:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:7:13 | LL | let _ = u16::min as usize; | ^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let _ = u16::MIN as usize; | error: casting function pointer `u16::max_value` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:7:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:8:13 | LL | let _ = u16::max_value as usize; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + let _ = u16::MAX as usize; | error: casting function pointer `u16::min_value` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:8:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:9:13 | LL | let _ = u16::min_value as usize; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + let _ = u16::MIN as usize; | error: casting function pointer `f32::maximum` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:10:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:11:13 | LL | let _ = f32::maximum as usize; | ^^^^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + let _ = f32::MAX as usize; | error: casting function pointer `f32::max` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:11:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:12:13 | LL | let _ = f32::max as usize; | ^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + let _ = f32::MAX as usize; | error: casting function pointer `f32::minimum` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:12:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:13:13 | LL | let _ = f32::minimum as usize; | ^^^^^^^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + let _ = f32::MIN as usize; | error: casting function pointer `f32::min` to `usize` - --> tests/ui/confusing_method_to_numeric_cast.rs:13:13 + --> tests/ui/confusing_method_to_numeric_cast.rs:14:13 | LL | let _ = f32::min as usize; | ^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/derive.rs b/tests/ui/derive.rs index 6b60421c35490..036f6c444b649 100644 --- a/tests/ui/derive.rs +++ b/tests/ui/derive.rs @@ -9,7 +9,6 @@ #![expect(incomplete_features)] // `unsafe_fields` is incomplete for the time being #![feature(unsafe_fields)] // `clone()` cannot be derived automatically on unsafe fields - #[derive(Copy)] struct Qux; diff --git a/tests/ui/derive.stderr b/tests/ui/derive.stderr index 2b97a58e9d6f6..2701680e788de 100644 --- a/tests/ui/derive.stderr +++ b/tests/ui/derive.stderr @@ -1,5 +1,5 @@ error: you are implementing `Clone` explicitly on a `Copy` type - --> tests/ui/derive.rs:16:1 + --> tests/ui/derive.rs:15:1 | LL | / impl Clone for Qux { LL | | @@ -14,7 +14,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]` error: you are implementing `Clone` explicitly on a `Copy` type - --> tests/ui/derive.rs:42:1 + --> tests/ui/derive.rs:41:1 | LL | / impl<'a> Clone for Lt<'a> { LL | | @@ -27,7 +27,7 @@ LL | | } = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type - --> tests/ui/derive.rs:55:1 + --> tests/ui/derive.rs:54:1 | LL | / impl Clone for BigArray { LL | | @@ -40,7 +40,7 @@ LL | | } = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type - --> tests/ui/derive.rs:68:1 + --> tests/ui/derive.rs:67:1 | LL | / impl Clone for FnPtr { LL | | @@ -53,7 +53,7 @@ LL | | } = help: consider deriving `Clone` or removing `Copy` error: you are implementing `Clone` explicitly on a `Copy` type - --> tests/ui/derive.rs:90:1 + --> tests/ui/derive.rs:89:1 | LL | / impl Clone for Generic2 { LL | | diff --git a/tests/ui/fn_to_numeric_cast.64bit.stderr b/tests/ui/fn_to_numeric_cast.64bit.stderr index 48961d14f2bba..694690ae5bfa2 100644 --- a/tests/ui/fn_to_numeric_cast.64bit.stderr +++ b/tests/ui/fn_to_numeric_cast.64bit.stderr @@ -1,5 +1,5 @@ error: casting function pointer `foo` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:12:13 + --> tests/ui/fn_to_numeric_cast.rs:13:13 | LL | let _ = foo as i8; | ^^^^^^^^^ help: try: `foo as usize` @@ -8,19 +8,19 @@ LL | let _ = foo as i8; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast_with_truncation)]` error: casting function pointer `foo` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:14:13 + --> tests/ui/fn_to_numeric_cast.rs:15:13 | LL | let _ = foo as i16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i32`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:16:13 + --> tests/ui/fn_to_numeric_cast.rs:17:13 | LL | let _ = foo as i32; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:19:13 + --> tests/ui/fn_to_numeric_cast.rs:20:13 | LL | let _ = foo as i64; | ^^^^^^^^^^ help: try: `foo as usize` @@ -29,115 +29,115 @@ LL | let _ = foo as i64; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast)]` error: casting function pointer `foo` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:21:13 + --> tests/ui/fn_to_numeric_cast.rs:22:13 | LL | let _ = foo as i128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:23:13 + --> tests/ui/fn_to_numeric_cast.rs:24:13 | LL | let _ = foo as isize; | ^^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:26:13 + --> tests/ui/fn_to_numeric_cast.rs:27:13 | LL | let _ = foo as u8; | ^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:28:13 + --> tests/ui/fn_to_numeric_cast.rs:29:13 | LL | let _ = foo as u16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u32`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:30:13 + --> tests/ui/fn_to_numeric_cast.rs:31:13 | LL | let _ = foo as u32; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:33:13 + --> tests/ui/fn_to_numeric_cast.rs:34:13 | LL | let _ = foo as u64; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:35:13 + --> tests/ui/fn_to_numeric_cast.rs:36:13 | LL | let _ = foo as u128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `abc` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:49:13 + --> tests/ui/fn_to_numeric_cast.rs:50:13 | LL | let _ = abc as i8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:51:13 + --> tests/ui/fn_to_numeric_cast.rs:52:13 | LL | let _ = abc as i16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i32`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:53:13 + --> tests/ui/fn_to_numeric_cast.rs:54:13 | LL | let _ = abc as i32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:56:13 + --> tests/ui/fn_to_numeric_cast.rs:57:13 | LL | let _ = abc as i64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:58:13 + --> tests/ui/fn_to_numeric_cast.rs:59:13 | LL | let _ = abc as i128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:60:13 + --> tests/ui/fn_to_numeric_cast.rs:61:13 | LL | let _ = abc as isize; | ^^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:63:13 + --> tests/ui/fn_to_numeric_cast.rs:64:13 | LL | let _ = abc as u8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:65:13 + --> tests/ui/fn_to_numeric_cast.rs:66:13 | LL | let _ = abc as u16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u32`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:67:13 + --> tests/ui/fn_to_numeric_cast.rs:68:13 | LL | let _ = abc as u32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:70:13 + --> tests/ui/fn_to_numeric_cast.rs:71:13 | LL | let _ = abc as u64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:72:13 + --> tests/ui/fn_to_numeric_cast.rs:73:13 | LL | let _ = abc as u128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `f` to `i32`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:80:5 + --> tests/ui/fn_to_numeric_cast.rs:81:5 | LL | f as i32 | ^^^^^^^^ help: try: `f as usize` diff --git a/tests/ui/fn_to_numeric_cast.rs b/tests/ui/fn_to_numeric_cast.rs index f53cbacdb3771..0a07aeff366eb 100644 --- a/tests/ui/fn_to_numeric_cast.rs +++ b/tests/ui/fn_to_numeric_cast.rs @@ -3,6 +3,7 @@ //@[64bit]ignore-bitwidth: 32 //@no-rustfix #![warn(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] +#![allow(function_casts_as_integer)] fn foo() -> String { String::new() diff --git a/tests/ui/fn_to_numeric_cast_any.rs b/tests/ui/fn_to_numeric_cast_any.rs index 42f2128cd3783..83c1e9a8387ef 100644 --- a/tests/ui/fn_to_numeric_cast_any.rs +++ b/tests/ui/fn_to_numeric_cast_any.rs @@ -1,5 +1,6 @@ #![warn(clippy::fn_to_numeric_cast_any)] #![allow(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] +#![allow(function_casts_as_integer)] //@no-rustfix fn foo() -> u8 { 0 diff --git a/tests/ui/fn_to_numeric_cast_any.stderr b/tests/ui/fn_to_numeric_cast_any.stderr index 58fac2d406a0f..f7c49b8ff88b5 100644 --- a/tests/ui/fn_to_numeric_cast_any.stderr +++ b/tests/ui/fn_to_numeric_cast_any.stderr @@ -1,5 +1,5 @@ error: casting function pointer `foo` to `i8` - --> tests/ui/fn_to_numeric_cast_any.rs:23:13 + --> tests/ui/fn_to_numeric_cast_any.rs:24:13 | LL | let _ = foo as i8; | ^^^^^^^^^ @@ -12,7 +12,7 @@ LL | let _ = foo() as i8; | ++ error: casting function pointer `foo` to `i16` - --> tests/ui/fn_to_numeric_cast_any.rs:26:13 + --> tests/ui/fn_to_numeric_cast_any.rs:27:13 | LL | let _ = foo as i16; | ^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | let _ = foo() as i16; | ++ error: casting function pointer `foo` to `i32` - --> tests/ui/fn_to_numeric_cast_any.rs:29:13 + --> tests/ui/fn_to_numeric_cast_any.rs:30:13 | LL | let _ = foo as i32; | ^^^^^^^^^^ @@ -34,7 +34,7 @@ LL | let _ = foo() as i32; | ++ error: casting function pointer `foo` to `i64` - --> tests/ui/fn_to_numeric_cast_any.rs:32:13 + --> tests/ui/fn_to_numeric_cast_any.rs:33:13 | LL | let _ = foo as i64; | ^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | let _ = foo() as i64; | ++ error: casting function pointer `foo` to `i128` - --> tests/ui/fn_to_numeric_cast_any.rs:35:13 + --> tests/ui/fn_to_numeric_cast_any.rs:36:13 | LL | let _ = foo as i128; | ^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = foo() as i128; | ++ error: casting function pointer `foo` to `isize` - --> tests/ui/fn_to_numeric_cast_any.rs:38:13 + --> tests/ui/fn_to_numeric_cast_any.rs:39:13 | LL | let _ = foo as isize; | ^^^^^^^^^^^^ @@ -67,7 +67,7 @@ LL | let _ = foo() as isize; | ++ error: casting function pointer `foo` to `u8` - --> tests/ui/fn_to_numeric_cast_any.rs:41:13 + --> tests/ui/fn_to_numeric_cast_any.rs:42:13 | LL | let _ = foo as u8; | ^^^^^^^^^ @@ -78,7 +78,7 @@ LL | let _ = foo() as u8; | ++ error: casting function pointer `foo` to `u16` - --> tests/ui/fn_to_numeric_cast_any.rs:44:13 + --> tests/ui/fn_to_numeric_cast_any.rs:45:13 | LL | let _ = foo as u16; | ^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | let _ = foo() as u16; | ++ error: casting function pointer `foo` to `u32` - --> tests/ui/fn_to_numeric_cast_any.rs:47:13 + --> tests/ui/fn_to_numeric_cast_any.rs:48:13 | LL | let _ = foo as u32; | ^^^^^^^^^^ @@ -100,7 +100,7 @@ LL | let _ = foo() as u32; | ++ error: casting function pointer `foo` to `u64` - --> tests/ui/fn_to_numeric_cast_any.rs:50:13 + --> tests/ui/fn_to_numeric_cast_any.rs:51:13 | LL | let _ = foo as u64; | ^^^^^^^^^^ @@ -111,7 +111,7 @@ LL | let _ = foo() as u64; | ++ error: casting function pointer `foo` to `u128` - --> tests/ui/fn_to_numeric_cast_any.rs:53:13 + --> tests/ui/fn_to_numeric_cast_any.rs:54:13 | LL | let _ = foo as u128; | ^^^^^^^^^^^ @@ -122,7 +122,7 @@ LL | let _ = foo() as u128; | ++ error: casting function pointer `foo` to `usize` - --> tests/ui/fn_to_numeric_cast_any.rs:56:13 + --> tests/ui/fn_to_numeric_cast_any.rs:57:13 | LL | let _ = foo as usize; | ^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL | let _ = foo() as usize; | ++ error: casting function pointer `Struct::static_method` to `usize` - --> tests/ui/fn_to_numeric_cast_any.rs:61:13 + --> tests/ui/fn_to_numeric_cast_any.rs:62:13 | LL | let _ = Struct::static_method as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -144,7 +144,7 @@ LL | let _ = Struct::static_method() as usize; | ++ error: casting function pointer `f` to `usize` - --> tests/ui/fn_to_numeric_cast_any.rs:66:5 + --> tests/ui/fn_to_numeric_cast_any.rs:67:5 | LL | f as usize | ^^^^^^^^^^ @@ -155,7 +155,7 @@ LL | f() as usize | ++ error: casting function pointer `T::static_method` to `usize` - --> tests/ui/fn_to_numeric_cast_any.rs:71:5 + --> tests/ui/fn_to_numeric_cast_any.rs:72:5 | LL | T::static_method as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -166,7 +166,7 @@ LL | T::static_method() as usize | ++ error: casting function pointer `(clos as fn(u32) -> u32)` to `usize` - --> tests/ui/fn_to_numeric_cast_any.rs:78:13 + --> tests/ui/fn_to_numeric_cast_any.rs:79:13 | LL | let _ = (clos as fn(u32) -> u32) as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -177,7 +177,7 @@ LL | let _ = (clos as fn(u32) -> u32)() as usize; | ++ error: casting function pointer `foo` to `*const ()` - --> tests/ui/fn_to_numeric_cast_any.rs:83:13 + --> tests/ui/fn_to_numeric_cast_any.rs:84:13 | LL | let _ = foo as *const (); | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/ptr_eq.fixed b/tests/ui/ptr_eq.fixed index 9629b3eea5870..d3624a129b5fd 100644 --- a/tests/ui/ptr_eq.fixed +++ b/tests/ui/ptr_eq.fixed @@ -1,4 +1,5 @@ #![warn(clippy::ptr_eq)] +#![allow(function_casts_as_integer)] macro_rules! mac { ($a:expr, $b:expr) => { diff --git a/tests/ui/ptr_eq.rs b/tests/ui/ptr_eq.rs index 2b741d8df4684..f06a99cabc814 100644 --- a/tests/ui/ptr_eq.rs +++ b/tests/ui/ptr_eq.rs @@ -1,4 +1,5 @@ #![warn(clippy::ptr_eq)] +#![allow(function_casts_as_integer)] macro_rules! mac { ($a:expr, $b:expr) => { diff --git a/tests/ui/ptr_eq.stderr b/tests/ui/ptr_eq.stderr index e7340624b5950..f6be4c3f016b5 100644 --- a/tests/ui/ptr_eq.stderr +++ b/tests/ui/ptr_eq.stderr @@ -1,5 +1,5 @@ error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:22:13 + --> tests/ui/ptr_eq.rs:23:13 | LL | let _ = a as *const _ as usize == b as *const _ as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(a, b)` @@ -8,31 +8,31 @@ LL | let _ = a as *const _ as usize == b as *const _ as usize; = help: to override `-D warnings` add `#[allow(clippy::ptr_eq)]` error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:24:13 + --> tests/ui/ptr_eq.rs:25:13 | LL | let _ = a as *const _ == b as *const _; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(a, b)` error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:50:13 + --> tests/ui/ptr_eq.rs:51:13 | LL | let _ = x as *const u32 == y as *mut u32 as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(x, y)` error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:53:13 + --> tests/ui/ptr_eq.rs:54:13 | LL | let _ = x as *const u32 != y as *mut u32 as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!std::ptr::eq(x, y)` error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:61:13 + --> tests/ui/ptr_eq.rs:62:13 | LL | let _ = mac!(cast a) as *const _ == mac!(cast b) as *const _; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(mac!(cast a), mac!(cast b))` error: use `std::ptr::eq` when comparing raw pointers - --> tests/ui/ptr_eq.rs:65:13 + --> tests/ui/ptr_eq.rs:66:13 | LL | let _ = mac!(cast a) as *const _ == mac!(cast b) as *const _; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::eq(mac!(cast a), mac!(cast b))` diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/tests/ui/transmutes_expressible_as_ptr_casts.fixed index 02f67f79e2b19..f332e02a2d327 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -4,6 +4,7 @@ #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] #![allow(unused, clippy::borrow_as_ptr, clippy::missing_transmute_annotations)] +#![allow(function_casts_as_integer)] use std::mem::{size_of, transmute}; diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.rs b/tests/ui/transmutes_expressible_as_ptr_casts.rs index c5e156405ebca..c29a42ddca53b 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -4,6 +4,7 @@ #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] #![allow(unused, clippy::borrow_as_ptr, clippy::missing_transmute_annotations)] +#![allow(function_casts_as_integer)] use std::mem::{size_of, transmute}; diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/tests/ui/transmutes_expressible_as_ptr_casts.stderr index f39a64d57eb49..5ddc3de6a039f 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -1,5 +1,5 @@ error: transmute from a pointer to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:19:38 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:20:38 | LL | let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr_i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let _ptr_i8_transmute = unsafe { ptr_i32.cast::() }; | error: transmute from a pointer to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:26:46 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:27:46 | LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] }; | error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:33:50 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:34:50 | LL | let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, usize>(ptr_i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr_i32 as usize` @@ -34,7 +34,7 @@ LL | let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, us = help: to override `-D warnings` add `#[allow(clippy::transmutes_expressible_as_ptr_casts)]` error: transmute from a reference to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:40:41 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:41:41 | LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]` @@ -43,31 +43,31 @@ LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]` error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:49:41 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:50:41 | LL | let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize` error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:54:49 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:55:49 | LL | let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as usize` error: transmute from `*const u32` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:58:36 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:59:36 | LL | let _usize_from_ref = unsafe { transmute::<*const u32, usize>(&1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&1u32 as *const u32 as usize` error: transmute from a reference to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:70:14 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:71:14 | LL | unsafe { transmute::<&[i32; 1], *const u8>(in_param) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8` error: transmute from `fn()` to `*const u8` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:89:28 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:90:28 | LL | let _x: u8 = unsafe { *std::mem::transmute::(f) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(f as *const u8)` From 4e35148a502ace4f1608d9f78a8075a824a0266d Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 31 Oct 2025 16:57:49 +0000 Subject: [PATCH 63/80] Fix website history interactions --- util/gh-pages/index_template.html | 76 +++-- util/gh-pages/script.js | 520 ++++++++++-------------------- util/gh-pages/style.css | 14 +- 3 files changed, 233 insertions(+), 377 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index d34ff0a597329..e443baff08086 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -28,7 +28,7 @@ {# #} {# #} {# #} -
{# #} +