Skip to content

Commit 7ea4592

Browse files
committed
Auto merge of rust-lang#9056 - Jarcho:let_unit_indirect, r=llogiq
Allow `let_unit_value` in more cases fixes rust-lang#8998 changelog: Lint `let_unit_value` less aggressively when the type is specified
2 parents 4d1b976 + 2872b7e commit 7ea4592

File tree

6 files changed

+280
-80
lines changed

6 files changed

+280
-80
lines changed

clippy_lints/src/unit_types/let_unit_value.rs

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,40 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::get_parent_node;
23
use clippy_utils::source::snippet_with_macro_callsite;
3-
use clippy_utils::visitors::for_each_value_source;
4+
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
45
use core::ops::ControlFlow;
56
use rustc_errors::Applicability;
67
use rustc_hir::def::{DefKind, Res};
7-
use rustc_hir::{Expr, ExprKind, Local, PatKind};
8+
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
89
use rustc_lint::{LateContext, LintContext};
910
use rustc_middle::lint::in_external_macro;
10-
use rustc_middle::ty::{self, Ty, TypeFoldable, TypeSuperFoldable, TypeVisitor};
11+
use rustc_middle::ty;
1112

1213
use super::LET_UNIT_VALUE;
1314

14-
pub(super) fn check(cx: &LateContext<'_>, local: &Local<'_>) {
15+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
1516
if let Some(init) = local.init
1617
&& !local.pat.span.from_expansion()
1718
&& !in_external_macro(cx.sess(), local.span)
1819
&& cx.typeck_results().pat_ty(local.pat).is_unit()
1920
{
20-
let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
21-
ControlFlow::Continue(())
22-
} else {
23-
ControlFlow::Break(())
24-
}).is_continue();
25-
26-
if needs_inferred {
27-
if !matches!(local.pat.kind, PatKind::Wild) {
21+
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
22+
|| matches!(local.pat.kind, PatKind::Tuple([], None)))
23+
&& expr_needs_inferred_result(cx, init)
24+
{
25+
if !matches!(local.pat.kind, PatKind::Wild | PatKind::Tuple([], None)) {
2826
span_lint_and_then(
2927
cx,
3028
LET_UNIT_VALUE,
3129
local.span,
3230
"this let-binding has unit value",
3331
|diag| {
34-
diag.span_suggestion(
35-
local.pat.span,
36-
"use a wild (`_`) binding",
37-
"_",
38-
Applicability::MaybeIncorrect, // snippet
39-
);
32+
diag.span_suggestion(
33+
local.pat.span,
34+
"use a wild (`_`) binding",
35+
"_",
36+
Applicability::MaybeIncorrect, // snippet
37+
);
4038
},
4139
);
4240
}
@@ -62,48 +60,106 @@ pub(super) fn check(cx: &LateContext<'_>, local: &Local<'_>) {
6260
}
6361
}
6462

65-
fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
66-
let id = match e.kind {
63+
/// Checks sub-expressions which create the value returned by the given expression for whether
64+
/// return value inference is needed. This checks through locals to see if they also need inference
65+
/// at this point.
66+
///
67+
/// e.g.
68+
/// ```rust,ignore
69+
/// let bar = foo();
70+
/// let x: u32 = if true { baz() } else { bar };
71+
/// ```
72+
/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the
73+
/// initialization of `bar`. If both `foo` and `baz` have a return type which require type
74+
/// inference then this function would return `true`.
75+
fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
76+
// The locals used for initialization which have yet to be checked.
77+
let mut locals_to_check = Vec::new();
78+
// All the locals which have been added to `locals_to_check`. Needed to prevent cycles.
79+
let mut seen_locals = HirIdSet::default();
80+
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
81+
return false;
82+
}
83+
while let Some(id) = locals_to_check.pop() {
84+
if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
85+
if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
86+
return false;
87+
}
88+
if let Some(e) = l.init {
89+
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
90+
return false;
91+
}
92+
} else if for_each_local_assignment(cx, id, |e| {
93+
if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
94+
ControlFlow::Continue(())
95+
} else {
96+
ControlFlow::Break(())
97+
}
98+
})
99+
.is_break()
100+
{
101+
return false;
102+
}
103+
}
104+
}
105+
106+
true
107+
}
108+
109+
fn each_value_source_needs_inference(
110+
cx: &LateContext<'_>,
111+
e: &Expr<'_>,
112+
locals_to_check: &mut Vec<HirId>,
113+
seen_locals: &mut HirIdSet,
114+
) -> bool {
115+
for_each_value_source(e, &mut |e| {
116+
if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) {
117+
ControlFlow::Continue(())
118+
} else {
119+
ControlFlow::Break(())
120+
}
121+
})
122+
.is_continue()
123+
}
124+
125+
fn needs_inferred_result_ty(
126+
cx: &LateContext<'_>,
127+
e: &Expr<'_>,
128+
locals_to_check: &mut Vec<HirId>,
129+
seen_locals: &mut HirIdSet,
130+
) -> bool {
131+
let (id, args) = match e.kind {
67132
ExprKind::Call(
68133
Expr {
69134
kind: ExprKind::Path(ref path),
70135
hir_id,
71136
..
72137
},
73-
_,
138+
args,
74139
) => match cx.qpath_res(path, *hir_id) {
75-
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id,
140+
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args),
76141
_ => return false,
77142
},
78-
ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
79-
Some(id) => id,
143+
ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
144+
Some(id) => (id, args),
80145
None => return false,
81146
},
147+
ExprKind::Path(QPath::Resolved(None, path)) => {
148+
if let Res::Local(id) = path.res
149+
&& seen_locals.insert(id)
150+
{
151+
locals_to_check.push(id);
152+
}
153+
return true;
154+
},
82155
_ => return false,
83156
};
84157
let sig = cx.tcx.fn_sig(id).skip_binder();
85158
if let ty::Param(output_ty) = *sig.output().kind() {
86-
sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
159+
sig.inputs().iter().zip(args).all(|(&ty, arg)| {
160+
!ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals)
161+
})
87162
} else {
88163
false
89164
}
90165
}
91-
92-
fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
93-
struct Visitor(u32);
94-
impl<'tcx> TypeVisitor<'tcx> for Visitor {
95-
type BreakTy = ();
96-
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
97-
if let ty::Param(ty) = *ty.kind() {
98-
if ty.index == self.0 {
99-
ControlFlow::BREAK
100-
} else {
101-
ControlFlow::CONTINUE
102-
}
103-
} else {
104-
ty.super_visit_with(self)
105-
}
106-
}
107-
}
108-
ty.visit_with(&mut Visitor(index)).is_break()
109-
}

