From bb58083ce59b44c54866a4d8305f59c7e7742593 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:16:37 +0200 Subject: [PATCH 1/7] new lint: `while_pop_unwrap` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 1 + clippy_lints/src/loops/mod.rs | 3 + clippy_lints/src/while_pop_unwrap.rs | 126 +++++++++++++++++++++++++++ clippy_utils/src/paths.rs | 4 + tests/ui/while_pop_unwrap.rs | 47 ++++++++++ tests/ui/while_pop_unwrap.stderr | 43 +++++++++ 8 files changed, 226 insertions(+) create mode 100644 clippy_lints/src/while_pop_unwrap.rs create mode 100644 tests/ui/while_pop_unwrap.rs create mode 100644 tests/ui/while_pop_unwrap.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f4f97ee07c..da03f5c012b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5160,6 +5160,7 @@ Released 2018-09-13 [`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition [`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop [`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator +[`while_pop_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_pop_unwrap [`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies [`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm [`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 4aebd0b7d011..ddc4b139670c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -645,6 +645,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::useless_conversion::USELESS_CONVERSION_INFO, crate::vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, + crate::while_pop_unwrap::WHILE_POP_UNWRAP_INFO, crate::wildcard_imports::ENUM_GLOB_USE_INFO, crate::wildcard_imports::WILDCARD_IMPORTS_INFO, crate::write::PRINTLN_EMPTY_STRING_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 48dbecc9f6aa..0d594612b796 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -324,6 +324,7 @@ mod use_self; mod useless_conversion; mod vec; mod vec_init_then_push; +mod while_pop_unwrap; mod wildcard_imports; mod write; mod zero_div_zero; diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 610a0233eee1..90012694062e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -25,6 +25,8 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor}; +use crate::while_pop_unwrap; + declare_clippy_lint! { /// ### What it does /// Checks for for-loops that manually copy items between @@ -643,6 +645,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let Some(higher::While { condition, body }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); missing_spin_loop::check(cx, condition, body); + while_pop_unwrap::check(cx, condition, body); } } } diff --git a/clippy_lints/src/while_pop_unwrap.rs b/clippy_lints/src/while_pop_unwrap.rs new file mode 100644 index 000000000000..fc77febad6b6 --- /dev/null +++ b/clippy_lints/src/while_pop_unwrap.rs @@ -0,0 +1,126 @@ +use clippy_utils::{diagnostics::span_lint_and_then, match_def_path, paths, source::snippet}; +use rustc_errors::Applicability; +use rustc_hir::*; +use rustc_lint::LateContext; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{symbol::Ident, Span}; + +declare_clippy_lint! { + /// ### What it does + /// Looks for loops that check for emptiness of a `Vec` in the condition and pop an element + /// in the body as a separate operation. + /// + /// ### Why is this bad? + /// Such loops can be written in a more idiomatic way by using a while..let loop and directly + /// pattern matching on the return value of `Vec::pop()`. + /// + /// ### Example + /// ```rust + /// let mut numbers = vec![1, 2, 3, 4, 5]; + /// while !numbers.is_empty() { + /// let number = numbers.pop().unwrap(); + /// // use `number` + /// } + /// ``` + /// Use instead: + /// ```rust + /// let mut numbers = vec![1, 2, 3, 4, 5]; + /// while let Some(number) = numbers.pop() { + /// // use `number` + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub WHILE_POP_UNWRAP, + style, + "checking for emptiness of a `Vec` in the loop condition and popping an element in the body" +} +declare_lint_pass!(WhilePopUnwrap => [WHILE_POP_UNWRAP]); + +fn report_lint<'tcx>( + cx: &LateContext<'tcx>, + pop_span: Span, + ident: Option, + loop_span: Span, + receiver_span: Span, +) { + span_lint_and_then( + cx, + WHILE_POP_UNWRAP, + pop_span, + "you seem to be trying to pop elements from a `Vec` in a loop", + |diag| { + diag.span_suggestion( + loop_span, + "try", + format!( + "while let Some({}) = {}.pop()", + ident.as_ref().map(Ident::as_str).unwrap_or("element"), + snippet(cx, receiver_span, "..") + ), + Applicability::MaybeIncorrect, + ) + .note("this while loop can be written in a more idiomatic way"); + }, + ); +} + +fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: &[&str]) -> bool { + if let ExprKind::MethodCall(..) = expr.kind + && let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && match_def_path(cx, id, method) + { + true + } else { + false + } +} + +fn is_vec_pop<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { + match_method_call(cx, expr, &paths::VEC_POP) +} + +fn is_vec_pop_unwrap<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { + if let ExprKind::MethodCall(_, inner, ..) = expr.kind + && (match_method_call(cx, expr, &paths::OPTION_UNWRAP) || match_method_call(cx, expr, &paths::OPTION_EXPECT)) + && is_vec_pop(cx, inner) + { + true + } else { + false + } +} + +fn check_local<'tcx>(cx: &LateContext<'tcx>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { + if let StmtKind::Local(local) = stmt.kind + && let PatKind::Binding(.., ident, _) = local.pat.kind + && let Some(init) = local.init + && let ExprKind::MethodCall(_, inner, ..) = init.kind + && is_vec_pop_unwrap(cx, init) + { + report_lint(cx, init.span.to(inner.span), Some(ident), loop_span, recv_span); + } +} + +fn check_call_arguments<'tcx>(cx: &LateContext<'tcx>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { + if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind { + if let ExprKind::MethodCall(_, _, args, _) | ExprKind::Call(_, args) = expr.kind { + let offending_arg = args.iter().find_map(|arg| is_vec_pop_unwrap(cx, arg).then(|| arg.span)); + + if let Some(offending_arg) = offending_arg { + report_lint(cx, offending_arg, None, loop_span, recv_span); + } + } + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { + if let ExprKind::Unary(UnOp::Not, cond) = cond.kind + && let ExprKind::MethodCall(_, Expr { span: recv_span, .. }, _, _) = cond.kind + && match_method_call(cx, cond, &paths::VEC_IS_EMPTY) + && let ExprKind::Block(body, _) = body.kind + && let Some(stmt) = body.stmts.first() + { + check_local(cx, stmt, cond.span, *recv_span); + check_call_arguments(cx, stmt, cond.span, *recv_span); + } +} diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 9be2d0eae80a..0f0792fdaa96 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -159,3 +159,7 @@ pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"]; pub const INSTANT_NOW: [&str; 4] = ["std", "time", "Instant", "now"]; pub const INSTANT: [&str; 3] = ["std", "time", "Instant"]; +pub const VEC_IS_EMPTY: [&str; 4] = ["alloc", "vec", "Vec", "is_empty"]; +pub const VEC_POP: [&str; 4] = ["alloc", "vec", "Vec", "pop"]; +pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"]; +pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"]; diff --git a/tests/ui/while_pop_unwrap.rs b/tests/ui/while_pop_unwrap.rs new file mode 100644 index 000000000000..1b6de8f1f89e --- /dev/null +++ b/tests/ui/while_pop_unwrap.rs @@ -0,0 +1,47 @@ +#![allow(unused)] +#![warn(clippy::while_pop_unwrap)] + +struct VecInStruct { + numbers: Vec, + unrelated: String, +} + +fn accept_i32(_: i32) {} + +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + while !numbers.is_empty() { + let number = numbers.pop().unwrap(); + } + + let mut val = VecInStruct { + numbers: vec![1, 2, 3, 4, 5], + unrelated: String::new(), + }; + while !val.numbers.is_empty() { + let number = val.numbers.pop().unwrap(); + } + + while !numbers.is_empty() { + accept_i32(numbers.pop().unwrap()); + } + + while !numbers.is_empty() { + accept_i32(numbers.pop().expect("")); + } + + // This should not warn. It "conditionally" pops elements. + while !numbers.is_empty() { + if true { + accept_i32(numbers.pop().unwrap()); + } + } + + // This should also not warn. It conditionally pops elements. + while !numbers.is_empty() { + if false { + continue; + } + accept_i32(numbers.pop().unwrap()); + } +} diff --git a/tests/ui/while_pop_unwrap.stderr b/tests/ui/while_pop_unwrap.stderr new file mode 100644 index 000000000000..8dc77db8b5a8 --- /dev/null +++ b/tests/ui/while_pop_unwrap.stderr @@ -0,0 +1,43 @@ +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:14:22 + | +LL | while !numbers.is_empty() { + | ------------------ help: try: `while let Some(number) = numbers.pop()` +LL | let number = numbers.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this while loop can be written in a more idiomatic way + = note: `-D clippy::while-pop-unwrap` implied by `-D warnings` + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:22:22 + | +LL | while !val.numbers.is_empty() { + | ---------------------- help: try: `while let Some(number) = val.numbers.pop()` +LL | let number = val.numbers.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this while loop can be written in a more idiomatic way + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:26:20 + | +LL | while !numbers.is_empty() { + | ------------------ help: try: `while let Some(element) = numbers.pop()` +LL | accept_i32(numbers.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this while loop can be written in a more idiomatic way + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:30:20 + | +LL | while !numbers.is_empty() { + | ------------------ help: try: `while let Some(element) = numbers.pop()` +LL | accept_i32(numbers.pop().expect("")); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this while loop can be written in a more idiomatic way + +error: aborting due to 4 previous errors + From bcdcc34ba95cd27cab6da68fcac820ca6968d3e2 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 15 Apr 2023 23:01:53 +0200 Subject: [PATCH 2/7] elide lifetimes, get rid of glob import --- clippy_lints/src/while_pop_unwrap.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/while_pop_unwrap.rs b/clippy_lints/src/while_pop_unwrap.rs index fc77febad6b6..0a3f08888a3b 100644 --- a/clippy_lints/src/while_pop_unwrap.rs +++ b/clippy_lints/src/while_pop_unwrap.rs @@ -1,6 +1,6 @@ use clippy_utils::{diagnostics::span_lint_and_then, match_def_path, paths, source::snippet}; use rustc_errors::Applicability; -use rustc_hir::*; +use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind, UnOp}; use rustc_lint::LateContext; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{symbol::Ident, Span}; @@ -36,13 +36,7 @@ declare_clippy_lint! { } declare_lint_pass!(WhilePopUnwrap => [WHILE_POP_UNWRAP]); -fn report_lint<'tcx>( - cx: &LateContext<'tcx>, - pop_span: Span, - ident: Option, - loop_span: Span, - receiver_span: Span, -) { +fn report_lint(cx: &LateContext<'_>, pop_span: Span, ident: Option, loop_span: Span, receiver_span: Span) { span_lint_and_then( cx, WHILE_POP_UNWRAP, @@ -54,7 +48,7 @@ fn report_lint<'tcx>( "try", format!( "while let Some({}) = {}.pop()", - ident.as_ref().map(Ident::as_str).unwrap_or("element"), + ident.as_ref().map_or("element", Ident::as_str), snippet(cx, receiver_span, "..") ), Applicability::MaybeIncorrect, @@ -75,11 +69,11 @@ fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: &[&str]) -> } } -fn is_vec_pop<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { +fn is_vec_pop(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { match_method_call(cx, expr, &paths::VEC_POP) } -fn is_vec_pop_unwrap<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { +fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::MethodCall(_, inner, ..) = expr.kind && (match_method_call(cx, expr, &paths::OPTION_UNWRAP) || match_method_call(cx, expr, &paths::OPTION_EXPECT)) && is_vec_pop(cx, inner) @@ -90,7 +84,7 @@ fn is_vec_pop_unwrap<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool { } } -fn check_local<'tcx>(cx: &LateContext<'tcx>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { +fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { if let StmtKind::Local(local) = stmt.kind && let PatKind::Binding(.., ident, _) = local.pat.kind && let Some(init) = local.init @@ -101,10 +95,12 @@ fn check_local<'tcx>(cx: &LateContext<'tcx>, stmt: &Stmt<'_>, loop_span: Span, r } } -fn check_call_arguments<'tcx>(cx: &LateContext<'tcx>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { +fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind { if let ExprKind::MethodCall(_, _, args, _) | ExprKind::Call(_, args) = expr.kind { - let offending_arg = args.iter().find_map(|arg| is_vec_pop_unwrap(cx, arg).then(|| arg.span)); + let offending_arg = args + .iter() + .find_map(|arg| is_vec_pop_unwrap(cx, arg).then_some(arg.span)); if let Some(offending_arg) = offending_arg { report_lint(cx, offending_arg, None, loop_span, recv_span); From 1d08325293f0dcccd29700d6b14755f8fbd5f076 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 18 Apr 2023 00:14:56 +0200 Subject: [PATCH 3/7] move lint to loops, emit proper suggestion, more tests --- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 1 - clippy_lints/src/loops/mod.rs | 38 ++++++- clippy_lints/src/loops/while_pop_unwrap.rs | 109 ++++++++++++++++++ clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/while_pop_unwrap.rs | 122 --------------------- clippy_utils/src/higher.rs | 6 +- tests/ui/while_pop_unwrap.fixed | 93 ++++++++++++++++ tests/ui/while_pop_unwrap.rs | 46 ++++++++ tests/ui/while_pop_unwrap.stderr | 82 ++++++++++---- 10 files changed, 351 insertions(+), 150 deletions(-) create mode 100644 clippy_lints/src/loops/while_pop_unwrap.rs delete mode 100644 clippy_lints/src/while_pop_unwrap.rs create mode 100644 tests/ui/while_pop_unwrap.fixed diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ddc4b139670c..ff418092e4bf 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -258,6 +258,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::loops::WHILE_IMMUTABLE_CONDITION_INFO, crate::loops::WHILE_LET_LOOP_INFO, crate::loops::WHILE_LET_ON_ITERATOR_INFO, + crate::loops::WHILE_POP_UNWRAP_INFO, crate::macro_use::MACRO_USE_IMPORTS_INFO, crate::main_recursion::MAIN_RECURSION_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, @@ -645,7 +646,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::useless_conversion::USELESS_CONVERSION_INFO, crate::vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, - crate::while_pop_unwrap::WHILE_POP_UNWRAP_INFO, crate::wildcard_imports::ENUM_GLOB_USE_INFO, crate::wildcard_imports::WILDCARD_IMPORTS_INFO, crate::write::PRINTLN_EMPTY_STRING_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0d594612b796..48dbecc9f6aa 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -324,7 +324,6 @@ mod use_self; mod useless_conversion; mod vec; mod vec_init_then_push; -mod while_pop_unwrap; mod wildcard_imports; mod write; mod zero_div_zero; diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 90012694062e..02197e9c187e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -17,6 +17,7 @@ mod utils; mod while_immutable_condition; mod while_let_loop; mod while_let_on_iterator; +mod while_pop_unwrap; use clippy_utils::higher; use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; @@ -25,8 +26,6 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor}; -use crate::while_pop_unwrap; - declare_clippy_lint! { /// ### What it does /// Checks for for-loops that manually copy items between @@ -577,6 +576,36 @@ declare_clippy_lint! { "manual implementation of `Iterator::find`" } +declare_clippy_lint! { + /// ### What it does + /// Looks for loops that check for emptiness of a `Vec` in the condition and pop an element + /// in the body as a separate operation. + /// + /// ### Why is this bad? + /// Such loops can be written in a more idiomatic way by using a while..let loop and directly + /// pattern matching on the return value of `Vec::pop()`. + /// + /// ### Example + /// ```rust + /// let mut numbers = vec![1, 2, 3, 4, 5]; + /// while !numbers.is_empty() { + /// let number = numbers.pop().unwrap(); + /// // use `number` + /// } + /// ``` + /// Use instead: + /// ```rust + /// let mut numbers = vec![1, 2, 3, 4, 5]; + /// while let Some(number) = numbers.pop() { + /// // use `number` + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub WHILE_POP_UNWRAP, + style, + "checking for emptiness of a `Vec` in the loop condition and popping an element in the body" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -596,6 +625,7 @@ declare_lint_pass!(Loops => [ SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, MANUAL_FIND, + WHILE_POP_UNWRAP ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -642,10 +672,10 @@ impl<'tcx> LateLintPass<'tcx> for Loops { while_let_on_iterator::check(cx, expr); - if let Some(higher::While { condition, body }) = higher::While::hir(expr) { + if let Some(higher::While { condition, body, span }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); missing_spin_loop::check(cx, condition, body); - while_pop_unwrap::check(cx, condition, body); + while_pop_unwrap::check(cx, condition, body, span); } } } diff --git a/clippy_lints/src/loops/while_pop_unwrap.rs b/clippy_lints/src/loops/while_pop_unwrap.rs new file mode 100644 index 000000000000..1e31184b34d6 --- /dev/null +++ b/clippy_lints/src/loops/while_pop_unwrap.rs @@ -0,0 +1,109 @@ +use clippy_utils::{ + diagnostics::{multispan_sugg_with_applicability, span_lint_and_then}, + match_def_path, paths, + source::snippet, + SpanlessEq, +}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Pat, Stmt, StmtKind, UnOp}; +use rustc_lint::LateContext; +use rustc_span::Span; +use std::borrow::Cow; + +use super::WHILE_POP_UNWRAP; + +/// The kind of statement that the `pop()` call appeared in. +/// +/// Depending on whether the value was assigned to a variable or not changes what pattern +/// we use for the suggestion. +enum PopStmt<'hir> { + /// `x.pop().unwrap()` was and assigned to a variable. + /// The pattern of this local variable will be used and the local statement + /// is deleted in the suggestion. + Local(&'hir Pat<'hir>), + /// `x.pop().unwrap()` appeared in an arbitrary expression and was not assigned to a variable. + /// The suggestion will use some placeholder identifier and the `x.pop().unwrap()` expression + /// is replaced with that identifier. + Anonymous, +} + +fn report_lint(cx: &LateContext<'_>, pop_span: Span, pop_stmt_kind: PopStmt<'_>, loop_span: Span, receiver_span: Span) { + span_lint_and_then( + cx, + WHILE_POP_UNWRAP, + pop_span, + "you seem to be trying to pop elements from a `Vec` in a loop", + |diag| { + let (pat, pop_replacement) = match &pop_stmt_kind { + PopStmt::Local(pat) => (snippet(cx, pat.span, ".."), String::new()), + PopStmt::Anonymous => (Cow::Borrowed("element"), "element".into()), + }; + + let loop_replacement = format!("while let Some({}) = {}.pop()", pat, snippet(cx, receiver_span, "..")); + multispan_sugg_with_applicability( + diag, + "consider using a `while..let` loop", + Applicability::MachineApplicable, + [(loop_span, loop_replacement), (pop_span, pop_replacement)], + ); + }, + ); +} + +fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: &[&str]) -> bool { + if let ExprKind::MethodCall(..) = expr.kind + && let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + { + match_def_path(cx, id, method) + } else { + false + } +} + +fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>, is_empty_recv: &Expr<'_>) -> bool { + if (match_method_call(cx, expr, &paths::OPTION_UNWRAP) || match_method_call(cx, expr, &paths::OPTION_EXPECT)) + && let ExprKind::MethodCall(_, unwrap_recv, ..) = expr.kind + && match_method_call(cx, unwrap_recv, &paths::VEC_POP) + && let ExprKind::MethodCall(_, pop_recv, ..) = unwrap_recv.kind + { + // make sure they're the same `Vec` + SpanlessEq::new(cx).eq_expr(pop_recv, is_empty_recv) + } else { + false + } +} + +fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, loop_span: Span) { + if let StmtKind::Local(local) = stmt.kind + && let Some(init) = local.init + && is_vec_pop_unwrap(cx, init, is_empty_recv) + { + report_lint(cx, stmt.span, PopStmt::Local(&local.pat), loop_span, is_empty_recv.span); + } +} + +fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, loop_span: Span) { + if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind { + if let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind { + let offending_arg = args + .iter() + .find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span)); + + if let Some(offending_arg) = offending_arg { + report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span); + } + } + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, full_cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>, loop_span: Span) { + if let ExprKind::Unary(UnOp::Not, cond) = full_cond.kind + && let ExprKind::MethodCall(_, is_empty_recv, _, _) = cond.kind + && match_method_call(cx, cond, &paths::VEC_IS_EMPTY) + && let ExprKind::Block(body, _) = body.kind + && let Some(stmt) = body.stmts.first() + { + check_local(cx, stmt, is_empty_recv, loop_span); + check_call_arguments(cx, stmt, is_empty_recv, loop_span); + } +} diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 1f632916c570..108077b9d158 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -333,7 +333,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { #[allow(clippy::too_many_lines)] fn expr(&self, expr: &Binding<&hir::Expr<'_>>) { - if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) { + if let Some(higher::While { condition, body, .. }) = higher::While::hir(expr.value) { bind!(self, condition, body); chain!( self, diff --git a/clippy_lints/src/while_pop_unwrap.rs b/clippy_lints/src/while_pop_unwrap.rs deleted file mode 100644 index 0a3f08888a3b..000000000000 --- a/clippy_lints/src/while_pop_unwrap.rs +++ /dev/null @@ -1,122 +0,0 @@ -use clippy_utils::{diagnostics::span_lint_and_then, match_def_path, paths, source::snippet}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind, UnOp}; -use rustc_lint::LateContext; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{symbol::Ident, Span}; - -declare_clippy_lint! { - /// ### What it does - /// Looks for loops that check for emptiness of a `Vec` in the condition and pop an element - /// in the body as a separate operation. - /// - /// ### Why is this bad? - /// Such loops can be written in a more idiomatic way by using a while..let loop and directly - /// pattern matching on the return value of `Vec::pop()`. - /// - /// ### Example - /// ```rust - /// let mut numbers = vec![1, 2, 3, 4, 5]; - /// while !numbers.is_empty() { - /// let number = numbers.pop().unwrap(); - /// // use `number` - /// } - /// ``` - /// Use instead: - /// ```rust - /// let mut numbers = vec![1, 2, 3, 4, 5]; - /// while let Some(number) = numbers.pop() { - /// // use `number` - /// } - /// ``` - #[clippy::version = "1.70.0"] - pub WHILE_POP_UNWRAP, - style, - "checking for emptiness of a `Vec` in the loop condition and popping an element in the body" -} -declare_lint_pass!(WhilePopUnwrap => [WHILE_POP_UNWRAP]); - -fn report_lint(cx: &LateContext<'_>, pop_span: Span, ident: Option, loop_span: Span, receiver_span: Span) { - span_lint_and_then( - cx, - WHILE_POP_UNWRAP, - pop_span, - "you seem to be trying to pop elements from a `Vec` in a loop", - |diag| { - diag.span_suggestion( - loop_span, - "try", - format!( - "while let Some({}) = {}.pop()", - ident.as_ref().map_or("element", Ident::as_str), - snippet(cx, receiver_span, "..") - ), - Applicability::MaybeIncorrect, - ) - .note("this while loop can be written in a more idiomatic way"); - }, - ); -} - -fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: &[&str]) -> bool { - if let ExprKind::MethodCall(..) = expr.kind - && let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && match_def_path(cx, id, method) - { - true - } else { - false - } -} - -fn is_vec_pop(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match_method_call(cx, expr, &paths::VEC_POP) -} - -fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::MethodCall(_, inner, ..) = expr.kind - && (match_method_call(cx, expr, &paths::OPTION_UNWRAP) || match_method_call(cx, expr, &paths::OPTION_EXPECT)) - && is_vec_pop(cx, inner) - { - true - } else { - false - } -} - -fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { - if let StmtKind::Local(local) = stmt.kind - && let PatKind::Binding(.., ident, _) = local.pat.kind - && let Some(init) = local.init - && let ExprKind::MethodCall(_, inner, ..) = init.kind - && is_vec_pop_unwrap(cx, init) - { - report_lint(cx, init.span.to(inner.span), Some(ident), loop_span, recv_span); - } -} - -fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, loop_span: Span, recv_span: Span) { - if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind { - if let ExprKind::MethodCall(_, _, args, _) | ExprKind::Call(_, args) = expr.kind { - let offending_arg = args - .iter() - .find_map(|arg| is_vec_pop_unwrap(cx, arg).then_some(arg.span)); - - if let Some(offending_arg) = offending_arg { - report_lint(cx, offending_arg, None, loop_span, recv_span); - } - } - } -} - -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { - if let ExprKind::Unary(UnOp::Not, cond) = cond.kind - && let ExprKind::MethodCall(_, Expr { span: recv_span, .. }, _, _) = cond.kind - && match_method_call(cx, cond, &paths::VEC_IS_EMPTY) - && let ExprKind::Block(body, _) = body.kind - && let Some(stmt) = body.stmts.first() - { - check_local(cx, stmt, cond.span, *recv_span); - check_call_arguments(cx, stmt, cond.span, *recv_span); - } -} diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 50bef3709309..a61e4c380886 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -311,6 +311,8 @@ pub struct While<'hir> { pub condition: &'hir Expr<'hir>, /// `while` loop body pub body: &'hir Expr<'hir>, + /// Span of the loop header + pub span: Span, } impl<'hir> While<'hir> { @@ -336,10 +338,10 @@ impl<'hir> While<'hir> { }, _, LoopSource::While, - _, + span, ) = expr.kind { - return Some(Self { condition, body }); + return Some(Self { condition, body, span }); } None } diff --git a/tests/ui/while_pop_unwrap.fixed b/tests/ui/while_pop_unwrap.fixed new file mode 100644 index 000000000000..a635e054e6ec --- /dev/null +++ b/tests/ui/while_pop_unwrap.fixed @@ -0,0 +1,93 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::while_pop_unwrap)] + +struct VecInStruct { + numbers: Vec, + unrelated: String, +} + +struct Foo { + a: i32, + b: i32, +} + +fn accept_i32(_: i32) {} +fn accept_optional_i32(_: Option) {} +fn accept_i32_tuple(_: (i32, i32)) {} + +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + while let Some(number) = numbers.pop() { + + } + + let mut val = VecInStruct { + numbers: vec![1, 2, 3, 4, 5], + unrelated: String::new(), + }; + while let Some(number) = val.numbers.pop() { + + } + + while let Some(element) = numbers.pop() { + accept_i32(element); + } + + while let Some(element) = numbers.pop() { + accept_i32(element); + } + + // This should not warn. It "conditionally" pops elements. + while !numbers.is_empty() { + if true { + accept_i32(numbers.pop().unwrap()); + } + } + + // This should also not warn. It conditionally pops elements. + while !numbers.is_empty() { + if false { + continue; + } + accept_i32(numbers.pop().unwrap()); + } + + // This should not warn. It pops elements, but does not unwrap it. + // Might handle the Option in some other arbitrary way. + while !numbers.is_empty() { + accept_optional_i32(numbers.pop()); + } + + let unrelated_vec: Vec = Vec::new(); + // This should not warn. It pops elements from a different vector. + while !unrelated_vec.is_empty() { + accept_i32(numbers.pop().unwrap()); + } + + macro_rules! generate_loop { + () => { + while !numbers.is_empty() { + accept_i32(numbers.pop().unwrap()); + } + }; + } + // Do not warn if the loop is in a macro. + generate_loop!(); + + // Try other kinds of patterns + let mut numbers = vec![(0, 0), (1, 1), (2, 2)]; + while let Some((a, b)) = numbers.pop() { + + } + + while let Some(element) = numbers.pop() { + accept_i32_tuple(element); + } + + let mut results = vec![Foo { a: 1, b: 2 }, Foo { a: 3, b: 4 }]; + while let Some(Foo { a, b }) = results.pop() { + + } +} diff --git a/tests/ui/while_pop_unwrap.rs b/tests/ui/while_pop_unwrap.rs index 1b6de8f1f89e..820ff211086c 100644 --- a/tests/ui/while_pop_unwrap.rs +++ b/tests/ui/while_pop_unwrap.rs @@ -1,3 +1,5 @@ +// run-rustfix + #![allow(unused)] #![warn(clippy::while_pop_unwrap)] @@ -6,7 +8,14 @@ struct VecInStruct { unrelated: String, } +struct Foo { + a: i32, + b: i32, +} + fn accept_i32(_: i32) {} +fn accept_optional_i32(_: Option) {} +fn accept_i32_tuple(_: (i32, i32)) {} fn main() { let mut numbers = vec![1, 2, 3, 4, 5]; @@ -44,4 +53,41 @@ fn main() { } accept_i32(numbers.pop().unwrap()); } + + // This should not warn. It pops elements, but does not unwrap it. + // Might handle the Option in some other arbitrary way. + while !numbers.is_empty() { + accept_optional_i32(numbers.pop()); + } + + let unrelated_vec: Vec = Vec::new(); + // This should not warn. It pops elements from a different vector. + while !unrelated_vec.is_empty() { + accept_i32(numbers.pop().unwrap()); + } + + macro_rules! generate_loop { + () => { + while !numbers.is_empty() { + accept_i32(numbers.pop().unwrap()); + } + }; + } + // Do not warn if the loop is in a macro. + generate_loop!(); + + // Try other kinds of patterns + let mut numbers = vec![(0, 0), (1, 1), (2, 2)]; + while !numbers.is_empty() { + let (a, b) = numbers.pop().unwrap(); + } + + while !numbers.is_empty() { + accept_i32_tuple(numbers.pop().unwrap()); + } + + let mut results = vec![Foo { a: 1, b: 2 }, Foo { a: 3, b: 4 }]; + while !results.is_empty() { + let Foo { a, b } = results.pop().unwrap(); + } } diff --git a/tests/ui/while_pop_unwrap.stderr b/tests/ui/while_pop_unwrap.stderr index 8dc77db8b5a8..f8a6cfcda0ea 100644 --- a/tests/ui/while_pop_unwrap.stderr +++ b/tests/ui/while_pop_unwrap.stderr @@ -1,43 +1,87 @@ error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:14:22 + --> $DIR/while_pop_unwrap.rs:23:9 | -LL | while !numbers.is_empty() { - | ------------------ help: try: `while let Some(number) = numbers.pop()` LL | let number = numbers.pop().unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this while loop can be written in a more idiomatic way = note: `-D clippy::while-pop-unwrap` implied by `-D warnings` +help: consider using a `while..let` loop + | +LL ~ while let Some(number) = numbers.pop() { +LL ~ + | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:22:22 + --> $DIR/while_pop_unwrap.rs:31:9 | -LL | while !val.numbers.is_empty() { - | ---------------------- help: try: `while let Some(number) = val.numbers.pop()` LL | let number = val.numbers.pop().unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using a `while..let` loop + | +LL ~ while let Some(number) = val.numbers.pop() { +LL ~ | - = note: this while loop can be written in a more idiomatic way error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:26:20 + --> $DIR/while_pop_unwrap.rs:35:20 | -LL | while !numbers.is_empty() { - | ------------------ help: try: `while let Some(element) = numbers.pop()` LL | accept_i32(numbers.pop().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: this while loop can be written in a more idiomatic way +help: consider using a `while..let` loop + | +LL ~ while let Some(element) = numbers.pop() { +LL ~ accept_i32(element); + | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:30:20 + --> $DIR/while_pop_unwrap.rs:39:20 | -LL | while !numbers.is_empty() { - | ------------------ help: try: `while let Some(element) = numbers.pop()` LL | accept_i32(numbers.pop().expect("")); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this while loop can be written in a more idiomatic way +help: consider using a `while..let` loop + | +LL ~ while let Some(element) = numbers.pop() { +LL ~ accept_i32(element); + | + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:82:9 + | +LL | let (a, b) = numbers.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using a `while..let` loop + | +LL ~ while let Some((a, b)) = numbers.pop() { +LL ~ + | + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:86:26 + | +LL | accept_i32_tuple(numbers.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using a `while..let` loop + | +LL ~ while let Some(element) = numbers.pop() { +LL ~ accept_i32_tuple(element); + | + +error: you seem to be trying to pop elements from a `Vec` in a loop + --> $DIR/while_pop_unwrap.rs:91:9 + | +LL | let Foo { a, b } = results.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using a `while..let` loop + | +LL ~ while let Some(Foo { a, b }) = results.pop() { +LL ~ + | -error: aborting due to 4 previous errors +error: aborting due to 7 previous errors From ab9b7a5ad23bd2cb2097719ad9439277b8f5d987 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 18 Apr 2023 00:25:39 +0200 Subject: [PATCH 4/7] remove unnecessary reference --- clippy_lints/src/loops/while_pop_unwrap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/loops/while_pop_unwrap.rs b/clippy_lints/src/loops/while_pop_unwrap.rs index 1e31184b34d6..fc6797d72c0d 100644 --- a/clippy_lints/src/loops/while_pop_unwrap.rs +++ b/clippy_lints/src/loops/while_pop_unwrap.rs @@ -34,7 +34,7 @@ fn report_lint(cx: &LateContext<'_>, pop_span: Span, pop_stmt_kind: PopStmt<'_>, pop_span, "you seem to be trying to pop elements from a `Vec` in a loop", |diag| { - let (pat, pop_replacement) = match &pop_stmt_kind { + let (pat, pop_replacement) = match pop_stmt_kind { PopStmt::Local(pat) => (snippet(cx, pat.span, ".."), String::new()), PopStmt::Anonymous => (Cow::Borrowed("element"), "element".into()), }; @@ -78,7 +78,7 @@ fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, && let Some(init) = local.init && is_vec_pop_unwrap(cx, init, is_empty_recv) { - report_lint(cx, stmt.span, PopStmt::Local(&local.pat), loop_span, is_empty_recv.span); + report_lint(cx, stmt.span, PopStmt::Local(local.pat), loop_span, is_empty_recv.span); } } From f10e39fd2be243b7f0d4f82f56420ba1cbd4a7bb Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 18 Apr 2023 00:40:03 +0200 Subject: [PATCH 5/7] make PopStmt copy+clone --- clippy_lints/src/loops/while_pop_unwrap.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_lints/src/loops/while_pop_unwrap.rs b/clippy_lints/src/loops/while_pop_unwrap.rs index fc6797d72c0d..3a583396230c 100644 --- a/clippy_lints/src/loops/while_pop_unwrap.rs +++ b/clippy_lints/src/loops/while_pop_unwrap.rs @@ -16,6 +16,7 @@ use super::WHILE_POP_UNWRAP; /// /// Depending on whether the value was assigned to a variable or not changes what pattern /// we use for the suggestion. +#[derive(Copy, Clone)] enum PopStmt<'hir> { /// `x.pop().unwrap()` was and assigned to a variable. /// The pattern of this local variable will be used and the local statement From 8d8178f93184d09cf1e3f813257d6aeb19d08c57 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:26:50 +0200 Subject: [PATCH 6/7] rename lint to `manual_while_let_some` --- CHANGELOG.md | 2 +- clippy_lints/src/declared_lints.rs | 2 +- ...le_pop_unwrap.rs => manual_while_let_some.rs} | 4 ++-- clippy_lints/src/loops/mod.rs | 10 +++++----- ..._unwrap.fixed => manual_while_let_some.fixed} | 4 ++-- ...le_pop_unwrap.rs => manual_while_let_some.rs} | 4 ++-- ...nwrap.stderr => manual_while_let_some.stderr} | 16 ++++++++-------- 7 files changed, 21 insertions(+), 21 deletions(-) rename clippy_lints/src/loops/{while_pop_unwrap.rs => manual_while_let_some.rs} (98%) rename tests/ui/{while_pop_unwrap.fixed => manual_while_let_some.fixed} (95%) rename tests/ui/{while_pop_unwrap.rs => manual_while_let_some.rs} (95%) rename tests/ui/{while_pop_unwrap.stderr => manual_while_let_some.stderr} (85%) diff --git a/CHANGELOG.md b/CHANGELOG.md index da03f5c012b9..d1e98a93940d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4797,6 +4797,7 @@ Released 2018-09-13 [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap [`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or +[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some [`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names [`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone [`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit @@ -5160,7 +5161,6 @@ Released 2018-09-13 [`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition [`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop [`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator -[`while_pop_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_pop_unwrap [`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies [`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm [`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ff418092e4bf..44e725be710e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -249,6 +249,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::loops::MANUAL_FIND_INFO, crate::loops::MANUAL_FLATTEN_INFO, crate::loops::MANUAL_MEMCPY_INFO, + crate::loops::MANUAL_WHILE_LET_SOME_INFO, crate::loops::MISSING_SPIN_LOOP_INFO, crate::loops::MUT_RANGE_BOUND_INFO, crate::loops::NEEDLESS_RANGE_LOOP_INFO, @@ -258,7 +259,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::loops::WHILE_IMMUTABLE_CONDITION_INFO, crate::loops::WHILE_LET_LOOP_INFO, crate::loops::WHILE_LET_ON_ITERATOR_INFO, - crate::loops::WHILE_POP_UNWRAP_INFO, crate::macro_use::MACRO_USE_IMPORTS_INFO, crate::main_recursion::MAIN_RECURSION_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, diff --git a/clippy_lints/src/loops/while_pop_unwrap.rs b/clippy_lints/src/loops/manual_while_let_some.rs similarity index 98% rename from clippy_lints/src/loops/while_pop_unwrap.rs rename to clippy_lints/src/loops/manual_while_let_some.rs index 3a583396230c..cb9c84be4c7a 100644 --- a/clippy_lints/src/loops/while_pop_unwrap.rs +++ b/clippy_lints/src/loops/manual_while_let_some.rs @@ -10,7 +10,7 @@ use rustc_lint::LateContext; use rustc_span::Span; use std::borrow::Cow; -use super::WHILE_POP_UNWRAP; +use super::MANUAL_WHILE_LET_SOME; /// The kind of statement that the `pop()` call appeared in. /// @@ -31,7 +31,7 @@ enum PopStmt<'hir> { fn report_lint(cx: &LateContext<'_>, pop_span: Span, pop_stmt_kind: PopStmt<'_>, loop_span: Span, receiver_span: Span) { span_lint_and_then( cx, - WHILE_POP_UNWRAP, + MANUAL_WHILE_LET_SOME, pop_span, "you seem to be trying to pop elements from a `Vec` in a loop", |diag| { diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 02197e9c187e..f83ad388a742 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -7,6 +7,7 @@ mod iter_next_loop; mod manual_find; mod manual_flatten; mod manual_memcpy; +mod manual_while_let_some; mod missing_spin_loop; mod mut_range_bound; mod needless_range_loop; @@ -17,7 +18,6 @@ mod utils; mod while_immutable_condition; mod while_let_loop; mod while_let_on_iterator; -mod while_pop_unwrap; use clippy_utils::higher; use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; @@ -582,7 +582,7 @@ declare_clippy_lint! { /// in the body as a separate operation. /// /// ### Why is this bad? - /// Such loops can be written in a more idiomatic way by using a while..let loop and directly + /// Such loops can be written in a more idiomatic way by using a while-let loop and directly /// pattern matching on the return value of `Vec::pop()`. /// /// ### Example @@ -601,7 +601,7 @@ declare_clippy_lint! { /// } /// ``` #[clippy::version = "1.70.0"] - pub WHILE_POP_UNWRAP, + pub MANUAL_WHILE_LET_SOME, style, "checking for emptiness of a `Vec` in the loop condition and popping an element in the body" } @@ -625,7 +625,7 @@ declare_lint_pass!(Loops => [ SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, MANUAL_FIND, - WHILE_POP_UNWRAP + MANUAL_WHILE_LET_SOME ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -675,7 +675,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let Some(higher::While { condition, body, span }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); missing_spin_loop::check(cx, condition, body); - while_pop_unwrap::check(cx, condition, body, span); + manual_while_let_some::check(cx, condition, body, span); } } } diff --git a/tests/ui/while_pop_unwrap.fixed b/tests/ui/manual_while_let_some.fixed similarity index 95% rename from tests/ui/while_pop_unwrap.fixed rename to tests/ui/manual_while_let_some.fixed index a635e054e6ec..75738209bad1 100644 --- a/tests/ui/while_pop_unwrap.fixed +++ b/tests/ui/manual_while_let_some.fixed @@ -1,7 +1,7 @@ // run-rustfix #![allow(unused)] -#![warn(clippy::while_pop_unwrap)] +#![warn(clippy::manual_while_let_some)] struct VecInStruct { numbers: Vec, @@ -73,7 +73,7 @@ fn main() { } }; } - // Do not warn if the loop is in a macro. + // Do not warn if the loop comes from a macro. generate_loop!(); // Try other kinds of patterns diff --git a/tests/ui/while_pop_unwrap.rs b/tests/ui/manual_while_let_some.rs similarity index 95% rename from tests/ui/while_pop_unwrap.rs rename to tests/ui/manual_while_let_some.rs index 820ff211086c..142c14715983 100644 --- a/tests/ui/while_pop_unwrap.rs +++ b/tests/ui/manual_while_let_some.rs @@ -1,7 +1,7 @@ // run-rustfix #![allow(unused)] -#![warn(clippy::while_pop_unwrap)] +#![warn(clippy::manual_while_let_some)] struct VecInStruct { numbers: Vec, @@ -73,7 +73,7 @@ fn main() { } }; } - // Do not warn if the loop is in a macro. + // Do not warn if the loop comes from a macro. generate_loop!(); // Try other kinds of patterns diff --git a/tests/ui/while_pop_unwrap.stderr b/tests/ui/manual_while_let_some.stderr similarity index 85% rename from tests/ui/while_pop_unwrap.stderr rename to tests/ui/manual_while_let_some.stderr index f8a6cfcda0ea..633fe05c49b8 100644 --- a/tests/ui/while_pop_unwrap.stderr +++ b/tests/ui/manual_while_let_some.stderr @@ -1,10 +1,10 @@ error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:23:9 + --> $DIR/manual_while_let_some.rs:23:9 | LL | let number = numbers.pop().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `-D clippy::while-pop-unwrap` implied by `-D warnings` + = note: `-D clippy::manual-while-let-some` implied by `-D warnings` help: consider using a `while..let` loop | LL ~ while let Some(number) = numbers.pop() { @@ -12,7 +12,7 @@ LL ~ | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:31:9 + --> $DIR/manual_while_let_some.rs:31:9 | LL | let number = val.numbers.pop().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL ~ | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:35:20 + --> $DIR/manual_while_let_some.rs:35:20 | LL | accept_i32(numbers.pop().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^ @@ -36,7 +36,7 @@ LL ~ accept_i32(element); | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:39:20 + --> $DIR/manual_while_let_some.rs:39:20 | LL | accept_i32(numbers.pop().expect("")); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL ~ accept_i32(element); | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:82:9 + --> $DIR/manual_while_let_some.rs:82:9 | LL | let (a, b) = numbers.pop().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL ~ | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:86:26 + --> $DIR/manual_while_let_some.rs:86:26 | LL | accept_i32_tuple(numbers.pop().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL ~ accept_i32_tuple(element); | error: you seem to be trying to pop elements from a `Vec` in a loop - --> $DIR/while_pop_unwrap.rs:91:9 + --> $DIR/manual_while_let_some.rs:91:9 | LL | let Foo { a, b } = results.pop().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 9613ea85c65c8c4f75b443e061739c82f75e317d Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:10:52 +0200 Subject: [PATCH 7/7] fix run-rustfix directive --- tests/ui/manual_while_let_some.fixed | 2 +- tests/ui/manual_while_let_some.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/manual_while_let_some.fixed b/tests/ui/manual_while_let_some.fixed index 75738209bad1..8b610919536c 100644 --- a/tests/ui/manual_while_let_some.fixed +++ b/tests/ui/manual_while_let_some.fixed @@ -1,4 +1,4 @@ -// run-rustfix +//@run-rustfix #![allow(unused)] #![warn(clippy::manual_while_let_some)] diff --git a/tests/ui/manual_while_let_some.rs b/tests/ui/manual_while_let_some.rs index 142c14715983..85a0a084a424 100644 --- a/tests/ui/manual_while_let_some.rs +++ b/tests/ui/manual_while_let_some.rs @@ -1,4 +1,4 @@ -// run-rustfix +//@run-rustfix #![allow(unused)] #![warn(clippy::manual_while_let_some)]