summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs85
1 files changed, 85 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
new file mode 100644
index 000000000..1d6ddf4b9
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
@@ -0,0 +1,85 @@
+use super::utils::make_iterator_snippet;
+use super::MANUAL_FLATTEN;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{is_lang_ctor, path_to_local_id, peel_blocks_with_stmt};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionSome, ResultOk};
+use rustc_hir::{Expr, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+
+/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
+/// iterator element is used.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ span: Span,
+) {
+ let inner_expr = peel_blocks_with_stmt(body);
+ if_chain! {
+ if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
+ = higher::IfLet::hir(cx, inner_expr);
+ // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
+ if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
+ if path_to_local_id(let_expr, pat_hir_id);
+ // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
+ if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
+ let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
+ let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
+ if some_ctor || ok_ctor;
+ // Ensure expr in `if let` is not used afterwards
+ if !is_local_used(cx, if_then, pat_hir_id);
+ then {
+ let if_let_type = if some_ctor { "Some" } else { "Ok" };
+ // Prepare the error message
+ let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);
+
+ // Prepare the help message
+ let mut applicability = Applicability::MaybeIncorrect;
+ let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
+ let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
+ ty::Ref(_, inner, _) => match inner.kind() {
+ ty::Ref(..) => ".copied()",
+ _ => ""
+ }
+ _ => ""
+ };
+
+ let sugg = format!("{arg_snippet}{copied}.flatten()");
+
+ // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
+ // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
+ // to refer to the correct relative position of the suggestion.
+ let help_msg = if sugg.contains('\n') {
+ "remove the `if let` statement in the for loop and then..."
+ } else {
+ "...and remove the `if let` statement in the for loop"
+ };
+
+ span_lint_and_then(
+ cx,
+ MANUAL_FLATTEN,
+ span,
+ &msg,
+ |diag| {
+ diag.span_suggestion(
+ arg.span,
+ "try",
+ sugg,
+ applicability,
+ );
+ diag.span_help(
+ inner_expr.span,
+ help_msg,
+ );
+ }
+ );
+ }
+ }
+}