clippy_lints/src/unit_types/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ declare_clippy_lint! {
9898

9999
declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
100100

101-
impl LateLintPass<'_> for UnitTypes {
102-
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
101+
impl<'tcx> LateLintPass<'tcx> for UnitTypes {
102+
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
103103
let_unit_value::check(cx, local);
104104
}
105105

clippy_utils/src/visitors.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,49 @@ pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx
617617
})
618618
.is_break()
619619
}
620+
621+
/// Runs the given function for each path expression referencing the given local which occur after
622+
/// the given expression.
623+
pub fn for_each_local_assignment<'tcx, B>(
624+
cx: &LateContext<'tcx>,
625+
local_id: HirId,
626+
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
627+
) -> ControlFlow<B> {
628+
struct V<'cx, 'tcx, F, B> {
629+
cx: &'cx LateContext<'tcx>,
630+
local_id: HirId,
631+
res: ControlFlow<B>,
632+
f: F,
633+
}
634+
impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
635+
type NestedFilter = nested_filter::OnlyBodies;
636+
fn nested_visit_map(&mut self) -> Self::Map {
637+
self.cx.tcx.hir()
638+
}
639+
640+
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
641+
if let ExprKind::Assign(lhs, rhs, _) = e.kind
642+
&& self.res.is_continue()
643+
&& path_to_local_id(lhs, self.local_id)
644+
{
645+
self.res = (self.f)(rhs);
646+
self.visit_expr(rhs);
647+
} else {
648+
walk_expr(self, e);
649+
}
650+
}
651+
}
652+
653+
if let Some(b) = get_enclosing_block(cx, local_id) {
654+
let mut v = V {
655+
cx,
656+
local_id,
657+
res: ControlFlow::Continue(()),
658+
f,
659+
};
660+
v.visit_block(b);
661+
v.res
662+
} else {
663+
ControlFlow::Continue(())
664+
}
665+
}

tests/ui/let_unit.fixed

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
#![feature(lint_reasons)]
44
#![warn(clippy::let_unit_value)]
5-
#![allow(clippy::no_effect)]
6-
#![allow(unused)]
5+
#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)]
76

87
macro_rules! let_and_return {
98
($n:expr) => {{
@@ -73,8 +72,8 @@ fn _returns_generic() {
7372
fn f3<T>(x: T) -> T {
7473
x
7574
}
76-
fn f4<T>(mut x: Vec<T>) -> T {
77-
x.pop().unwrap()
75+
fn f5<T: Default>(x: bool) -> Option<T> {
76+
x.then(|| T::default())
7877
}
7978

8079
let _: () = f(); // Ok
@@ -86,8 +85,12 @@ fn _returns_generic() {
8685
f3(()); // Lint
8786
f3(()); // Lint
8887

89-
f4(vec![()]); // Lint
90-
f4(vec![()]); // Lint
88+
// Should lint:
89+
// fn f4<T>(mut x: Vec<T>) -> T {
90+
// x.pop().unwrap()
91+
// }
92+
// let _: () = f4(vec![()]);
93+
// let x: () = f4(vec![()]);
9194

9295
// Ok
9396
let _: () = {
@@ -113,6 +116,55 @@ fn _returns_generic() {
113116
Some(1) => f2(3),
114117
Some(_) => (),
115118
};
119+
120+
let _: () = f5(true).unwrap();
121+
122+
#[allow(clippy::let_unit_value)]
123+
{
124+
let x = f();
125+
let y;
126+
let z;
127+
match 0 {
128+
0 => {
129+
y = f();
130+
z = f();
131+
},
132+
1 => {
133+
println!("test");
134+
y = f();
135+
z = f3(());
136+
},
137+
_ => panic!(),
138+
}
139+
140+
let x1;
141+
let x2;
142+
if true {
143+
x1 = f();
144+
x2 = x1;
145+
} else {
146+
x2 = f();
147+
x1 = x2;
148+
}
149+
150+
let opt;
151+
match f5(true) {
152+
Some(x) => opt = x,
153+
None => panic!(),
154+
};
155+
156+
#[warn(clippy::let_unit_value)]
157+
{
158+
let _: () = x;
159+
let _: () = y;
160+
z;
161+
let _: () = x1;
162+
let _: () = x2;
163+
let _: () = opt;
164+
}
165+
}
166+
167+
let () = f();
116168
}
117169

118170
fn attributes() {

0 commit comments

Comments
 (0)