Skip to content

Commit dde1d06

Browse files
committed
Don't lint let_unit_value when needed for type inferenece
1 parent 8ec7ba4 commit dde1d06

File tree

4 files changed

+162
-6
lines changed

4 files changed

+162
-6
lines changed

clippy_lints/src/unit_types/let_unit_value.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::source::snippet_with_macro_callsite;
3+
use core::ops::ControlFlow;
34
use rustc_errors::Applicability;
4-
use rustc_hir::{Stmt, StmtKind};
5+
use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
56
use rustc_lint::{LateContext, LintContext};
67
use rustc_middle::lint::in_external_macro;
8+
use rustc_middle::ty::{self, Ty, TypeFoldable, TypeVisitor};
79

810
use super::LET_UNIT_VALUE;
911

1012
pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
11-
if let StmtKind::Local(local) = stmt.kind {
12-
if cx.typeck_results().pat_ty(local.pat).is_unit() {
13-
if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() {
14-
return;
13+
if let StmtKind::Local(local) = stmt.kind
14+
&& !local.pat.span.from_expansion()
15+
&& !in_external_macro(cx.sess(), stmt.span)
16+
&& cx.typeck_results().pat_ty(local.pat).is_unit()
17+
{
18+
if local.init.map_or(false, |e| needs_inferred_result_ty(cx, e)) {
19+
if !matches!(local.pat.kind, PatKind::Wild) {
20+
span_lint_and_then(
21+
cx,
22+
LET_UNIT_VALUE,
23+
stmt.span,
24+
"this let-binding has unit value",
25+
|diag| {
26+
diag.span_suggestion(
27+
local.pat.span,
28+
"use a wild (`_`) binding",
29+
"_".into(),
30+
Applicability::MaybeIncorrect, // snippet
31+
);
32+
},
33+
);
1534
}
35+
} else {
1636
span_lint_and_then(
1737
cx,
1838
LET_UNIT_VALUE,
@@ -33,3 +53,45 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
3353
}
3454
}
3555
}
56+
57+
fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
58+
let id = match e.kind {
59+
ExprKind::Call(
60+
Expr {
61+
kind: ExprKind::Path(ref path),
62+
hir_id,
63+
..
64+
},
65+
_,
66+
) => cx.qpath_res(path, *hir_id).opt_def_id(),
67+
ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(e.hir_id),
68+
_ => return false,
69+
};
70+
if let Some(id) = id
71+
&& let sig = cx.tcx.fn_sig(id).skip_binder()
72+
&& let ty::Param(output_ty) = *sig.output().kind()
73+
{
74+
sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
75+
} else {
76+
false
77+
}
78+
}
79+
80+
fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
81+
struct Visitor(u32);
82+
impl<'tcx> TypeVisitor<'tcx> for Visitor {
83+
type BreakTy = ();
84+
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
85+
if let ty::Param(ty) = *ty.kind() {
86+
if ty.index == self.0 {
87+
ControlFlow::BREAK
88+
} else {
89+
ControlFlow::CONTINUE
90+
}
91+
} else {
92+
ty.super_visit_with(self)
93+
}
94+
}
95+
}
96+
ty.visit_with(&mut Visitor(index)).is_break()
97+
}

tests/ui/let_unit.fixed

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,30 @@ fn multiline_sugg() {
6161

6262
#[derive(Copy, Clone)]
6363
pub struct ContainsUnit(()); // should be fine
64+
65+
fn _returns_generic() {
66+
fn f<T>() -> T {
67+
unimplemented!()
68+
}
69+
fn f2<T, U>(_: T) -> U {
70+
unimplemented!()
71+
}
72+
fn f3<T>(x: T) -> T {
73+
x
74+
}
75+
fn f4<T>(mut x: Vec<T>) -> T {
76+
x.pop().unwrap()
77+
}
78+
79+
let _: () = f(); // Ok
80+
let _: () = f(); // Lint.
81+
82+
let _: () = f2(0i32); // Ok
83+
let _: () = f2(0i32); // Lint.
84+
85+
f3(()); // Lint
86+
f3(()); // Lint
87+
88+
f4(vec![()]); // Lint
89+
f4(vec![()]); // Lint
90+
}

tests/ui/let_unit.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,30 @@ fn multiline_sugg() {
6161

6262
#[derive(Copy, Clone)]
6363
pub struct ContainsUnit(()); // should be fine
64+
65+
fn _returns_generic() {
66+
fn f<T>() -> T {
67+
unimplemented!()
68+
}
69+
fn f2<T, U>(_: T) -> U {
70+
unimplemented!()
71+
}
72+
fn f3<T>(x: T) -> T {
73+
x
74+
}
75+
fn f4<T>(mut x: Vec<T>) -> T {
76+
x.pop().unwrap()
77+
}
78+
79+
let _: () = f(); // Ok
80+
let x: () = f(); // Lint.
81+
82+
let _: () = f2(0i32); // Ok
83+
let x: () = f2(0i32); // Lint.
84+
85+
let _: () = f3(()); // Lint
86+
let x: () = f3(()); // Lint
87+
88+
let _: () = f4(vec![()]); // Lint
89+
let x: () = f4(vec![()]); // Lint
90+
}

tests/ui/let_unit.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,45 @@ LL + .map(|_| ())
3434
LL + .next()
3535
...
3636

37-
error: aborting due to 3 previous errors
37+
error: this let-binding has unit value
38+
--> $DIR/let_unit.rs:80:5
39+
|
40+
LL | let x: () = f(); // Lint.
41+
| ^^^^-^^^^^^^^^^^
42+
| |
43+
| help: use a wild (`_`) binding: `_`
44+
45+
error: this let-binding has unit value
46+
--> $DIR/let_unit.rs:83:5
47+
|
48+
LL | let x: () = f2(0i32); // Lint.
49+
| ^^^^-^^^^^^^^^^^^^^^^
50+
| |
51+
| help: use a wild (`_`) binding: `_`
52+
53+
error: this let-binding has unit value
54+
--> $DIR/let_unit.rs:85:5
55+
|
56+
LL | let _: () = f3(()); // Lint
57+
| ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());`
58+
59+
error: this let-binding has unit value
60+
--> $DIR/let_unit.rs:86:5
61+
|
62+
LL | let x: () = f3(()); // Lint
63+
| ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());`
64+
65+
error: this let-binding has unit value
66+
--> $DIR/let_unit.rs:88:5
67+
|
68+
LL | let _: () = f4(vec![()]); // Lint
69+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f4(vec![()]);`
70+
71+
error: this let-binding has unit value
72+
--> $DIR/let_unit.rs:89:5
73+
|
74+
LL | let x: () = f4(vec![()]); // Lint
75+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f4(vec![()]);`
76+
77+
error: aborting due to 9 previous errors
3878

0 commit comments

Comments
 (0)