Skip to content

Commit 14dfc03

Browse files
authored
feat: introduce path_to_local_with_projections (#15396)
As suggested in #15268 (comment) 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 #14140 changelog: none r? @y21
2 parents e6b63d1 + 04606e2 commit 14dfc03

9 files changed

+60
-84
lines changed

clippy_lints/src/methods/double_ended_iterator_last.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait};
3-
use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym};
3+
use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym};
44
use rustc_errors::Applicability;
55
use rustc_hir::{Expr, Node, PatKind};
66
use rustc_lint::LateContext;
@@ -37,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
3737
// TODO: Change this to lint only when the referred iterator is not used later. If it is used later,
3838
// changing to `next_back()` may change its behavior.
3939
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
40-
if let Some(hir_id) = path_to_local(self_expr)
40+
if let Some(hir_id) = path_to_local_with_projections(self_expr)
4141
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
4242
&& let PatKind::Binding(_, _, ident, _) = pat.kind
4343
{

clippy_lints/src/methods/filter_next.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
2+
use clippy_utils::path_to_local_with_projections;
23
use clippy_utils::source::snippet;
34
use clippy_utils::ty::implements_trait;
45
use rustc_ast::{BindingMode, Mutability};
@@ -9,21 +10,6 @@ use rustc_span::sym;
910

1011
use super::FILTER_NEXT;
1112

12-
fn path_to_local(expr: &hir::Expr<'_>) -> Option<hir::HirId> {
13-
match expr.kind {
14-
hir::ExprKind::Field(f, _) => path_to_local(f),
15-
hir::ExprKind::Index(recv, _, _) => path_to_local(recv),
16-
hir::ExprKind::Path(hir::QPath::Resolved(
17-
_,
18-
hir::Path {
19-
res: rustc_hir::def::Res::Local(local),
20-
..
21-
},
22-
)) => Some(*local),
23-
_ => None,
24-
}
25-
}
26-
2713
/// lint use of `filter().next()` for `Iterators`
2814
pub(super) fn check<'tcx>(
2915
cx: &LateContext<'tcx>,
@@ -44,7 +30,7 @@ pub(super) fn check<'tcx>(
4430
let iter_snippet = snippet(cx, recv.span, "..");
4531
// add note if not multi-line
4632
span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
47-
let (applicability, pat) = if let Some(id) = path_to_local(recv)
33+
let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
4834
&& let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
4935
&& let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
5036
{

clippy_lints/src/transmute/eager_transmute.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::{eq_expr_value, path_to_local, sym};
2+
use clippy_utils::{eq_expr_value, path_to_local_with_projections, sym};
33
use rustc_abi::WrappingRange;
44
use rustc_errors::Applicability;
55
use rustc_hir::{Expr, ExprKind, Node};
@@ -63,11 +63,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
6363
/// Checks if an expression is a path to a local variable (with optional projections), e.g.
6464
/// `x.field[0].field2` would return true.
6565
fn is_local_with_projections(expr: &Expr<'_>) -> bool {
66-
match expr.kind {
67-
ExprKind::Path(_) => path_to_local(expr).is_some(),
68-
ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr),
69-
_ => false,
70-
}
66+
path_to_local_with_projections(expr).is_some()
7167
}
7268

7369
pub(super) fn check<'tcx>(

clippy_utils/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
460460
path_to_local(expr) == Some(id)
461461
}
462462

463+
/// If the expression is a path to a local (with optional projections),
464+
/// returns the canonical `HirId` of the local.
465+
///
466+
/// For example, `x.field[0].field2` would return the `HirId` of `x`.
467+
pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
468+
match expr.kind {
469+
ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
470+
ExprKind::Path(QPath::Resolved(
471+
_,
472+
Path {
473+
res: Res::Local(local), ..
474+
},
475+
)) => Some(*local),
476+
_ => None,
477+
}
478+
}
479+
463480
pub trait MaybePath<'hir> {
464481
fn hir_id(&self) -> HirId;
465482
fn qpath_opt(&self) -> Option<&QPath<'hir>>;

tests/ui/double_ended_iterator_last.fixed

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ fn issue_14139() {
8181
let (subindex, _) = (index.by_ref().take(3), 42);
8282
let _ = subindex.last();
8383
let _ = index.next();
84+
85+
let mut index = [true, true, false, false, false, true].iter();
86+
let subindex = (index.by_ref().take(3), 42);
87+
let _ = subindex.0.last();
88+
let _ = index.next();
8489
}
8590

8691
fn drop_order() {
@@ -108,6 +113,12 @@ fn drop_order() {
108113
let mut v = DropDeIterator(v.into_iter());
109114
println!("Last element is {}", v.next_back().unwrap().0);
110115
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
116+
117+
let v = vec![S("four"), S("five"), S("six")];
118+
let mut v = (DropDeIterator(v.into_iter()), 42);
119+
println!("Last element is {}", v.0.next_back().unwrap().0);
120+
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
121+
111122
println!("Done");
112123
}
113124

tests/ui/double_ended_iterator_last.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ fn issue_14139() {
8181
let (subindex, _) = (index.by_ref().take(3), 42);
8282
let _ = subindex.last();
8383
let _ = index.next();
84+
85+
let mut index = [true, true, false, false, false, true].iter();
86+
let subindex = (index.by_ref().take(3), 42);
87+
let _ = subindex.0.last();
88+
let _ = index.next();
8489
}
8590

8691
fn drop_order() {
@@ -108,6 +113,12 @@ fn drop_order() {
108113
let v = DropDeIterator(v.into_iter());
109114
println!("Last element is {}", v.last().unwrap().0);
110115
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
116+
117+
let v = vec![S("four"), S("five"), S("six")];
118+
let v = (DropDeIterator(v.into_iter()), 42);
119+
println!("Last element is {}", v.0.last().unwrap().0);
120+
//~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator`
121+
111122
println!("Done");
112123
}
113124

tests/ui/double_ended_iterator_last.stderr

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ LL | let _ = DeIterator.last();
1818
| help: try: `next_back()`
1919

2020
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
21-
--> tests/ui/double_ended_iterator_last.rs:109:36
21+
--> tests/ui/double_ended_iterator_last.rs:114:36
2222
|
2323
LL | println!("Last element is {}", v.last().unwrap().0);
2424
| ^^^^^^^^
@@ -30,5 +30,18 @@ LL ~ let mut v = DropDeIterator(v.into_iter());
3030
LL ~ println!("Last element is {}", v.next_back().unwrap().0);
3131
|
3232

33-
error: aborting due to 3 previous errors
33+
error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
34+
--> tests/ui/double_ended_iterator_last.rs:119:36
35+
|
36+
LL | println!("Last element is {}", v.0.last().unwrap().0);
37+
| ^^^^^^^^^^
38+
|
39+
= note: this change will alter drop order which may be undesirable
40+
help: try
41+
|
42+
LL ~ let mut v = (DropDeIterator(v.into_iter()), 42);
43+
LL ~ println!("Last element is {}", v.0.next_back().unwrap().0);
44+
|
45+
46+
error: aborting due to 4 previous errors
3447

tests/ui/double_ended_iterator_last_unfixable.rs

Lines changed: 0 additions & 39 deletions
This file was deleted.

tests/ui/double_ended_iterator_last_unfixable.stderr

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)