Skip to content

Commit cdc4c69

Browse files
committedNov 15, 2023
Make expr_use_ctxt always return Some unless the syntax context changes.
1 parent b587871 commit cdc4c69

7 files changed

+112
-122
lines changed
 

‎clippy_utils/src/lib.rs

+72-104
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![feature(array_chunks)]
22
#![feature(box_patterns)]
3+
#![feature(control_flow_enum)]
34
#![feature(if_let_guard)]
45
#![feature(let_chains)]
56
#![feature(lint_reasons)]
@@ -2508,26 +2509,30 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
25082509
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
25092510
}
25102511

2511-
/// Walks the HIR tree from the given expression, up to the node where the value produced by the
2512-
/// expression is consumed. Calls the function for every node encountered this way until it returns
2513-
/// `Some`.
2512+
/// Walks up the HIR tree from the given expression in an attempt to find where the value is
2513+
/// consumed.
25142514
///
2515-
/// This allows walking through `if`, `match`, `break`, block expressions to find where the value
2516-
/// produced by the expression is consumed.
2515+
/// Termination has three conditions:
2516+
/// - The given function returns `Break`. This function will return the value.
2517+
/// - The consuming node is found. This function will return `Continue(use_node, child_id)`.
2518+
/// - No further parent nodes are found. This will trigger a debug assert or return `None`.
2519+
///
2520+
/// This allows walking through `if`, `match`, `break`, and block expressions to find where the
2521+
/// value produced by the expression is consumed.
25172522
pub fn walk_to_expr_usage<'tcx, T>(
25182523
cx: &LateContext<'tcx>,
25192524
e: &Expr<'tcx>,
2520-
mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>,
2521-
) -> Option<T> {
2525+
mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
2526+
) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
25222527
let map = cx.tcx.hir();
25232528
let mut iter = map.parent_iter(e.hir_id);
25242529
let mut child_id = e.hir_id;
25252530

25262531
while let Some((parent_id, parent)) = iter.next() {
2527-
if let Some(x) = f(parent, child_id) {
2528-
return Some(x);
2532+
if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
2533+
return Some(ControlFlow::Break(x));
25292534
}
2530-
let parent = match parent {
2535+
let parent_expr = match parent {
25312536
Node::Expr(e) => e,
25322537
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
25332538
child_id = parent_id;
@@ -2537,18 +2542,19 @@ pub fn walk_to_expr_usage<'tcx, T>(
25372542
child_id = parent_id;
25382543
continue;
25392544
},
2540-
_ => return None,
2545+
_ => return Some(ControlFlow::Continue((parent, child_id))),
25412546
};
2542-
match parent.kind {
2547+
match parent_expr.kind {
25432548
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
25442549
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
25452550
child_id = id;
25462551
iter = map.parent_iter(id);
25472552
},
2548-
ExprKind::Block(..) => child_id = parent_id,
2549-
_ => return None,
2553+
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
2554+
_ => return Some(ControlFlow::Continue((parent, child_id))),
25502555
}
25512556
}
2557+
debug_assert!(false, "no parent node found for `{child_id:?}`");
25522558
None
25532559
}
25542560

@@ -2592,6 +2598,8 @@ pub enum ExprUseNode<'tcx> {
25922598
Callee,
25932599
/// Access of a field.
25942600
FieldAccess(Ident),
2601+
Expr,
2602+
Other,
25952603
}
25962604
impl<'tcx> ExprUseNode<'tcx> {
25972605
/// Checks if the value is returned from the function.
@@ -2668,144 +2676,104 @@ impl<'tcx> ExprUseNode<'tcx> {
26682676
let sig = cx.tcx.fn_sig(id).skip_binder();
26692677
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
26702678
},
2671-
Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None,
2679+
Self::Local(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None,
26722680
}
26732681
}
26742682
}
26752683

