summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/methods
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/methods')
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs190
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs34
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs51
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_cmp_with_unwrap.rs44
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_last_cmp.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_next_cmp.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs132
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs43
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/cloned_instead_of_copied.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/err_expect.rs60
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs173
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/expect_used.rs36
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/extend_with_drain.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs41
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_map.rs197
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs22
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs42
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_next.rs42
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/flat_map_option.rs34
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs83
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs55
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs87
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs58
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs67
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs23
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs56
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/is_digit_ascii_radix.rs50
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs31
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_count.rs48
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs74
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_nth.rs39
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs30
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs59
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs46
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs47
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs21
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs164
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs99
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs47
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_flatten.rs73
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_identity.rs39
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs79
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/mod.rs3052
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/needless_option_as_deref.rs37
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs41
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs47
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/obfuscated_if_else.rs42
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/ok_expect.rs46
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs120
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs122
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs139
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs175
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs68
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/search_is_some.rs156
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs14
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs62
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs27
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs22
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/str_splitn.rs390
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs36
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs48
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs26
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs132
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs95
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs104
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_join.rs41
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs70
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs431
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs46
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs40
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/useless_asref.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/utils.rs168
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs154
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/zst_offset.rs18
79 files changed, 8763 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
new file mode 100644
index 000000000..2f117e4dc
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
@@ -0,0 +1,190 @@
+use super::{contains_return, BIND_INSTEAD_OF_MAP};
+use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{LangItem, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::DefIdTree;
+use rustc_span::Span;
+
+pub(crate) struct OptionAndThenSome;
+
+impl BindInsteadOfMap for OptionAndThenSome {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultAndThenOk;
+
+impl BindInsteadOfMap for ResultAndThenOk {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultOrElseErrInfo;
+
+impl BindInsteadOfMap for ResultOrElseErrInfo {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
+ const BAD_METHOD_NAME: &'static str = "or_else";
+ const GOOD_METHOD_NAME: &'static str = "map_err";
+}
+
+pub(crate) trait BindInsteadOfMap {
+ const VARIANT_LANG_ITEM: LangItem;
+ const BAD_METHOD_NAME: &'static str;
+ const GOOD_METHOD_NAME: &'static str;
+
+ fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id);
+ Some(format!(
+ "using `{}.{}({})`, which is a no-op",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ ))
+ }
+
+ fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id);
+ Some(format!(
+ "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ Self::GOOD_METHOD_NAME
+ ))
+ }
+
+ fn lint_closure_autofixable(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ closure_expr: &hir::Expr<'_>,
+ closure_args_span: Span,
+ ) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(inner_expr);
+ if let Some(msg) = Self::lint_msg(cx);
+ then {
+ let some_inner_snip = if inner_expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, inner_expr.span, "_")
+ } else {
+ snippet(cx, inner_expr.span, "_")
+ };
+
+ let closure_args_snip = snippet(cx, closure_args_span, "..");
+ let option_snip = snippet(cx, recv.span, "..");
+ let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "try this",
+ note,
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
+ let mut suggs = Vec::new();
+ let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
+ if_chain! {
+ if !ret_expr.span.from_expansion();
+ if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(arg);
+ then {
+ suggs.push((ret_expr.span, arg.span.source_callsite()));
+ true
+ } else {
+ false
+ }
+ }
+ });
+ let (span, msg) = if_chain! {
+ if can_sugg;
+ if let hir::ExprKind::MethodCall(segment, ..) = expr.kind;
+ if let Some(msg) = Self::lint_msg(cx);
+ then { (segment.ident.span, msg) } else { return false; }
+ };
+ span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
+ multispan_sugg_with_applicability(
+ diag,
+ "try this",
+ Applicability::MachineApplicable,
+ std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain(
+ suggs
+ .into_iter()
+ .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
+ ),
+ );
+ });
+ true
+ }
+
+ /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
+ fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
+ if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
+ if adt.did() == cx.tcx.parent(vid);
+ then {} else { return false; }
+ }
+
+ match arg.kind {
+ hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) => {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(&closure_body.value);
+
+ if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, fn_decl_span) {
+ true
+ } else {
+ Self::lint_closure(cx, expr, closure_expr)
+ }
+ },
+ // `_.and_then(Some)` case, which is no-op.
+ hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => {
+ if let Some(msg) = Self::no_op_msg(cx) {
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "use the expression directly",
+ snippet(cx, recv.span, "..").into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ true
+ },
+ _ => false,
+ }
+ }
+
+ fn is_variant(cx: &LateContext<'_>, res: Res) -> bool {
+ if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
+ if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) {
+ return cx.tcx.parent(id) == variant_id;
+ }
+ }
+ false
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs
new file mode 100644
index 000000000..44857d61f
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs
@@ -0,0 +1,34 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+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::BYTES_NTH;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) {
+ let ty = cx.typeck_results().expr_ty(recv).peel_refs();
+ let caller_type = if ty.is_str() {
+ "str"
+ } else if is_type_diagnostic_item(cx, ty, sym::String) {
+ "String"
+ } else {
+ return;
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BYTES_NTH,
+ expr.span,
+ &format!("called `.bytes().nth()` on a `{}`", caller_type),
+ "try",
+ format!(
+ "{}.as_bytes().get({})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, n_arg.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs b/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs
new file mode 100644
index 000000000..f7b79f083
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs
@@ -0,0 +1,51 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{method_chain_args, path_def_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_lint::Lint;
+use rustc_middle::ty::{self, DefIdTree};
+
+/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ info: &crate::methods::BinaryExprInfo<'_>,
+ chain_methods: &[&str],
+ lint: &'static Lint,
+ suggest: &str,
+) -> bool {
+ if_chain! {
+ if let Some(args) = method_chain_args(info.chain, chain_methods);
+ if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind;
+ if let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id));
+ if Some(id) == cx.tcx.lang_items().option_some_variant();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs();
+
+ if *self_ty.kind() != ty::Str {
+ return false;
+ }
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ info.expr.span,
+ &format!("you should use the `{}` method", suggest),
+ "like this",
+ format!("{}{}.{}({})",
+ if info.eq { "" } else { "!" },
+ snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
+ suggest,
+ snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)),
+ applicability,
+ );
+
+ return true;
+ }
+ }
+
+ false
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_cmp_with_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/chars_cmp_with_unwrap.rs
new file mode 100644
index 000000000..a7c0e4392
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_cmp_with_unwrap.rs
@@ -0,0 +1,44 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_lint::Lint;
+
+/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ info: &crate::methods::BinaryExprInfo<'_>,
+ chain_methods: &[&str],
+ lint: &'static Lint,
+ suggest: &str,
+) -> bool {
+ if_chain! {
+ if let Some(args) = method_chain_args(info.chain, chain_methods);
+ if let hir::ExprKind::Lit(ref lit) = info.other.kind;
+ if let ast::LitKind::Char(c) = lit.node;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ lint,
+ info.expr.span,
+ &format!("you should use the `{}` method", suggest),
+ "like this",
+ format!("{}{}.{}('{}')",
+ if info.eq { "" } else { "!" },
+ snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
+ suggest,
+ c.escape_default()),
+ applicability,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp.rs b/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp.rs
new file mode 100644
index 000000000..07bbc5ca1
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp.rs
@@ -0,0 +1,13 @@
+use crate::methods::chars_cmp;
+use rustc_lint::LateContext;
+
+use super::CHARS_LAST_CMP;
+
+/// Checks for the `CHARS_LAST_CMP` lint.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ if chars_cmp::check(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") {
+ true
+ } else {
+ chars_cmp::check(cx, info, &["chars", "next_back"], CHARS_LAST_CMP, "ends_with")
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs
new file mode 100644
index 000000000..c29ee0ec8
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs
@@ -0,0 +1,13 @@
+use crate::methods::chars_cmp_with_unwrap;
+use rustc_lint::LateContext;
+
+use super::CHARS_LAST_CMP;
+
+/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ if chars_cmp_with_unwrap::check(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") {
+ true
+ } else {
+ chars_cmp_with_unwrap::check(cx, info, &["chars", "next_back", "unwrap"], CHARS_LAST_CMP, "ends_with")
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp.rs b/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp.rs
new file mode 100644
index 000000000..a6701d883
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp.rs
@@ -0,0 +1,8 @@
+use rustc_lint::LateContext;
+
+use super::CHARS_NEXT_CMP;
+
+/// Checks for the `CHARS_NEXT_CMP` lint.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ crate::methods::chars_cmp::check(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with")
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs
new file mode 100644
index 000000000..28ede28e9
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs
@@ -0,0 +1,8 @@
+use rustc_lint::LateContext;
+
+use super::CHARS_NEXT_CMP;
+
+/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
+ crate::methods::chars_cmp_with_unwrap::check(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with")
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
new file mode 100644
index 000000000..0b38a0720
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
@@ -0,0 +1,132 @@
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::get_parent_node;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::sugg;
+use clippy_utils::ty::is_copy;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, adjustment::Adjust};
+use rustc_span::symbol::{sym, Symbol};
+
+use super::CLONE_DOUBLE_REF;
+use super::CLONE_ON_COPY;
+
+/// Checks for the `CLONE_ON_COPY` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, args: &[Expr<'_>]) {
+ let arg = match args {
+ [arg] if method_name == sym::clone => arg,
+ _ => return,
+ };
+ if cx
+ .typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .and_then(|id| cx.tcx.trait_of_item(id))
+ .zip(cx.tcx.lang_items().clone_trait())
+ .map_or(true, |(x, y)| x != y)
+ {
+ return;
+ }
+ let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
+ let arg_ty = arg_adjustments
+ .last()
+ .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
+
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Ref(_, inner, _) = arg_ty.kind() {
+ if let ty::Ref(_, innermost, _) = inner.kind() {
+ span_lint_and_then(
+ cx,
+ CLONE_DOUBLE_REF,
+ expr.span,
+ &format!(
+ "using `clone` on a double-reference; \
+ this will copy the reference of type `{}` instead of cloning the inner type",
+ ty
+ ),
+ |diag| {
+ if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) {
+ let mut ty = innermost;
+ let mut n = 0;
+ while let ty::Ref(_, inner, _) = ty.kind() {
+ ty = inner;
+ n += 1;
+ }
+ let refs = "&".repeat(n + 1);
+ let derefs = "*".repeat(n);
+ let explicit = format!("<{}{}>::clone({})", refs, ty, snip);
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing it",
+ format!("{}({}{}).clone()", refs, derefs, snip.deref()),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or try being explicit if you are sure, that you want to clone a reference",
+ explicit,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ return; // don't report clone_on_copy
+ }
+ }
+
+ if is_copy(cx, ty) {
+ let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
+ Some(Node::Expr(parent)) => match parent.kind {
+ // &*x is a nop, &x.clone() is not
+ ExprKind::AddrOf(..) => return,
+ // (*x).func() is useless, x.clone().func() can work in case func borrows self
+ ExprKind::MethodCall(_, [self_arg, ..], _)
+ if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
+ {
+ return;
+ },
+ ExprKind::MethodCall(_, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true,
+ ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+ | ExprKind::Field(..)
+ | ExprKind::Index(..) => true,
+ _ => false,
+ },
+ // local binding capturing a reference
+ Some(Node::Local(l))
+ if matches!(
+ l.pat.kind,
+ PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..)
+ ) =>
+ {
+ return;
+ },
+ _ => false,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
+
+ let deref_count = arg_adjustments
+ .iter()
+ .take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ .count();
+ let (help, sugg) = if deref_count == 0 {
+ ("try removing the `clone` call", snip.into())
+ } else if parent_is_suffix_expr {
+ ("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip))
+ } else {
+ ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_COPY,
+ expr.span,
+ &format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
+ help,
+ sugg,
+ app,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs
new file mode 100644
index 000000000..6417bc813
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs
@@ -0,0 +1,43 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::paths;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{is_type_diagnostic_item, match_type};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::{sym, Symbol};
+
+use super::CLONE_ON_REF_PTR;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
+ if !(args.len() == 1 && method_name == sym::clone) {
+ return;
+ }
+ let arg = &args[0];
+ let obj_ty = cx.typeck_results().expr_ty(arg).peel_refs();
+
+ if let ty::Adt(_, subst) = obj_ty.kind() {
+ let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) {
+ "Rc"
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) {
+ "Arc"
+ } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) {
+ "Weak"
+ } else {
+ return;
+ };
+
+ let snippet = snippet_with_macro_callsite(cx, arg.span, "..");
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_REF_PTR,
+ expr.span,
+ "using `.clone()` on a ref-counted pointer",
+ "try this",
+ format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet),
+ Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/cloned_instead_of_copied.rs b/src/tools/clippy/clippy_lints/src/methods/cloned_instead_of_copied.rs
new file mode 100644
index 000000000..e9aeab2d5
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/cloned_instead_of_copied.rs
@@ -0,0 +1,45 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::{get_iterator_item_ty, is_copy};
+use clippy_utils::{is_trait_method, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span};
+
+use super::CLONED_INSTEAD_OF_COPIED;
+
+pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option<RustcVersion>) {
+ let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
+ let inner_ty = match recv_ty.kind() {
+ // `Option<T>` -> `T`
+ ty::Adt(adt, subst)
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, msrvs::OPTION_COPIED) =>
+ {
+ subst.type_at(0)
+ },
+ _ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) => {
+ match get_iterator_item_ty(cx, recv_ty) {
+ // <T as Iterator>::Item
+ Some(ty) => ty,
+ _ => return,
+ }
+ },
+ _ => return,
+ };
+ match inner_ty.kind() {
+ // &T where T: Copy
+ ty::Ref(_, ty, _) if is_copy(cx, *ty) => {},
+ _ => return,
+ };
+ span_lint_and_sugg(
+ cx,
+ CLONED_INSTEAD_OF_COPIED,
+ span,
+ "used `cloned` where `copied` could be used instead",
+ "try",
+ "copied".into(),
+ Applicability::MachineApplicable,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/err_expect.rs b/src/tools/clippy/clippy_lints/src/methods/err_expect.rs
new file mode 100644
index 000000000..570a1b873
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/err_expect.rs
@@ -0,0 +1,60 @@
+use super::ERR_EXPECT;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
+use rustc_errors::Applicability;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_middle::ty::Ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ _expr: &rustc_hir::Expr<'_>,
+ recv: &rustc_hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+ expect_span: Span,
+ err_span: Span,
+) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ // Test the version to make sure the lint can be showed (expect_err has been
+ // introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
+ if meets_msrv(msrv, msrvs::EXPECT_ERR);
+
+ // Grabs the `Result<T, E>` type
+ let result_type = cx.typeck_results().expr_ty(recv);
+ // Tests if the T type in a `Result<T, E>` is not None
+ if let Some(data_type) = get_data_type(cx, result_type);
+ // Tests if the T type in a `Result<T, E>` implements debug
+ if has_debug_impl(data_type, cx);
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ERR_EXPECT,
+ err_span.to(expect_span),
+ "called `.err().expect()` on a `Result` value",
+ "try",
+ "expect_err".to_string(),
+ Applicability::MachineApplicable
+ );
+ }
+ };
+}
+
+/// Given a `Result<T, E>` type, return its data (`T`).
+fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
+ match ty.kind() {
+ ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(),
+ _ => None,
+ }
+}
+
+/// Given a type, very if the Debug trait has been impl'd
+fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ cx.tcx
+ .get_diagnostic_item(sym::Debug)
+ .map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
new file mode 100644
index 000000000..6f2307d8f
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
@@ -0,0 +1,173 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use std::borrow::Cow;
+
+use super::EXPECT_FUN_CALL;
+
+/// Checks for the `EXPECT_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ args: &'tcx [hir::Expr<'tcx>],
+) {
+ // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
+ // `&str`
+ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+ let mut arg_root = arg;
+ loop {
+ arg_root = match &arg_root.kind {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
+ hir::ExprKind::MethodCall(method_name, call_args, _) => {
+ if call_args.len() == 1
+ && (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref)
+ && {
+ let arg_type = cx.typeck_results().expr_ty(&call_args[0]);
+ let base_type = arg_type.peel_refs();
+ *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::String)
+ }
+ {
+ &call_args[0]
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ };
+ }
+ arg_root
+ }
+
+ // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
+ // converted to string.
+ fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ if is_type_diagnostic_item(cx, arg_ty, sym::String) {
+ return false;
+ }
+ if let ty::Ref(_, ty, ..) = arg_ty.kind() {
+ if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
+ return false;
+ }
+ };
+ true
+ }
+
+ // Check if an expression could have type `&'static str`, knowing that it
+ // has type `&str` for some lifetime.
+ fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ match arg.kind {
+ hir::ExprKind::Lit(_) => true,
+ hir::ExprKind::Call(fun, _) => {
+ if let hir::ExprKind::Path(ref p) = fun.kind {
+ match cx.qpath_res(p, fun.hir_id) {
+ hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
+ cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
+ ty::Ref(re, ..) if re.is_static(),
+ ),
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ hir::ExprKind::MethodCall(..) => {
+ cx.typeck_results()
+ .type_dependent_def_id(arg.hir_id)
+ .map_or(false, |method_id| {
+ matches!(
+ cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
+ ty::Ref(re, ..) if re.is_static()
+ )
+ })
+ },
+ hir::ExprKind::Path(ref p) => matches!(
+ cx.qpath_res(p, arg.hir_id),
+ hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static(_), _)
+ ),
+ _ => false,
+ }
+ }
+
+ fn is_call(node: &hir::ExprKind<'_>) -> bool {
+ match node {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
+ is_call(&expr.kind)
+ },
+ hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ // These variants are debatable or require further examination
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Block{ .. } => true,
+ _ => false,
+ }
+ }
+
+ if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) {
+ return;
+ }
+
+ let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]);
+ let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
+ "||"
+ } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
+ "|_|"
+ } else {
+ return;
+ };
+
+ let arg_root = get_arg_root(cx, &args[1]);
+
+ let span_replace_word = method_span.with_hi(expr.span.hi());
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ //Special handling for `format!` as arg_root
+ if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
+ if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
+ return;
+ }
+ let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
+ let span = format_args.inputs_span();
+ let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
+ applicability,
+ );
+ return;
+ }
+
+ let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
+ if requires_to_string(cx, arg_root) {
+ arg_root_snippet.to_mut().push_str(".to_string()");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!(
+ "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
+ closure_args, arg_root_snippet
+ ),
+ applicability,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_used.rs b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs
new file mode 100644
index 000000000..fbc3348f1
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs
@@ -0,0 +1,36 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_in_test_function;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::EXPECT_USED;
+
+/// lint use of `expect()` for `Option`s and `Result`s
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_expect_in_tests: bool) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
+ Some((EXPECT_USED, "an Option", "None"))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
+ Some((EXPECT_USED, "a Result", "Err"))
+ } else {
+ None
+ };
+
+ if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
+ return;
+ }
+
+ if let Some((lint, kind, none_value)) = mess {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
+ &format!("used `expect()` on `{}` value", kind,),
+ None,
+ &format!("if this value is an `{}`, it will panic", none_value,),
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/extend_with_drain.rs b/src/tools/clippy/clippy_lints/src/methods/extend_with_drain.rs
new file mode 100644
index 000000000..a15fe6094
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/extend_with_drain.rs
@@ -0,0 +1,45 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::EXTEND_WITH_DRAIN;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(recv).peel_refs();
+ if_chain! {
+ if is_type_diagnostic_item(cx, ty, sym::Vec);
+ //check source object
+ if let ExprKind::MethodCall(src_method, [drain_vec, drain_arg], _) = &arg.kind;
+ if src_method.ident.as_str() == "drain";
+ let src_ty = cx.typeck_results().expr_ty(drain_vec);
+ //check if actual src type is mutable for code suggestion
+ let immutable = src_ty.is_mutable_ptr();
+ let src_ty = src_ty.peel_refs();
+ if is_type_diagnostic_item(cx, src_ty, sym::Vec);
+ //check drain range
+ if let src_ty_range = cx.typeck_results().expr_ty(drain_arg).peel_refs();
+ if is_type_lang_item(cx, src_ty_range, LangItem::RangeFull);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ EXTEND_WITH_DRAIN,
+ expr.span,
+ "use of `extend` instead of `append` for adding the full range of a second vector",
+ "try this",
+ format!(
+ "{}.append({}{})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ if immutable { "" } else { "&mut " },
+ snippet_with_applicability(cx, drain_vec.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs b/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs
new file mode 100644
index 000000000..7b2967feb
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/filetype_is_file.rs
@@ -0,0 +1,41 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::match_type;
+use clippy_utils::{get_parent_expr, paths};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+
+use super::FILETYPE_IS_FILE;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(recv);
+
+ if !match_type(cx, ty, &paths::FILE_TYPE) {
+ return;
+ }
+
+ let span: Span;
+ let verb: &str;
+ let lint_unary: &str;
+ let help_unary: &str;
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(op, _) = parent.kind;
+ if op == hir::UnOp::Not;
+ then {
+ lint_unary = "!";
+ verb = "denies";
+ help_unary = "";
+ span = parent.span;
+ } else {
+ lint_unary = "";
+ verb = "covers";
+ help_unary = "!";
+ span = expr.span;
+ }
+ }
+ let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb);
+ let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary);
+ span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg);
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs
new file mode 100644
index 000000000..692e22a7c
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs
@@ -0,0 +1,197 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{sym, Symbol};
+use std::borrow::Cow;
+
+use super::MANUAL_FILTER_MAP;
+use super::MANUAL_FIND_MAP;
+use super::OPTION_FILTER_MAP;
+
+fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
+ match &expr.kind {
+ hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
+ hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
+ segments.segments.last().unwrap().ident.name == method_name
+ },
+ hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+ let body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(&body.value);
+ let arg_id = body.params[0].pat.hir_id;
+ match closure_expr.kind {
+ hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, args, _) => {
+ if_chain! {
+ if ident.name == method_name;
+ if let hir::ExprKind::Path(path) = &args[0].kind;
+ if let Res::Local(ref local) = cx.qpath_res(path, args[0].hir_id);
+ then {
+ return arg_id == *local
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+}
+
+fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool {
+ is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
+}
+
+/// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
+fn is_filter_some_map_unwrap(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ filter_recv: &hir::Expr<'_>,
+ filter_arg: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+) -> bool {
+ let iterator = is_trait_method(cx, expr, sym::Iterator);
+ let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option);
+
+ (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg)
+}
+
+/// lint use of `filter().map()` or `find().map()` for `Iterators`
+#[allow(clippy::too_many_arguments)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ filter_recv: &hir::Expr<'_>,
+ filter_arg: &hir::Expr<'_>,
+ filter_span: Span,
+ map_recv: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ map_span: Span,
+ is_find: bool,
+) {
+ if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) {
+ span_lint_and_sugg(
+ cx,
+ OPTION_FILTER_MAP,
+ filter_span.with_hi(expr.span.hi()),
+ "`filter` for `Some` followed by `unwrap`",
+ "consider using `flatten` instead",
+ reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(),
+ Applicability::MachineApplicable,
+ );
+
+ return;
+ }
+
+ if_chain! {
+ if is_trait_method(cx, map_recv, sym::Iterator);
+
+ // filter(|x| ...is_some())...
+ if let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind;
+ let filter_body = cx.tcx.hir().body(filter_body_id);
+ if 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 {
+ (ref_pat, true)
+ } else {
+ (filter_param.pat, false)
+ };
+ // closure ends with is_some() or is_ok()
+ if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
+ if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind;
+ if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def();
+ if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
+ Some(false)
+ } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
+ Some(true)
+ } else {
+ None
+ };
+ if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
+
+ // ...map(|x| ...unwrap())
+ if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind;
+ let map_body = cx.tcx.hir().body(map_body_id);
+ if let [map_param] = map_body.params;
+ if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
+ // closure ends with expect() or unwrap()
+ if let ExprKind::MethodCall(seg, [map_arg, ..], _) = map_body.value.kind;
+ if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
+
+ // .filter(..).map(|y| f(y).copied().unwrap())
+ // ~~~~
+ let map_arg_peeled = match map_arg.kind {
+ ExprKind::MethodCall(method, [original_arg], _) if acceptable_methods(method) => {
+ original_arg
+ },
+ _ => map_arg,
+ };
+
+ // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
+ let simple_equal = path_to_local_id(filter_arg, filter_param_id)
+ && path_to_local_id(map_arg_peeled, map_param_id);
+
+ let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
+ // in `filter(|x| ..)`, replace `*x` with `x`
+ let a_path = if_chain! {
+ if !is_filter_param_ref;
+ if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
+ then { expr_path } else { a }
+ };
+ // let the filter closure arg and the map closure arg be equal
+ path_to_local_id(a_path, filter_param_id)
+ && path_to_local_id(b, map_param_id)
+ && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
+ };
+
+ if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
+ then {
+ let span = filter_span.with_hi(expr.span.hi());
+ let (filter_name, lint) = if is_find {
+ ("find", MANUAL_FIND_MAP)
+ } else {
+ ("filter", MANUAL_FILTER_MAP)
+ };
+ let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
+ let (to_opt, deref) = if is_result {
+ (".ok()", String::new())
+ } else {
+ let derefs = cx.typeck_results()
+ .expr_adjustments(map_arg)
+ .iter()
+ .filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ .count();
+
+ ("", "*".repeat(derefs))
+ };
+ let sugg = format!(
+ "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
+ snippet(cx, map_arg.span, ".."),
+ );
+ span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
+ }
+ }
+}
+
+fn acceptable_methods(method: &PathSegment<'_>) -> bool {
+ let methods: [Symbol; 8] = [
+ sym::clone,
+ sym::as_ref,
+ sym!(copied),
+ sym!(cloned),
+ sym!(as_deref),
+ sym!(as_mut),
+ sym!(as_deref_mut),
+ sym!(to_owned),
+ ];
+
+ methods.contains(&method.ident.name)
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs
new file mode 100644
index 000000000..d1b5e945d
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_identity.rs
@@ -0,0 +1,22 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::{is_expr_identity_function, is_trait_method};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::FILTER_MAP_IDENTITY;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) {
+ if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, filter_map_arg) {
+ span_lint_and_sugg(
+ cx,
+ FILTER_MAP_IDENTITY,
+ filter_map_span.with_hi(expr.span.hi()),
+ "use of `filter_map` with an identity function",
+ "try",
+ "flatten()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs
new file mode 100644
index 000000000..38ec4d8e3
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_next.rs
@@ -0,0 +1,42 @@
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::{is_trait_method, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::FILTER_MAP_NEXT;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if !meets_msrv(msrv, msrvs::ITERATOR_FIND_MAP) {
+ return;
+ }
+
+ let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find_map(..)` instead";
+ let filter_snippet = snippet(cx, arg.span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ let iter_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ FILTER_MAP_NEXT,
+ expr.span,
+ msg,
+ "try this",
+ format!("{}.find_map({})", iter_snippet, filter_snippet),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, FILTER_MAP_NEXT, expr.span, msg);
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_next.rs b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs
new file mode 100644
index 000000000..bcf8d93b6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_next.rs
@@ -0,0 +1,42 @@
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::implements_trait;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::FILTER_NEXT;
+
+/// lint use of `filter().next()` for `Iterators`
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ filter_arg: &'tcx hir::Expr<'_>,
+) {
+ // lint if caller of `.filter().next()` is an Iterator
+ let recv_impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])
+ });
+ if recv_impls_iterator {
+ let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find(..)` instead";
+ let filter_snippet = snippet(cx, filter_arg.span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ let iter_snippet = snippet(cx, recv.span, "..");
+ // add note if not multi-line
+ span_lint_and_sugg(
+ cx,
+ FILTER_NEXT,
+ expr.span,
+ msg,
+ "try this",
+ format!("{}.find({})", iter_snippet, filter_snippet),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, FILTER_NEXT, expr.span, msg);
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs
new file mode 100644
index 000000000..6f911d79d
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/flat_map_identity.rs
@@ -0,0 +1,28 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::{is_expr_identity_function, is_trait_method};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::FLAT_MAP_IDENTITY;
+
+/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ flat_map_arg: &'tcx hir::Expr<'_>,
+ flat_map_span: Span,
+) {
+ if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, flat_map_arg) {
+ span_lint_and_sugg(
+ cx,
+ FLAT_MAP_IDENTITY,
+ flat_map_span.with_hi(expr.span.hi()),
+ "use of `flat_map` with an identity function",
+ "try",
+ "flatten()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/flat_map_option.rs b/src/tools/clippy/clippy_lints/src/methods/flat_map_option.rs
new file mode 100644
index 000000000..615bde941
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/flat_map_option.rs
@@ -0,0 +1,34 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::{source_map::Span, sym};
+
+use super::FLAT_MAP_OPTION;
+use clippy_utils::ty::is_type_diagnostic_item;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) {
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+ let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
+ let sig = match arg_ty.kind() {
+ ty::Closure(_, substs) => substs.as_closure().sig(),
+ _ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx),
+ _ => return,
+ };
+ if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::Option) {
+ return;
+ }
+ span_lint_and_sugg(
+ cx,
+ FLAT_MAP_OPTION,
+ span,
+ "used `flat_map` where `filter_map` could be used instead",
+ "try",
+ "filter_map".into(),
+ Applicability::MachineApplicable,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs
new file mode 100644
index 000000000..6436e28a6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/from_iter_instead_of_collect.rs
@@ -0,0 +1,83 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{is_expr_path_def_path, paths, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::sym;
+
+use super::FROM_ITER_INSTEAD_OF_COLLECT;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
+ if_chain! {
+ if is_expr_path_def_path(cx, func, &paths::FROM_ITERATOR_METHOD);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let arg_ty = cx.typeck_results().expr_ty(&args[0]);
+ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+
+ if implements_trait(cx, arg_ty, iter_id, &[]);
+ then {
+ // `expr` implements `FromIterator` trait
+ let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
+ let turbofish = extract_turbofish(cx, expr, ty);
+ let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish);
+ span_lint_and_sugg(
+ cx,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ expr.span,
+ "usage of `FromIterator::from_iter`",
+ "use `.collect()` instead of `::from_iter()`",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+}
+
+fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'_>) -> String {
+ fn strip_angle_brackets(s: &str) -> Option<&str> {
+ s.strip_prefix('<')?.strip_suffix('>')
+ }
+
+ let call_site = expr.span.source_callsite();
+ if_chain! {
+ if let Some(snippet) = snippet_opt(cx, call_site);
+ let snippet_split = snippet.split("::").collect::<Vec<_>>();
+ if let Some((_, elements)) = snippet_split.split_last();
+
+ then {
+ if_chain! {
+ if let [type_specifier, _] = snippet_split.as_slice();
+ if let Some(type_specifier) = strip_angle_brackets(type_specifier);
+ if let Some((type_specifier, ..)) = type_specifier.split_once(" as ");
+ then {
+ type_specifier.to_string()
+ } else {
+ // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
+ if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) {
+ // remove the type specifier from the path elements
+ let without_ts = elements.iter().filter_map(|e| {
+ if e == type_specifier { None } else { Some((*e).to_string()) }
+ }).collect::<Vec<_>>();
+ // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
+ format!("{}{}", without_ts.join("::"), type_specifier)
+ } else {
+ // type is not explicitly specified so wildcards are needed
+ // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
+ let ty_str = ty.to_string();
+ let start = ty_str.find('<').unwrap_or(0);
+ let end = ty_str.find('>').unwrap_or(ty_str.len());
+ let nb_wildcard = ty_str[start..end].split(',').count();
+ let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
+ format!("{}<{}>", elements.join("::"), wildcards)
+ }
+ }
+ }
+ } else {
+ ty.to_string()
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs b/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs
new file mode 100644
index 000000000..23368238e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/get_last_with_len.rs
@@ -0,0 +1,55 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::SpanlessEq;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+use super::GET_LAST_WITH_LEN;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
+ // Argument to "get" is a subtraction
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub, ..
+ },
+ lhs,
+ rhs,
+ ) = arg.kind
+
+ // LHS of subtraction is "x.len()"
+ && let ExprKind::MethodCall(lhs_path, [lhs_recv], _) = &lhs.kind
+ && lhs_path.ident.name == sym::len
+
+ // RHS of subtraction is 1
+ && let ExprKind::Lit(rhs_lit) = &rhs.kind
+ && let LitKind::Int(1, ..) = rhs_lit.node
+
+ // check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
+ && SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
+ && !recv.can_have_side_effects()
+ {
+ let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
+ ty::Adt(def, _) if cx.tcx.is_diagnostic_item(sym::VecDeque, def.did()) => "back",
+ ty::Slice(_) => "last",
+ _ => return,
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ let recv_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability);
+
+ span_lint_and_sugg(
+ cx,
+ GET_LAST_WITH_LEN,
+ expr.span,
+ &format!("accessing last element with `{recv_snippet}.get({recv_snippet}.len() - 1)`"),
+ "try",
+ format!("{recv_snippet}.{method}()"),
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs
new file mode 100644
index 000000000..18e08d6ee
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/get_unwrap.rs
@@ -0,0 +1,87 @@
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::get_parent_expr;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::GET_UNWRAP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'tcx>,
+ get_arg: &'tcx hir::Expr<'_>,
+ is_mut: bool,
+) {
+ // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`,
+ // because they do not implement `IndexMut`
+ let mut applicability = Applicability::MachineApplicable;
+ let expr_ty = cx.typeck_results().expr_ty(recv);
+ let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability);
+ let mut needs_ref;
+ let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "slice"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym::Vec) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "Vec"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym::VecDeque) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "VecDeque"
+ } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::HashMap) {
+ needs_ref = true;
+ "HashMap"
+ } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::BTreeMap) {
+ needs_ref = true;
+ "BTreeMap"
+ } else {
+ return; // caller is not a type that we want to lint
+ };
+
+ let mut span = expr.span;
+
+ // Handle the case where the result is immediately dereferenced
+ // by not requiring ref and pulling the dereference into the
+ // suggestion.
+ if_chain! {
+ if needs_ref;
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind;
+ then {
+ needs_ref = false;
+ span = parent.span;
+ }
+ }
+
+ let mut_str = if is_mut { "_mut" } else { "" };
+ let borrow_str = if !needs_ref {
+ ""
+ } else if is_mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ GET_UNWRAP,
+ span,
+ &format!(
+ "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise",
+ mut_str, caller_type
+ ),
+ "try this",
+ format!(
+ "{}{}[{}]",
+ borrow_str,
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ get_args_str
+ ),
+ applicability,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
new file mode 100644
index 000000000..9651a52be
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
@@ -0,0 +1,58 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::peel_mid_ty_refs;
+use clippy_utils::{is_diag_item_method, is_diag_trait_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::IMPLICIT_CLONE;
+
+pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ if_chain! {
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_clone_like(cx, method_name, method_def_id);
+ let return_type = cx.typeck_results().expr_ty(expr);
+ let input_type = cx.typeck_results().expr_ty(recv);
+ let (input_type, ref_count) = peel_mid_ty_refs(input_type);
+ if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did()));
+ if return_type == input_type;
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_CLONE,
+ expr.span,
+ &format!("implicitly cloning a `{}` by calling `{}` on its dereferenced type", ty_name, method_name),
+ "consider using",
+ if ref_count > 1 {
+ format!("({}{}).clone()", "*".repeat(ref_count - 1), recv_snip)
+ } else {
+ format!("{}.clone()", recv_snip)
+ },
+ app,
+ );
+ }
+ }
+}
+
+/// Returns true if the named method can be used to clone the receiver.
+/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call
+/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g.,
+/// `is_to_owned_like` in `unnecessary_to_owned.rs`.
+pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir::def_id::DefId) -> bool {
+ match method_name {
+ "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
+ "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
+ "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
+ "to_vec" => cx
+ .tcx
+ .impl_of_method(method_def_id)
+ .filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none())
+ .is_some(),
+ _ => false,
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs
new file mode 100644
index 000000000..f52170df6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs
@@ -0,0 +1,67 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_type_diagnostic_item, walk_ptrs_ty_depth};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::{sym, Symbol};
+
+use super::INEFFICIENT_TO_STRING;
+
+/// Checks for the `INEFFICIENT_TO_STRING` lint
+pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
+ if_chain! {
+ if args.len() == 1 && method_name == sym::to_string;
+ if let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD);
+ if let Some(substs) = cx.typeck_results().node_substs_opt(expr.hir_id);
+ let arg_ty = cx.typeck_results().expr_ty_adjusted(&args[0]);
+ let self_ty = substs.type_at(0);
+ let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty);
+ if deref_count >= 1;
+ if specializes_tostring(cx, deref_self_ty);
+ then {
+ span_lint_and_then(
+ cx,
+ INEFFICIENT_TO_STRING,
+ expr.span,
+ &format!("calling `to_string` on `{}`", arg_ty),
+ |diag| {
+ diag.help(&format!(
+ "`{}` implements `ToString` through a slower blanket impl, but `{}` has a fast specialization of `ToString`",
+ self_ty, deref_self_ty
+ ));
+ let mut applicability = Applicability::MachineApplicable;
+ let arg_snippet = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing the receiver",
+ format!("({}{}).to_string()", "*".repeat(deref_count), arg_snippet),
+ applicability,
+ );
+ },
+ );
+ }
+ }
+}
+
+/// Returns whether `ty` specializes `ToString`.
+/// Currently, these are `str`, `String`, and `Cow<'_, str>`.
+fn specializes_tostring(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ if let ty::Str = ty.kind() {
+ return true;
+ }
+
+ if is_type_diagnostic_item(cx, ty, sym::String) {
+ return true;
+ }
+
+ if let ty::Adt(adt, substs) = ty.kind() {
+ match_def_path(cx, adt.did(), &paths::COW) && substs.type_at(1).is_str()
+ } else {
+ false
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs
new file mode 100644
index 000000000..7fd3ef1a6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs
@@ -0,0 +1,23 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_trait_method;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::INSPECT_FOR_EACH;
+
+/// lint use of `inspect().for_each()` for `Iterators`
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ let msg = "called `inspect(..).for_each(..)` on an `Iterator`";
+ let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`";
+ span_lint_and_help(
+ cx,
+ INSPECT_FOR_EACH,
+ inspect_span.with_hi(expr.span.hi()),
+ msg,
+ None,
+ hint,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs b/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs
new file mode 100644
index 000000000..da13b4ba3
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/into_iter_on_ref.rs
@@ -0,0 +1,56 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::ty::has_iter_method;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{sym, Symbol};
+
+use super::INTO_ITER_ON_REF;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ method_name: Symbol,
+ args: &[hir::Expr<'_>],
+) {
+ let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]);
+ if_chain! {
+ if let ty::Ref(..) = self_ty.kind();
+ if method_name == sym::into_iter;
+ if is_trait_method(cx, expr, sym::IntoIterator);
+ if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ty);
+ then {
+ span_lint_and_sugg(
+ cx,
+ INTO_ITER_ON_REF,
+ method_span,
+ &format!(
+ "this `.into_iter()` call is equivalent to `.{}()` and will not consume the `{}`",
+ method_name, kind,
+ ),
+ "call directly",
+ method_name.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> {
+ has_iter_method(cx, self_ref_ty).map(|ty_name| {
+ let mutbl = match self_ref_ty.kind() {
+ ty::Ref(_, _, mutbl) => mutbl,
+ _ => unreachable!(),
+ };
+ let method_name = match mutbl {
+ hir::Mutability::Not => "iter",
+ hir::Mutability::Mut => "iter_mut",
+ };
+ (ty_name, method_name)
+ })
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/is_digit_ascii_radix.rs b/src/tools/clippy/clippy_lints/src/methods/is_digit_ascii_radix.rs
new file mode 100644
index 000000000..aa176dcc8
--- /dev/null
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs b/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs
new file mode 100644
index 000000000..30d56113c
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_cloned_collect.rs
@@ -0,0 +1,31 @@
+use crate::methods::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_CLONED_COLLECT;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec);
+ if let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv));
+ if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite());
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ITER_CLONED_COLLECT,
+ to_replace,
+ &format!("called `iter().{}().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
+ more readable", method_name),
+ "try",
+ ".to_vec()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_count.rs b/src/tools/clippy/clippy_lints/src/methods/iter_count.rs
new file mode 100644
index 000000000..052be3d8e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_count.rs
@@ -0,0 +1,48 @@
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+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::ITER_COUNT;
+
+pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: &str) {
+ let ty = cx.typeck_results().expr_ty(recv);
+ let caller_type = if derefs_to_slice(cx, recv, ty).is_some() {
+ "slice"
+ } else if is_type_diagnostic_item(cx, ty, sym::Vec) {
+ "Vec"
+ } else if is_type_diagnostic_item(cx, ty, sym::VecDeque) {
+ "VecDeque"
+ } else if is_type_diagnostic_item(cx, ty, sym::HashSet) {
+ "HashSet"
+ } else if is_type_diagnostic_item(cx, ty, sym::HashMap) {
+ "HashMap"
+ } else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
+ "BTreeMap"
+ } else if is_type_diagnostic_item(cx, ty, sym::BTreeSet) {
+ "BTreeSet"
+ } else if is_type_diagnostic_item(cx, ty, sym::LinkedList) {
+ "LinkedList"
+ } else if is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
+ "BinaryHeap"
+ } else {
+ return;
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_COUNT,
+ expr.span,
+ &format!("called `.{}().count()` on a `{}`", iter_method, caller_type),
+ "try",
+ format!(
+ "{}.len()",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs b/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs
new file mode 100644
index 000000000..b8d1dabe0
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_next_slice.rs
@@ -0,0 +1,74 @@
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, higher};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+use super::ITER_NEXT_SLICE;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, caller_expr: &'tcx hir::Expr<'_>) {
+ // Skip lint if the `iter().next()` expression is a for loop argument,
+ // since it is already covered by `&loops::ITER_NEXT_LOOP`
+ let mut parent_expr_opt = get_parent_expr(cx, expr);
+ while let Some(parent_expr) = parent_expr_opt {
+ if higher::ForLoop::hir(parent_expr).is_some() {
+ return;
+ }
+ parent_expr_opt = get_parent_expr(cx, parent_expr);
+ }
+
+ if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() {
+ // caller is a Slice
+ if_chain! {
+ if let hir::ExprKind::Index(caller_var, index_expr) = &caller_expr.kind;
+ if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen })
+ = higher::Range::hir(index_expr);
+ if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind;
+ if let ast::LitKind::Int(start_idx, _) = start_lit.node;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let suggest = if start_idx == 0 {
+ format!("{}.first()", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability))
+ } else {
+ format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx)
+ };
+ span_lint_and_sugg(
+ cx,
+ ITER_NEXT_SLICE,
+ expr.span,
+ "using `.iter().next()` on a Slice without end index",
+ "try calling",
+ suggest,
+ applicability,
+ );
+ }
+ }
+ } else if is_vec_or_array(cx, caller_expr) {
+ // caller is a Vec or an Array
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_NEXT_SLICE,
+ expr.span,
+ "using `.iter().next()` on an array",
+ "try calling",
+ format!(
+ "{}.first()",
+ snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
+
+fn is_vec_or_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec)
+ || matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs b/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs
new file mode 100644
index 000000000..80ca4c942
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_nth.rs
@@ -0,0 +1,39 @@
+use super::utils::derefs_to_slice;
+use crate::methods::iter_nth_zero;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::ITER_NTH;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ iter_recv: &'tcx hir::Expr<'tcx>,
+ nth_recv: &hir::Expr<'_>,
+ nth_arg: &hir::Expr<'_>,
+ is_mut: bool,
+) {
+ let mut_str = if is_mut { "_mut" } else { "" };
+ let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() {
+ "slice"
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::Vec) {
+ "Vec"
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::VecDeque) {
+ "VecDeque"
+ } else {
+ iter_nth_zero::check(cx, expr, nth_recv, nth_arg);
+ return; // caller is not a type that we want to lint
+ };
+
+ span_lint_and_help(
+ cx,
+ ITER_NTH,
+ expr.span,
+ &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type),
+ None,
+ &format!("calling `.get{}()` is both faster and more readable", mut_str),
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs b/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs
new file mode 100644
index 000000000..68d906c3e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_nth_zero.rs
@@ -0,0 +1,30 @@
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_NTH_ZERO;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ if_chain! {
+ if is_trait_method(cx, expr, sym::Iterator);
+ if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_NTH_ZERO,
+ expr.span,
+ "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent",
+ "try calling `.next()` instead of `.nth(0)`",
+ format!("{}.next()", snippet_with_applicability(cx, recv.span, "..", &mut applicability)),
+ applicability,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
new file mode 100644
index 000000000..06a39c599
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
@@ -0,0 +1,59 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{get_associated_type, implements_trait, is_copy};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+use super::ITER_OVEREAGER_CLONED;
+use crate::redundant_clone::REDUNDANT_CLONE;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ cloned_call: &'tcx Expr<'_>,
+ cloned_recv: &'tcx Expr<'_>,
+ is_count: bool,
+ needs_into_iter: bool,
+) {
+ let typeck = cx.typeck_results();
+ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
+ && let Some(method_id) = typeck.type_dependent_def_id(expr.hir_id)
+ && cx.tcx.trait_of_item(method_id) == Some(iter_id)
+ && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id)
+ && cx.tcx.trait_of_item(method_id) == Some(iter_id)
+ && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv)
+ && let Some(iter_assoc_ty) = get_associated_type(cx, cloned_recv_ty, iter_id, "Item")
+ && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty))
+ {
+ if needs_into_iter
+ && let Some(into_iter_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
+ && !implements_trait(cx, iter_assoc_ty, into_iter_id, &[])
+ {
+ return;
+ }
+
+ let (lint, msg, trailing_clone) = if is_count {
+ (REDUNDANT_CLONE, "unneeded cloning of iterator items", "")
+ } else {
+ (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()")
+ };
+
+ span_lint_and_then(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ |diag| {
+ let method_span = expr.span.with_lo(cloned_call.span.hi());
+ if let Some(mut snip) = snippet_opt(cx, method_span) {
+ snip.push_str(trailing_clone);
+ let replace_span = expr.span.with_lo(cloned_recv.span.hi());
+ diag.span_suggestion(replace_span, "try this", snip, Applicability::MachineApplicable);
+ }
+ }
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs b/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs
new file mode 100644
index 000000000..43e9451f7
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_skip_next.rs
@@ -0,0 +1,46 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::is_trait_method;
+use clippy_utils::path_to_local;
+use clippy_utils::source::snippet;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{BindingAnnotation, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_SKIP_NEXT;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ // lint if caller of skip is an Iterator
+ if is_trait_method(cx, expr, sym::Iterator) {
+ let mut application = Applicability::MachineApplicable;
+ span_lint_and_then(
+ cx,
+ ITER_SKIP_NEXT,
+ expr.span.trim_start(recv.span).unwrap(),
+ "called `skip(..).next()` on an iterator",
+ |diag| {
+ if_chain! {
+ if let Some(id) = path_to_local(recv);
+ if let Node::Pat(pat) = cx.tcx.hir().get(id);
+ if let PatKind::Binding(ann, _, _, _) = pat.kind;
+ if ann != BindingAnnotation::Mutable;
+ then {
+ application = Applicability::Unspecified;
+ diag.span_help(
+ pat.span,
+ &format!("for this change `{}` has to be mutable", snippet(cx, pat.span, "..")),
+ );
+ }
+ }
+
+ diag.span_suggestion(
+ expr.span.trim_start(recv.span).unwrap(),
+ "use `nth` instead",
+ format!(".nth({})", snippet(cx, arg.span, "..")),
+ application,
+ );
+ },
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
new file mode 100644
index 000000000..152072e09
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
@@ -0,0 +1,47 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::Range;
+use clippy_utils::is_integer_const;
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+use super::ITER_WITH_DRAIN;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
+ 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,
+ );
+ };
+}
+
+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/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs b/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs
new file mode 100644
index 000000000..64c09214a
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iterator_step_by_zero.rs
@@ -0,0 +1,21 @@
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_trait_method;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITERATOR_STEP_BY_ZERO;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg) {
+ span_lint(
+ cx,
+ ITERATOR_STEP_BY_ZERO,
+ expr.span,
+ "`Iterator::step_by(0)` will panic at runtime",
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs
new file mode 100644
index 000000000..0fe510bea
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs
@@ -0,0 +1,164 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{match_def_path, path_def_id};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::layout::LayoutOf;
+
+pub fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ arith_lhs: &hir::Expr<'_>,
+ arith_rhs: &hir::Expr<'_>,
+ unwrap_arg: &hir::Expr<'_>,
+ arith: &str,
+) {
+ let ty = cx.typeck_results().expr_ty(arith_lhs);
+ if !ty.is_integral() {
+ return;
+ }
+
+ let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) {
+ mm
+ } else {
+ return;
+ };
+
+ if ty.is_signed() {
+ use self::{
+ MinMax::{Max, Min},
+ Sign::{Neg, Pos},
+ };
+
+ let sign = if let Some(sign) = lit_sign(arith_rhs) {
+ sign
+ } else {
+ return;
+ };
+
+ match (arith, sign, mm) {
+ ("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
+ // "mul" is omitted because lhs can be negative.
+ _ => return,
+ }
+ } else {
+ match (mm, arith) {
+ (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
+ _ => return,
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ super::MANUAL_SATURATING_ARITHMETIC,
+ expr.span,
+ "manual saturating arithmetic",
+ &format!("try using `saturating_{}`", arith),
+ format!(
+ "{}.saturating_{}({})",
+ snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
+ arith,
+ snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
+
+#[derive(PartialEq, Eq)]
+enum MinMax {
+ Min,
+ Max,
+}
+
+fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<MinMax> {
+ // `T::max_value()` `T::min_value()` inherent methods
+ if_chain! {
+ if let hir::ExprKind::Call(func, args) = &expr.kind;
+ if args.is_empty();
+ if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind;
+ then {
+ match segment.ident.as_str() {
+ "max_value" => return Some(MinMax::Max),
+ "min_value" => return Some(MinMax::Min),
+ _ => {}
+ }
+ }
+ }
+
+ let ty = cx.typeck_results().expr_ty(expr);
+ let ty_str = ty.to_string();
+
+ // `std::T::MAX` `std::T::MIN` constants
+ if let Some(id) = path_def_id(cx, expr) {
+ if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
+ return Some(MinMax::Max);
+ }
+
+ if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
+ return Some(MinMax::Min);
+ }
+ }
+
+ // Literals
+ let bits = cx.layout_of(ty).unwrap().size.bits();
+ let (minval, maxval): (u128, u128) = if ty.is_signed() {
+ let minval = 1 << (bits - 1);
+ let mut maxval = !(1 << (bits - 1));
+ if bits != 128 {
+ maxval &= (1 << bits) - 1;
+ }
+ (minval, maxval)
+ } else {
+ (0, if bits == 128 { !0 } else { (1 << bits) - 1 })
+ };
+
+ let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
+ if let hir::ExprKind::Lit(lit) = &expr.kind {
+ if let ast::LitKind::Int(value, _) = lit.node {
+ if value == maxval {
+ return Some(MinMax::Max);
+ }
+
+ if check_min && value == minval {
+ return Some(MinMax::Min);
+ }
+ }
+ }
+
+ None
+ };
+
+ if let r @ Some(_) = check_lit(expr, !ty.is_signed()) {
+ return r;
+ }
+
+ if ty.is_signed() {
+ if let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind {
+ return check_lit(val, true);
+ }
+ }
+
+ None
+}
+
+#[derive(PartialEq, Eq)]
+enum Sign {
+ Pos,
+ Neg,
+}
+
+fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
+ if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
+ if let hir::ExprKind::Lit(..) = &inner.kind {
+ return Some(Sign::Neg);
+ }
+ } else if let hir::ExprKind::Lit(..) = &expr.kind {
+ return Some(Sign::Pos);
+ }
+
+ None
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs b/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs
new file mode 100644
index 000000000..46d2fc493
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/manual_str_repeat.rs
@@ -0,0 +1,99 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
+use clippy_utils::{is_expr_path_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+use std::borrow::Cow;
+
+use super::MANUAL_STR_REPEAT;
+
+enum RepeatKind {
+ String,
+ Char(char),
+}
+
+fn get_ty_param(ty: Ty<'_>) -> Option<Ty<'_>> {
+ if let ty::Adt(_, subs) = ty.kind() {
+ subs.types().next()
+ } else {
+ None
+ }
+}
+
+fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<RepeatKind> {
+ if let ExprKind::Lit(lit) = &e.kind {
+ match lit.node {
+ LitKind::Str(..) => Some(RepeatKind::String),
+ LitKind::Char(c) => Some(RepeatKind::Char(c)),
+ _ => None,
+ }
+ } else {
+ let ty = cx.typeck_results().expr_ty(e);
+ if is_type_diagnostic_item(cx, ty, sym::String)
+ || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, Ty::is_str))
+ || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, Ty::is_str))
+ {
+ Some(RepeatKind::String)
+ } else {
+ let ty = ty.peel_refs();
+ (ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then_some(RepeatKind::String)
+ }
+ }
+}
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ collect_expr: &Expr<'_>,
+ take_expr: &Expr<'_>,
+ take_self_arg: &Expr<'_>,
+ take_arg: &Expr<'_>,
+) {
+ if_chain! {
+ if let ExprKind::Call(repeat_fn, [repeat_arg]) = take_self_arg.kind;
+ if is_expr_path_def_path(cx, repeat_fn, &paths::ITER_REPEAT);
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(collect_expr), sym::String);
+ if let Some(collect_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id);
+ if let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id);
+ if let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ if cx.tcx.trait_of_item(collect_id) == Some(iter_trait_id);
+ if cx.tcx.trait_of_item(take_id) == Some(iter_trait_id);
+ if let Some(repeat_kind) = parse_repeat_arg(cx, repeat_arg);
+ let ctxt = collect_expr.span.ctxt();
+ if ctxt == take_expr.span.ctxt();
+ if ctxt == take_self_arg.span.ctxt();
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let count_snip = snippet_with_context(cx, take_arg.span, ctxt, "..", &mut app).0;
+
+ let val_str = match repeat_kind {
+ RepeatKind::Char(_) if repeat_arg.span.ctxt() != ctxt => return,
+ RepeatKind::Char('\'') => r#""'""#.into(),
+ RepeatKind::Char('"') => r#""\"""#.into(),
+ RepeatKind::Char(_) =>
+ match snippet_with_applicability(cx, repeat_arg.span, "..", &mut app) {
+ Cow::Owned(s) => Cow::Owned(format!("\"{}\"", &s[1..s.len() - 1])),
+ s @ Cow::Borrowed(_) => s,
+ },
+ RepeatKind::String =>
+ Sugg::hir_with_context(cx, repeat_arg, ctxt, "..", &mut app).maybe_par().to_string().into(),
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_STR_REPEAT,
+ collect_expr.span,
+ "manual implementation of `str::repeat` using iterators",
+ "try this",
+ format!("{}.repeat({})", val_str, count_snip),
+ app
+ )
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs b/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs
new file mode 100644
index 000000000..d420f144e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/map_collect_result_unit.rs
@@ -0,0 +1,47 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+use super::MAP_COLLECT_RESULT_UNIT;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ iter: &hir::Expr<'_>,
+ map_fn: &hir::Expr<'_>,
+ collect_recv: &hir::Expr<'_>,
+) {
+ if_chain! {
+ // called on Iterator
+ if is_trait_method(cx, collect_recv, sym::Iterator);
+ // return of collect `Result<(),_>`
+ let collect_ret_ty = cx.typeck_results().expr_ty(expr);
+ if is_type_diagnostic_item(cx, collect_ret_ty, sym::Result);
+ if let ty::Adt(_, substs) = collect_ret_ty.kind();
+ if let Some(result_t) = substs.types().next();
+ if result_t.is_unit();
+ // get parts for snippet
+ then {
+ span_lint_and_sugg(
+ cx,
+ MAP_COLLECT_RESULT_UNIT,
+ expr.span,
+ "`.map().collect()` can be replaced with `.try_for_each()`",
+ "try this",
+ format!(
+ "{}.try_for_each({})",
+ snippet(cx, iter.span, ".."),
+ snippet(cx, map_fn.span, "..")
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs
new file mode 100644
index 000000000..13853dec9
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs
@@ -0,0 +1,73 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+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_middle::ty;
+use rustc_span::{symbol::sym, Span};
+
+use super::MAP_FLATTEN;
+
+/// lint use of `map().flatten()` for `Iterators` and 'Options'
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
+ if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ MAP_FLATTEN,
+ expr.span.with_lo(map_span.lo()),
+ &format!("called `map(..).flatten()` on `{}`", caller_ty_name),
+ &format!(
+ "try replacing `map` with `{}` and remove the `.flatten()`",
+ method_to_use
+ ),
+ format!("{}({})", method_to_use, closure_snippet),
+ applicability,
+ );
+ }
+}
+
+fn try_get_caller_ty_name_and_method_name(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ caller_expr: &Expr<'_>,
+ map_arg: &Expr<'_>,
+) -> Option<(&'static str, &'static str)> {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if is_map_to_option(cx, map_arg) {
+ // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
+ Some(("Iterator", "filter_map"))
+ } else {
+ // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
+ Some(("Iterator", "flat_map"))
+ }
+ } else {
+ if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ return Some(("Option", "and_then"));
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+ return Some(("Result", "and_then"));
+ }
+ }
+ None
+ }
+}
+
+fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
+ let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
+ match map_closure_ty.kind() {
+ ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
+ let map_closure_sig = match map_closure_ty.kind() {
+ ty::Closure(_, substs) => substs.as_closure().sig(),
+ _ => map_closure_ty.fn_sig(cx.tcx),
+ };
+ let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
+ is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
+ },
+ _ => false,
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs
new file mode 100644
index 000000000..862a9578e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs
@@ -0,0 +1,39 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_expr_identity_function, is_trait_method};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::MAP_IDENTITY;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ caller: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ name: &str,
+ _map_span: Span,
+) {
+ let caller_ty = cx.typeck_results().expr_ty(caller);
+
+ if_chain! {
+ if is_trait_method(cx, expr, sym::Iterator)
+ || is_type_diagnostic_item(cx, caller_ty, sym::Result)
+ || is_type_diagnostic_item(cx, caller_ty, sym::Option);
+ if is_expr_identity_function(cx, map_arg);
+ if let Some(sugg_span) = expr.span.trim_start(caller.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ MAP_IDENTITY,
+ sugg_span,
+ "unnecessary map of the identity function",
+ &format!("remove the call to `{}`", name),
+ String::new(),
+ Applicability::MachineApplicable,
+ )
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs
new file mode 100644
index 000000000..4a8e7ce4d
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs
@@ -0,0 +1,79 @@
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::sym;
+
+use super::MAP_UNWRAP_OR;
+
+/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
+/// Return true if lint triggered
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ map_arg: &'tcx hir::Expr<'_>,
+ unwrap_arg: &'tcx hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
+ // lint if the caller of `map()` is an `Option`
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ if is_result && !meets_msrv(msrv, msrvs::RESULT_MAP_OR_ELSE) {
+ return false;
+ }
+
+ if is_option || is_result {
+ // Don't make a suggestion that may fail to compile due to mutably borrowing
+ // the same variable twice.
+ let map_mutated_vars = mutated_variables(recv, cx);
+ let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
+ if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
+ if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // 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"
+ } 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"
+ };
+ // get snippets for args to map() and unwrap_or_else()
+ let map_snippet = snippet(cx, map_arg.span, "..");
+ let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
+ // lint, with note if neither arg is > 1 line and both map() and
+ // unwrap_or_else() have the same span
+ let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
+ let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt();
+ if same_span && !multiline {
+ let var_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ MAP_UNWRAP_OR,
+ expr.span,
+ msg,
+ "try this",
+ format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ } else if same_span && multiline {
+ span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
+ return true;
+ }
+ }
+
+ false
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs
new file mode 100644
index 000000000..202fbc1f7
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs
@@ -0,0 +1,3052 @@
+mod bind_instead_of_map;
+mod bytes_nth;
+mod chars_cmp;
+mod chars_cmp_with_unwrap;
+mod chars_last_cmp;
+mod chars_last_cmp_with_unwrap;
+mod chars_next_cmp;
+mod chars_next_cmp_with_unwrap;
+mod clone_on_copy;
+mod clone_on_ref_ptr;
+mod cloned_instead_of_copied;
+mod err_expect;
+mod expect_fun_call;
+mod expect_used;
+mod extend_with_drain;
+mod filetype_is_file;
+mod filter_map;
+mod filter_map_identity;
+mod filter_map_next;
+mod filter_next;
+mod flat_map_identity;
+mod flat_map_option;
+mod from_iter_instead_of_collect;
+mod get_last_with_len;
+mod get_unwrap;
+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;
+mod iter_nth;
+mod iter_nth_zero;
+mod iter_overeager_cloned;
+mod iter_skip_next;
+mod iter_with_drain;
+mod iterator_step_by_zero;
+mod manual_saturating_arithmetic;
+mod manual_str_repeat;
+mod map_collect_result_unit;
+mod map_flatten;
+mod map_identity;
+mod map_unwrap_or;
+mod needless_option_as_deref;
+mod needless_option_take;
+mod no_effect_replace;
+mod obfuscated_if_else;
+mod ok_expect;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod or_then_unwrap;
+mod search_is_some;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
+mod str_splitn;
+mod string_extend_chars;
+mod suspicious_map;
+mod suspicious_splitn;
+mod uninit_assumed_init;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_iter_cloned;
+mod unnecessary_join;
+mod unnecessary_lazy_eval;
+mod unnecessary_to_owned;
+mod unwrap_or_else_default;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, TraitRef, Ty};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `cloned()` on an `Iterator` or `Option` where
+ /// `copied()` could be used instead.
+ ///
+ /// ### Why is this bad?
+ /// `copied()` is better because it guarantees that the type being cloned
+ /// implements `Copy`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1, 2, 3].iter().cloned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// [1, 2, 3].iter().copied();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub CLONED_INSTEAD_OF_COPIED,
+ pedantic,
+ "used `cloned` where `copied` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
+ ///
+ /// ### Why is this bad?
+ /// It's often inefficient to clone all elements of an iterator, when eventually, only some
+ /// of them will be consumed.
+ ///
+ /// ### Known Problems
+ /// This `lint` removes the side of effect of cloning items in the iterator.
+ /// A code that relies on that side-effect could fail.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ /// vec.iter().cloned().take(10);
+ /// vec.iter().cloned().last();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec!["string".to_string()];
+ /// vec.iter().take(10).cloned();
+ /// vec.iter().last().cloned();
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub ITER_OVEREAGER_CLONED,
+ perf,
+ "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+ /// used instead.
+ ///
+ /// ### Why is this bad?
+ /// When applicable, `filter_map()` is more clear since it shows that
+ /// `Option` is used to produce 0 or 1 items.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub FLAT_MAP_OPTION,
+ pedantic,
+ "used `flat_map` where `filter_map` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.unwrap()` calls on `Option`s and on `Result`s.
+ ///
+ /// ### Why is this bad?
+ /// It is better to handle the `None` or `Err` case,
+ /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of
+ /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is
+ /// `Allow` by default.
+ ///
+ /// `result.unwrap()` will let the thread panic on `Err` values.
+ /// Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// Even if you want to panic on errors, not all `Error`s implement good
+ /// messages on display. Therefore, it may be beneficial to look at the places
+ /// where they may get displayed. Activate this lint to do just that.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.unwrap();
+ /// result.unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.expect("more helpful message");
+ /// result.expect("more helpful message");
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub UNWRAP_USED,
+ restriction,
+ "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.expect()` calls on `Option`s and `Result`s.
+ ///
+ /// ### Why is this bad?
+ /// Usually it is better to handle the `None` or `Err` case.
+ /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why
+ /// this lint is `Allow` by default.
+ ///
+ /// `result.expect()` will let the thread panic on `Err`
+ /// values. Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// ### Examples
+ /// ```rust,ignore
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option.expect("one");
+ /// result.expect("one");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// option?;
+ ///
+ /// // or
+ ///
+ /// result?;
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub EXPECT_USED,
+ restriction,
+ "using `.expect()` on `Result` or `Option`, which might be better handled"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods that should live in a trait
+ /// implementation of a `std` trait (see [llogiq's blog
+ /// post](http://llogiq.github.io/2015/07/30/traits.html) for further
+ /// information) instead of an inherent implementation.
+ ///
+ /// ### Why is this bad?
+ /// Implementing the traits improve ergonomics for users of
+ /// the code, often with very little cost. Also people seeing a `mul(...)`
+ /// method
+ /// may expect `*` to work equally, so you should have good reason to disappoint
+ /// them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn add(&self, other: &X) -> X {
+ /// // ..
+ /// # X
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SHOULD_IMPLEMENT_TRAIT,
+ style,
+ "defining a method that should be implementing a std trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// 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 |`&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.
+ /// Clippy can not tell if a type that implements a trait is `Copy` or not.
+ /// - Traits implementation, when `&self` is taken.
+ /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+ /// (see e.g. the `std::string::ToString` trait).
+ ///
+ /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required.
+ ///
+ /// Please find more info here:
+ /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ ///
+ /// ### Why is this bad?
+ /// Consistency breeds readability. If you follow the
+ /// conventions, your users won't be surprised that they, e.g., need to supply a
+ /// mutable reference to a `as_..` function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct X;
+ /// impl X {
+ /// fn as_str(self) -> &'static str {
+ /// // ..
+ /// # ""
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub WRONG_SELF_CONVENTION,
+ style,
+ "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `ok().expect(..)`.
+ ///
+ /// ### Why is this bad?
+ /// Because you usually call `expect()` on the `Result`
+ /// directly to get a better error message.
+ ///
+ /// ### Known problems
+ /// The error type needs to implement `Debug`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ /// x.ok().expect("why did I do this again?");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ /// x.expect("why did I do this again?");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OK_EXPECT,
+ style,
+ "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.err().expect()` calls on the `Result` type.
+ ///
+ /// ### Why is this bad?
+ /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`.
+ ///
+ /// ### Example
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.err().expect("Testing err().expect()");
+ /// ```
+ /// Use instead:
+ /// ```should_panic
+ /// let x: Result<u32, &str> = Ok(10);
+ /// x.expect_err("Testing expect_err");
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub ERR_EXPECT,
+ style,
+ r#"using `.err().expect("")` when `.expect_err("")` can be used"#
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
+ /// `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written as `_.unwrap_or_default`, which is
+ /// simpler and more concise.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let x = Some(1);
+ /// x.unwrap_or_else(Default::default);
+ /// x.unwrap_or_else(u32::default);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = Some(1);
+ /// x.unwrap_or_default();
+ /// ```
+ #[clippy::version = "1.56.0"]
+ pub UNWRAP_OR_ELSE_DEFAULT,
+ style,
+ "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
+ /// `result.map(_).unwrap_or_else(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written more concisely (resp.) as
+ /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ /// option.map(|a| a + 1).unwrap_or(0);
+ /// result.map(|a| a + 1).unwrap_or_else(some_function);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let option = Some(1);
+ /// # let result: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ /// option.map_or(0, |a| a + 1);
+ /// result.map_or_else(some_function, |a| a + 1);
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MAP_UNWRAP_OR,
+ pedantic,
+ "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, _)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.and_then(_)`.
+ ///
+ /// ### Known problems
+ /// The order of the arguments is not in execution order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some(1);
+ /// opt.map_or(None, |a| Some(a + 1));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let opt = Some(1);
+ /// opt.and_then(|a| Some(a + 1));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_MAP_OR_NONE,
+ style,
+ "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map_or(None, Some)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ok()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.map_or(None, Some));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.ok());
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub RESULT_MAP_OR_INTO_OPTION,
+ style,
+ "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
+ /// `_.or_else(|x| Err(y))`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.map(|x| y)` or `_.map_err(|x| y)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().and_then(|s| Some(s.len()));
+ /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) });
+ /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) });
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().map(|s| s.len());
+ /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });
+ /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub BIND_INSTEAD_OF_MAP,
+ complexity,
+ "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().filter(|x| **x == 0).next();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FILTER_NEXT,
+ complexity,
+ "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.skip_while(condition).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find(!condition)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().skip_while(|x| **x == 0).next();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x != 0);
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub SKIP_WHILE_NEXT,
+ complexity,
+ "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![vec![1]];
+ /// let opt = Some(5);
+ ///
+ /// vec.iter().map(|x| x.iter()).flatten();
+ /// opt.map(|x| Some(x * 2)).flatten();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![vec![1]];
+ /// # let opt = Some(5);
+ /// vec.iter().flat_map(|x| x.iter());
+ /// opt.and_then(|x| Some(x * 2));
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub MAP_FLATTEN,
+ complexity,
+ "using combinations of `flatten` and `map` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter(_).map(_)` that can be written more simply
+ /// as `filter_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `filter` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// (0_i32..10)
+ /// .filter(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #[allow(unused)]
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FILTER_MAP,
+ complexity,
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.find(_).map(_)` that can be written more simply
+ /// as `find_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `find` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0_i32..10)
+ /// .find(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// (0_i32..10).find_map(|n| n.checked_add(1));
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub MANUAL_FIND_MAP,
+ complexity,
+ "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter_map(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find_map(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
+ /// ```
+ /// Can be written as
+ ///
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
+ /// ```
+ #[clippy::version = "1.36.0"]
+ pub FILTER_MAP_NEXT,
+ pedantic,
+ "using combination of `filter_map` and `next` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `flat_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flat_map(|x| x);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for an iterator or string search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as:
+ /// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
+ /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ ///
+ /// "hello world".find("world").is_none();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ ///
+ /// # #[allow(unused)]
+ /// !"hello world".contains("world");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.chars().next()` on a `str` to check
+ /// if it starts with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.starts_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_NEXT_CMP,
+ style,
+ "using `.chars().next()` to check if a string starts with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+ /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
+ /// `unwrap_or_default` instead.
+ ///
+ /// ### Why is this bad?
+ /// The function will always be called and potentially
+ /// allocate an object acting as the default.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantic of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or(String::new());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_else(String::new);
+ ///
+ /// // or
+ ///
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_default();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OR_FUN_CALL,
+ perf,
+ "using any `*or` method with a function call, which suggests `*or_else`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.or(…).unwrap()` calls to Options and Results.
+ ///
+ /// ### Why is this bad?
+ /// You should use `.unwrap_or(…)` instead for clarity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # type Error = &'static str;
+ /// # let result: Result<&str, Error> = Err("error");
+ /// let value = result.or::<Error>(Ok(fallback)).unwrap();
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.or(Some(fallback)).unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # let result: Result<&str, &str> = Err("error");
+ /// let value = result.unwrap_or(fallback);
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.unwrap_or(fallback);
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub OR_THEN_UNWRAP,
+ complexity,
+ "checks for `.or(…).unwrap()` calls to Options and Results."
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
+ /// etc., and suggests to use `unwrap_or_else` instead
+ ///
+ /// ### Why is this bad?
+ /// The function will always be called.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantics of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(&format!("Err {}: {}", err_code, err_msg));
+ ///
+ /// // or
+ ///
+ /// # let foo = Some(String::new());
+ /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPECT_FUN_CALL,
+ perf,
+ "using any `expect` method with a function call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a `Copy` type.
+ ///
+ /// ### Why is this bad?
+ /// The only reason `Copy` types implement `Clone` is for
+ /// generics, not for using the `clone` method on a concrete type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_COPY,
+ complexity,
+ "using `clone` on a `Copy` type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on a ref-counted pointer,
+ /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+ /// function syntax instead (e.g., `Rc::clone(foo)`).
+ ///
+ /// ### Why is this bad?
+ /// Calling '.clone()' on an Rc, Arc, or Weak
+ /// can obscure the fact that only the pointer is being cloned, not the underlying
+ /// data.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// let x = Rc::new(1);
+ ///
+ /// x.clone();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// # let x = Rc::new(1);
+ /// Rc::clone(&x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_ON_REF_PTR,
+ restriction,
+ "using 'clone' on a ref-counted pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.clone()` on an `&&T`.
+ ///
+ /// ### Why is this bad?
+ /// Cloning an `&&T` copies the inner `&T`, instead of
+ /// cloning the underlying `T`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = vec![1];
+ /// let y = &&x;
+ /// let z = y.clone();
+ /// println!("{:p} {:p}", *y, z); // prints out the same pointer
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CLONE_DOUBLE_REF,
+ correctness,
+ "using `clone` on `&&T`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.to_string()` on an `&&T` where
+ /// `T` implements `ToString` directly (like `&&str` or `&&String`).
+ ///
+ /// ### Why is this bad?
+ /// This bypasses the specialized implementation of
+ /// `ToString` and instead goes through the more expensive string formatting
+ /// facilities.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Generic implementation for `T: Display` is used (slow)
+ /// ["foo", "bar"].iter().map(|s| s.to_string());
+ ///
+ /// // OK, the specialized impl is used
+ /// ["foo", "bar"].iter().map(|&s| s.to_string());
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub INEFFICIENT_TO_STRING,
+ pedantic,
+ "using `to_string` on `&&T` where `T: ToString`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `new` not returning a type that contains `Self`.
+ ///
+ /// ### Why is this bad?
+ /// As a convention, `new` methods are used to make a new
+ /// instance of a type.
+ ///
+ /// ### Example
+ /// In an impl block:
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEW_RET_NO_SELF,
+ style,
+ "not returning type containing `Self` in a `new` method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string methods that receive a single-character
+ /// `str` as an argument, e.g., `_.split("x")`.
+ ///
+ /// ### Why is this bad?
+ /// Performing these methods using a `char` is faster than
+ /// using a `str`.
+ ///
+ /// ### Known problems
+ /// Does not catch multi-byte unicode characters.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// _.split("x");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// _.split('x');
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_CHAR_PATTERN,
+ perf,
+ "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// ### Why is this bad?
+ /// This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// ### Example
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITERATOR_STEP_BY_ZERO,
+ correctness,
+ "using `Iterator::step_by(0)`, which will panic at runtime"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for indirect collection of populated `Option`
+ ///
+ /// ### Why is this bad?
+ /// `Option` is like a collection of 0-1 things, so `flatten`
+ /// automatically does this without suspicious-looking `unwrap` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().flatten();
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub OPTION_FILTER_MAP,
+ complexity,
+ "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `iter.nth(0)`.
+ ///
+ /// ### Why is this bad?
+ /// `iter.next()` is equivalent to
+ /// `iter.nth(0)`, as they both consume the next element,
+ /// but is more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().nth(0);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().next();
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub ITER_NTH_ZERO,
+ style,
+ "replace `iter.nth(0)` with `iter.next()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.iter().nth()` (and the related
+ /// `.iter_mut().nth()`) on standard library types with *O*(1) element access.
+ ///
+ /// ### Why is this bad?
+ /// `.get()` and `.get_mut()` are more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.get(3);
+ /// let bad_slice = &some_vec[..].get(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_NTH,
+ perf,
+ "using `.iter().nth()` on a standard library type with O(1) element access"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.skip(x).next()` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.nth(x)` is cleaner
+ ///
+ /// ### Example
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().skip(3).next();
+ /// let bad_slice = &some_vec[..].iter().skip(3).next();
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_SKIP_NEXT,
+ style,
+ "using `.skip(x).next()` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
+ ///
+ /// ### Why is this bad?
+ /// `.into_iter()` is simpler with better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let mut foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.drain(..).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// let foo = vec![0, 1, 2, 3];
+ /// let bar: HashSet<usize> = foo.into_iter().collect();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub ITER_WITH_DRAIN,
+ nursery,
+ "replace `.drain(..)` with `.into_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `x.get(x.len() - 1)` instead of
+ /// `x.last()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `x.last()` is easier to read and has the same
+ /// result.
+ ///
+ /// Note that using `x[x.len() - 1]` is semantically different from
+ /// `x.last()`. Indexing into the array will panic on out-of-bounds
+ /// accesses, while `x.get()` and `x.last()` will return `None`.
+ ///
+ /// There is another lint (get_unwrap) that covers the case of using
+ /// `x.get(index).unwrap()` instead of `x[index]`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.get(x.len() - 1);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.last();
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub GET_LAST_WITH_LEN,
+ complexity,
+ "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.get().unwrap()` (or
+ /// `.get_mut().unwrap`) on a standard library type which implements `Index`
+ ///
+ /// ### Why is this bad?
+ /// Using the Index trait (`[]`) is more clear and more
+ /// concise.
+ ///
+ /// ### Known problems
+ /// Not a replacement for error handling: Using either
+ /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic`
+ /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a
+ /// temporary placeholder for dealing with the `Option` type, then this does
+ /// not mitigate the need for error handling. If there is a chance that `.get()`
+ /// will be `None` in your program, then it is advisable that the `None` case
+ /// is handled in a future refactor instead of using `.unwrap()` or the Index
+ /// trait.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec.get(3).unwrap();
+ /// *some_vec.get_mut(0).unwrap() = 1;
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec[3];
+ /// some_vec[0] = 1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for occurrences where one vector gets extended instead of append
+ ///
+ /// ### Why is this bad?
+ /// Using `append` instead of `extend` is more concise and faster
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// a.extend(b.drain(..));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// a.append(&mut b);
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub EXTEND_WITH_DRAIN,
+ perf,
+ "using vec.append(&mut vec) to move the full range of a vector to another"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.extend(s.chars())` where s is a
+ /// `&str` or `String`.
+ ///
+ /// ### Why is this bad?
+ /// `.push_str(s)` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.extend(abc.chars());
+ /// s.extend(def.chars());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.push_str(abc);
+ /// s.push_str(&def);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_EXTEND_CHARS,
+ style,
+ "using `x.extend(s.chars())` where s is a `&str` or `String`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.cloned().collect()` on slice to
+ /// create a `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// `.to_vec()` is clearer
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s[..].iter().cloned().collect();
+ /// ```
+ /// The better use would be:
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s.to_vec();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ITER_CLONED_COLLECT,
+ style,
+ "using `.cloned().collect()` on slice to create a `Vec`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.chars().last()` or
+ /// `_.chars().next_back()` on a `str` to check if it ends with a given char.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.ends_with(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "_";
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-');
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let name = "_";
+ /// name.ends_with('_') || name.ends_with('-');
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CHARS_LAST_CMP,
+ style,
+ "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.as_ref()` or `.as_mut()` where the
+ /// types before and after the call are the same.
+ ///
+ /// ### Why is this bad?
+ /// The call is unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x.as_ref());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_ASREF,
+ complexity,
+ "using `as_ref` where the types before and after the call are the same"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `fold` when a more succinct alternative exists.
+ /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
+ /// `sum` or `product`.
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[allow(unused)]
+ /// (0..3).fold(false, |acc, x| acc || x > 2);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// (0..3).any(|x| x > 2);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_FOLD,
+ style,
+ "using `fold` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
+ /// More specifically it checks if the closure provided is only performing one of the
+ /// filter or map operations and suggests the appropriate option.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).filter(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).filter_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1);
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub UNNECESSARY_FILTER_MAP,
+ complexity,
+ "using `filter_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `find_map` calls that could be replaced by `find` or `map`. More
+ /// specifically it checks if the closure provided is only performing one of the
+ /// find or map operations and suggests the appropriate option.
+ ///
+ /// ### Why is this bad?
+ /// Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).find(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).find_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1).next();
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_FIND_MAP,
+ complexity,
+ "using `find_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `into_iter` calls on references which should be replaced by `iter`
+ /// or `iter_mut`.
+ ///
+ /// ### Why is this bad?
+ /// Readability. Calling `into_iter` on a reference will not move out its
+ /// content into the resulting iterator, which is confusing. It is better just call `iter` or
+ /// `iter_mut` directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![3, 4, 5];
+ /// (&vec).into_iter();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let vec = vec![3, 4, 5];
+ /// (&vec).iter();
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub INTO_ITER_ON_REF,
+ style,
+ "using `.into_iter()` on a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `map` followed by a `count`.
+ ///
+ /// ### Why is this bad?
+ /// It looks suspicious. Maybe `map` was confused with `filter`.
+ /// If the `map` call is intentional, this should be rewritten
+ /// using `inspect`. Or, if you intend to drive the iterator to
+ /// completion, you can just use `for_each` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).map(|x| x + 2).count();
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub SUSPICIOUS_MAP,
+ suspicious,
+ "suspicious usage of map"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `MaybeUninit::uninit().assume_init()`.
+ ///
+ /// ### Why is this bad?
+ /// For most types, this is undefined behavior.
+ ///
+ /// ### Known problems
+ /// For now, we accept empty tuples and tuples / arrays
+ /// of `MaybeUninit`. There may be other types that allow uninitialized
+ /// data, but those are not yet rigorously defined.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Beware the UB
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
+ /// ```
+ ///
+ /// Note that the following is OK:
+ ///
+ /// ```rust
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: [MaybeUninit<bool>; 5] = unsafe {
+ /// MaybeUninit::uninit().assume_init()
+ /// };
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub UNINIT_ASSUMED_INIT,
+ correctness,
+ "`MaybeUninit::uninit().assume_init()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
+ ///
+ /// ### Why is this bad?
+ /// These can be written simply with `saturating_add/sub` methods.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.checked_add(y).unwrap_or(u32::MAX);
+ /// let sub = x.checked_sub(y).unwrap_or(u32::MIN);
+ /// ```
+ ///
+ /// can be written using dedicated methods for saturating addition/subtraction as:
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.saturating_add(y);
+ /// let sub = x.saturating_sub(y);
+ /// ```
+ #[clippy::version = "1.39.0"]
+ pub MANUAL_SATURATING_ARITHMETIC,
+ style,
+ "`.checked_add/sub(x).unwrap_or(MAX/MIN)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+ /// zero-sized types
+ ///
+ /// ### Why is this bad?
+ /// This is a no-op, and likely unintended
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub ZST_OFFSET,
+ correctness,
+ "Check for offset calculations on raw pointers to zero-sized types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `FileType::is_file()`.
+ ///
+ /// ### Why is this bad?
+ /// When people testing a file type with `FileType::is_file`
+ /// they are testing whether a path is something they can get bytes from. But
+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if filetype.is_file() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ ///
+ /// should be written as:
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if !filetype.is_dir() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.as_deref()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_ref().map(String::as_str)
+ /// # ;
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_deref()
+ /// # ;
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub OPTION_AS_REF_DEREF,
+ complexity,
+ "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `iter().next()` on a Slice or an Array
+ ///
+ /// ### Why is this bad?
+ /// These can be shortened into `.get()`
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a[2..].iter().next();
+ /// b.iter().next();
+ /// ```
+ /// should be written as:
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a.get(2);
+ /// b.get(0);
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub ITER_NEXT_SLICE,
+ style,
+ "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns when using `push_str`/`insert_str` with a single-character string literal
+ /// where `push`/`insert` with a `char` would work fine.
+ ///
+ /// ### Why is this bad?
+ /// It's less clear that we are pushing a single character.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut string = String::new();
+ /// string.insert_str(0, "R");
+ /// string.push_str("R");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let mut string = String::new();
+ /// string.insert(0, 'R');
+ /// string.push('R');
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub SINGLE_CHAR_ADD_STR,
+ style,
+ "`push_str()` or `insert_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// ### Why is this bad?
+ /// Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// ### Known problems
+ /// It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).collect::<Result<(), _>()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `try_for_each` instead is more readable and idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// (0..3).try_for_each(|t| Err(t));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MAP_COLLECT_RESULT_UNIT,
+ style,
+ "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `from_iter()` function calls on types that implement the `FromIterator`
+ /// trait.
+ ///
+ /// ### Why is this bad?
+ /// It is recommended style to use collect. See
+ /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
+ ///
+ /// ### Example
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v = Vec::from_iter(five_fives);
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v: Vec<i32> = five_fives.collect();
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub FROM_ITER_INSTEAD_OF_COLLECT,
+ pedantic,
+ "use `.collect()` instead of `::from_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `inspect().for_each()`.
+ ///
+ /// ### Why is this bad?
+ /// It is the same as performing the computation
+ /// inside `inspect` at the beginning of the closure in `for_each`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .inspect(|&x| println!("inspect the number: {}", x))
+ /// .for_each(|&x| {
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .for_each(|&x| {
+ /// println!("inspect the number: {}", x);
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub INSPECT_FOR_EACH,
+ complexity,
+ "using `.inspect().for_each()`, which can be replaced with `.for_each()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `filter_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.filter_map(|x| x);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.flatten();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub FILTER_MAP_IDENTITY,
+ complexity,
+ "call to `filter_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map(f)` where `f` is the identity function.
+ ///
+ /// ### Why is this bad?
+ /// It can be written more concisely without the call to `map`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = [1, 2, 3];
+ /// let y: Vec<_> = x.iter().map(|x| 2*x).collect();
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub MAP_IDENTITY,
+ complexity,
+ "using iterator.map(|x| x)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.bytes().nth()`.
+ ///
+ /// ### Why is this bad?
+ /// `.as_bytes().get()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[allow(unused)]
+ /// "Hello".bytes().nth(3);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #[allow(unused)]
+ /// "Hello".as_bytes().get(3);
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub BYTES_NTH,
+ style,
+ "replace `.bytes().nth()` with `.as_bytes().get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
+ ///
+ /// ### Why is this bad?
+ /// These methods do the same thing as `_.clone()` but may be confusing as
+ /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.to_vec();
+ /// let c = a.to_owned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.clone();
+ /// let c = a.clone();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub IMPLICIT_CLONE,
+ pedantic,
+ "implicitly cloning a value by invoking a function on its dereferenced type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.iter().count()`.
+ ///
+ /// ### Why is this bad?
+ /// `.len()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let some_vec = vec![0, 1, 2, 3];
+ ///
+ /// some_vec.iter().count();
+ /// &some_vec[..].iter().count();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ ///
+ /// some_vec.len();
+ /// &some_vec[..].len();
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub ITER_COUNT,
+ complexity,
+ "replace `.iter().count()` with `.len()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to [`splitn`]
+ /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
+ /// related functions with either zero or one splits.
+ ///
+ /// ### Why is this bad?
+ /// These calls don't actually split the value and are
+ /// likely to be intended as a different number.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let s = "";
+ /// for x in s.splitn(1, ":") {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let s = "";
+ /// for x in s.splitn(2, ":") {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub SUSPICIOUS_SPLITN,
+ correctness,
+ "checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of `str::repeat`
+ ///
+ /// ### Why is this bad?
+ /// These are both harder to read, as well as less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: String = std::iter::repeat('x').take(10).collect();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x: String = "x".repeat(10);
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub MANUAL_STR_REPEAT,
+ perf,
+ "manual implementation of `str::repeat`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn(2, _)`
+ ///
+ /// ### Why is this bad?
+ /// `split_once` is both clearer in intent and slightly more efficient.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// 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
+ /// 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,
+ "replace `.splitn(2, pat)` with `.split_once(pat)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same.
+ /// ### Why is this bad?
+ /// The function `split` is simpler and there is no performance difference in these cases, considering
+ /// that both functions return a lazy iterator.
+ /// ### Example
+ /// ```rust
+ /// let str = "key=value=add";
+ /// let _ = str.splitn(3, '=').next().unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let str = "key=value=add";
+ /// let _ = str.split('=').next().unwrap();
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub NEEDLESS_SPLITN,
+ complexity,
+ "usages of `str::splitn` that can be replaced with `str::split`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned)
+ /// and other `to_owned`-like functions.
+ ///
+ /// ### Why is this bad?
+ /// The unnecessary calls result in useless allocations.
+ ///
+ /// ### Known problems
+ /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an
+ /// owned copy of a resource and the resource is later used mutably. See
+ /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy().to_string());
+ /// fn foo(s: &str) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let path = std::path::Path::new("x");
+ /// foo(&path.to_string_lossy());
+ /// fn foo(s: &str) {}
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub UNNECESSARY_TO_OWNED,
+ perf,
+ "unnecessary calls to `to_owned`-like functions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.collect::<String>()` is more concise and might be more performant
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
+ /// println!("{}", output);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ /// println!("{}", output);
+ /// ```
+ /// ### Known problems
+ /// While `.collect::<String>()` is sometimes more performant, there are cases where
+ /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+ /// will prevent loop unrolling and will result in a negative performance impact.
+ ///
+ /// 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,
+ pedantic,
+ "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`,
+ /// for example, `Option<&T>::as_deref()` returns the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code and improving readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a;
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub NEEDLESS_OPTION_AS_DEREF,
+ complexity,
+ "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.62.0"]
+ pub IS_DIGIT_ASCII_RADIX,
+ style,
+ "use of `char::is_digit(..)` with literal radix of 10 or 16"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `take` function after `as_ref`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code. `take` writes `None` to its argument.
+ /// In this case the modification is useless as it's a temporary that cannot be read from afterwards.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref().take();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = Some(3);
+ /// x.as_ref();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub NEEDLESS_OPTION_TAKE,
+ complexity,
+ "using `.as_ref().take()` on a temporary value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `replace` statements which have no effect.
+ ///
+ /// ### Why is this bad?
+ /// It's either a mistake or confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// "1234".replace("12", "12");
+ /// "1234".replacen("12", "12", 1);
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub NO_EFFECT_REPLACE,
+ suspicious,
+ "replace with no effect"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `.then_some(..).unwrap_or(..)`
+ ///
+ /// ### Why is this bad?
+ /// This can be written more clearly with `if .. else ..`
+ ///
+ /// ### Limitations
+ /// This lint currently only looks for usages of
+ /// `.then_some(..).unwrap_or(..)`, but will be expanded
+ /// to account for similar patterns.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = true;
+ /// x.then_some("a").unwrap_or("b");
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = true;
+ /// if x { "a" } else { "b" };
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub OBFUSCATED_IF_ELSE,
+ style,
+ "use of `.then_some(..).unwrap_or(..)` can be written \
+ more clearly with `if .. else ..`"
+}
+
+pub struct Methods {
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
+ allow_expect_in_tests: bool,
+ allow_unwrap_in_tests: bool,
+}
+
+impl Methods {
+ #[must_use]
+ pub fn new(
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
+ allow_expect_in_tests: bool,
+ allow_unwrap_in_tests: bool,
+ ) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ msrv,
+ allow_expect_in_tests,
+ allow_unwrap_in_tests,
+ }
+ }
+}
+
+impl_lint_pass!(Methods => [
+ UNWRAP_USED,
+ EXPECT_USED,
+ SHOULD_IMPLEMENT_TRAIT,
+ WRONG_SELF_CONVENTION,
+ OK_EXPECT,
+ UNWRAP_OR_ELSE_DEFAULT,
+ MAP_UNWRAP_OR,
+ RESULT_MAP_OR_INTO_OPTION,
+ OPTION_MAP_OR_NONE,
+ BIND_INSTEAD_OF_MAP,
+ OR_FUN_CALL,
+ OR_THEN_UNWRAP,
+ EXPECT_FUN_CALL,
+ CHARS_NEXT_CMP,
+ CHARS_LAST_CMP,
+ CLONE_ON_COPY,
+ CLONE_ON_REF_PTR,
+ CLONE_DOUBLE_REF,
+ ITER_OVEREAGER_CLONED,
+ CLONED_INSTEAD_OF_COPIED,
+ FLAT_MAP_OPTION,
+ INEFFICIENT_TO_STRING,
+ NEW_RET_NO_SELF,
+ SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_ADD_STR,
+ SEARCH_IS_SOME,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP_IDENTITY,
+ MAP_IDENTITY,
+ MANUAL_FILTER_MAP,
+ MANUAL_FIND_MAP,
+ OPTION_FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_COUNT,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ BYTES_NTH,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ GET_LAST_WITH_LEN,
+ STRING_EXTEND_CHARS,
+ ITER_CLONED_COLLECT,
+ ITER_WITH_DRAIN,
+ USELESS_ASREF,
+ UNNECESSARY_FOLD,
+ UNNECESSARY_FILTER_MAP,
+ UNNECESSARY_FIND_MAP,
+ INTO_ITER_ON_REF,
+ SUSPICIOUS_MAP,
+ UNINIT_ASSUMED_INIT,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ MAP_COLLECT_RESULT_UNIT,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ INSPECT_FOR_EACH,
+ IMPLICIT_CLONE,
+ SUSPICIOUS_SPLITN,
+ MANUAL_STR_REPEAT,
+ EXTEND_WITH_DRAIN,
+ MANUAL_SPLIT_ONCE,
+ NEEDLESS_SPLITN,
+ UNNECESSARY_TO_OWNED,
+ UNNECESSARY_JOIN,
+ ERR_EXPECT,
+ NEEDLESS_OPTION_AS_DEREF,
+ IS_DIGIT_ASCII_RADIX,
+ NEEDLESS_OPTION_TAKE,
+ NO_EFFECT_REPLACE,
+ OBFUSCATED_IF_ELSE,
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx str, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, args, _) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) {
+ let name = path.ident.name.as_str();
+ return Some((name, args, path.ident.span));
+ }
+ }
+ None
+}
+
+impl<'tcx> LateLintPass<'tcx> for Methods {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ self.check_methods(cx, expr);
+
+ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ from_iter_instead_of_collect::check(cx, expr, args, func);
+ },
+ hir::ExprKind::MethodCall(method_call, args, _) => {
+ let method_span = method_call.ident.span;
+ or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
+ expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), args);
+ clone_on_copy::check(cx, expr, method_call.ident.name, args);
+ clone_on_ref_ptr::check(cx, expr, method_call.ident.name, args);
+ inefficient_to_string::check(cx, expr, method_call.ident.name, args);
+ 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, self.msrv);
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ },
+ _ => (),
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.def_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
+ if_chain! {
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
+ if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next();
+
+ let method_sig = cx.tcx.fn_sig(impl_item.def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
+
+ let first_arg_ty = method_sig.inputs().iter().next();
+
+ // check conventions w.r.t. conversion method names and predicates
+ if let Some(first_arg_ty) = first_arg_ty;
+
+ then {
+ // if this impl block implements a trait, lint in trait definition instead
+ if !implements_trait && cx.access_levels.is_exported(impl_item.def_id) {
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name &&
+ sig.decl.inputs.len() == method_config.param_count &&
+ method_config.output_type.matches(&sig.decl.output) &&
+ method_config.self_kind.matches(cx, self_ty, *first_arg_ty) &&
+ fn_header_equals(method_config.fn_header, sig.header) &&
+ method_config.lifetime_param_cond(impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name,
+ method_config.trait_name,
+ method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ )
+ );
+ }
+ }
+ }
+
+ if sig.decl.implicit_self.has_implicit_self()
+ && !(self.avoid_breaking_exported_api
+ && cx.access_levels.is_exported(impl_item.def_id))
+ {
+ wrong_self_convention::check(
+ cx,
+ name,
+ self_ty,
+ *first_arg_ty,
+ first_arg.pat.span,
+ implements_trait,
+ false
+ );
+ }
+ }
+ }
+
+ // if this impl block implements a trait, lint in trait definition instead
+ if implements_trait {
+ return;
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
+ // walk the return type and check for Self (this does not check associated types)
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(ret_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(ret_ty, self_ty) {
+ return;
+ }
+
+ // if return type is impl trait, check the associated types
+ if let ty::Opaque(def_id, _) = *ret_ty.kind() {
+ // one of the associated types must be Self
+ for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
+ if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ let assoc_ty = match projection_predicate.term {
+ ty::Term::Ty(ty) => ty,
+ ty::Term::Const(_c) => continue,
+ };
+ // walk the associated type and check for Self
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(assoc_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(assoc_ty, self_ty) {
+ return;
+ }
+ }
+ }
+ }
+
+ if name == "new" && ret_ty != self_ty {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+
+ if_chain! {
+ if let TraitItemKind::Fn(ref sig, _) = item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
+
+ then {
+ let first_arg_span = first_arg_ty.span;
+ let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ wrong_self_convention::check(
+ cx,
+ item.ident.name.as_str(),
+ self_ty,
+ first_arg_ty,
+ first_arg_span,
+ false,
+ true
+ );
+ }
+ }
+
+ if_chain! {
+ if item.ident.name == sym::new;
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id());
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ if !contains_ty(ret_ty, self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+impl Methods {
+ #[allow(clippy::too_many_lines)]
+ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some((name, [recv, args @ ..], span)) = method_call(expr) {
+ match (name, args) {
+ ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
+ zst_offset::check(cx, expr, recv);
+ },
+ ("and_then", [arg]) => {
+ let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
+ let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
+ if !biom_option_linted && !biom_result_linted {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
+ }
+ },
+ ("as_deref" | "as_deref_mut", []) => {
+ needless_option_as_deref::check(cx, expr, recv, name);
+ },
+ ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
+ ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
+ ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
+ ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv),
+ ("collect", []) => match method_call(recv) {
+ Some((name @ ("cloned" | "copied"), [recv2], _)) => {
+ iter_cloned_collect::check(cx, name, expr, recv2);
+ },
+ Some(("map", [m_recv, m_arg], _)) => {
+ map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
+ },
+ Some(("take", [take_self_arg, take_arg], _)) => {
+ if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
+ manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
+ }
+ },
+ _ => {},
+ },
+ ("count", []) => match method_call(recv) {
+ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
+ Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
+ iter_count::check(cx, expr, recv2, name2);
+ },
+ Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
+ _ => {},
+ },
+ ("drain", [arg]) => {
+ iter_with_drain::check(cx, expr, recv, span, arg);
+ },
+ ("expect", [_]) => match method_call(recv) {
+ Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
+ Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
+ _ => expect_used::check(cx, expr, recv, self.allow_expect_in_tests),
+ },
+ ("extend", [arg]) => {
+ string_extend_chars::check(cx, expr, recv, arg);
+ extend_with_drain::check(cx, expr, recv, arg);
+ },
+ ("filter_map", [arg]) => {
+ unnecessary_filter_map::check(cx, expr, arg, name);
+ filter_map_identity::check(cx, expr, arg, span);
+ },
+ ("find_map", [arg]) => {
+ unnecessary_filter_map::check(cx, expr, arg, name);
+ },
+ ("flat_map", [arg]) => {
+ flat_map_identity::check(cx, expr, arg, span);
+ flat_map_option::check(cx, expr, arg, span);
+ },
+ ("flatten", []) => match method_call(recv) {
+ Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
+ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true),
+ _ => {},
+ },
+ ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
+ ("for_each", [_]) => {
+ if let Some(("inspect", [_, _], span2)) = method_call(recv) {
+ inspect_for_each::check(cx, expr, span2);
+ }
+ },
+ ("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg),
+ ("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, self.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]) => {
+ if let Some(("collect", _, span)) = method_call(recv) {
+ unnecessary_join::check(cx, expr, recv, join_arg, span);
+ }
+ },
+ ("last", []) | ("skip", [_]) => {
+ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
+ }
+ }
+ },
+ (name @ ("map" | "map_err"), [m_arg]) => {
+ if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) {
+ match (name, args) {
+ ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
+ ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
+ ("filter", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
+ },
+ ("find", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true);
+ },
+ _ => {},
+ }
+ }
+ map_identity::check(cx, expr, recv, m_arg, name, span);
+ },
+ ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
+ ("next", []) => {
+ if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) {
+ match (name2, args2) {
+ ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
+ ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
+ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
+ ("iter", []) => iter_next_slice::check(cx, expr, recv2),
+ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
+ ("skip_while", [_]) => skip_while_next::check(cx, expr),
+ _ => {},
+ }
+ }
+ },
+ ("nth", [n_arg]) => match method_call(recv) {
+ Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
+ Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
+ Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
+ Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
+ _ => iter_nth_zero::check(cx, expr, recv, n_arg),
+ },
+ ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
+ ("or_else", [arg]) => {
+ if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
+ }
+ },
+ ("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);
+ str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
+ }
+ },
+ ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
+ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+ suspicious_splitn::check(cx, name, expr, recv, count);
+ }
+ },
+ ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
+ ("take", [_arg]) => {
+ if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
+ if let ("cloned", []) = (name2, args2) {
+ iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
+ }
+ }
+ },
+ ("take", []) => needless_option_take::check(cx, expr, recv),
+ ("then", [arg]) => {
+ if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
+ return;
+ }
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
+ },
+ ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
+ implicit_clone::check(cx, name, expr, recv);
+ },
+ ("unwrap", []) => {
+ match method_call(recv) {
+ Some(("get", [recv, get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, false);
+ },
+ Some(("get_mut", [recv, get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, true);
+ },
+ Some(("or", [recv, or_arg], or_span)) => {
+ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+ },
+ _ => {},
+ }
+ unwrap_used::check(cx, expr, recv, self.allow_unwrap_in_tests);
+ },
+ ("unwrap_or", [u_arg]) => match method_call(recv) {
+ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
+ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
+ Some(("map", [m_recv, m_arg], span)) => {
+ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
+ Some(("then_some", [t_recv, t_arg], _)) => {
+ obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
+ },
+ _ => {},
+ },
+ ("unwrap_or_else", [u_arg]) => match method_call(recv) {
+ Some(("map", [recv, map_arg], _))
+ if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
+ _ => {
+ unwrap_or_else_default::check(cx, expr, recv, u_arg);
+ unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
+ },
+ },
+ ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => {
+ no_effect_replace::check(cx, expr, arg1, arg2);
+ },
+ _ => {},
+ }
+ }
+ }
+}
+
+fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
+ if let Some((name @ ("find" | "position" | "rposition"), [f_recv, arg], span)) = method_call(recv) {
+ search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
+ }
+}
+
+/// Used for `lint_binary_expr_with_method_call`.
+#[derive(Copy, Clone)]
+struct BinaryExprInfo<'a> {
+ expr: &'a hir::Expr<'a>,
+ chain: &'a hir::Expr<'a>,
+ other: &'a hir::Expr<'a>,
+ eq: bool,
+}
+
+/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) {
+ macro_rules! lint_with_both_lhs_and_rhs {
+ ($func:expr, $cx:expr, $info:ident) => {
+ if !$func($cx, $info) {
+ ::std::mem::swap(&mut $info.chain, &mut $info.other);
+ if $func($cx, $info) {
+ return;
+ }
+ }
+ };
+ }
+
+ lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
+}
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ // FIXME: default doesn't work
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+];
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No,
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = *ty.kind() {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => return false,
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ !matches_value(cx, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty)
+ && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty)
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => matches_none(cx, parent_ty, ty),
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "`self` by value",
+ Self::Ref => "`self` by reference",
+ Self::RefMut => "`self` by mutable reference",
+ Self::No => "no `self`",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn is_bool(ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ matches!(path.res, Res::PrimTy(PrimTy::Bool))
+ } else {
+ false
+ }
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_option_as_deref.rs b/src/tools/clippy/clippy_lints/src/methods/needless_option_as_deref.rs
new file mode 100644
index 000000000..7030baf19
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/needless_option_as_deref.rs
@@ -0,0 +1,37 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::path_res;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::local_used_after_expr;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::NEEDLESS_OPTION_AS_DEREF;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: &str) {
+ let typeck = cx.typeck_results();
+ let outer_ty = typeck.expr_ty(expr);
+
+ if is_type_diagnostic_item(cx, outer_ty, sym::Option) && outer_ty == typeck.expr_ty(recv) {
+ if name == "as_deref_mut" && recv.is_syntactic_place_expr() {
+ let Res::Local(binding_id) = path_res(cx, recv) else { return };
+
+ if local_used_after_expr(cx, binding_id, recv) {
+ return;
+ }
+ }
+
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_OPTION_AS_DEREF,
+ expr.span,
+ "derefed type is same as origin",
+ "try this",
+ snippet_opt(cx, recv.span).unwrap(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs b/src/tools/clippy/clippy_lints/src/methods/needless_option_take.rs
new file mode 100644
index 000000000..829c118d2
--- /dev/null
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs b/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs
new file mode 100644
index 000000000..a76341855
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/no_effect_replace.rs
@@ -0,0 +1,47 @@
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::ExprKind;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::NO_EFFECT_REPLACE;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx rustc_hir::Expr<'_>,
+ arg1: &'tcx rustc_hir::Expr<'_>,
+ arg2: &'tcx rustc_hir::Expr<'_>,
+) {
+ let ty = cx.typeck_results().expr_ty(expr).peel_refs();
+ if !(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Lit(spanned) = &arg1.kind;
+ if let Some(param1) = lit_string_value(&spanned.node);
+
+ if let ExprKind::Lit(spanned) = &arg2.kind;
+ if let LitKind::Str(param2, _) = &spanned.node;
+ if param1 == param2.as_str();
+
+ then {
+ span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
+ }
+ }
+
+ if SpanlessEq::new(cx).eq_expr(arg1, arg2) {
+ span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
+ }
+}
+
+fn lit_string_value(node: &LitKind) -> Option<String> {
+ match node {
+ LitKind::Char(value) => Some(value.to_string()),
+ LitKind::Str(value, _) => Some(value.as_str().to_owned()),
+ _ => None,
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/obfuscated_if_else.rs b/src/tools/clippy/clippy_lints/src/methods/obfuscated_if_else.rs
new file mode 100644
index 000000000..4d7427b26
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/obfuscated_if_else.rs
@@ -0,0 +1,42 @@
+// run-rustfix
+
+use super::OBFUSCATED_IF_ELSE;
+use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_with_applicability};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ then_recv: &'tcx hir::Expr<'_>,
+ then_arg: &'tcx hir::Expr<'_>,
+ unwrap_arg: &'tcx hir::Expr<'_>,
+) {
+ // something.then_some(blah).unwrap_or(blah)
+ // ^^^^^^^^^-then_recv ^^^^-then_arg ^^^^- unwrap_arg
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr
+
+ let recv_ty = cx.typeck_results().expr_ty(then_recv);
+
+ if recv_ty.is_bool() {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = format!(
+ "if {} {{ {} }} else {{ {} }}",
+ snippet_with_applicability(cx, then_recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, then_arg.span, "..", &mut applicability),
+ snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability)
+ );
+
+ span_lint_and_sugg(
+ cx,
+ OBFUSCATED_IF_ELSE,
+ expr.span,
+ "use of `.then_some(..).unwrap_or(..)` can be written \
+ more clearly with `if .. else ..`",
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs
new file mode 100644
index 000000000..d64a9f320
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs
@@ -0,0 +1,46 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+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<'_>) {
+ if_chain! {
+ // lint if the caller of `ok()` is a `Result`
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ let result_type = cx.typeck_results().expr_ty(recv);
+ if let Some(error_type) = get_error_type(cx, result_type);
+ if has_debug_impl(error_type, cx);
+
+ then {
+ span_lint_and_help(
+ cx,
+ OK_EXPECT,
+ expr.span,
+ "called `ok().expect()` on a `Result` value",
+ None,
+ "you can call `expect()` directly on the `Result`",
+ );
+ }
+ }
+}
+
+/// Given a `Result<T, E>` type, return its error type (`E`).
+fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
+ match ty.kind() {
+ ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().nth(1),
+ _ => None,
+ }
+}
+
+/// This checks whether a given type is known to implement Debug.
+fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ cx.tcx
+ .get_diagnostic_item(sym::Debug)
+ .map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs
new file mode 100644
index 000000000..20cad0f18
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs
@@ -0,0 +1,120 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::OPTION_AS_REF_DEREF;
+
+/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ as_ref_recv: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ is_mut: bool,
+ msrv: Option<RustcVersion>,
+) {
+ if !meets_msrv(msrv, msrvs::OPTION_AS_DEREF) {
+ return;
+ }
+
+ let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
+
+ let option_ty = cx.typeck_results().expr_ty(as_ref_recv);
+ if !is_type_diagnostic_item(cx, option_ty, sym::Option) {
+ return;
+ }
+
+ let deref_aliases: [&[&str]; 9] = [
+ &paths::DEREF_TRAIT_METHOD,
+ &paths::DEREF_MUT_TRAIT_METHOD,
+ &paths::CSTRING_AS_C_STR,
+ &paths::OS_STRING_AS_OS_STR,
+ &paths::PATH_BUF_AS_PATH,
+ &paths::STRING_AS_STR,
+ &paths::STRING_AS_MUT_STR,
+ &paths::VEC_AS_SLICE,
+ &paths::VEC_AS_MUT_SLICE,
+ ];
+
+ let is_deref = match map_arg.kind {
+ hir::ExprKind::Path(ref expr_qpath) => cx
+ .qpath_res(expr_qpath, map_arg.hir_id)
+ .opt_def_id()
+ .map_or(false, |fun_def_id| {
+ deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
+ }),
+ hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(&closure_body.value);
+
+ match &closure_expr.kind {
+ hir::ExprKind::MethodCall(_, args, _) => {
+ if_chain! {
+ if args.len() == 1;
+ if path_to_local_id(&args[0], closure_body.params[0].pat.hir_id);
+ let adj = cx
+ .typeck_results()
+ .expr_adjustments(&args[0])
+ .iter()
+ .map(|x| &x.kind)
+ .collect::<Box<[_]>>();
+ if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
+ then {
+ let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
+ deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
+ } else {
+ false
+ }
+ }
+ },
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, inner) if same_mutability(m) => {
+ if_chain! {
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind;
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind;
+ then {
+ path_to_local_id(inner2, closure_body.params[0].pat.hir_id)
+ } else {
+ false
+ }
+ }
+ },
+ _ => false,
+ }
+ },
+ _ => false,
+ };
+
+ if is_deref {
+ let current_method = if is_mut {
+ format!(".as_mut().map({})", snippet(cx, map_arg.span, ".."))
+ } else {
+ format!(".as_ref().map({})", snippet(cx, map_arg.span, ".."))
+ };
+ let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
+ let hint = format!("{}.{}()", snippet(cx, as_ref_recv.span, ".."), method_hint);
+ let suggestion = format!("try using {} instead", method_hint);
+
+ let msg = format!(
+ "called `{0}` on an Option value. This can be done more directly \
+ by calling `{1}` instead",
+ current_method, hint
+ );
+ span_lint_and_sugg(
+ cx,
+ OPTION_AS_REF_DEREF,
+ expr.span,
+ &msg,
+ &suggestion,
+ hint,
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
new file mode 100644
index 000000000..5a39b82b0
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
@@ -0,0 +1,122 @@
+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_lang_ctor, path_def_id};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_middle::ty::DefIdTree;
+use rustc_span::symbol::sym;
+
+use super::OPTION_MAP_OR_NONE;
+use super::RESULT_MAP_OR_INTO_OPTION;
+
+// The expression inside a closure may or may not have surrounding braces
+// which causes problems when generating a suggestion.
+fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
+ match expr.kind {
+ hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
+ hir::ExprKind::Block(block, _) => {
+ match (block.stmts, block.expr) {
+ (&[], Some(inner_expr)) => {
+ // If block only contains an expression,
+ // reduce `|x| { x + 1 }` to `|x| x + 1`
+ reduce_unit_expression(inner_expr)
+ },
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+}
+
+/// lint use of `_.map_or(None, _)` for `Option`s and `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<'_>,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ // There are two variants of this `map_or` lint:
+ // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
+ // (2) using `map_or` as a combinator instead of `and_then`
+ //
+ // (For this lint) we don't care if any other type calls `map_or`
+ if !is_option && !is_result {
+ return;
+ }
+
+ let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
+ is_lang_ctor(cx, qpath, OptionNone)
+ } else {
+ return;
+ };
+
+ if !default_arg_is_none {
+ // nothing to lint!
+ return;
+ }
+
+ let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
+ is_lang_ctor(cx, qpath, OptionSome)
+ } else {
+ false
+ };
+
+ if is_option {
+ let self_snippet = snippet(cx, recv.span, "..");
+ if_chain! {
+ if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind;
+ let arg_snippet = snippet(cx, fn_decl_span, "..");
+ let body = cx.tcx.hir().body(body);
+ if let Some((func, [arg_char])) = reduce_unit_expression(&body.value);
+ if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id));
+ if Some(id) == cx.tcx.lang_items().option_some_variant();
+ then {
+ 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";
+ return span_lint_and_sugg(
+ cx,
+ OPTION_MAP_OR_NONE,
+ expr.span,
+ msg,
+ "try using `map` instead",
+ format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ 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";
+ span_lint_and_sugg(
+ cx,
+ OPTION_MAP_OR_NONE,
+ expr.span,
+ msg,
+ "try using `and_then` instead",
+ format!("{0}.and_then({1})", self_snippet, func_snippet),
+ 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 self_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ RESULT_MAP_OR_INTO_OPTION,
+ expr.span,
+ msg,
+ "try using `ok` instead",
+ format!("{0}.ok()", self_snippet),
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs
new file mode 100644
index 000000000..6c641af59
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs
@@ -0,0 +1,139 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_copy;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_path, Visitor};
+use rustc_hir::{self, HirId, Path};
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_span::source_map::Span;
+use rustc_span::{sym, Symbol};
+
+use super::MAP_UNWRAP_OR;
+
+/// lint use of `map().unwrap_or()` for `Option`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &rustc_hir::Expr<'_>,
+ recv: &rustc_hir::Expr<'_>,
+ map_arg: &'tcx rustc_hir::Expr<'_>,
+ unwrap_recv: &rustc_hir::Expr<'_>,
+ unwrap_arg: &'tcx rustc_hir::Expr<'_>,
+ map_span: Span,
+) {
+ // lint if the caller of `map()` is an `Option`
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
+ if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
+ // Do not lint if the `map` argument uses identifiers in the `map`
+ // argument that are also used in the `unwrap_or` argument
+
+ let mut unwrap_visitor = UnwrapVisitor {
+ cx,
+ identifiers: FxHashSet::default(),
+ };
+ unwrap_visitor.visit_expr(unwrap_arg);
+
+ let mut map_expr_visitor = MapExprVisitor {
+ cx,
+ identifiers: unwrap_visitor.identifiers,
+ found_identifier: false,
+ };
+ map_expr_visitor.visit_expr(map_arg);
+
+ if map_expr_visitor.found_identifier {
+ return;
+ }
+ }
+
+ if unwrap_arg.span.ctxt() != map_span.ctxt() {
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ // get snippet for unwrap_or()
+ let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
+ // lint message
+ // comparing the snippet from source to raw text ("None") below is safe
+ // because we already have checked the type.
+ let arg = if unwrap_snippet == "None" { "None" } else { "<a>" };
+ let unwrap_snippet_none = unwrap_snippet == "None";
+ let suggest = if unwrap_snippet_none {
+ "and_then(<f>)"
+ } else {
+ "map_or(<a>, <f>)"
+ };
+ let msg = &format!(
+ "called `map(<f>).unwrap_or({})` on an `Option` value. \
+ This can be done more directly by calling `{}` instead",
+ arg, suggest
+ );
+
+ span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
+ let map_arg_span = map_arg.span;
+
+ let mut suggestion = vec![
+ (
+ map_span,
+ String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
+ ),
+ (expr.span.with_lo(unwrap_recv.span.hi()), String::from("")),
+ ];
+
+ if !unwrap_snippet_none {
+ suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)));
+ }
+
+ diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability);
+ });
+ }
+}
+
+struct UnwrapVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ identifiers: FxHashSet<Symbol>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
+ self.identifiers.insert(ident(path));
+ walk_path(self, path);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+struct MapExprVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ identifiers: FxHashSet<Symbol>,
+ found_identifier: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
+ if self.identifiers.contains(&ident(path)) {
+ self.found_identifier = true;
+ return;
+ }
+ walk_path(self, path);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+fn ident(path: &Path<'_>) -> Symbol {
+ path.segments
+ .last()
+ .expect("segments should be composed of at least 1 element")
+ .ident
+ .name
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
new file mode 100644
index 000000000..6af134019
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
@@ -0,0 +1,175 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::ty::{implements_trait, match_type};
+use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{kw, sym};
+use std::borrow::Cow;
+
+use super::OR_FUN_CALL;
+
+/// Checks for the `OR_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ args: &'tcx [hir::Expr<'_>],
+) {
+ /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_unwrap_or_default(
+ cx: &LateContext<'_>,
+ name: &str,
+ fun: &hir::Expr<'_>,
+ arg: &hir::Expr<'_>,
+ or_has_args: bool,
+ span: Span,
+ method_span: Span,
+ ) -> bool {
+ let is_default_default = || is_trait_item(cx, fun, sym::Default);
+
+ let implements_default = |arg, default_trait_id| {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ implements_trait(cx, arg_ty, default_trait_id, &[])
+ };
+
+ if_chain! {
+ if !or_has_args;
+ if name == "unwrap_or";
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ let path = last_path_segment(qpath).ident.name;
+ // needs to target Default::default in particular or be *::new and have a Default impl
+ // available
+ if (matches!(path, kw::Default) && is_default_default())
+ || (matches!(path, sym::new) && implements_default(arg, default_trait_id));
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ method_span.with_hi(span.hi()),
+ &format!("use of `{}` followed by a call to `{}`", name, path),
+ "try this",
+ "unwrap_or_default()".to_string(),
+ Applicability::MachineApplicable,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ /// Checks for `*or(foo())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_general_case<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: &str,
+ method_span: Span,
+ self_expr: &hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ span: Span,
+ // None if lambda is required
+ fun_span: Option<Span>,
+ ) {
+ // (path, fn_has_argument, methods, suffix)
+ static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
+ (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
+ (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
+ ];
+
+ if_chain! {
+ if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
+
+ if switch_to_lazy_eval(cx, arg);
+ if !contains_return(arg);
+
+ let self_ty = cx.typeck_results().expr_ty(self_expr);
+
+ if let Some(&(_, fn_has_arguments, poss, suffix)) =
+ KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0));
+
+ if poss.contains(&name);
+
+ then {
+ let macro_expanded_snipped;
+ let sugg: Cow<'_, str> = {
+ let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
+ (false, Some(fun_span)) => (fun_span, false),
+ _ => (arg.span, true),
+ };
+ let snippet = {
+ let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
+ if not_macro_argument_snippet == "vec![]" {
+ macro_expanded_snipped = snippet(cx, snippet_span, "..");
+ match macro_expanded_snipped.strip_prefix("$crate::vec::") {
+ Some(stripped) => Cow::from(stripped),
+ None => macro_expanded_snipped
+ }
+ }
+ else {
+ not_macro_argument_snippet
+ }
+ };
+
+ if use_lambda {
+ let l_arg = if fn_has_arguments { "_" } else { "" };
+ format!("|{}| {}", l_arg, snippet).into()
+ } else {
+ snippet
+ }
+ };
+ let span_replace_word = method_span.with_hi(span.hi());
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("{}_{}({})", name, suffix, sugg),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+
+ if let [self_arg, arg] = args {
+ let inner_arg = if let hir::ExprKind::Block(
+ hir::Block {
+ stmts: [],
+ expr: Some(expr),
+ ..
+ },
+ _,
+ ) = arg.kind
+ {
+ expr
+ } else {
+ arg
+ };
+ match inner_arg.kind {
+ hir::ExprKind::Call(fun, or_args) => {
+ let or_has_args = !or_args.is_empty();
+ if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
+ let fun_span = if or_has_args { None } else { Some(fun.span) };
+ check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
+ }
+ },
+ hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
+ check_general_case(cx, name, method_span, self_arg, arg, expr.span, None);
+ },
+ _ => (),
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs
new file mode 100644
index 000000000..be5768c35
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs
@@ -0,0 +1,68 @@
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor};
+use rustc_errors::Applicability;
+use rustc_hir::{lang_items::LangItem, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::OR_THEN_UNWRAP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ unwrap_expr: &Expr<'_>,
+ recv: &'tcx Expr<'tcx>,
+ or_arg: &'tcx Expr<'_>,
+ or_span: Span,
+) {
+ let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
+ let title;
+ let or_arg_content: Span;
+
+ if is_type_diagnostic_item(cx, ty, sym::Option) {
+ title = "found `.or(Some(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+ title = "found `.or(Ok(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else {
+ // Someone has implemented a struct with .or(...).unwrap() chaining,
+ // but it's not an Option or a Result, so bail
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = format!(
+ "unwrap_or({})",
+ snippet_with_applicability(cx, or_arg_content, "..", &mut applicability)
+ );
+
+ span_lint_and_sugg(
+ cx,
+ OR_THEN_UNWRAP,
+ unwrap_expr.span.with_lo(or_span.lo()),
+ title,
+ "try this",
+ suggestion,
+ applicability,
+ );
+}
+
+fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
+ if let ExprKind::Call(some_expr, [arg]) = expr.kind
+ && let ExprKind::Path(qpath) = &some_expr.kind
+ && is_lang_ctor(cx, qpath, item)
+ {
+ Some(arg.span)
+ } else {
+ None
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs
new file mode 100644
index 000000000..7572ba3fe
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs
@@ -0,0 +1,156 @@
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::sugg::deref_closure_args;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_trait_method, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::PatKind;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+use super::SEARCH_IS_SOME;
+
+/// lint searching an Iterator followed by `is_some()`
+/// or calling `find()` on a string followed by `is_some()` or `is_none()`
+#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'_>,
+ expr: &'tcx hir::Expr<'_>,
+ search_method: &str,
+ is_some: bool,
+ search_recv: &hir::Expr<'_>,
+ search_arg: &'tcx hir::Expr<'_>,
+ is_some_recv: &hir::Expr<'_>,
+ method_span: Span,
+) {
+ let option_check_method = if is_some { "is_some" } else { "is_none" };
+ // lint if caller of search is an Iterator
+ if is_trait_method(cx, is_some_recv, sym::Iterator) {
+ let msg = format!(
+ "called `{}()` after searching an `Iterator` with `{}`",
+ option_check_method, 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_chain! {
+ if search_method == "find";
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind;
+ let closure_body = cx.tcx.hir().body(body);
+ if let Some(closure_arg) = closure_body.params.get(0);
+ then {
+ if let hir::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())
+ }
+ } else {
+ None
+ }
+ } 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,
+ "use `any()` instead",
+ format!(
+ "any({})",
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
+ applicability,
+ );
+ } else {
+ let iter = snippet(cx, search_recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.any()` instead",
+ format!(
+ "!{}.any({})",
+ iter,
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
+ applicability,
+ );
+ }
+ } else {
+ let hint = format!(
+ "this is more succinctly expressed by calling `any()`{}",
+ if option_check_method == "is_none" {
+ " with negation"
+ } else {
+ ""
+ }
+ );
+ span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, &hint);
+ }
+ }
+ // lint if `find()` is called by `String` or `&str`
+ else if search_method == "find" {
+ let is_string_or_str_slice = |e| {
+ let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
+ if is_type_diagnostic_item(cx, self_ty, sym::String) {
+ true
+ } else {
+ *self_ty.kind() == ty::Str
+ }
+ };
+ if_chain! {
+ if is_string_or_str_slice(search_recv);
+ if is_string_or_str_slice(search_arg);
+ then {
+ let msg = format!("called `{}()` after calling `find()` on a string", option_check_method);
+ match option_check_method {
+ "is_some" => {
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "use `contains()` instead",
+ format!("contains({})", find_arg),
+ applicability,
+ );
+ },
+ "is_none" => {
+ let string = snippet(cx, search_recv.span, "..");
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.contains()` instead",
+ format!("!{}.contains({})", string, find_arg),
+ applicability,
+ );
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs
new file mode 100644
index 000000000..9a5fabcf7
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs
@@ -0,0 +1,14 @@
+use crate::methods::{single_char_insert_string, single_char_push_string};
+use clippy_utils::{match_def_path, paths};
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ if match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
+ single_char_push_string::check(cx, expr, args);
+ } else if match_def_path(cx, fn_def_id, &paths::INSERT_STR) {
+ single_char_insert_string::check(cx, expr, args);
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs
new file mode 100644
index 000000000..6cdc954c0
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/single_char_insert_string.rs
@@ -0,0 +1,28 @@
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::SINGLE_CHAR_ADD_STR;
+
+/// lint for length-1 `str`s as argument for `insert_str`
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) {
+ let base_string_snippet =
+ snippet_with_applicability(cx, args[0].span.source_callsite(), "_", &mut applicability);
+ let pos_arg = snippet_with_applicability(cx, args[1].span, "..", &mut applicability);
+ let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string);
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_ADD_STR,
+ expr.span,
+ "calling `insert_str()` using a single-character string literal",
+ "consider using `insert` with a character literal",
+ sugg,
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs
new file mode 100644
index 000000000..bf9006c69
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs
@@ -0,0 +1,62 @@
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::Symbol;
+
+use super::SINGLE_CHAR_PATTERN;
+
+const PATTERN_METHODS: [(&str, usize); 24] = [
+ ("contains", 1),
+ ("starts_with", 1),
+ ("ends_with", 1),
+ ("find", 1),
+ ("rfind", 1),
+ ("split", 1),
+ ("split_inclusive", 1),
+ ("rsplit", 1),
+ ("split_terminator", 1),
+ ("rsplit_terminator", 1),
+ ("splitn", 2),
+ ("rsplitn", 2),
+ ("split_once", 1),
+ ("rsplit_once", 1),
+ ("matches", 1),
+ ("rmatches", 1),
+ ("match_indices", 1),
+ ("rmatch_indices", 1),
+ ("strip_prefix", 1),
+ ("strip_suffix", 1),
+ ("trim_start_matches", 1),
+ ("trim_end_matches", 1),
+ ("replace", 1),
+ ("replacen", 1),
+];
+
+/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
+pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
+ for &(method, pos) in &PATTERN_METHODS {
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind();
+ if *ty.kind() == ty::Str;
+ if method_name.as_str() == method && args.len() > pos;
+ let arg = &args[pos];
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability);
+ then {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PATTERN,
+ arg.span,
+ "single-character string constant used as pattern",
+ "try using a `char` instead",
+ hint,
+ applicability,
+ );
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs
new file mode 100644
index 000000000..0237d39cb
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/single_char_push_string.rs
@@ -0,0 +1,27 @@
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::SINGLE_CHAR_ADD_STR;
+
+/// lint for length-1 `str`s as argument for `push_str`
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) {
+ let base_string_snippet =
+ snippet_with_applicability(cx, args[0].span.source_callsite(), "..", &mut applicability);
+ let sugg = format!("{}.push({})", base_string_snippet, extension_string);
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_ADD_STR,
+ expr.span,
+ "calling `push_str()` using a single-character string literal",
+ "consider using `push` with a character literal",
+ sugg,
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs b/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs
new file mode 100644
index 000000000..9f0b6c34e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/skip_while_next.rs
@@ -0,0 +1,22 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_trait_method;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::SKIP_WHILE_NEXT;
+
+/// lint use of `skip_while().next()` for `Iterators`
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ // lint if caller of `.skip_while().next()` is an Iterator
+ if is_trait_method(cx, expr, sym::Iterator) {
+ span_lint_and_help(
+ cx,
+ SKIP_WHILE_NEXT,
+ expr.span,
+ "called `skip_while(<p>).next()` on an `Iterator`",
+ None,
+ "this is more succinctly expressed by calling `.find(!<p>)` instead",
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs b/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
new file mode 100644
index 000000000..4ac738272
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/str_splitn.rs
@@ -0,0 +1,390 @@
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_context;
+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::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;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span, Symbol, SyntaxContext};
+
+use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+ count: u128,
+ msrv: Option<RustcVersion>,
+) {
+ 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 (msg, reverse) = if method_name == "splitn" {
+ ("manual implementation of `split_once`", false)
+ } else {
+ ("manual implementation of `rsplit_once`", true)
+ };
+
+ 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;
+
+ let sugg = match usage.kind {
+ IterUsageKind::NextTuple => {
+ if reverse {
+ format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
+ } else {
+ 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,
+ "",
+ app,
+ );
+ diag.span_suggestion(
+ second.span,
+ &remove_msg,
+ "",
+ 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 {
+ Nth(u128),
+ NextTuple,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum UnwrapKind {
+ Unwrap,
+ QuestionMark,
+}
+
+#[derive(Debug)]
+struct IterUsage {
+ kind: IterUsageKind,
+ unwrap_kind: Option<UnwrapKind>,
+ span: Span,
+}
+
+#[allow(clippy::too_many_lines)]
+fn parse_iter_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ ctxt: SyntaxContext,
+ mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
+) -> Option<IterUsage> {
+ let (kind, span) = 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 None;
+ };
+ let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
+ 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) => (IterUsageKind::Nth(0), e.span),
+ ("next_tuple", []) => {
+ return if_chain! {
+ if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
+ if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
+ if let ty::Tuple(subs) = subs.type_at(0).kind();
+ if subs.len() == 2;
+ then {
+ Some(IterUsage {
+ kind: IterUsageKind::NextTuple,
+ span: e.span,
+ unwrap_kind: None
+ })
+ } else {
+ None
+ }
+ };
+ },
+ ("nth" | "skip", [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) {
+ let span = if name.ident.as_str() == "nth" {
+ e.span
+ } else {
+ if_chain! {
+ if let Some((_, Node::Expr(next_expr))) = iter.next();
+ if let ExprKind::MethodCall(next_name, [_], _) = next_expr.kind;
+ if next_name.ident.name == sym::next;
+ if next_expr.span.ctxt() == ctxt;
+ if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
+ if cx.tcx.trait_of_item(next_id) == Some(iter_id);
+ then {
+ next_expr.span
+ } else {
+ return None;
+ }
+ }
+ };
+ (IterUsageKind::Nth(idx), span)
+ } else {
+ return None;
+ }
+ },
+ _ => return None,
+ }
+ },
+ _ => return None,
+ };
+
+ let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
+ match e.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
+ ..
+ },
+ _,
+ ) => {
+ let parent_span = e.span.parent_callsite().unwrap();
+ if parent_span.ctxt() == ctxt {
+ (Some(UnwrapKind::QuestionMark), parent_span)
+ } else {
+ (None, span)
+ }
+ },
+ _ if e.span.ctxt() != ctxt => (None, span),
+ ExprKind::MethodCall(name, [_], _)
+ if name.ident.name == sym::unwrap
+ && cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
+ {
+ (Some(UnwrapKind::Unwrap), e.span)
+ },
+ _ => (None, span),
+ }
+ } else {
+ (None, span)
+ };
+
+ Some(IterUsage {
+ kind,
+ unwrap_kind,
+ span,
+ })
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs
new file mode 100644
index 000000000..d06658f2a
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs
@@ -0,0 +1,45 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+use super::STRING_EXTEND_CHARS;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+ if !is_type_diagnostic_item(cx, obj_ty, sym::String) {
+ return;
+ }
+ if let Some(arglists) = method_chain_args(arg, &["chars"]) {
+ let target = &arglists[0][0];
+ let self_ty = cx.typeck_results().expr_ty(target).peel_refs();
+ let ref_str = if *self_ty.kind() == ty::Str {
+ ""
+ } else if is_type_diagnostic_item(cx, self_ty, sym::String) {
+ "&"
+ } else {
+ return;
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ STRING_EXTEND_CHARS,
+ expr.span,
+ "calling `.extend(_.chars())`",
+ "try this",
+ format!(
+ "{}.push_str({}{})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ ref_str,
+ snippet_with_applicability(cx, target.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs
new file mode 100644
index 000000000..9c3375bf3
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs
@@ -0,0 +1,36 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{expr_or_init, is_trait_method};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::SUSPICIOUS_MAP;
+
+pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) {
+ if_chain! {
+ if is_trait_method(cx, count_recv, sym::Iterator);
+ let closure = expr_or_init(cx, map_arg);
+ if let Some(def_id) = cx.tcx.hir().opt_local_def_id(closure.hir_id);
+ if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(def_id);
+ let closure_body = cx.tcx.hir().body(body_id);
+ if !cx.typeck_results().expr_ty(&closure_body.value).is_unit();
+ then {
+ if let Some(map_mutated_vars) = mutated_variables(&closure_body.value, cx) {
+ // A variable is used mutably inside of the closure. Suppress the lint.
+ if !map_mutated_vars.is_empty() {
+ return;
+ }
+ }
+ span_lint_and_help(
+ cx,
+ SUSPICIOUS_MAP,
+ expr.span,
+ "this call to `map()` won't have an effect on the call to `count()`",
+ None,
+ "make sure you did not confuse `map` with `filter`, `for_each` or `inspect`",
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs
new file mode 100644
index 000000000..55567d862
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs
@@ -0,0 +1,48 @@
+use clippy_utils::diagnostics::span_lint_and_note;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Spanned;
+
+use super::SUSPICIOUS_SPLITN;
+
+pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
+ if_chain! {
+ if count <= 1;
+ if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
+ if cx.tcx.impl_trait_ref(impl_id).is_none();
+ let self_ty = cx.tcx.type_of(impl_id);
+ if self_ty.is_slice() || self_ty.is_str();
+ then {
+ // Ignore empty slice and string literals when used with a literal count.
+ if matches!(self_arg.kind, ExprKind::Array([]))
+ || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
+ {
+ return;
+ }
+
+ let (msg, note_msg) = if count == 0 {
+ (format!("`{}` called with `0` splits", method_name),
+ "the resulting iterator will always return `None`")
+ } else {
+ (format!("`{}` called with `1` split", method_name),
+ if self_ty.is_slice() {
+ "the resulting iterator will always return the entire slice followed by `None`"
+ } else {
+ "the resulting iterator will always return the entire string followed by `None`"
+ })
+ };
+
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_SPLITN,
+ expr.span,
+ &msg,
+ None,
+ note_msg,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs b/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs
new file mode 100644
index 000000000..77d21f1d3
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/uninit_assumed_init.rs
@@ -0,0 +1,26 @@
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNINIT_ASSUMED_INIT;
+
+/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter)
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::Call(callee, args) = recv.kind;
+ if args.is_empty();
+ if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit);
+ if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr));
+ then {
+ span_lint(
+ cx,
+ UNINIT_ASSUMED_INIT,
+ expr.span,
+ "this call for this type may be undefined behavior"
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs
new file mode 100644
index 000000000..bafa6fc58
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs
@@ -0,0 +1,132 @@
+use super::utils::clone_or_copy_needed;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_copy;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+use super::UNNECESSARY_FILTER_MAP;
+use super::UNNECESSARY_FIND_MAP;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
+ let body = cx.tcx.hir().body(body);
+ let arg_id = body.params[0].pat.hir_id;
+ let mutates_arg =
+ mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
+ let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value);
+
+ let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value);
+
+ let mut return_visitor = ReturnVisitor::new(cx, arg_id);
+ return_visitor.visit_expr(&body.value);
+ found_mapping |= return_visitor.found_mapping;
+ found_filtering |= return_visitor.found_filtering;
+
+ let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
+ let sugg = if !found_filtering {
+ if name == "filter_map" { "map" } else { "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 == "filter_map" { "filter" } else { "find" }
+ },
+ _ => return,
+ }
+ } else {
+ return;
+ };
+ span_lint(
+ cx,
+ if name == "filter_map" {
+ UNNECESSARY_FILTER_MAP
+ } else {
+ UNNECESSARY_FIND_MAP
+ },
+ expr.span,
+ &format!("this `.{}` can be written more simply using `.{}`", name, sugg),
+ );
+ }
+}
+
+// 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::Call(func, args) => {
+ if let hir::ExprKind::Path(ref path) = func.kind {
+ if is_lang_ctor(cx, path, OptionSome) {
+ if path_to_local_id(&args[0], arg_id) {
+ return (false, false);
+ }
+ return (true, false);
+ }
+ }
+ (true, true)
+ },
+ hir::ExprKind::Block(block, _) => block
+ .expr
+ .as_ref()
+ .map_or((false, false), |expr| check_expression(cx, arg_id, expr)),
+ hir::ExprKind::Match(_, arms, _) => {
+ let mut found_mapping = false;
+ let mut found_filtering = false;
+ for arm in *arms {
+ let (m, f) = check_expression(cx, arg_id, arm.body);
+ found_mapping |= m;
+ found_filtering |= f;
+ }
+ (found_mapping, found_filtering)
+ },
+ // There must be an else_arm or there will be a type error
+ hir::ExprKind::If(_, if_arm, Some(else_arm)) => {
+ let if_check = check_expression(cx, arg_id, if_arm);
+ 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(path) if is_lang_ctor(cx, path, OptionNone) => (false, true),
+ _ => (true, true),
+ }
+}
+
+struct ReturnVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ arg_id: hir::HirId,
+ // Found a non-None return that isn't Some(input)
+ found_mapping: bool,
+ // Found a return that isn't Some
+ found_filtering: bool,
+}
+
+impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
+ ReturnVisitor {
+ cx,
+ arg_id,
+ found_mapping: false,
+ found_filtering: false,
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if let hir::ExprKind::Ret(Some(expr)) = &expr.kind {
+ let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
+ self.found_mapping |= found_mapping;
+ self.found_filtering |= found_filtering;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs
new file mode 100644
index 000000000..c3531d4d0
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_fold.rs
@@ -0,0 +1,95 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::PatKind;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::UNNECESSARY_FOLD;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ init: &hir::Expr<'_>,
+ acc: &hir::Expr<'_>,
+ fold_span: Span,
+) {
+ fn check_fold_with_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ acc: &hir::Expr<'_>,
+ fold_span: Span,
+ op: hir::BinOpKind,
+ replacement_method_name: &str,
+ replacement_has_args: bool,
+ ) {
+ if_chain! {
+ // Extract the body of the closure passed to fold
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(&closure_body.value);
+
+ // Check if the closure body is of the form `acc <op> some_expr(x)`
+ if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
+ if bin_op.node == op;
+
+ // Extract the names of the two arguments to the closure
+ if let [param_a, param_b] = closure_body.params;
+ if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
+ if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
+
+ if path_to_local_id(left_expr, first_arg_id);
+ if replacement_has_args || path_to_local_id(right_expr, second_arg_id);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = if replacement_has_args {
+ format!(
+ "{replacement}(|{s}| {r})",
+ replacement = replacement_method_name,
+ s = second_arg_ident,
+ r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
+ )
+ } else {
+ format!(
+ "{replacement}()",
+ replacement = replacement_method_name,
+ )
+ };
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_FOLD,
+ fold_span.with_hi(expr.span.hi()),
+ // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
+ "this `.fold` can be written more succinctly using another method",
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+
+ // Check that this is a call to Iterator::fold rather than just some function called fold
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+
+ // Check if the first argument to .fold is a suitable literal
+ if let hir::ExprKind::Lit(ref lit) = init.kind {
+ match lit.node {
+ ast::LitKind::Bool(false) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, "any", true),
+ ast::LitKind::Bool(true) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, "all", true),
+ ast::LitKind::Int(0, _) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, "sum", false),
+ ast::LitKind::Int(1, _) => {
+ check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, "product", false);
+ },
+ _ => (),
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
new file mode 100644
index 000000000..19037093e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
@@ -0,0 +1,104 @@
+use super::utils::clone_or_copy_needed;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::ForLoop;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
+use clippy_utils::{fn_def_id, get_parent_expr};
+use rustc_errors::Applicability;
+use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Symbol};
+
+use super::UNNECESSARY_TO_OWNED;
+
+pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let Some(callee_def_id) = fn_def_id(cx, parent);
+ if is_into_iter(cx, callee_def_id);
+ then {
+ check_for_loop_iter(cx, parent, method_name, receiver, false)
+ } else {
+ false
+ }
+ }
+}
+
+/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the
+/// iterated-over items could be iterated over by reference. The reason why `check` above does not
+/// include this code directly is so that it can be called from
+/// `unnecessary_into_owned::check_into_iter_call_arg`.
+pub fn check_for_loop_iter(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ cloned_before_iter: bool,
+) -> bool {
+ if_chain! {
+ if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent));
+ if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent);
+ let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body);
+ if !clone_or_copy_needed;
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ let snippet = if_chain! {
+ if let ExprKind::MethodCall(maybe_iter_method_name, [collection], _) = receiver.kind;
+ if maybe_iter_method_name.ident.name == sym::iter;
+
+ if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ if implements_trait(cx, receiver_ty, iterator_trait_id, &[]);
+ if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty);
+
+ if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator);
+ let collection_ty = cx.typeck_results().expr_ty(collection);
+ if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]);
+ if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item");
+
+ if iter_item_ty == into_iter_item_ty;
+ if let Some(collection_snippet) = snippet_opt(cx, collection.span);
+ then {
+ collection_snippet
+ } else {
+ receiver_snippet
+ }
+ };
+ span_lint_and_then(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ expr.span,
+ &format!("unnecessary use of `{}`", method_name),
+ |diag| {
+ // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to
+ // a `to_owned`-like function was removed, then the next suggestion may be
+ // incorrect. This is because the iterator that results from the call's removal
+ // could hold a reference to a resource that is used mutably. See
+ // https://github.com/rust-lang/rust-clippy/issues/8148.
+ let applicability = if cloned_before_iter {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+ diag.span_suggestion(expr.span, "use", snippet, applicability);
+ for addr_of_expr in addr_of_exprs {
+ match addr_of_expr.kind {
+ ExprKind::AddrOf(_, _, referent) => {
+ let span = addr_of_expr.span.with_hi(referent.span.lo());
+ diag.span_suggestion(span, "remove this `&`", "", applicability);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Returns true if the named method is `IntoIterator::into_iter`.
+pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
+ cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_join.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_join.rs
new file mode 100644
index 000000000..973b8a7e6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_join.rs
@@ -0,0 +1,41 @@
+use clippy_utils::{diagnostics::span_lint_and_sugg, 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_middle::ty::{Ref, Slice};
+use rustc_span::{sym, Span};
+
+use super::UNNECESSARY_JOIN;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ join_self_arg: &'tcx Expr<'tcx>,
+ join_arg: &'tcx Expr<'tcx>,
+ span: Span,
+) {
+ let applicability = Applicability::MachineApplicable;
+ let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg);
+ if_chain! {
+ // the turbofish for collect is ::<Vec<String>>
+ if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind();
+ if let Slice(slice) = ref_type.kind();
+ if is_type_diagnostic_item(cx, *slice, sym::String);
+ // the argument for join is ""
+ if let ExprKind::Lit(spanned) = &join_arg.kind;
+ if let LitKind::Str(symbol, _) = spanned.node;
+ if symbol.is_empty();
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_JOIN,
+ span.with_hi(expr.span.hi()),
+ r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
+ "try using",
+ "collect::<String>()".to_owned(),
+ applicability,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs
new file mode 100644
index 000000000..1876c7fb9
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_lazy_eval.rs
@@ -0,0 +1,70 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{eager_or_lazy, usage};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNNECESSARY_LAZY_EVALUATIONS;
+
+/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
+/// replaced with `<fn>(return value of simple closure)`
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ simplify_using: &str,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
+
+ if is_option || is_result || is_bool {
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
+ let body = cx.tcx.hir().body(body);
+ let body_expr = &body.value;
+
+ if usage::BindingUsageFinder::are_params_used(cx, body) {
+ return;
+ }
+
+ if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
+ let msg = if is_option {
+ "unnecessary closure used to substitute value for `Option::None`"
+ } else if is_result {
+ "unnecessary closure used to substitute value for `Result::Err`"
+ } else {
+ "unnecessary closure used with `bool::then`"
+ };
+ let applicability = if body
+ .params
+ .iter()
+ // bindings are checked to be unused above
+ .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild))
+ {
+ Applicability::MachineApplicable
+ } else {
+ // replacing the lambda may break type inference
+ Applicability::MaybeIncorrect
+ };
+
+ // This is a duplicate of what's happening in clippy_lints::methods::method_call,
+ // which isn't ideal, We want to get the method call span,
+ // but prefer to avoid changing the signature of the function itself.
+ if let hir::ExprKind::MethodCall(_, _, span) = expr.kind {
+ span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| {
+ diag.span_suggestion(
+ span,
+ &format!("use `{}(..)` instead", simplify_using),
+ format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")),
+ applicability,
+ );
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
new file mode 100644
index 000000000..b3276f139
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -0,0 +1,431 @@
+use super::implicit_clone::is_clone_like;
+use super::unnecessary_iter_cloned::{self, is_into_iter};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+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};
+use rustc_lint::LateContext;
+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>],
+ 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;
+ then {
+ if is_cloned_or_copied(cx, method_name, method_def_id) {
+ unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
+ } else if is_to_owned_like(cx, method_name, method_def_id) {
+ // At this point, we know the call is of a `to_owned`-like function. The functions
+ // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
+ // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
+ // argument in a `into_iter` call, or an argument in the call of some other function.
+ 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, msrv) {
+ return;
+ }
+ check_other_call_arg(cx, expr, method_name, receiver);
+ }
+ }
+ }
+}
+
+/// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
+/// call of a `to_owned`-like function is unnecessary.
+#[allow(clippy::too_many_lines)]
+fn check_addr_of_expr(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ method_def_id: DefId,
+ receiver: &Expr<'_>,
+) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
+ let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
+ if let
+ // For matching uses of `Cow::from`
+ [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
+ ]
+ // For matching uses of arrays
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Pointer(_),
+ target: target_ty,
+ },
+ ]
+ // For matching everything else
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Deref(Some(OverloadedDeref { .. })),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
+ ] = adjustments[..];
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
+ let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
+ // Only flag cases satisfying at least one of the following three conditions:
+ // * the referent and receiver types are distinct
+ // * the referent/receiver type is a copyable array
+ // * the method is `Cow::into_owned`
+ // This restriction is to ensure there is no overlap between `redundant_clone` and this
+ // lint. It also avoids the following false positive:
+ // https://github.com/rust-lang/rust-clippy/issues/8759
+ // Arrays are a bit of a corner case. Non-copyable arrays are handled by
+ // `redundant_clone`, but copyable arrays are not.
+ if *referent_ty != receiver_ty
+ || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
+ || is_cow_into_owned(cx, method_name, method_def_id);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!(
+ "{:&>width$}{}",
+ "",
+ receiver_snippet,
+ width = n_target_refs - n_receiver_refs
+ ),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ if_chain! {
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
+ if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
+ then {
+ if n_receiver_refs > 0 {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ receiver_snippet,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ expr.span.with_lo(receiver.span.hi()),
+ &format!("unnecessary use of `{}`", method_name),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ return true;
+ }
+ }
+ if_chain! {
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{}.as_ref()", receiver_snippet),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ }
+ }
+ false
+}
+
+/// 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<'_>,
+ 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);
+ if is_into_iter(cx, callee_def_id);
+ if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ let parent_ty = cx.typeck_results().expr_ty(parent);
+ if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
+ if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ 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) && 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.
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
+/// of a `to_owned`-like function is unnecessary.
+fn check_other_call_arg<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ receiver: &'tcx Expr<'tcx>,
+) -> bool {
+ if_chain! {
+ if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
+ if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
+ if let Some(input) = fn_sig.inputs().get(i);
+ let (input, n_refs) = peel_mid_ty_refs(*input);
+ if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
+ if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
+ if let [trait_predicate] = trait_predicates
+ .iter()
+ .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
+ .collect::<Vec<_>>()[..];
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
+ // types of `trait_predicate.trait_ref.substs`.
+ if if trait_predicate.def_id() == deref_trait_id {
+ if let [projection_predicate] = projection_predicates[..] {
+ let normalized_ty =
+ cx.tcx
+ .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
+ implements_trait(cx, receiver_ty, deref_trait_id, &[])
+ && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
+ .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
+ } else {
+ false
+ }
+ } else if trait_predicate.def_id() == as_ref_trait_id {
+ let composed_substs = compose_substs(
+ cx,
+ &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
+ call_substs,
+ );
+ implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
+ } else {
+ false
+ };
+ // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
+ // `Target = T`.
+ if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
+ let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
+ // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
+ // `T` must not be instantiated with a reference
+ // (https://github.com/rust-lang/rust-clippy/issues/8507).
+ if (n_refs == 0 && !receiver_ty.is_ref())
+ || trait_predicate.def_id() != as_ref_trait_id
+ || !contains_ty(fn_sig.output(), input);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ maybe_arg.span,
+ &format!("unnecessary use of `{}`", method_name),
+ "use",
+ format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
+/// expression found (if any) along with the immediately prior expression.
+fn skip_addr_of_ancestors<'tcx>(
+ cx: &LateContext<'tcx>,
+ mut expr: &'tcx Expr<'tcx>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ while let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
+ expr = parent;
+ } else {
+ return Some((parent, expr));
+ }
+ }
+ None
+}
+
+/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
+/// `Substs`, and arguments.
+fn get_callee_substs_and_args<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
+ if_chain! {
+ if let ExprKind::Call(callee, args) = expr.kind;
+ let callee_ty = cx.typeck_results().expr_ty(callee);
+ if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
+ then {
+ let substs = cx.typeck_results().node_substs(callee.hir_id);
+ return Some((*callee_def_id, substs, args));
+ }
+ }
+ if_chain! {
+ if let ExprKind::MethodCall(_, args, _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ then {
+ let substs = cx.typeck_results().node_substs(expr.hir_id);
+ return Some((method_def_id, substs, args));
+ }
+ }
+ None
+}
+
+/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
+fn get_input_traits_and_projections<'tcx>(
+ cx: &LateContext<'tcx>,
+ callee_def_id: DefId,
+ input: Ty<'tcx>,
+) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
+ let mut trait_predicates = Vec::new();
+ let mut projection_predicates = Vec::new();
+ for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() {
+ // `substs` should have 1 + n elements. The first is the type on the left hand side of an
+ // `as`. The remaining n are trait parameters.
+ let is_input_substs = |substs: SubstsRef<'tcx>| {
+ if_chain! {
+ if let Some(arg) = substs.iter().next();
+ if let GenericArgKind::Type(arg_ty) = arg.unpack();
+ if arg_ty == input;
+ then { true } else { false }
+ }
+ };
+ match predicate.kind().skip_binder() {
+ PredicateKind::Trait(trait_predicate) => {
+ if is_input_substs(trait_predicate.trait_ref.substs) {
+ trait_predicates.push(trait_predicate);
+ }
+ },
+ PredicateKind::Projection(projection_predicate) => {
+ if is_input_substs(projection_predicate.projection_ty.substs) {
+ projection_predicates.push(projection_predicate);
+ }
+ },
+ _ => {},
+ }
+ }
+ (trait_predicates, projection_predicates)
+}
+
+/// Composes two substitutions by applying the latter to the types of the former.
+fn compose_substs<'tcx>(
+ cx: &LateContext<'tcx>,
+ left: &[GenericArg<'tcx>],
+ right: SubstsRef<'tcx>,
+) -> Vec<GenericArg<'tcx>> {
+ left.iter()
+ .map(|arg| {
+ if let GenericArgKind::Type(arg_ty) = arg.unpack() {
+ let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
+ GenericArg::from(normalized_ty)
+ } else {
+ *arg
+ }
+ })
+ .collect()
+}
+
+/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
+fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
+ && is_diag_trait_item(cx, method_def_id, sym::Iterator)
+}
+
+/// Returns true if the named method can be used to convert the receiver to its "owned"
+/// representation.
+fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ is_clone_like(cx, method_name.as_str(), method_def_id)
+ || is_cow_into_owned(cx, method_name, method_def_id)
+ || is_to_string(cx, method_name, method_def_id)
+}
+
+/// Returns true if the named method is `Cow::into_owned`.
+fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
+}
+
+/// Returns true if the named method is `ToString::to_string`.
+fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ method_name == sym::to_string && is_diag_trait_item(cx, method_def_id, sym::ToString)
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs b/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs
new file mode 100644
index 000000000..f3af281d6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs
@@ -0,0 +1,46 @@
+//! Lint for `some_result_or_option.unwrap_or_else(Default::default)`
+
+use super::UNWRAP_OR_ELSE_DEFAULT;
+use clippy_utils::{
+ diagnostics::span_lint_and_sugg, is_default_equivalent_call, source::snippet_with_applicability,
+ ty::is_type_diagnostic_item,
+};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ u_arg: &'tcx hir::Expr<'_>,
+) {
+ // something.unwrap_or_else(Default::default)
+ // ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr
+ let recv_ty = cx.typeck_results().expr_ty(recv);
+ let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option);
+ let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result);
+
+ if_chain! {
+ if is_option || is_result;
+ if is_default_equivalent_call(cx, u_arg);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_sugg(
+ cx,
+ UNWRAP_OR_ELSE_DEFAULT,
+ expr.span,
+ "use of `.unwrap_or_else(..)` to construct default value",
+ "try",
+ format!(
+ "{}.unwrap_or_default()",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs
new file mode 100644
index 000000000..5c7610149
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs
@@ -0,0 +1,40 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_in_test_function;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNWRAP_USED;
+
+/// lint use of `unwrap()` for `Option`s and `Result`s
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) {
+ let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
+
+ let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
+ Some((UNWRAP_USED, "an Option", "None"))
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
+ Some((UNWRAP_USED, "a Result", "Err"))
+ } else {
+ None
+ };
+
+ if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) {
+ return;
+ }
+
+ if let Some((lint, kind, none_value)) = mess {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
+ &format!("used `unwrap()` on `{}` value", kind,),
+ None,
+ &format!(
+ "if you don't want to handle the `{}` case gracefully, consider \
+ using `expect()` to provide a better panic message",
+ none_value,
+ ),
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs
new file mode 100644
index 000000000..ca5d33ee8
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/useless_asref.rs
@@ -0,0 +1,45 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::walk_ptrs_ty_depth;
+use clippy_utils::{get_parent_expr, match_trait_method, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::USELESS_ASREF;
+
+/// Checks for the `USELESS_ASREF` lint.
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
+ // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
+ // check if the call is to the actual `AsRef` or `AsMut` trait
+ if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) {
+ // check if the type after `as_ref` or `as_mut` is the same as before
+ let rcv_ty = cx.typeck_results().expr_ty(recvr);
+ let res_ty = cx.typeck_results().expr_ty(expr);
+ let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty);
+ let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty);
+ if base_rcv_ty == base_res_ty && rcv_depth >= res_depth {
+ // allow the `as_ref` or `as_mut` if it is followed by another method call
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::MethodCall(segment, ..) = parent.kind;
+ if segment.ident.span != expr.span;
+ then {
+ return;
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ USELESS_ASREF,
+ expr.span,
+ &format!("this call to `{}` does nothing", call_name),
+ "try this",
+ snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(),
+ applicability,
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/utils.rs b/src/tools/clippy/clippy_lints/src/methods/utils.rs
new file mode 100644
index 000000000..3015531e8
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/utils.rs
@@ -0,0 +1,168 @@
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, path_to_local_id, usage};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+
+pub(super) fn derefs_to_slice<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'tcx>,
+ ty: Ty<'tcx>,
+) -> Option<&'tcx hir::Expr<'tcx>> {
+ fn may_slice<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> bool {
+ match ty.kind() {
+ ty::Slice(_) => true,
+ ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()),
+ ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec),
+ ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(),
+ ty::Ref(_, inner, _) => may_slice(cx, *inner),
+ _ => false,
+ }
+ }
+
+ if let hir::ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind {
+ if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(self_arg)) {
+ Some(self_arg)
+ } else {
+ None
+ }
+ } else {
+ match ty.kind() {
+ ty::Slice(_) => Some(expr),
+ ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr),
+ ty::Ref(_, inner, _) => {
+ if may_slice(cx, *inner) {
+ Some(expr)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+ }
+}
+
+pub(super) fn get_hint_if_single_char_arg(
+ cx: &LateContext<'_>,
+ arg: &hir::Expr<'_>,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ if_chain! {
+ if let hir::ExprKind::Lit(lit) = &arg.kind;
+ if let ast::LitKind::Str(r, style) = lit.node;
+ let string = r.as_str();
+ if string.chars().count() == 1;
+ then {
+ let snip = snippet_with_applicability(cx, arg.span, string, applicability);
+ let ch = if let ast::StrStyle::Raw(nhash) = style {
+ let nhash = nhash as usize;
+ // for raw string: r##"a"##
+ &snip[(nhash + 2)..(snip.len() - 1 - nhash)]
+ } else {
+ // for regular string: "a"
+ &snip[1..(snip.len() - 1)]
+ };
+
+ let hint = format!("'{}'", match ch {
+ "'" => "\\'" ,
+ r"\" => "\\\\",
+ _ => ch,
+ });
+
+ Some(hint)
+ } else {
+ None
+ }
+ }
+}
+
+/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
+/// use of `CloneOrCopyVisitor`.
+pub(super) fn clone_or_copy_needed<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &Pat<'tcx>,
+ body: &'tcx Expr<'tcx>,
+) -> (bool, Vec<&'tcx Expr<'tcx>>) {
+ let mut visitor = CloneOrCopyVisitor {
+ cx,
+ binding_hir_ids: pat_bindings(pat),
+ clone_or_copy_needed: false,
+ addr_of_exprs: Vec::new(),
+ };
+ visitor.visit_expr(body);
+ (visitor.clone_or_copy_needed, visitor.addr_of_exprs)
+}
+
+/// Returns a vector of all `HirId`s bound by the pattern.
+fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
+ let mut collector = usage::ParamBindingIdCollector {
+ binding_hir_ids: Vec::new(),
+ };
+ collector.visit_pat(pat);
+ collector.binding_hir_ids
+}
+
+/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
+/// operations performed on `binding_hir_ids` are:
+/// * to take non-mutable references to them
+/// * to use them as non-mutable `&self` in method calls
+/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
+/// when `CloneOrCopyVisitor` is done visiting.
+struct CloneOrCopyVisitor<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ binding_hir_ids: Vec<HirId>,
+ clone_or_copy_needed: bool,
+ addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
+}
+
+impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ walk_expr(self, expr);
+ if self.is_binding(expr) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ match parent.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
+ self.addr_of_exprs.push(parent);
+ return;
+ },
+ ExprKind::MethodCall(_, args, _) => {
+ if_chain! {
+ if args.iter().skip(1).all(|arg| !self.is_binding(arg));
+ if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
+ let method_ty = self.cx.tcx.type_of(method_def_id);
+ let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
+ if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
+ then {
+ return;
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+ self.clone_or_copy_needed = true;
+ }
+ }
+}
+
+impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
+ fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
+ self.binding_hir_ids
+ .iter()
+ .any(|hir_id| path_to_local_id(expr, *hir_id))
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs b/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs
new file mode 100644
index 000000000..4b368d3ff
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/wrong_self_convention.rs
@@ -0,0 +1,154 @@
+use crate::methods::SelfKind;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_copy;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::source_map::Span;
+use std::fmt;
+
+use super::WRONG_SELF_CONVENTION;
+
+#[rustfmt::skip]
+const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
+ (&[Convention::Eq("new")], &[SelfKind::No]),
+ (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
+ (&[Convention::StartsWith("from_")], &[SelfKind::No]),
+ (&[Convention::StartsWith("into_")], &[SelfKind::Value]),
+ (&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]),
+ (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
+ (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),
+
+ // Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types).
+ // Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false),
+ Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]),
+ (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true),
+ Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Value]),
+];
+
+enum Convention {
+ Eq(&'static str),
+ StartsWith(&'static str),
+ EndsWith(&'static str),
+ NotEndsWith(&'static str),
+ IsSelfTypeCopy(bool),
+ ImplementsTrait(bool),
+ IsTraitItem(bool),
+}
+
+impl Convention {
+ #[must_use]
+ fn check<'tcx>(
+ &self,
+ cx: &LateContext<'tcx>,
+ self_ty: Ty<'tcx>,
+ other: &str,
+ implements_trait: bool,
+ is_trait_item: bool,
+ ) -> bool {
+ match *self {
+ Self::Eq(this) => this == other,
+ Self::StartsWith(this) => other.starts_with(this) && this != other,
+ Self::EndsWith(this) => other.ends_with(this) && this != other,
+ Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, implements_trait, is_trait_item),
+ Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty),
+ Self::ImplementsTrait(is_true) => is_true == implements_trait,
+ Self::IsTraitItem(is_true) => is_true == is_trait_item,
+ }
+ }
+}
+
+impl fmt::Display for Convention {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match *self {
+ Self::Eq(this) => format!("`{}`", this).fmt(f),
+ Self::StartsWith(this) => format!("`{}*`", this).fmt(f),
+ Self::EndsWith(this) => format!("`*{}`", this).fmt(f),
+ Self::NotEndsWith(this) => format!("`~{}`", this).fmt(f),
+ Self::IsSelfTypeCopy(is_true) => {
+ format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
+ },
+ Self::ImplementsTrait(is_true) => {
+ let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
+ format!("method{} implement{} a trait", negation, s_suffix).fmt(f)
+ },
+ Self::IsTraitItem(is_true) => {
+ let suffix = if is_true { " is" } else { " is not" };
+ format!("method{} a trait item", suffix).fmt(f)
+ },
+ }
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ item_name: &str,
+ self_ty: Ty<'tcx>,
+ first_arg_ty: Ty<'tcx>,
+ first_arg_span: Span,
+ implements_trait: bool,
+ is_trait_item: bool,
+) {
+ if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
+ convs
+ .iter()
+ .all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item))
+ }) {
+ // don't lint if it implements a trait but not willing to check `Copy` types conventions (see #7032)
+ if implements_trait
+ && !conventions
+ .iter()
+ .any(|conv| matches!(conv, Convention::IsSelfTypeCopy(_)))
+ {
+ return;
+ }
+ if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
+ let suggestion = {
+ if conventions.len() > 1 {
+ // Don't mention `NotEndsWith` when there is also `StartsWith` convention present
+ let cut_ends_with_conv = conventions.iter().any(|conv| matches!(conv, Convention::StartsWith(_)))
+ && conventions
+ .iter()
+ .any(|conv| matches!(conv, Convention::NotEndsWith(_)));
+
+ let s = conventions
+ .iter()
+ .filter_map(|conv| {
+ if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
+ || matches!(conv, Convention::ImplementsTrait(_))
+ || matches!(conv, Convention::IsTraitItem(_))
+ {
+ None
+ } else {
+ Some(conv.to_string())
+ }
+ })
+ .collect::<Vec<_>>()
+ .join(" and ");
+
+ format!("methods with the following characteristics: ({})", &s)
+ } else {
+ format!("methods called {}", &conventions[0])
+ }
+ };
+
+ span_lint_and_help(
+ cx,
+ WRONG_SELF_CONVENTION,
+ first_arg_span,
+ &format!(
+ "{} usually take {}",
+ suggestion,
+ &self_kinds
+ .iter()
+ .map(|k| k.description())
+ .collect::<Vec<_>>()
+ .join(" or ")
+ ),
+ None,
+ "consider choosing a less ambiguous name",
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs b/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs
new file mode 100644
index 000000000..e9f268da6
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/zst_offset.rs
@@ -0,0 +1,18 @@
+use clippy_utils::diagnostics::span_lint;
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+
+use super::ZST_OFFSET;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ if_chain! {
+ if let ty::RawPtr(ty::TypeAndMut { ty, .. }) = cx.typeck_results().expr_ty(recv).kind();
+ if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(*ty));
+ if layout.is_zst();
+ then {
+ span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value");
+ }
+ }
+}