]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
Auto merge of #99505 - joboet:futex_once, r=thomcc
[rust.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([], ddpos) if ddpos.as_opt_usize().is_none()))
23             && expr_needs_inferred_result(cx, init)
24         {
25             if !matches!(local.pat.kind, PatKind::Wild)
26                && !matches!(local.pat.kind, PatKind::Tuple([], ddpos) if ddpos.as_opt_usize().is_none())
27             {
28                 span_lint_and_then(
29                     cx,
30                     LET_UNIT_VALUE,
31                     local.span,
32                     "this let-binding has unit value",
33                     |diag| {
34                         diag.span_suggestion(
35                             local.pat.span,
36                             "use a wild (`_`) binding",
37                             "_",
38                             Applicability::MaybeIncorrect, // snippet
39                         );
40                     },
41                 );
42             }
43         } else {
44             span_lint_and_then(
45                 cx,
46                 LET_UNIT_VALUE,
47                 local.span,
48                 "this let-binding has unit value",
49                 |diag| {
50                     if let Some(expr) = &local.init {
51                         let snip = snippet_with_macro_callsite(cx, expr.span, "()");
52                         diag.span_suggestion(
53                             local.span,
54                             "omit the `let` binding",
55                             format!("{snip};"),
56                             Applicability::MachineApplicable, // snippet
57                         );
58                     }
59                 },
60             );
61         }
62     }
63 }
64
65 /// Checks sub-expressions which create the value returned by the given expression for whether
66 /// return value inference is needed. This checks through locals to see if they also need inference
67 /// at this point.
68 ///
69 /// e.g.
70 /// ```rust,ignore
71 /// let bar = foo();
72 /// let x: u32 = if true { baz() } else { bar };
73 /// ```
74 /// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the
75 /// initialization of `bar`. If both `foo` and `baz` have a return type which require type
76 /// inference then this function would return `true`.
77 fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
78     // The locals used for initialization which have yet to be checked.
79     let mut locals_to_check = Vec::new();
80     // All the locals which have been added to `locals_to_check`. Needed to prevent cycles.
81     let mut seen_locals = HirIdSet::default();
82     if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
83         return false;
84     }
85     while let Some(id) = locals_to_check.pop() {
86         if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
87             if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
88                 return false;
89             }
90             if let Some(e) = l.init {
91                 if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
92                     return false;
93                 }
94             } else if for_each_local_assignment(cx, id, |e| {
95                 if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
96                     ControlFlow::Continue(())
97                 } else {
98                     ControlFlow::Break(())
99                 }
100             })
101             .is_break()
102             {
103                 return false;
104             }
105         }
106     }
107
108     true
109 }
110
111 fn each_value_source_needs_inference(
112     cx: &LateContext<'_>,
113     e: &Expr<'_>,
114     locals_to_check: &mut Vec<HirId>,
115     seen_locals: &mut HirIdSet,
116 ) -> bool {
117     for_each_value_source(e, &mut |e| {
118         if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) {
119             ControlFlow::Continue(())
120         } else {
121             ControlFlow::Break(())
122         }
123     })
124     .is_continue()
125 }
126
127 fn needs_inferred_result_ty(
128     cx: &LateContext<'_>,
129     e: &Expr<'_>,
130     locals_to_check: &mut Vec<HirId>,
131     seen_locals: &mut HirIdSet,
132 ) -> bool {
133     let (id, receiver, args) = match e.kind {
134         ExprKind::Call(
135             Expr {
136                 kind: ExprKind::Path(ref path),
137                 hir_id,
138                 ..
139             },
140             args,
141         ) => match cx.qpath_res(path, *hir_id) {
142             Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, None, args),
143             _ => return false,
144         },
145         ExprKind::MethodCall(_, receiver, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
146             Some(id) => (id, Some(receiver), args),
147             None => return false,
148         },
149         ExprKind::Path(QPath::Resolved(None, path)) => {
150             if let Res::Local(id) = path.res
151                 && seen_locals.insert(id)
152             {
153                 locals_to_check.push(id);
154             }
155             return true;
156         },
157         _ => return false,
158     };
159     let sig = cx.tcx.fn_sig(id).skip_binder();
160     if let ty::Param(output_ty) = *sig.output().kind() {
161         let args: Vec<&Expr<'_>> = if let Some(receiver) = receiver {
162             std::iter::once(receiver).chain(args.iter()).collect()
163         } else {
164             args.iter().collect()
165         };
166         sig.inputs().iter().zip(args).all(|(&ty, arg)| {
167             !ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals)
168         })
169     } else {
170         false
171     }
172 }