26762684
/// Gets the context an expression's value is used in.
2677-
#[expect(clippy::too_many_lines)]
26782685
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
26792686
let mut adjustments = [].as_slice();
26802687
let mut is_ty_unified = false;
26812688
let mut moved_before_use = false;
26822689
let ctxt = e.span.ctxt();
2683-
walk_to_expr_usage(cx, e, &mut |parent, child_id| {
2684-
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
2690+
walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| {
26852691
if adjustments.is_empty()
26862692
&& let Node::Expr(e) = cx.tcx.hir().get(child_id)
26872693
{
26882694
adjustments = cx.typeck_results().expr_adjustments(e);
26892695
}
2690-
match parent {
2691-
Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt {
2692-
node: ExprUseNode::Local(l),
2693-
adjustments,
2694-
is_ty_unified,
2695-
moved_before_use,
2696-
}),
2696+
if !cx.tcx.hir().opt_span(parent_id).is_some_and(|x| x.ctxt() == ctxt) {
2697+
return ControlFlow::Break(());
2698+
}
2699+
if let Node::Expr(e) = parent {
2700+
match e.kind {
2701+
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
2702+
is_ty_unified = true;
2703+
moved_before_use = true;
2704+
},
2705+
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
2706+
is_ty_unified = true;
2707+
moved_before_use = true;
2708+
},
2709+
ExprKind::Block(..) => moved_before_use = true,
2710+
_ => {},
2711+
}
2712+
}
2713+
ControlFlow::Continue(())
2714+
})?
2715+
.continue_value()
2716+
.map(|(use_node, child_id)| {
2717+
let node = match use_node {
2718+
Node::Local(l) => ExprUseNode::Local(l),
2719+
Node::ExprField(field) => ExprUseNode::Field(field),
2720+
26972721
Node::Item(&Item {
26982722
kind: ItemKind::Static(..) | ItemKind::Const(..),
26992723
owner_id,
2700-
span,
27012724
..
27022725
})
27032726
| Node::TraitItem(&TraitItem {
27042727
kind: TraitItemKind::Const(..),
27052728
owner_id,
2706-
span,
27072729
..
27082730
})
27092731
| Node::ImplItem(&ImplItem {
27102732
kind: ImplItemKind::Const(..),
27112733
owner_id,
2712-
span,
27132734
..
2714-
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
2715-
node: ExprUseNode::ConstStatic(owner_id),
2716-
adjustments,
2717-
is_ty_unified,
2718-
moved_before_use,
2719-
}),
2735+
}) => ExprUseNode::ConstStatic(owner_id),
27202736

27212737
Node::Item(&Item {
27222738
kind: ItemKind::Fn(..),
27232739
owner_id,
2724-
span,
27252740
..
27262741
})
27272742
| Node::TraitItem(&TraitItem {
27282743
kind: TraitItemKind::Fn(..),
27292744
owner_id,
2730-
span,
27312745
..
27322746
})
27332747
| Node::ImplItem(&ImplItem {
27342748
kind: ImplItemKind::Fn(..),
27352749
owner_id,
2736-
span,
27372750
..
2738-
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
2739-
node: ExprUseNode::Return(owner_id),
2740-
adjustments,
2741-
is_ty_unified,
2742-
moved_before_use,
2743-
}),
2744-
2745-
Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt {
2746-
node: ExprUseNode::Field(field),
2747-
adjustments,
2748-
is_ty_unified,
2749-
moved_before_use,
2750-
}),
2751+
}) => ExprUseNode::Return(owner_id),
27512752

2752-
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
2753-
ExprKind::Ret(_) => Some(ExprUseCtxt {
2754-
node: ExprUseNode::Return(OwnerId {
2755-
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
2756-
}),
2757-
adjustments,
2758-
is_ty_unified,
2759-
moved_before_use,
2760-
}),
2761-
ExprKind::Closure(closure) => Some(ExprUseCtxt {
2762-
node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
2763-
adjustments,
2764-
is_ty_unified,
2765-
moved_before_use,
2766-
}),
2767-
ExprKind::Call(func, args) => Some(ExprUseCtxt {
2768-
node: match args.iter().position(|arg| arg.hir_id == child_id) {
2769-
Some(i) => ExprUseNode::FnArg(func, i),
2770-
None => ExprUseNode::Callee,
2771-
},
2772-
adjustments,
2773-
is_ty_unified,
2774-
moved_before_use,
2753+
Node::Expr(use_expr) => match use_expr.kind {
2754+
ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
2755+
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
27752756
}),
2776-
ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
2777-
node: ExprUseNode::MethodArg(
2778-
parent.hir_id,
2779-
name.args,
2780-
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
2781-
),
2782-
adjustments,
2783-
is_ty_unified,
2784-
moved_before_use,
2785-
}),
2786-
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
2787-
node: ExprUseNode::FieldAccess(name),
2788-
adjustments,
2789-
is_ty_unified,
2790-
moved_before_use,
2791-
}),
2792-
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
2793-
is_ty_unified = true;
2794-
moved_before_use = true;
2795-
None
2796-
},
2797-
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
2798-
is_ty_unified = true;
2799-
moved_before_use = true;
2800-
None
2757+
ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
2758+
ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) {
2759+
Some(i) => ExprUseNode::FnArg(func, i),
2760+
None => ExprUseNode::Callee,
28012761
},
2802-
ExprKind::Block(..) => {
2803-
moved_before_use = true;
2804-
None
2805-
},
2806-
_ => None,
2762+
ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
2763+
use_expr.hir_id,
2764+
name.args,
2765+
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
2766+
),
2767+
ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name),
2768+
_ => ExprUseNode::Expr,
28072769
},
2808-
_ => None,
2770+
_ => ExprUseNode::Other,
2771+
};
2772+
ExprUseCtxt {
2773+
node,
2774+
adjustments,
2775+
is_ty_unified,
2776+
moved_before_use,
28092777
}
28102778
})
28112779
}

