about summary refs log tree commit diff
path: root/clippy_lints/src/methods
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src/methods')
-rw-r--r--clippy_lints/src/methods/iter_kv_map.rs5
-rw-r--r--clippy_lints/src/methods/join_absolute_paths.rs52
-rw-r--r--clippy_lints/src/methods/manual_try_fold.rs5
-rw-r--r--clippy_lints/src/methods/map_unwrap_or.rs6
-rw-r--r--clippy_lints/src/methods/mod.rs88
-rw-r--r--clippy_lints/src/methods/needless_collect.rs5
-rw-r--r--clippy_lints/src/methods/option_as_ref_deref.rs5
-rw-r--r--clippy_lints/src/methods/option_map_or_err_ok.rs41
-rw-r--r--clippy_lints/src/methods/option_map_or_none.rs9
-rw-r--r--clippy_lints/src/methods/option_map_unwrap_or.rs5
-rw-r--r--clippy_lints/src/methods/result_map_or_else_none.rs42
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,
+        );
+    }
+}