diff options
| author | flip1995 <philipp.krones@embecosm.com> | 2022-05-05 15:12:52 +0100 |
|---|---|---|
| committer | flip1995 <philipp.krones@embecosm.com> | 2022-05-05 15:12:52 +0100 |
| commit | 7cd86aa1be18d608d828239c11e887a762efc92a (patch) | |
| tree | f2f098e889406b343beba0dbbf58e492c4e5dff6 /clippy_lints/src/methods | |
| parent | 82f469f81b6daafb448e36c0e811cf2d40836edb (diff) | |
Merge commit '7c21f91b15b7604f818565646b686d90f99d1baf' into clippyup
Diffstat (limited to 'clippy_lints/src/methods')
| -rw-r--r-- | clippy_lints/src/methods/is_digit_ascii_radix.rs | 50 | ||||
| -rw-r--r-- | clippy_lints/src/methods/iter_with_drain.rs | 91 | ||||
| -rw-r--r-- | clippy_lints/src/methods/mod.rs | 109 | ||||
| -rw-r--r-- | clippy_lints/src/methods/needless_option_take.rs | 41 | ||||
| -rw-r--r-- | clippy_lints/src/methods/str_splitn.rs | 413 | ||||
| -rw-r--r-- | clippy_lints/src/methods/unnecessary_to_owned.rs | 23 | ||||
| -rw-r--r-- | clippy_lints/src/methods/wrong_self_convention.rs | 2 |
7 files changed, 471 insertions, 258 deletions
diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs new file mode 100644 index 00000000000..ad333df2f2d --- /dev/null +++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs @@ -0,0 +1,50 @@ +//! Lint for `c.is_digit(10)` + +use super::IS_DIGIT_ASCII_RADIX; +use clippy_utils::{ + consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs, + source::snippet_with_applicability, +}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + self_arg: &'tcx Expr<'_>, + radix: &'tcx Expr<'_>, + msrv: Option<&RustcVersion>, +) { + if !meets_msrv(msrv, &msrvs::IS_ASCII_DIGIT) { + return; + } + + if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() { + return; + } + + if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) { + let (num, replacement) = match radix_val { + FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"), + FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"), + _ => return, + }; + let mut applicability = Applicability::MachineApplicable; + + span_lint_and_sugg( + cx, + IS_DIGIT_ASCII_RADIX, + expr.span, + &format!("use of `char::is_digit` with literal radix of {}", num), + "try", + format!( + "{}.{}()", + snippet_with_applicability(cx, self_arg.span, "..", &mut applicability), + replacement + ), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs index 958c3773087..152072e09c7 100644 --- a/clippy_lints/src/methods/iter_with_drain.rs +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -1,72 +1,47 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::higher::Range; use clippy_utils::is_integer_const; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{ - higher::{self, Range}, - SpanlessEq, -}; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::sym; use rustc_span::Span; use super::ITER_WITH_DRAIN; -const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque]; - pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) { - // Refuse to emit `into_iter` suggestion on draining struct fields due - // to the strong possibility of processing unmovable field. - if let ExprKind::Field(..) = recv.kind { - return; - } + if !matches!(recv.kind, ExprKind::Field(..)) + && let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() + && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) + && matches!(ty_name, sym::Vec | sym::VecDeque) + && let Some(range) = Range::hir(arg) + && is_full_range(cx, recv, range) + { + span_lint_and_sugg( + cx, + ITER_WITH_DRAIN, + span.with_hi(expr.span.hi()), + &format!("`drain(..)` used on a `{}`", ty_name), + "try this", + "into_iter()".to_string(), + Applicability::MaybeIncorrect, + ); + }; +} - if let Some(range) = higher::Range::hir(arg) { - let left_full = match range { - Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true, - Range { start: None, .. } => true, - _ => false, - }; - let full = left_full - && match range { - Range { - end: Some(end), - limits: RangeLimits::HalfOpen, - .. - } => { - // `x.drain(..x.len())` call - if_chain! { - if let ExprKind::MethodCall(len_path, len_args, _) = end.kind; - if len_path.ident.name == sym::len && len_args.len() == 1; - if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind; - if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind; - if SpanlessEq::new(cx).eq_path(drain_path, len_path); - then { true } - else { false } - } - }, - Range { - end: None, - limits: RangeLimits::HalfOpen, - .. - } => true, - _ => false, - }; - if full { - span_lint_and_sugg( - cx, - ITER_WITH_DRAIN, - span.with_hi(expr.span.hi()), - &format!("`drain(..)` used on a `{}`", drained_type), - "try this", - "into_iter()".to_string(), - Applicability::MaybeIncorrect, - ); +fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool { + range.start.map_or(true, |e| is_integer_const(cx, e, 0)) + && range.end.map_or(true, |e| { + if range.limits == RangeLimits::HalfOpen + && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind + && let ExprKind::MethodCall(name, [self_arg], _) = e.kind + && name.ident.name == sym::len + && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind + { + container_path.res == path.res + } else { + false } - } - } + }) } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 70d021a1668..f3be71f6b8b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -26,6 +26,7 @@ mod implicit_clone; mod inefficient_to_string; mod inspect_for_each; mod into_iter_on_ref; +mod is_digit_ascii_radix; mod iter_cloned_collect; mod iter_count; mod iter_next_slice; @@ -42,6 +43,7 @@ mod map_flatten; mod map_identity; mod map_unwrap_or; mod needless_option_as_deref; +mod needless_option_take; mod ok_expect; mod option_as_ref_deref; mod option_map_or_none; @@ -294,15 +296,15 @@ declare_clippy_lint! { /// Checks for methods with certain name prefixes and which /// doesn't match how self is taken. The actual rules are: /// - /// |Prefix |Postfix |`self` taken | `self` type | - /// |-------|------------|-----------------------|--------------| - /// |`as_` | none |`&self` or `&mut self` | any | - /// |`from_`| none | none | any | - /// |`into_`| none |`self` | any | - /// |`is_` | none |`&self` or none | any | - /// |`to_` | `_mut` |`&mut self` | any | - /// |`to_` | not `_mut` |`self` | `Copy` | - /// |`to_` | not `_mut` |`&self` | not `Copy` | + /// |Prefix |Postfix |`self` taken | `self` type | + /// |-------|------------|-------------------------------|--------------| + /// |`as_` | none |`&self` or `&mut self` | any | + /// |`from_`| none | none | any | + /// |`into_`| none |`self` | any | + /// |`is_` | none |`&mut self` or `&self` or none | any | + /// |`to_` | `_mut` |`&mut self` | any | + /// |`to_` | not `_mut` |`self` | `Copy` | + /// |`to_` | not `_mut` |`&self` | not `Copy` | /// /// Note: Clippy doesn't trigger methods with `to_` prefix in: /// - Traits definition. @@ -1265,7 +1267,7 @@ declare_clippy_lint! { #[clippy::version = "1.55.0"] pub EXTEND_WITH_DRAIN, perf, - "using vec.append(&mut vec) to move the full range of a vecor to another" + "using vec.append(&mut vec) to move the full range of a vector to another" } declare_clippy_lint! { @@ -2007,13 +2009,27 @@ declare_clippy_lint! { /// ### Example /// ```rust,ignore /// // Bad - /// let (key, value) = _.splitn(2, '=').next_tuple()?; - /// let value = _.splitn(2, '=').nth(1)?; + /// let s = "key=value=add"; + /// let (key, value) = s.splitn(2, '=').next_tuple()?; + /// let value = s.splitn(2, '=').nth(1)?; /// + /// let mut parts = s.splitn(2, '='); + /// let key = parts.next()?; + /// let value = parts.next()?; + /// ``` + /// Use instead: + /// ```rust,ignore /// // Good - /// let (key, value) = _.split_once('=')?; - /// let value = _.split_once('=')?.1; + /// let s = "key=value=add"; + /// let (key, value) = s.split_once('=')?; + /// let value = s.split_once('=')?.1; + /// + /// let (key, value) = s.split_once('=')?; /// ``` + /// + /// ### Limitations + /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` + /// in two separate `let` statements that immediately follow the `splitn()` #[clippy::version = "1.57.0"] pub MANUAL_SPLIT_ONCE, complexity, @@ -2099,7 +2115,7 @@ declare_clippy_lint! { /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")` /// will prevent loop unrolling and will result in a negative performance impact. /// - /// Additionlly, differences have been observed between aarch64 and x86_64 assembly output, + /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, /// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()` #[clippy::version = "1.61.0"] pub UNNECESSARY_JOIN, @@ -2131,6 +2147,56 @@ declare_clippy_lint! { "no-op use of `deref` or `deref_mut` method to `Option`." } +declare_clippy_lint! { + /// ### What it does + /// Finds usages of [`char::is_digit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that + /// can be replaced with [`is_ascii_digit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or + /// [`is_ascii_hexdigit`] + /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). + /// + /// ### Why is this bad? + /// `is_digit(..)` is slower and requires specifying the radix. + /// + /// ### Example + /// ```rust + /// let c: char = '6'; + /// c.is_digit(10); + /// c.is_digit(16); + /// ``` + /// Use instead: + /// ```rust + /// let c: char = '6'; + /// c.is_ascii_digit(); + /// c.is_ascii_hexdigit(); + /// ``` + #[clippy::version = "1.61.0"] + pub IS_DIGIT_ASCII_RADIX, + style, + "use of `char::is_digit(..)` with literal radix of 10 or 16" +} + +declare_clippy_lint! { + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// let x = Some(3); + /// x.as_ref().take(); + /// ``` + /// Use instead: + /// ```rust + /// let x = Some(3); + /// x.as_ref(); + /// ``` + #[clippy::version = "1.61.0"] + pub NEEDLESS_OPTION_TAKE, + complexity, + "using `.as_ref().take()` on a temporary value" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>, @@ -2219,6 +2285,8 @@ impl_lint_pass!(Methods => [ UNNECESSARY_JOIN, ERR_EXPECT, NEEDLESS_OPTION_AS_DEREF, + IS_DIGIT_ASCII_RADIX, + NEEDLESS_OPTION_TAKE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2254,7 +2322,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { single_char_add_str::check(cx, expr, args); into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args); single_char_pattern::check(cx, expr, method_call.ident.name, args); - unnecessary_to_owned::check(cx, expr, method_call.ident.name, args); + unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv.as_ref()); }, hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { @@ -2516,6 +2584,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio }, ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), ("is_file", []) => filetype_is_file::check(cx, expr, recv), + ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, msrv), ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), ("join", [join_arg]) => { @@ -2574,12 +2643,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio ("splitn" | "rsplitn", [count_arg, pat_arg]) => { if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { suspicious_splitn::check(cx, name, expr, recv, count); - if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) { - str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg); - } - if count >= 2 { - str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count); - } + str_splitn::check(cx, name, expr, recv, pat_arg, count, msrv); } }, ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => { @@ -2595,6 +2659,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio } } }, + ("take", []) => needless_option_take::check(cx, expr, recv), ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { implicit_clone::check(cx, name, expr, recv); }, diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs new file mode 100644 index 00000000000..829c118d291 --- /dev/null +++ b/clippy_lints/src/methods/needless_option_take.rs @@ -0,0 +1,41 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::match_def_path; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::NEEDLESS_OPTION_TAKE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place + if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_OPTION_TAKE, + expr.span, + "called `Option::take()` on a temporary value", + "try", + format!( + "{}", + snippet_with_applicability(cx, recv.span, "..", &mut applicability) + ), + applicability, + ); + } +} + +fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let expr_type = cx.typeck_results().expr_ty(expr); + is_type_diagnostic_item(cx, expr_type, sym::Option) +} + +fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]); + } + false +} diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 8125930b304..52891eeed06 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -1,36 +1,83 @@ use clippy_utils::consts::{constant, Constant}; -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_context; -use clippy_utils::{is_diag_item_method, match_def_path, paths}; +use clippy_utils::usage::local_used_after_expr; +use clippy_utils::visitors::expr_visitor; +use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath}; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{ + BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind, +}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, adjustment::Adjust}; -use rustc_span::{symbol::sym, Span, SyntaxContext}; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_span::{sym, Span, Symbol, SyntaxContext}; -use super::MANUAL_SPLIT_ONCE; +use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; -pub(super) fn check_manual_split_once( +pub(super) fn check( cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>, + count: u128, + msrv: Option<&RustcVersion>, ) { - if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { + if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { return; } + let needless = |usage_kind| match usage_kind { + IterUsageKind::Nth(n) => count > n + 1, + IterUsageKind::NextTuple => count > 2, + }; + let manual = count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE); + + match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) { + Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg), + Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage), + None if manual => { + check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg); + }, + _ => {}, + } +} + +fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) { + let mut app = Applicability::MachineApplicable; + let r = if method_name == "splitn" { "" } else { "r" }; + + span_lint_and_sugg( + cx, + NEEDLESS_SPLITN, + expr.span, + &format!("unnecessary use of `{r}splitn`"), + "try this", + format!( + "{}.{r}split({})", + snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0, + snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0, + ), + app, + ); +} + +fn check_manual_split_once( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, + usage: &IterUsage, +) { let ctxt = expr.span.ctxt(); - let (method_name, msg, reverse) = if method_name == "splitn" { - ("split_once", "manual implementation of `split_once`", false) + let (msg, reverse) = if method_name == "splitn" { + ("manual implementation of `split_once`", false) } else { - ("rsplit_once", "manual implementation of `rsplit_once`", true) - }; - let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) { - Some(x) => x, - None => return, + ("manual implementation of `rsplit_once`", true) }; let mut app = Applicability::MachineApplicable; @@ -39,84 +86,198 @@ pub(super) fn check_manual_split_once( let sugg = match usage.kind { IterUsageKind::NextTuple => { - format!("{}.{}({})", self_snip, method_name, pat_snip) - }, - IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip), - IterUsageKind::Next | IterUsageKind::Second => { - let self_deref = { - let adjust = cx.typeck_results().expr_adjustments(self_arg); - if adjust.len() < 2 { - String::new() - } else if cx.typeck_results().expr_ty(self_arg).is_box() - || adjust - .iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box()) - { - format!("&{}", "*".repeat(adjust.len().saturating_sub(1))) - } else { - "*".repeat(adjust.len().saturating_sub(2)) - } - }; - if matches!(usage.kind, IterUsageKind::Next) { - match usage.unwrap_kind { - Some(UnwrapKind::Unwrap) => { - if reverse { - format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip) - } else { - format!( - "{}.{}({}).map_or({}{}, |x| x.0)", - self_snip, method_name, pat_snip, self_deref, &self_snip - ) - } - }, - Some(UnwrapKind::QuestionMark) => { - format!( - "{}.{}({}).map_or({}{}, |x| x.0)", - self_snip, method_name, pat_snip, self_deref, &self_snip - ) - }, - None => { - format!( - "Some({}.{}({}).map_or({}{}, |x| x.0))", - &self_snip, method_name, pat_snip, self_deref, &self_snip - ) - }, - } + if reverse { + format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))") } else { - match usage.unwrap_kind { - Some(UnwrapKind::Unwrap) => { - if reverse { - // In this case, no better suggestion is offered. - return; - } - format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip) - }, - Some(UnwrapKind::QuestionMark) => { - format!("{}.{}({})?.1", self_snip, method_name, pat_snip) - }, - None => { - format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip) - }, - } + format!("{self_snip}.split_once({pat_snip})") + } + }, + IterUsageKind::Nth(1) => { + let (r, field) = if reverse { ("r", 0) } else { ("", 1) }; + + match usage.unwrap_kind { + Some(UnwrapKind::Unwrap) => { + format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}") + }, + Some(UnwrapKind::QuestionMark) => { + format!("{self_snip}.{r}split_once({pat_snip})?.{field}") + }, + None => { + format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})") + }, } }, + IterUsageKind::Nth(_) => return, }; span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app); } +/// checks for +/// +/// ``` +/// let mut iter = "a.b.c".splitn(2, '.'); +/// let a = iter.next(); +/// let b = iter.next(); +/// ``` +fn check_manual_split_once_indirect( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, +) -> Option<()> { + let ctxt = expr.span.ctxt(); + let mut parents = cx.tcx.hir().parent_iter(expr.hir_id); + if let (_, Node::Local(local)) = parents.next()? + && let PatKind::Binding(BindingAnnotation::Mutable, iter_binding_id, iter_ident, None) = local.pat.kind + && let (iter_stmt_id, Node::Stmt(_)) = parents.next()? + && let (_, Node::Block(enclosing_block)) = parents.next()? + + && let mut stmts = enclosing_block + .stmts + .iter() + .skip_while(|stmt| stmt.hir_id != iter_stmt_id) + .skip(1) + + && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? + && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)? + && first.unwrap_kind == second.unwrap_kind + && first.name != second.name + && !local_used_after_expr(cx, iter_binding_id, second.init_expr) + { + let (r, lhs, rhs) = if method_name == "splitn" { + ("", first.name, second.name) + } else { + ("r", second.name, first.name) + }; + let msg = format!("manual implementation of `{r}split_once`"); + + let mut app = Applicability::MachineApplicable; + let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; + let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0; + + span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| { + diag.span_label(first.span, "first usage here"); + diag.span_label(second.span, "second usage here"); + + let unwrap = match first.unwrap_kind { + UnwrapKind::Unwrap => ".unwrap()", + UnwrapKind::QuestionMark => "?", + }; + diag.span_suggestion_verbose( + local.span, + &format!("try `{r}split_once`"), + format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"), + app, + ); + + let remove_msg = format!("remove the `{iter_ident}` usages"); + diag.span_suggestion( + first.span, + &remove_msg, + String::new(), + app, + ); + diag.span_suggestion( + second.span, + &remove_msg, + String::new(), + app, + ); + }); + } + + Some(()) +} + +#[derive(Debug)] +struct IndirectUsage<'a> { + name: Symbol, + span: Span, + init_expr: &'a Expr<'a>, + unwrap_kind: UnwrapKind, +} + +/// returns `Some(IndirectUsage)` for e.g. +/// +/// ```ignore +/// let name = binding.next()?; +/// let name = binding.next().unwrap(); +/// ``` +fn indirect_usage<'tcx>( + cx: &LateContext<'tcx>, + stmt: &Stmt<'tcx>, + binding: HirId, + ctxt: SyntaxContext, +) -> Option<IndirectUsage<'tcx>> { + if let StmtKind::Local(Local { + pat: + Pat { + kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None), + .. + }, + init: Some(init_expr), + hir_id: local_hir_id, + .. + }) = stmt.kind + { + let mut path_to_binding = None; + expr_visitor(cx, |expr| { + if path_to_local_id(expr, binding) { + path_to_binding = Some(expr); + } + + path_to_binding.is_none() + }) + .visit_expr(init_expr); + + let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id); + let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?; + + let (parent_id, _) = parents.find(|(_, node)| { + !matches!( + node, + Node::Expr(Expr { + kind: ExprKind::Match(.., MatchSource::TryDesugar), + .. + }) + ) + })?; + + if let IterUsage { + kind: IterUsageKind::Nth(0), + unwrap_kind: Some(unwrap_kind), + .. + } = iter_usage + { + if parent_id == *local_hir_id { + return Some(IndirectUsage { + name: ident.name, + span: stmt.span, + init_expr, + unwrap_kind, + }); + } + } + } + + None +} + +#[derive(Debug, Clone, Copy)] enum IterUsageKind { - Next, - Second, + Nth(u128), NextTuple, - RNextTuple, } +#[derive(Debug, PartialEq)] enum UnwrapKind { Unwrap, QuestionMark, } +#[derive(Debug)] struct IterUsage { kind: IterUsageKind, unwrap_kind: Option<UnwrapKind>, @@ -128,7 +289,6 @@ fn parse_iter_usage<'tcx>( cx: &LateContext<'tcx>, ctxt: SyntaxContext, mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>, - reverse: bool, ) -> Option<IterUsage> { let (kind, span) = match iter.next() { Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { @@ -141,13 +301,7 @@ fn parse_iter_usage<'tcx>( let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - if reverse { - (IterUsageKind::Second, e.span) - } else { - (IterUsageKind::Next, e.span) - } - }, + ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), ("next_tuple", []) => { return if_chain! { if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE); @@ -157,7 +311,7 @@ fn parse_iter_usage<'tcx>( if subs.len() == 2; then { Some(IterUsage { - kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple }, + kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None }) @@ -185,11 +339,7 @@ fn parse_iter_usage<'tcx>( } } }; - match if reverse { idx ^ 1 } else { idx } { - 0 => (IterUsageKind::Next, span), - 1 => (IterUsageKind::Second, span), - _ => return None, - } + (IterUsageKind::Nth(idx), span) } else { return None; } @@ -238,86 +388,3 @@ fn parse_iter_usage<'tcx>( span, }) } - -use super::NEEDLESS_SPLITN; - -pub(super) fn check_needless_splitn( - cx: &LateContext<'_>, - method_name: &str, - expr: &Expr<'_>, - self_arg: &Expr<'_>, - pat_arg: &Expr<'_>, - count: u128, -) { - if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { - return; - } - let ctxt = expr.span.ctxt(); - let mut app = Applicability::MachineApplicable; - let (reverse, message) = if method_name == "splitn" { - (false, "unnecessary use of `splitn`") - } else { - (true, "unnecessary use of `rsplitn`") - }; - if_chain! { - if count >= 2; - if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count); - then { - span_lint_and_sugg( - cx, - NEEDLESS_SPLITN, - expr.span, - message, - "try this", - format!( - "{}.{}({})", - snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0, - if reverse {"rsplit"} else {"split"}, - snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0 - ), - app, - ); - } - } -} - -fn check_iter<'tcx>( - cx: &LateContext<'tcx>, - ctxt: SyntaxContext, - mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>, - count: u128, -) -> bool { - match iter.next() { - Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { - let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind { - (name, args) - } else { - return false; - }; - if_chain! { - if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id); - if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator); - then { - match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - return true; - }, - ("next_tuple", []) if count > 2 => { - return true; - }, - ("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { - if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) { - if count > idx + 1 { - return true; - } - } - }, - _ => return false, - } - } - } - }, - _ => return false, - }; - false -} diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 1555758fc4a..02b882e8b55 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -5,6 +5,8 @@ use clippy_utils::source::snippet_opt; use clippy_utils::ty::{ contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, }; +use clippy_utils::{meets_msrv, msrvs}; + use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; @@ -13,12 +15,19 @@ use rustc_middle::mir::Mutability; use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty}; +use rustc_semver::RustcVersion; use rustc_span::{sym, Symbol}; use std::cmp::max; use super::UNNECESSARY_TO_OWNED; -pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) { +pub fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + args: &'tcx [Expr<'tcx>], + msrv: Option<&RustcVersion>, +) { if_chain! { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let [receiver] = args; @@ -33,7 +42,7 @@ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) { return; } - if check_into_iter_call_arg(cx, expr, method_name, receiver) { + if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) { return; } check_other_call_arg(cx, expr, method_name, receiver); @@ -178,7 +187,13 @@ fn check_addr_of_expr( /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its /// call of a `to_owned`-like function is unnecessary. -fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { +fn check_into_iter_call_arg( + cx: &LateContext<'_>, + expr: &Expr<'_>, + method_name: Symbol, + receiver: &Expr<'_>, + msrv: Option<&RustcVersion>, +) -> bool { if_chain! { if let Some(parent) = get_parent_expr(cx, expr); if let Some(callee_def_id) = fn_def_id(cx, parent); @@ -192,7 +207,7 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } - let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" }; + let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) { "copied" } else { "cloned" }; // The next suggestion may be incorrect because the removal of the `to_owned`-like // function could cause the iterator to hold a reference to a resource that is used // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148. diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index aecfea9c141..4b368d3ffae 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -14,7 +14,7 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]), (&[Convention::StartsWith("from_")], &[SelfKind::No]), (&[Convention::StartsWith("into_")], &[SelfKind::Value]), - (&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]), + (&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]), (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]), (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]), |
