-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add iterator reduction coverage to never_loop #16222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
36201bd
89a848c
fefd58b
51580d3
9dca2a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ mod while_let_on_iterator; | |
|
|
||
| use clippy_config::Conf; | ||
| use clippy_utils::msrvs::Msrv; | ||
| use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; | ||
| use clippy_utils::{higher, sym}; | ||
| use rustc_ast::Label; | ||
| use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; | ||
|
|
@@ -881,13 +882,31 @@ impl<'tcx> LateLintPass<'tcx> for Loops { | |
| manual_while_let_some::check(cx, condition, body, span); | ||
| } | ||
|
|
||
| if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind | ||
| && matches!( | ||
| path.ident.name, | ||
| sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map | ||
| ) | ||
| { | ||
| unused_enumerate_index::check_method(cx, expr, recv, arg); | ||
| if let ExprKind::MethodCall(path, recv, args, _) = expr.kind { | ||
| let name = path.ident.name; | ||
| let is_iterator_method = cx | ||
| .ty_based_def(expr) | ||
| .assoc_fn_parent(cx) | ||
| .is_diag_item(cx, sym::Iterator); | ||
|
|
||
| match (name, args) { | ||
| (sym::for_each | sym::all | sym::any, [arg]) => { | ||
| unused_enumerate_index::check_method(cx, expr, recv, arg); | ||
| if is_iterator_method { | ||
| never_loop::check_iterator_reduction(cx, expr, recv, args); | ||
| } | ||
|
Comment on lines
+894
to
+897
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you pull out the checks shared between the two lints to happen here. Both the check for |
||
| }, | ||
|
|
||
| (sym::filter_map | sym::find_map | sym::flat_map | sym::map, [arg]) => { | ||
| unused_enumerate_index::check_method(cx, expr, recv, arg); | ||
| }, | ||
|
|
||
| (sym::try_for_each | sym::reduce | sym::fold | sym::try_fold, args) if is_iterator_method => { | ||
| never_loop::check_iterator_reduction(cx, expr, recv, args); | ||
| }, | ||
|
|
||
| _ => {}, | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,15 @@ use super::utils::make_iterator_snippet; | |
| use clippy_utils::diagnostics::span_lint_and_then; | ||
| use clippy_utils::higher::ForLoop; | ||
| use clippy_utils::macros::root_macro_call_first_node; | ||
| use clippy_utils::source::snippet; | ||
| use clippy_utils::source::{snippet, snippet_with_context}; | ||
| use clippy_utils::sym; | ||
| use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; | ||
| use rustc_errors::Applicability; | ||
| use rustc_hir::{ | ||
| Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, | ||
| }; | ||
| use rustc_lint::LateContext; | ||
| use rustc_span::{BytePos, Span, sym}; | ||
| use rustc_span::{BytePos, Span}; | ||
| use std::iter::once; | ||
| use std::ops::ControlFlow; | ||
|
|
||
|
|
@@ -72,6 +73,54 @@ pub(super) fn check<'tcx>( | |
| } | ||
| } | ||
|
|
||
| pub(super) fn check_iterator_reduction<'tcx>( | ||
| cx: &LateContext<'tcx>, | ||
| expr: &'tcx Expr<'tcx>, | ||
| recv: &'tcx Expr<'tcx>, | ||
| args: &'tcx [Expr<'tcx>], | ||
| ) { | ||
| // identify which argument is the closure based on the method kind | ||
| let Some(method_name) = (match expr.kind { | ||
| ExprKind::MethodCall(path, ..) => Some(path.ident.name), | ||
| _ => None, | ||
| }) else { | ||
| return; | ||
| }; | ||
|
|
||
| let closure_arg = match method_name { | ||
| sym::for_each | sym::try_for_each | sym::reduce | sym::all | sym::any => args.first(), | ||
| sym::fold | sym::try_fold => args.get(1), | ||
| _ => None, | ||
| }; | ||
|
|
||
| let Some(closure_arg) = closure_arg else { | ||
| return; | ||
| }; | ||
|
Comment on lines
+82
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should be splitting this before getting to this function. |
||
|
|
||
| // extract the closure body | ||
| let closure_body = match closure_arg.kind { | ||
| ExprKind::Closure(closure) => cx.tcx.hir_body(closure.body).value, | ||
| _ => return, | ||
| }; | ||
|
|
||
| let body_ty = cx.typeck_results().expr_ty(closure_body); | ||
| if body_ty.is_never() { | ||
| span_lint_and_then( | ||
| cx, | ||
| NEVER_LOOP, | ||
| expr.span, | ||
| "this iterator reduction never loops (closure always diverges)", | ||
| |diag| { | ||
| let mut app = Applicability::HasPlaceholders; | ||
| let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "<iter>", &mut app).0; | ||
| diag.note("if you only need one element, `if let Some(x) = iter.next()` is clearer"); | ||
| let sugg = format!("if let Some(x) = {recv_snip}.next() {{ ... }}"); | ||
| diag.span_suggestion_verbose(expr.span, "consider this pattern", sugg, app); | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| fn contains_any_break_or_continue(block: &Block<'_>) -> bool { | ||
| for_each_expr_without_closures(block, |e| match e.kind { | ||
| ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| //@no-rustfix | ||
| #![warn(clippy::never_loop)] | ||
|
|
||
| fn main() { | ||
| // diverging closure: should trigger | ||
| [0, 1].into_iter().for_each(|x| { | ||
| //~^ never_loop | ||
|
|
||
| let _ = x; | ||
| panic!("boom"); | ||
| }); | ||
|
|
||
| // benign closure: should NOT trigger | ||
| [0, 1].into_iter().for_each(|x| { | ||
| let _ = x + 1; | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| error: this iterator reduction never loops (closure always diverges) | ||
| --> tests/ui/never_loop_iterator_reduction.rs:6:5 | ||
| | | ||
| LL | / [0, 1].into_iter().for_each(|x| { | ||
| LL | | | ||
| LL | | | ||
| LL | | let _ = x; | ||
| LL | | panic!("boom"); | ||
| LL | | }); | ||
| | |______^ | ||
| | | ||
| = note: if you only need one element, `if let Some(x) = iter.next()` is clearer | ||
| = note: `-D clippy::never-loop` implied by `-D warnings` | ||
| = help: to override `-D warnings` add `#[allow(clippy::never_loop)]` | ||
| help: consider this pattern | ||
| | | ||
| LL - [0, 1].into_iter().for_each(|x| { | ||
| LL - | ||
| LL - | ||
| LL - let _ = x; | ||
| LL - panic!("boom"); | ||
| LL - }); | ||
| LL + if let Some(x) = [0, 1].into_iter().next() { ... }; | ||
| | | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thisshould be delayed until after the name check. Type checking and item identification is notably slower than matching on a symbol.Defining a closure would be fine.