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 82d5310663ee..449f5dabe23f 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 7e899a476666..fa82a316d64d 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 37a96f2287c8..6c1fae89e230 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 82fc43aaa5ea..c2d9d168b4a1 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 2d42e76dcbc9..169f07f28aef 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 169f07f28aef..aef5c59e8f10 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 aef5c59e8f10..b4fafc998229 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 81fecc87256c..bac35e7f8c70 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 dcce90649958..b4e7c771a54f 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 68e51dace2db..4b157dc41142 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 d6cf07fdaf3f..b490866f0a11 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 b4fafc998229..671b266ba008 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 8250104b0ca2..58aa82675aae 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 7cd5a16f5b46..4e06f010bd59 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 46d9febb187f..0f0a83f455ef 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 c71a4072b962..93811a0ad2fc 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 686c2644da17..a644a29a03c9 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 7500523ab4ac..e7000bf506b6 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 27059e3f2e1f..8e0adec48da7 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 5261eae2f26a..1ec8abbdae99 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 786715463232..6997a5a28349 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 0f0a83f455ef..d0fd5c63ed04 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 d0fd5c63ed04..91b180e203a8 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 91b180e203a8..3d781d68237b 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 93811a0ad2fc..a41f98c8fa35 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 a644a29a03c9..879f60647472 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 e7000bf506b6..e31e9c9c8fdf 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: aborting due to 8 previous errors +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 9 previous errors diff --git a/tests/ui/rc_buffer_arc.fixed b/tests/ui/rc_buffer_arc.fixed index 8e0adec48da7..36b989ec1b60 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 1ec8abbdae99..f8e78dc5c18f 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 6997a5a28349..043f7a15ec00 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: aborting due to 8 previous errors +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 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 3d781d68237b..34fd3ed2dc86 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 6e9aebcf18ae..4353f6302c4b 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 c9c1f4865b81..400f22580582 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 c9066be51c44..8e0733eff87b 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 400f22580582..5f1cae130dae 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 000000000000..2a05b8805e4d --- /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 efb56f242a74..3761aa26f6e8 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 a9e3533d8ca1..848a10e671db 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 449f5dabe23f..e40e21c490f3 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 c2d9d168b4a1..8a402dcca847 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 c9066be51c44..68e07dec9b72 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 6323e728666c..5e584e85a3c9 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 000000000000..d7fb5ae2283f --- /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 90ea2616890a..0cf1ad348953 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 d7224052ebc5..afa6f22ea75b 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 235cb9e4ecce..1026bc49f1f6 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 410e973f855b..54d2062bfc12 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 3285023b34aa..0f821027fe75 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 c32c74a8fe60..da6da32889cb 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 3ea6ba341bed..2545f81f1afa 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 3342bf834785..9cf2d5d85433 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 fb83f7cf65dd..1ebbd209ae52 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 1026bc49f1f6..485d6bdc6ee8 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 7ac57fae690c..f633c5839739 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 da6da32889cb..85cf483fce90 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 c8943523e179..e1ba40934620 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 dfc8411baa00..7f3ef58c93d1 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 c38cf83f4410..34917252d049 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 afa6f22ea75b..da68f8421c16 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 c9293412fba8..3259655c9a1f 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 485d6bdc6ee8..09e49c16624c 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 f633c5839739..30d703df4df4 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 ac3cbaec55f3..0f32f89666a0 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 8183c30f8c56..cdef98be14af 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 342ffea51d65..817388915f18 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 e2946c22a46b..d5477b9be4c1 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 7f729ac7ca94..72f1c42da2ee 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 54d2062bfc12..da6f03931e24 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 e1ba40934620..cfe2442461ca 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 0a6499e09583..91a069559f7b 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) { + 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; } - if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { - return; - } - - let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { + if 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, + (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(n), None) => match (&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) - => true, - _ => false, - }, - (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 3245b2c983e1..b7ed596d811e 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 4150493ba94a..22742a82601a 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 a064a5910ef9..edd2a1e0b858 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 b893b0baad49..ef30d4d51ef8 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 a26dd1deebb1..c1af8a5757b9 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 a7d9b2e0fab0..000000000000 --- 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 edd2a1e0b858..21198c3c8bc2 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 ef30d4d51ef8..82ded453616d 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 14d1d20a66e4..c742cc8a85ba 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 22b74ab16d61..4df1eb508713 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 4f005103d23f..055fdcabdd21 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 842d77dbc8c5..ba1451bf9704 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 98d8d27321d2..e054cd01e6f5 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 00745eb2923c..c77674dc55d4 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 919702c5714a..e95816353df6 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 c9b6821ad98f..9c6cf66019f0 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 d39e315cae1f..7a1dd94567b1 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 2a0ae14a4b08..9cdad980f238 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 e67ea1f5e370..928a4e02fa97 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 074b79263d37..250c277ab5e1 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 68e51dace2db..92cc11dae8b1 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 eb3f442ac754..6b922a20ca20 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 6ee991eae137..7b3de69d9086 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 e69de29bb2d1..000000000000 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 1e0d5e213199..0593dbb00fb6 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 76f5fdfaa8dc..22767901614e 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 4df1eb508713..839b46325b5e 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 29352fd468ea..ae29f3a57462 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 928727527592..d930339e106c 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 ca37406c8114..374098fa2b14 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 f896782aaf3b..000000000000 --- 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 29803d6a8b18..000000000000 --- 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 000000000000..3a5660180779 --- /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 000000000000..0dd40e891893 --- /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 000000000000..e7eb99eb34ec --- /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 000000000000..699ac7ea7a5c --- /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 c39e4a4cc956..c42c047745bb 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 000000000000..75dddeb5d2a7 --- /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 000000000000..e4b51da3cadc --- /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 | 106 ++----- 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 | 110 ++++--- 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 | 160 ++++------ .../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 | 24 +- 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 | 257 ++++++++++----- tests/ui/option_map_unit_fn_unfixable.rs | 20 +- tests/ui/option_map_unit_fn_unfixable.stderr | 111 ++++++- 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 | 90 ++++-- tests/ui/search_is_some.rs | 65 ---- tests/ui/search_is_some.stderr | 104 +----- 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, 4520 insertions(+), 2785 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 37d46d349667..6cb2755be0ee 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 e3708bc48539..133072e0586b 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 bedcc300f856..fee885d8fa7e 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 c918aadf83c4..ae5fd07487ce 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 cca6d6ae7bf3..f3912f81d859 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 b9ecff1fa364..6569bdabf115 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 f8c748290e41..3f6b26d3334e 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 9993765b4bdc..2a042e6c3d85 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 3bdc5b277232..0401cfda7080 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 2b2138d3108d..781e37e6144e 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 16f413e0c862..dcca08aee7e6 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 1b6a590b896f..392c3aabf193 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 a14afd8c5f41..a180db6ad062 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 000000000000..de5caf4e1ef6 --- /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 000000000000..6dc003f326de --- /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 15392dd1d292..d11070bab85b 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 d62597428e21..8e30eb7ce95b 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 5f6e874ffe25..3d0da6846114 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 057951d0e33b..52452dd86b49 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 42486e182ee3..bc97746a1cba 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 375d179681da..a754eea31165 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 f087a894d76a..f010d17917f9 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 2a3fb8294611..1e1d6e69cc91 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()), - ); +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, + } } -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() - } - - 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 000000000000..65738434ac28 --- /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 bddf4702fb34..8defbeeaa5f2 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 b0389fd9a2f6..f96854411fe6 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 c0b0fd88d9e1..fb98cb30ae90 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 1c751643becb..3b9c70e23e20 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 bfe7a6355c00..716334656926 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 a1215491b48c..000000000000 --- 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 04a8e4739b85..877bd34a732b 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 a4ad9424b3eb..99cafc7fc6d8 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 519ec228c884..727e9b172a87 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, ..> - // ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^ + // 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); - // 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()`") + // `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 65e922ac07d3..000000000000 --- 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 c974d9cca7d2..a0de464ff7f8 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 528cc64fa7bc..0d37be17689a 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 298bf1075489..0f3d8b336675 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 86f0eff9cd86..dce0d105f4c5 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 681dc2ab5bc0..b07d4fe81f8a 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 e00d0c7f3d6c..abbc43d8e9b0 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 156118be347f..7ac57fae690c 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 - }; - 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 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 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 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", + }; + + // ``` + // 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 input_ty == output_ty { "" } else { ".map(|x| x as _)" }; + + 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 ac5da320bdff..fa44a56af182 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 2ca76a1fe694..a0f88cf911d8 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 000000000000..baf5b6f93f63 --- /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 48fa5f7bdc6e..20dfce914838 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 591f6aacaef8..156c624488eb 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 c9c75f3f38e2..8732eba6d4e8 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 d9d642015063..7f729ac7ca94 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 fffaf40c9d14..0eb5c36a28a2 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 c7c4976aeb7b..25fcc7ee568e 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 c90019f6ee16..8ec7e47ccc5b 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 51644f7dce6c..f756f94d97b3 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 54f50f11e034..71982023779e 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 000000000000..976b2d8b0c63 --- /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 885649074ab6..b32848a32337 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; -declare_lint_pass!(InvalidUpcastComparisons => [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; + }; + + 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,6 +64,47 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option< } } +fn upcast_comparison_bounds_err<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + rel: Rel, + lhs_bounds: Option<(FullInt, FullInt)>, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, + invert: bool, +) { + if let Some((lb, ub)) = lhs_bounds + && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt()) + { + match rel { + Rel::Eq => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, false); + } + }, + Rel::Ne => { + if norm_rhs_val < lb || ub < norm_rhs_val { + err_upcast_comparison(cx, span, lhs, true); + } + }, + Rel::Lt => { + 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 <= 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); + } + }, + } + } +} + 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; @@ -90,76 +127,3 @@ fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, alwa ); } } - -fn upcast_comparison_bounds_err<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - rel: Rel, - lhs_bounds: Option<(FullInt, FullInt)>, - lhs: &'tcx Expr<'_>, - rhs: &'tcx Expr<'_>, - invert: bool, -) { - 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 - } - }, - Rel::Le => { - if invert { - norm_rhs_val <= lb - } else { - ub <= norm_rhs_val - } - }, - 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 - } - }, - Rel::Le => { - if invert { - norm_rhs_val > ub - } else { - lb > norm_rhs_val - } - }, - 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); - } - } -} 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 ee531741a515..98aa47421537 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`" -} +use super::MANUAL_DIV_CEIL; -pub struct ManualDivCeil { - msrv: Msrv, -} +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, msrv: Msrv) { + let mut applicability = Applicability::MachineApplicable; -impl ManualDivCeil { - #[must_use] - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -impl_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]); - -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 aaea4ff11fc3..8db2cc1d3f57 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 ec6835db897e..034fe8edc715 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 960edc6b0280..412ca2fa4ed9 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 10df007f2a13..6d57bb6ef3a0 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 ad4f3d450db8..4621c22d6b53 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 c55c5ec2f51a..c39e4a4cc956 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 e529bc2d2fa5..6d5c7b86a0ae 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 d58b47bf6deb..a1e1b763dccb 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 9d12b46b9546..45463b4fa1db 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 839b46325b5e..e797c9615604 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 3904818cc9b6..7d6787fec295 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 11220a8c4c01..44abea2f0345 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 d1f6ebf35ce2..a3efece7d224 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 954a71f6c320..e29d45551d1b 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 5945a0c7b558..2593df103527 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 4de7b5fb5924..b73a7c7bb4d9 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 d5d96448a97d..d23fd74d9acc 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 205cd8ba4ee8..001a6ceb1b17 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 9efbb3908497..1f1afe4ea3a5 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 546509228717..629a500ff6f5 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 bb55539617fc..a7c20a787519 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 6ae5b0cb2f04..417ed370e7e5 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 3fd060620728..fd67ad372114 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 b0b60104c0b9..52564c1cb092 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 1b1108d6ce50..a78cf167e84b 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 8d543b042200..2e57078d8e16 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 6da311c50ee7..7040d8a41030 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 da958f76a5ca..e7439beef186 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 06af49f2f6f3..434ba3654f98 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 ca9d02ff2d4f..6e3fd0f78ddf 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 9ac68ecd4cac..666252a52654 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 7a71829dd62c..4c3b6004e800 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 5d213a09e812..0d4bbe4abf8a 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 71a40ad7de71..4392e5d4c028 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 d43e2cc164d7..a2aa1bdd5275 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 15bb67349976..331cccef9f93 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 8c3217624d44..0fbf29365ecf 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 dd5474ccb85a..693cc22fba2a 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 685e3319bf9a..0680eb35ef97 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 3670a050e88d..18ab7d2c4254 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 dedc513438d1..024af6840132 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 27f252485b71..8a76f2837f35 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 3a740e44cacf..51b5c6279b21 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 439fd0974f5f..000000000000 --- 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 000000000000..0deb0f57b0e4 --- /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 6a1ded9298ed..5aa2347b4ae0 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 3661a1537208..17ccac83ce98 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 ce8b67f9ca7b..58fbad64a78d 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 ff09533f2651..cca97c76b509 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 2295691c8127..82ac4db172d8 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 8ca01b91210f..2ec5b3b81446 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 5b74aecdacbe..6ac5fe6e7b54 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 7067434953d6..486903f653de 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 15cb61c6eebd..e6044cc50781 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 f7f21e1850d0..3069c8139abe 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 e42360d296f5..3c0bb595bd5b 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 ea9f0e716c7d..32a61a8585f0 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 b8573ef13b0e..50b96f7712d7 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 ef3c49c1ab30..a6acc2be714b 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 000000000000..8d4e6c2cc006 --- /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 000000000000..f59056c5c55b --- /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 000000000000..103ccf3dcbf5 --- /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 977e31c744a2..0617f06fafb7 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<()> { - let f = std::fs::File::open("/")?; - // Lint - BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); - //~^ lines_filter_map_ok - // Lint + // Lint: + + let f = std::fs::File::open("/")?; + BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); + //~^ lines_filter_map_ok 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 2196075bc445..dfd1ec431a52 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 f9038eec9fb2..c05dadd1b5f2 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 4b496a493283..3cd469fd43a0 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 3781ba1676f5..4523edec3c76 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 a1eea0419291..f4b1644c44ba 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 e240ae8eb7d9..37113e9eafe1 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 e12287a70939..8dd9e7a8a6fa 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 53cffcab5b56..f8e2c5f43745 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 320e895fb823..18764d1501d8 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 11023ac1142a..0c64ed1826b2 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 bf06d9af7d21..e99f7d44dde1 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 031100832b16..a9e78afe6f06 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 a39f0c9299bd..09a6ed169390 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 049928167901..347b6d186887 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 7b40cb80dc6e..df06e358f296 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: aborting due to 3 previous errors +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 6 previous errors diff --git a/tests/ui/match_overlapping_arm.rs b/tests/ui/match_overlapping_arm.rs index 176287e3d2e0..224cac055f25 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 000000000000..b7d95fbfa8f5 --- /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 000000000000..15c8b7c50878 --- /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 000000000000..9d408ce3dec3 --- /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 000000000000..1e98b1f18801 --- /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 0664abf0944d..2589819f019b 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 7507a6af408b..f9cc0122f5e7 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 84924cac62d5..6d7489921812 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 280cef43340c..a4cb89923164 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 b09efe9888f5..842d77dbc8c5 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 da4182966bb1..98d8d27321d2 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 fff6d2f34b8e..69764becfe66 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 cf01cae2e8fd..1b6fa45be0fe 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 9ba2ad306dac..79763b0f2248 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 c839156bed9b..0e0b0fa39c9b 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 11103af5c559..fb0ee5c9cc83 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 4b56843bd522..8684ec217a0a 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 01db64a446c0..9769ee0c3a37 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 4fda06cff4ab..49392d971ee3 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 f03f74dfafe2..d040ba6ee83c 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 c2377491f25b..f50af147c60c 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 1ae0f0064c6b..0ecd4775035b 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 4bf4b91888c8..fe4a48989b0f 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 9a192661aafc..cd0d231497ab 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 340be7c7e932..ddba1cd1291f 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 d902c87379b7..7bd1fe92bdb6 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 2405aa9a7ccf..70b6985406f4 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) }` - -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 + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `if let` instead + | +LL - option().map(do_nothing); +LL + if let Some(a) = option() { do_nothing(a) } | -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: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:?}") } + | + +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: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 dd2f80fefbee..a6d7ea4a4c8d 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 852785014329..8cc246a38d37 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 - -error[E0425]: cannot find value `x` in this scope - --> tests/ui/option_map_unit_fn_unfixable.rs:25:5 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +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 { ... } | -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: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 { ... } + | + +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: aborting due to 4 previous errors +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 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 42f03aae7bb8..136db902a975 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 0cd048e400e4..1c39f9acae8e 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 29789c949756..61dfca8b3728 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 e700cc56349f..9288529423dd 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 1bae076dd337..7940251c18a2 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 cbb78bff68bf..f0252c4484a5 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 c73a4020e2e5..5d47462114b8 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 50cd8f4b8146..f086cfe02890 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 ee3d9dc0de03..000000000000 --- 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 9dd216bd1449..000000000000 --- 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 000000000000..a379457f1869 --- /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 000000000000..e1360e59b412 --- /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 1141b5db3ebf..490948442e11 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 f60ddf468309..d40fe84693b0 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 980455da2ee7..b746bfd1c358 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 9cb0fe2e65f6..7be9f31e56bd 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 1cec19ab8c99..7c05eca1b70e 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 123573a8602b..1d4abca49281 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 dc9d6491691f..a54a4ce13d53 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 2e9714ad8e75..7252fce8cce6 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 800889b5fda0..735c444bb45b 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 1668c2ff2bba..eeca51e8be76 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 dab816716d59..261d82fc35c8 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 3fd70515d084..6cae4cc4b6b0 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 fdd851414746..c08819b21ff0 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 591c8ca53ac2..0f6b58e144d9 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 b54fec8c5794..4a7799be37d7 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 6bee013c3f47..b810e85a45fa 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 a206cfe6842f..18cd557f0454 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 eca844e06cc0..d8f6239764d1 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 fe3d8ece39f4..e3f5c7f996da 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 a6e38d808afa..9f80ec1bbbd0 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 { ... }` - -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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -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| 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:40: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 | | }); + | |______^ + | +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: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 802d27449abf..0a1e0b07d1eb 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 d5412f901110..ccbab5320ee2 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)` + | + = 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: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 - -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 cc4dbc919d81..720ed385fb48 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 fa31a9ddedc6..1cb5f9c39959 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 b079cf7ea361..909248357169 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 c7a4422f3737..1213fdcf6119 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 d6b1c67c9718..4294a39333f2 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 551a670d937f..cee1eb08876b 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 05009b2ddd41..d589bbc4affd 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 03982b069e67..fe28674ec18e 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 e28128e35ada..0f558cb5f786 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 7a9f80630964..1743ae6bf5a7 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 ef19c7cbde2b..d5cb8a24a609 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 b4f9d5867703..f2fab9217604 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 dae04ac4a62d..1460d77a094d 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 072e7b27b0d0..8574cf1c20a1 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 ab9bdde6cf90..19f8b231925b 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 6cffd813b835..7d1a09024895 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 166b9b417b62..a8fa53034036 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 0a0fe3a1990b..839987a474cd 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 a879633e10f2..8c33c08c267d 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 33ba7074d623..c5a8937488d9 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 3754a3d99538..8a269119df22 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 4440089b3633..d82a7b969080 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 7e6a493b86c1..32494435fff5 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 c91383e2cca5..b3a0fb6098d6 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 339d4a95084a..0391fb19b1f6 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 f5c99183b0c5..f0702668fa91 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 6d601ea5e57f..2c794463457f 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 7e5ea0161bff..12f3d3fca464 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 000000000000..89c127a12faf --- /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 000000000000..85168d3bc341 --- /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 2942f64741e9..9de7d2c67149 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 f2da414e9f65..38cd1175aa48 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 d16506d94aa3..157c236a214c 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 8ff7f3b0c18d..5c18cd41ff79 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 04da9776c302..278b473d23ce 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 2b6ee67c37dc..b468b52aea5b 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 4621c22d6b53..f49ed6fdaf5e 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 f49ed6fdaf5e..32418e861616 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 32418e861616..eb35ed173d83 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 eb35ed173d83..66a1dfc1f486 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 8446b6fbbea5..134034abf6cc 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 04b8ec50160b..c12279cf12e6 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 6f7762e6ae83..2771a16e00c5 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 8a75b0501119..381747cb3c65 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 000000000000..905b48e6d1d4 --- /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 134034abf6cc..6b2647e7b0a2 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; - } - - // 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(), + #[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); } } } - - 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 000000000000..30d708f436b4 --- /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 000000000000..fd9230f00a8b --- /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 000000000000..c982bb1ffbc5 --- /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 f54a26a77620..0a00981e15be 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 70d503018e0f..e89e4476bf82 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 9990c3b71205..d2c76673ca03 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 48c20cdd60db..1af5f8ba5c16 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 839b46325b5e..f5e61e365c57 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 8e35883ae9d4..49e6fb07ca77 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 4bbd1803a78d..638f6dc1532b 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 58c8ed1691d7..e3fc7190a9c9 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 e1fb223e4f21..1d5ca1b24994 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 7d9c85da1731..4b7bd4a0eeae 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 c96ff228b01a..9eb7e7cc9c9e 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 cf964d4b5804..5b4fe2a6b803 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 102ca3fa69f7..abc706b7772f 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 5fda388259a6..048c32ac1545 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 048c32ac1545..d643f7aea497 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 9d099137449e..abbdace0230a 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 abbdace0230a..ce15a861bb07 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 f823f08f31dc..72aa6303a202 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 f3985603c4d2..902ba70577b9 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 f50af147c60c..0de944f9edcf 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 0ecd4775035b..ac0bd6d8a491 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 b5155b3b1696..568e88007727 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 35d06780bcb8..808adb7e71ce 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 9018f38100ef..50bc576dd1e2 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 44c5eddf3d8b..9e219a2af073 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 b610de94b530..b686eda7530a 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 5e584e85a3c9..29399132a2c9 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 444a7f1c964f..2ab3e3b7825d 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 000000000000..3108e4e49065 --- /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 d7fb5ae2283f..728292a0ee2b 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 | 50 ++++++---------------- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index a08efbc52d45..f59c7615d745 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 15c8b7c50878..4b973d762ed9 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 9d408ce3dec3..991ceb0ff967 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:19:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here --> 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 + --> 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 b6f70a7f1830..91aaf1f3644e 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 f132b90ac4f2..9096d6f1c7b3 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 4439a28763a2..25db719c8214 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 af9f291f5deb..fba530d0dfca 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 f29bfec60f72..a19116820808 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 63bbadb01dcb..876b61d29519 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 b719ca1871b2..e92368bfffeb 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 5f7fde30f03f..691d9035d02c 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 b5c0aae8310f..bb42e5ea63f5 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 b50e00575629..4f8f001fc5e4 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 2fffc4244a73..3c3e5fea4970 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 9935dc309611..11d3f33331cb 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 222427cd3075..d7180f4908d0 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 f5e61e365c57..68fa3d191d28 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 7b8c7f3f0d6b..ac408a1b59e5 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 710b88e92154..dd411fe21bdd 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 f7a0c3e39afd..86d17a8231d5 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 88ba5f810b4e..959f5db11265 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 19a4e70e294e..9bfecf40fc03 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 a56a683de973..93488ea8e74d 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 000000000000..f2bd306a99e4 --- /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 000000000000..8e7d843a44c0 --- /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 000000000000..accc4d7b5bb8 --- /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 68fa3d191d28..f0b86ee3ea71 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 c3fc09343dbf..9b5cd7e1731f 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 c38cf83f4410..3455a47ba078 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 674f0da818f5..6870c9819fc0 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 c9293412fba8..9ced6c9d452b 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 235cb9e4ecce..19b3572bd3ae 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 5934ec409935..042817f5cdb8 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 be914429edb4..c20217563d62 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 7a1dd94567b1..757ecf75ed45 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 a0f88cf911d8..bc3783750e5c 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 44c4d7a31ff3..57a91cf846b8 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 26b19848fe1b..7b10c37de42d 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 8d95b70c6a4b..aaface3aa971 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 d43dc23a86b2..1c26648e26eb 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 1bc29c9c1dd1..a1aac96ccf86 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 1b6896827fed..29b4da93b7fb 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 4621c22d6b53..bbb1b831888f 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 7f9dbdd885a1..57f889818be3 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 771862ee5b96..208aa98f12f2 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 dd411fe21bdd..2433ca8b97f2 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 4c0d70d320b9..d98e3073b41d 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 eecf56f71a33..6d70387b6ce2 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 e698b99edd5c..b9691a3284ac 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 ef65c21563d9..b402cbf92cb2 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 ba90df2059af..0d5e08f88077 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 6b60421c3549..036f6c444b64 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 2b97a58e9d6f..2701680e788d 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 48961d14f2bb..694690ae5bfa 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 f53cbacdb377..0a07aeff366e 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 42f2128cd378..83c1e9a8387e 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 58fac2d406a0..f7c49b8ff88b 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 9629b3eea587..d3624a129b5f 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 2b741d8df468..f06a99cabc81 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 e7340624b595..f6be4c3f016b 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 02f67f79e2b1..f332e02a2d32 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 c5e156405ebc..c29a42ddca53 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 f39a64d57eb4..5ddc3de6a039 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 | 542 ++++++++++-------------------- util/gh-pages/style.css | 14 +- 3 files changed, 244 insertions(+), 388 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index d34ff0a59732..e443baff0808 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -28,7 +28,7 @@ Otherwise, have a great day =^.^= {# #} {# #} {# #} -
{# #} +