‎tests/ui/ignored_unit_patterns.fixed

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
//@aux-build:proc_macro_derive.rs
22
#![warn(clippy::ignored_unit_patterns)]
3-
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)]
3+
#![allow(
4+
clippy::let_unit_value,
5+
clippy::redundant_pattern_matching,
6+
clippy::single_match,
7+
clippy::needless_borrow
8+
)]
49

510
fn foo() -> Result<(), ()> {
611
unimplemented!()

‎tests/ui/ignored_unit_patterns.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
//@aux-build:proc_macro_derive.rs
22
#![warn(clippy::ignored_unit_patterns)]
3-
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)]
3+
#![allow(
4+
clippy::let_unit_value,
5+
clippy::redundant_pattern_matching,
6+
clippy::single_match,
7+
clippy::needless_borrow
8+
)]
49

510
fn foo() -> Result<(), ()> {
611
unimplemented!()

‎tests/ui/ignored_unit_patterns.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: matching over `()` is more explicit
2-
--> $DIR/ignored_unit_patterns.rs:11:12
2+
--> $DIR/ignored_unit_patterns.rs:16:12
33
|
44
LL | Ok(_) => {},
55
| ^ help: use `()` instead of `_`: `()`
@@ -8,49 +8,49 @@ LL | Ok(_) => {},
88
= help: to override `-D warnings` add `#[allow(clippy::ignored_unit_patterns)]`
99

1010
error: matching over `()` is more explicit
11-
--> $DIR/ignored_unit_patterns.rs:12:13
11+
--> $DIR/ignored_unit_patterns.rs:17:13
1212
|
1313
LL | Err(_) => {},
1414
| ^ help: use `()` instead of `_`: `()`
1515

1616
error: matching over `()` is more explicit
17-
--> $DIR/ignored_unit_patterns.rs:14:15
17+
--> $DIR/ignored_unit_patterns.rs:19:15
1818
|
1919
LL | if let Ok(_) = foo() {}
2020
| ^ help: use `()` instead of `_`: `()`
2121

2222
error: matching over `()` is more explicit
23-
--> $DIR/ignored_unit_patterns.rs:16:28
23+
--> $DIR/ignored_unit_patterns.rs:21:28
2424
|
2525
LL | let _ = foo().map_err(|_| todo!());
2626
| ^ help: use `()` instead of `_`: `()`
2727

2828
error: matching over `()` is more explicit
29-
--> $DIR/ignored_unit_patterns.rs:22:16
29+
--> $DIR/ignored_unit_patterns.rs:27:16
3030
|
3131
LL | Ok(_) => {},
3232
| ^ help: use `()` instead of `_`: `()`
3333

3434
error: matching over `()` is more explicit
35-
--> $DIR/ignored_unit_patterns.rs:24:17
35+
--> $DIR/ignored_unit_patterns.rs:29:17
3636
|
3737
LL | Err(_) => {},
3838
| ^ help: use `()` instead of `_`: `()`
3939

4040
error: matching over `()` is more explicit
41-
--> $DIR/ignored_unit_patterns.rs:36:9
41+
--> $DIR/ignored_unit_patterns.rs:41:9
4242
|
4343
LL | let _ = foo().unwrap();
4444
| ^ help: use `()` instead of `_`: `()`
4545

4646
error: matching over `()` is more explicit
47-
--> $DIR/ignored_unit_patterns.rs:45:13
47+
--> $DIR/ignored_unit_patterns.rs:50:13
4848
|
4949
LL | (1, _) => unimplemented!(),
5050
| ^ help: use `()` instead of `_`: `()`
5151

5252
error: matching over `()` is more explicit
53-
--> $DIR/ignored_unit_patterns.rs:52:13
53+
--> $DIR/ignored_unit_patterns.rs:57:13
5454
|
5555
LL | for (x, _) in v {
5656
| ^ help: use `()` instead of `_`: `()`

‎tests/ui/needless_borrow.fixed

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ fn main() {
131131
0
132132
}
133133
}
134+
135+
// issue #11786
136+
let x: (&str,) = ("",);
134137
}
135138

136139
#[allow(clippy::needless_borrowed_reference)]

‎tests/ui/needless_borrow.rs

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ fn main() {
131131
0
132132
}
133133
}
134+
135+
// issue #11786
136+
let x: (&str,) = (&"",);
134137
}
135138

136139
#[allow(clippy::needless_borrowed_reference)]

0 commit comments

Comments
 (0)