feat: introduce path_to_local_with_projections (#15396)
As suggested in https://github.com/rust-lang/rust-clippy/pull/15268#discussion_r2249249661 WIP because: - [x] what should be done with the now-error-pattern-less `tests/ui/double_ended_iterator_last_unfixable.rs`? - [x] is the change in behaviour of `double_ended_iterator_last` okay in general? - cc @samueltardieu because this changes the code you added in https://github.com/rust-lang/rust-clippy/pull/14140 changelog: none r? @y21
This commit is contained in:
commit
14dfc03597
9 changed files with 60 additions and 84 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait};
|
||||
use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym};
|
||||
use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Node, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -37,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
|
|||
// TODO: Change this to lint only when the referred iterator is not used later. If it is used later,
|
||||
// changing to `next_back()` may change its behavior.
|
||||
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
|
||||
if let Some(hir_id) = path_to_local(self_expr)
|
||||
if let Some(hir_id) = path_to_local_with_projections(self_expr)
|
||||
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
|
||||
&& let PatKind::Binding(_, _, ident, _) = pat.kind
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::path_to_local_with_projections;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_ast::{BindingMode, Mutability};
|
||||
|
|
@ -9,21 +10,6 @@ use rustc_span::sym;
|
|||
|
||||
use super::FILTER_NEXT;
|
||||
|
||||
fn path_to_local(expr: &hir::Expr<'_>) -> Option<hir::HirId> {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Field(f, _) => path_to_local(f),
|
||||
hir::ExprKind::Index(recv, _, _) => path_to_local(recv),
|
||||
hir::ExprKind::Path(hir::QPath::Resolved(
|
||||
_,
|
||||
hir::Path {
|
||||
res: rustc_hir::def::Res::Local(local),
|
||||
..
|
||||
},
|
||||
)) => Some(*local),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// lint use of `filter().next()` for `Iterators`
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
|
|
@ -44,7 +30,7 @@ pub(super) fn check<'tcx>(
|
|||
let iter_snippet = snippet(cx, recv.span, "..");
|
||||
// add note if not multi-line
|
||||
span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
|
||||
let (applicability, pat) = if let Some(id) = path_to_local(recv)
|
||||
let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
|
||||
&& let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
|
||||
&& let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{eq_expr_value, path_to_local, sym};
|
||||
use clippy_utils::{eq_expr_value, path_to_local_with_projections, sym};
|
||||
use rustc_abi::WrappingRange;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
|
|
@ -63,11 +63,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
|
|||
/// Checks if an expression is a path to a local variable (with optional projections), e.g.
|
||||
/// `x.field[0].field2` would return true.
|
||||
fn is_local_with_projections(expr: &Expr<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Path(_) => path_to_local(expr).is_some(),
|
||||
ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr),
|
||||
_ => false,
|
||||
}
|
||||
path_to_local_with_projections(expr).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
|
|
|
|||
|
|
@ -460,6 +460,23 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
|
|||
path_to_local(expr) == Some(id)
|
||||
}
|
||||
|
||||
/// If the expression is a path to a local (with optional projections),
|
||||
/// returns the canonical `HirId` of the local.
|
||||
///
|
||||
/// For example, `x.field[0].field2` would return the `HirId` of `x`.
|
||||
pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
|
||||
match expr.kind {
|
||||
ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
|
||||
ExprKind::Path(QPath::Resolved(
|
||||
_,
|
||||
Path {
|
||||
res: Res::Local(local), ..
|
||||
},
|
||||
)) => Some(*local),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MaybePath<'hir> {
|
||||
fn hir_id(&self) -> HirId;
|
||||
fn qpath_opt(&self) -> Option<&QPath<'hir>>;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ fn issue_14139() {
|
|||
let (subindex, _) = (index.by_ref().take(3), 42);
|
||||
let _ = subindex.last();
|
||||
let _ = index.next();
|
||||
|
||||
let mut index = [true, true, false, false, false, true].iter();
|
||||
let subindex = (index.by_ref().take(3), 42);
|
||||
let _ = subindex.0.last();
|
||||
let _ = index.next();
|
||||
}
|
||||
|
||||
fn drop_order() {
|
||||
|
|
@ -108,6 +113,12 @@ fn drop_order() {
|
|||
let mut v = DropDeIterator(v.into_iter());
|
||||
println!("Last element is {}", v.next_back().unwrap().0);
|
||||
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
|
||||
|
||||
let v = vec![S("four"), S("five"), S("six")];
|
||||
let mut v = (DropDeIterator(v.into_iter()), 42);
|
||||
println!("Last element is {}", v.0.next_back().unwrap().0);
|
||||
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
|
||||
|
||||
println!("Done");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ fn issue_14139() {
|
|||
let (subindex, _) = (index.by_ref().take(3), 42);
|
||||
let _ = subindex.last();
|
||||
let _ = index.next();
|
||||
|
||||
let mut index = [true, true, false, false, false, true].iter();
|
||||
let subindex = (index.by_ref().take(3), 42);
|
||||
let _ = subindex.0.last();
|
||||
let _ = index.next();
|
||||
}
|
||||
|
||||
fn drop_order() {
|
||||
|
|
@ -108,6 +113,12 @@ fn drop_order() {
|
|||
let v = DropDeIterator(v.into_iter());
|
||||
println!("Last element is {}", v.last().unwrap().0);
|
||||
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
|
||||
|
||||
let v = vec![S("four"), S("five"), S("six")];
|
||||
let v = (DropDeIterator(v.into_iter()), 42);
|
||||
println!("Last element is {}", v.0.last().unwrap().0);
|
||||
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
|
||||
|
||||
println!("Done");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ LL | let _ = DeIterator.last();
|
|||
| help: try: `next_back()`
|
||||
|
||||
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
|
||||
--> tests/ui/double_ended_iterator_last.rs:109:36
|
||||
--> tests/ui/double_ended_iterator_last.rs:114:36
|
||||
|
|
||||
LL | println!("Last element is {}", v.last().unwrap().0);
|
||||
| ^^^^^^^^
|
||||
|
|
@ -30,5 +30,18 @@ LL ~ let mut v = DropDeIterator(v.into_iter());
|
|||
LL ~ println!("Last element is {}", v.next_back().unwrap().0);
|
||||
|
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
|
||||
--> tests/ui/double_ended_iterator_last.rs:119:36
|
||||
|
|
||||
LL | println!("Last element is {}", v.0.last().unwrap().0);
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this change will alter drop order which may be undesirable
|
||||
help: try
|
||||
|
|
||||
LL ~ let mut v = (DropDeIterator(v.into_iter()), 42);
|
||||
LL ~ println!("Last element is {}", v.0.next_back().unwrap().0);
|
||||
|
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
//@no-rustfix: requires manual changes
|
||||
#![warn(clippy::double_ended_iterator_last)]
|
||||
|
||||
// Should not be linted because applying the lint would move the original iterator. This can only be
|
||||
// linted if the iterator is used thereafter.
|
||||
fn main() {
|
||||
let mut index = [true, true, false, false, false, true].iter();
|
||||
let subindex = (index.by_ref().take(3), 42);
|
||||
let _ = subindex.0.last();
|
||||
let _ = index.next();
|
||||
}
|
||||
|
||||
fn drop_order() {
|
||||
struct DropDeIterator(std::vec::IntoIter<S>);
|
||||
impl Iterator for DropDeIterator {
|
||||
type Item = S;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
impl DoubleEndedIterator for DropDeIterator {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.0.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
struct S(&'static str);
|
||||
impl std::ops::Drop for S {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropping {}", self.0);
|
||||
}
|
||||
}
|
||||
|
||||
let v = vec![S("one"), S("two"), S("three")];
|
||||
let v = (DropDeIterator(v.into_iter()), 42);
|
||||
println!("Last element is {}", v.0.last().unwrap().0);
|
||||
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
|
||||
println!("Done");
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
|
||||
--> tests/ui/double_ended_iterator_last_unfixable.rs:36:36
|
||||
|
|
||||
LL | println!("Last element is {}", v.0.last().unwrap().0);
|
||||
| ^^^^------
|
||||
| |
|
||||
| help: try: `next_back()`
|
||||
|
|
||||
= note: this change will alter drop order which may be undesirable
|
||||
note: this must be made mutable to use `.next_back()`
|
||||
--> tests/ui/double_ended_iterator_last_unfixable.rs:36:36
|
||||
|
|
||||
LL | println!("Last element is {}", v.0.last().unwrap().0);
|
||||
| ^^^
|
||||
= note: `-D clippy::double-ended-iterator-last` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue