diff options
Diffstat (limited to 'clippy_lints/src/methods')
| -rw-r--r-- | clippy_lints/src/methods/iter_kv_map.rs | 5 | ||||
| -rw-r--r-- | clippy_lints/src/methods/join_absolute_paths.rs | 52 | ||||
| -rw-r--r-- | clippy_lints/src/methods/manual_try_fold.rs | 5 | ||||
| -rw-r--r-- | clippy_lints/src/methods/map_unwrap_or.rs | 6 | ||||
| -rw-r--r-- | clippy_lints/src/methods/mod.rs | 88 | ||||
| -rw-r--r-- | clippy_lints/src/methods/needless_collect.rs | 5 | ||||
| -rw-r--r-- | clippy_lints/src/methods/option_as_ref_deref.rs | 5 | ||||
| -rw-r--r-- | clippy_lints/src/methods/option_map_or_err_ok.rs | 41 | ||||
| -rw-r--r-- | clippy_lints/src/methods/option_map_or_none.rs | 9 | ||||
| -rw-r--r-- | clippy_lints/src/methods/option_map_unwrap_or.rs | 5 | ||||
| -rw-r--r-- | clippy_lints/src/methods/result_map_or_else_none.rs | 42 |
11 files changed, 237 insertions, 26 deletions
diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index c5dbb6ad98b..e1b934d36ea 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -1,6 +1,7 @@ #![allow(unused_imports)] use super::ITER_KV_MAP; +use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_type_diagnostic_item; @@ -21,7 +22,11 @@ pub(super) fn check<'tcx>( expr: &'tcx Expr<'tcx>, // .iter().map(|(_, v_| v)) recv: &'tcx Expr<'tcx>, // hashmap m_arg: &'tcx Expr<'tcx>, // |(_, v)| v + msrv: &Msrv, ) { + if map_type == "into_iter" && !msrv.meets(msrvs::INTO_KEYS) { + return; + } if !expr.span.from_expansion() && let ExprKind::Closure(c) = m_arg.kind && let Body { diff --git a/clippy_lints/src/methods/join_absolute_paths.rs b/clippy_lints/src/methods/join_absolute_paths.rs new file mode 100644 index 00000000000..02f28779cf6 --- /dev/null +++ b/clippy_lints/src/methods/join_absolute_paths.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::expr_or_init; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use rustc_span::Span; + +use super::JOIN_ABSOLUTE_PATHS; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if (is_type_diagnostic_item(cx, ty, sym::Path) || is_type_diagnostic_item(cx, ty, sym::PathBuf)) + && let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind + && let LitKind::Str(symbol, _) = spanned.node + && let sym_str = symbol.as_str() + && sym_str.starts_with(['/', '\\']) + { + span_lint_and_then( + cx, + JOIN_ABSOLUTE_PATHS, + join_arg.span, + "argument to `Path::join` starts with a path separator", + |diag| { + let arg_str = snippet_opt(cx, spanned.span).unwrap_or_else(|| "..".to_string()); + + let no_separator = if sym_str.starts_with('/') { + arg_str.replacen('/', "", 1) + } else { + arg_str.replacen('\\', "", 1) + }; + + diag.note("joining a path starting with separator will replace the path instead") + .span_suggestion( + spanned.span, + "if this is unintentional, try removing the starting separator", + no_separator, + Applicability::Unspecified, + ) + .span_suggestion( + expr_span, + "if this is intentional, try using `Path::new` instead", + format!("PathBuf::from({arg_str})"), + Applicability::Unspecified, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/manual_try_fold.rs b/clippy_lints/src/methods/manual_try_fold.rs index 51145afda7f..f93edded729 100644 --- a/clippy_lints/src/methods/manual_try_fold.rs +++ b/clippy_lints/src/methods/manual_try_fold.rs @@ -1,14 +1,14 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_from_proc_macro; use clippy_utils::source::snippet_opt; use clippy_utils::ty::implements_trait; +use clippy_utils::{is_from_proc_macro, is_trait_method}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_span::Span; +use rustc_span::{sym, Span}; use super::MANUAL_TRY_FOLD; @@ -22,6 +22,7 @@ pub(super) fn check<'tcx>( ) { if !in_external_macro(cx.sess(), fold_span) && msrv.meets(msrvs::ITERATOR_TRY_FOLD) + && is_trait_method(cx, expr, sym::Iterator) && let init_ty = cx.typeck_results().expr_ty(init) && let Some(try_trait) = cx.tcx.lang_items().try_trait() && implements_trait(cx, init_ty, try_trait, &[]) diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index cb81b3919bf..52ea584a2c8 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -44,11 +44,9 @@ pub(super) fn check<'tcx>( // lint message let msg = if is_option { - "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling \ - `map_or_else(<g>, <f>)` instead" + "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value" } else { - "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \ - `.map_or_else(<g>, <f>)` instead" + "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value" }; // get snippets for args to map() and unwrap_or_else() let map_snippet = snippet(cx, map_arg.span, ".."); diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 31d44bc1b3f..25c681bb9d9 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -49,6 +49,7 @@ mod iter_skip_next; mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; +mod join_absolute_paths; mod manual_next_back; mod manual_ok_or; mod manual_saturating_arithmetic; @@ -69,6 +70,7 @@ mod obfuscated_if_else; mod ok_expect; mod open_options; mod option_as_ref_deref; +mod option_map_or_err_ok; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; @@ -80,6 +82,7 @@ mod read_line_without_trim; mod readonly_write_lock; mod redundant_as_str; mod repeat_once; +mod result_map_or_else_none; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; @@ -130,7 +133,7 @@ use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, TraitRef, Ty}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_session::impl_lint_pass; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -3609,7 +3612,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `as_str()` on a `String`` chained with a method available on the `String` itself. + /// Checks for usage of `as_str()` on a `String` chained with a method available on the `String` itself. /// /// ### Why is this bad? /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness. @@ -3618,14 +3621,16 @@ declare_clippy_lint! { /// ```no_run /// # #![allow(unused)] /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_str().as_bytes(); + /// owned_string.as_str().as_bytes() + /// # ; /// ``` /// /// Use instead: /// ```no_run /// # #![allow(unused)] /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_bytes(); + /// owned_string.as_bytes() + /// # ; /// ``` #[clippy::version = "1.74.0"] pub REDUNDANT_AS_STR, @@ -3682,6 +3687,71 @@ declare_clippy_lint! { "calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented" } +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`). + /// + /// ### Why is this bad? + /// If the argument to `Path::join` starts with a separator, it will overwrite + /// the original path. If this is intentional, prefer using `Path::new` instead. + /// + /// Note the behavior is platform dependent. A leading `\\` will be accepted + /// on unix systems as part of the file name + /// + /// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) + /// + /// ### Example + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// let joined_path = path.join("/sh"); + /// assert_eq!(joined_path, PathBuf::from("/sh")); + /// ``` + /// + /// Use instead; + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// + /// // If this was unintentional, remove the leading separator + /// let joined_path = path.join("sh"); + /// assert_eq!(joined_path, PathBuf::from("/bin/sh")); + /// + /// // If this was intentional, create a new path instead + /// let new = Path::new("/sh"); + /// assert_eq!(new, PathBuf::from("/sh")); + /// ``` + #[clippy::version = "1.76.0"] + pub JOIN_ABSOLUTE_PATHS, + suspicious, + "calls to `Path::join` which will overwrite the original path" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map_or(Err(_), Ok)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.ok_or(_)`. + /// + /// ### Example + /// ```no_run + /// # let opt = Some(1); + /// opt.map_or(Err("error"), Ok); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let opt = Some(1); + /// opt.ok_or("error"); + /// ``` + #[clippy::version = "1.76.0"] + pub OPTION_MAP_OR_ERR_OK, + style, + "using `Option.map_or(Err(_), Ok)`, which is more succinctly expressed as `Option.ok_or(_)`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3831,6 +3901,8 @@ impl_lint_pass!(Methods => [ REDUNDANT_AS_STR, WAKER_CLONE_WAKE, UNNECESSARY_FALLIBLE_CONVERSIONS, + JOIN_ABSOLUTE_PATHS, + OPTION_MAP_OR_ERR_OK, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4233,6 +4305,8 @@ impl Methods { ("join", [join_arg]) => { if let Some(("collect", _, _, span, _)) = method_call(recv) { unnecessary_join::check(cx, expr, recv, join_arg, span); + } else { + join_absolute_paths::check(cx, recv, join_arg, expr.span); } }, ("last", []) => { @@ -4255,7 +4329,7 @@ impl Methods { map_clone::check(cx, expr, recv, m_arg, &self.msrv); match method_call(recv) { Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { - iter_kv_map::check(cx, map_name, expr, recv2, m_arg); + iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); }, Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check( cx, @@ -4288,6 +4362,10 @@ impl Methods { ("map_or", [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); manual_ok_or::check(cx, expr, recv, def, map); + option_map_or_err_ok::check(cx, expr, recv, def, map); + }, + ("map_or_else", [def, map]) => { + result_map_or_else_none::check(cx, expr, recv, def, map); }, ("next", []) => { if let Some((name2, recv2, args2, _, _)) = method_call(recv) { diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 79ed5515ea2..293b4981c55 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -225,7 +225,10 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) - && let sig = cx.tcx.fn_sig(id).instantiate_identity() && sig.skip_binder().output().is_bool() && let [_, search_ty] = *sig.skip_binder().inputs() - && let ty::Ref(_, search_ty, Mutability::Not) = *cx.tcx.instantiate_bound_regions_with_erased(sig.rebind(search_ty)).kind() + && let ty::Ref(_, search_ty, Mutability::Not) = *cx + .tcx + .instantiate_bound_regions_with_erased(sig.rebind(search_ty)) + .kind() && let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator) && let Some(iter_item) = cx.tcx.associated_items(iter_trait).find_by_name_and_kind( cx.tcx, diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index 15111006133..756dbe62d84 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -99,10 +99,7 @@ pub(super) fn check( let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, "..")); let suggestion = format!("try using {method_hint} instead"); - let msg = format!( - "called `{current_method}` on an Option value. This can be done more directly \ - by calling `{hint}` instead" - ); + let msg = format!("called `{current_method}` on an `Option` value"); span_lint_and_sugg( cx, OPTION_AS_REF_DEREF, diff --git a/clippy_lints/src/methods/option_map_or_err_ok.rs b/clippy_lints/src/methods/option_map_or_err_ok.rs new file mode 100644 index 00000000000..91e39d5a1cd --- /dev/null +++ b/clippy_lints/src/methods/option_map_or_err_ok.rs @@ -0,0 +1,41 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_res_lang_ctor, path_res}; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{ResultErr, ResultOk}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::OPTION_MAP_OR_ERR_OK; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'_>, + or_expr: &'tcx Expr<'_>, + map_expr: &'tcx Expr<'_>, +) { + // We check that it's called on an `Option` type. + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) + // We check that first we pass an `Err`. + && let ExprKind::Call(call, &[arg]) = or_expr.kind + && is_res_lang_ctor(cx, path_res(cx, call), ResultErr) + // And finally we check that it is mapped as `Ok`. + && is_res_lang_ctor(cx, path_res(cx, map_expr), ResultOk) + { + let msg = "called `map_or(Err(_), Ok)` on an `Option` value"; + let self_snippet = snippet(cx, recv.span, ".."); + let err_snippet = snippet(cx, arg.span, ".."); + span_lint_and_sugg( + cx, + OPTION_MAP_OR_ERR_OK, + expr.span, + msg, + "try using `ok_or` instead", + format!("{self_snippet}.ok_or({err_snippet})"), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 418e6a7d6a0..ff4d8cc9e3e 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -66,8 +66,7 @@ pub(super) fn check<'tcx>( && Some(id) == cx.tcx.lang_items().option_some_variant() { let func_snippet = snippet(cx, arg_char.span, ".."); - let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ - `map(..)` instead"; + let msg = "called `map_or(None, ..)` on an `Option` value"; return span_lint_and_sugg( cx, OPTION_MAP_OR_NONE, @@ -80,8 +79,7 @@ pub(super) fn check<'tcx>( } let func_snippet = snippet(cx, map_arg.span, ".."); - let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ - `and_then(..)` instead"; + let msg = "called `map_or(None, ..)` on an `Option` value"; span_lint_and_sugg( cx, OPTION_MAP_OR_NONE, @@ -92,8 +90,7 @@ pub(super) fn check<'tcx>( Applicability::MachineApplicable, ); } else if f_arg_is_some { - let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ - `ok()` instead"; + let msg = "called `map_or(None, Some)` on a `Result` value"; let self_snippet = snippet(cx, recv.span, ".."); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index c78f8b71c78..575c2d8f157 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -97,10 +97,7 @@ pub(super) fn check<'tcx>( } else { "map_or(<a>, <f>)" }; - let msg = &format!( - "called `map(<f>).unwrap_or({arg})` on an `Option` value. \ - This can be done more directly by calling `{suggest}` instead" - ); + let msg = &format!("called `map(<f>).unwrap_or({arg})` on an `Option` value"); span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { let map_arg_span = map_arg.span; diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints/src/methods/result_map_or_else_none.rs new file mode 100644 index 00000000000..bc16a112816 --- /dev/null +++ b/clippy_lints/src/methods/result_map_or_else_none.rs @@ -0,0 +1,42 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::RESULT_MAP_OR_INTO_OPTION; + +/// lint use of `_.map_or_else(|_| None, Some)` for `Result`s +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + def_arg: &'tcx hir::Expr<'_>, + map_arg: &'tcx hir::Expr<'_>, +) { + // lint if the caller of `map_or_else()` is a `Result` + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) + // We check that it is mapped as `Some`. + && is_res_lang_ctor(cx, path_res(cx, map_arg), OptionSome) + && 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". + && is_res_lang_ctor(cx, path_res(cx, peel_blocks(body.value)), OptionNone) + { + let msg = "called `map_or_else(|_| None, Some)` on a `Result` value"; + let self_snippet = snippet(cx, recv.span, ".."); + span_lint_and_sugg( + cx, + RESULT_MAP_OR_INTO_OPTION, + expr.span, + msg, + "try using `ok` instead", + format!("{self_snippet}.ok()"), + Applicability::MachineApplicable, + ); + } +} |
