]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / unit_types / let_unit_value.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::get_parent_node;
3 use clippy_utils::source::snippet_with_macro_callsite;
4 use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
5 use core::ops::ControlFlow;
6 use rustc_errors::Applicability;
7 use rustc_hir::def::{DefKind, Res};
8 use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
9 use rustc_lint::{LateContext, LintContext};
10 use rustc_middle::lint::in_external_macro;
11 use rustc_middle::ty;
12
13 use super::LET_UNIT_VALUE;
14
15 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
16 if let Some(init) = local.init
17 && !local.pat.span.from_expansion()
18 && !in_external_macro(cx.sess(), local.span)
19 && cx.typeck_results().pat_ty(local.pat).is_unit()
20 {
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)) {
26 span_lint_and_then(
27 cx,
28 LET_UNIT_VALUE,
29 local.span,
30 "this let-binding has unit value",
31 |diag| {
32 diag.span_suggestion(
33 local.pat.span,
34 "use a wild (`_`) binding",
35 "_",
36 Applicability::MaybeIncorrect, // snippet
37 );
38 },
39 );
40 }
41 } else {
42 span_lint_and_then(
43 cx,
44 LET_UNIT_VALUE,
45 local.span,
46 "this let-binding has unit value",
47 |diag| {
48 if let Some(expr) = &local.init {
49 let snip = snippet_with_macro_callsite(cx, expr.span, "()");
50 diag.span_suggestion(
51 local.span,
52 "omit the `let` binding",
53 format!("{snip};"),
54 Applicability::MachineApplicable, // snippet
55 );
56 }
57 },
58 );
59 }
60 }
61 }
62
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 {
132 ExprKind::Call(
133 Expr {
134 kind: ExprKind::Path(ref path),
135 hir_id,
136 ..
137 },
138 args,
139 ) => match cx.qpath_res(path, *hir_id) {
140 Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args),
141 _ => return false,
142 },
143 ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
144 Some(id) => (id, args),
145 None => return false,
146 },
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 },
155 _ => return false,
156 };
157 let sig = cx.tcx.fn_sig(id).skip_binder();
158 if let ty::Param(output_ty) = *sig.output().kind() {
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 })
162 } else {
163 false
164 }
165 }