|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then;
|
| 2 | +use clippy_utils::get_parent_node; |
2 | 3 | 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}; |
4 | 5 | use core::ops::ControlFlow;
|
5 | 6 | use rustc_errors::Applicability;
|
6 | 7 | use rustc_hir::def::{DefKind, Res};
|
7 |
| -use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind}; |
| 8 | +use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node, PatKind, QPath, Stmt, StmtKind, TyKind}; |
8 | 9 | use rustc_lint::{LateContext, LintContext};
|
9 | 10 | use rustc_middle::lint::in_external_macro;
|
10 |
| -use rustc_middle::ty::{self, Ty, TypeFoldable, TypeSuperFoldable, TypeVisitor}; |
| 11 | +use rustc_middle::ty; |
11 | 12 |
|
12 | 13 | use super::LET_UNIT_VALUE;
|
13 | 14 |
|
14 |
| -pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { |
| 15 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { |
15 | 16 | if let StmtKind::Local(local) = stmt.kind
|
16 | 17 | && let Some(init) = local.init
|
17 | 18 | && !local.pat.span.from_expansion()
|
18 | 19 | && !in_external_macro(cx.sess(), stmt.span)
|
19 | 20 | && cx.typeck_results().pat_ty(local.pat).is_unit()
|
20 | 21 | {
|
21 |
| - let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) { |
22 |
| - ControlFlow::Continue(()) |
23 |
| - } else { |
24 |
| - ControlFlow::Break(()) |
25 |
| - }).is_continue(); |
26 |
| - |
27 |
| - if needs_inferred { |
| 22 | + if local.ty.is_some() && expr_needs_inferred_result(cx, init) { |
28 | 23 | if !matches!(local.pat.kind, PatKind::Wild) {
|
29 | 24 | span_lint_and_then(
|
30 | 25 | cx,
|
@@ -63,48 +58,106 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
63 | 58 | }
|
64 | 59 | }
|
65 | 60 |
|
66 |
| -fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
67 |
| - let id = match e.kind { |
| 61 | +/// Checks sub-expressions which create the value returned by the given expression for whether |
| 62 | +/// return value inference is needed. This checks through locals to see if they also need inference |
| 63 | +/// at this point. |
| 64 | +/// |
| 65 | +/// e.g. |
| 66 | +/// ```rust,ignore |
| 67 | +/// let bar = foo(); |
| 68 | +/// let x: u32 = if true { baz() } else { bar }; |
| 69 | +/// ``` |
| 70 | +/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the |
| 71 | +/// initialization of `bar`. If both `foo` and `baz` have a return type which require type |
| 72 | +/// inference then this function would return `true`. |
| 73 | +fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { |
| 74 | + // The locals used for initialization which have yet to be checked. |
| 75 | + let mut locals_to_check = Vec::new(); |
| 76 | + // All the locals which have been added to `locals_to_check`. Needed to prevent cycles. |
| 77 | + let mut seen_locals = HirIdSet::default(); |
| 78 | + if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { |
| 79 | + return false; |
| 80 | + } |
| 81 | + while let Some(id) = locals_to_check.pop() { |
| 82 | + if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) { |
| 83 | + if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) { |
| 84 | + return false; |
| 85 | + } |
| 86 | + if let Some(e) = l.init { |
| 87 | + if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { |
| 88 | + return false; |
| 89 | + } |
| 90 | + } else if for_each_local_assignment(cx, id, |e| { |
| 91 | + if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { |
| 92 | + ControlFlow::Continue(()) |
| 93 | + } else { |
| 94 | + ControlFlow::Break(()) |
| 95 | + } |
| 96 | + }) |
| 97 | + .is_break() |
| 98 | + { |
| 99 | + return false; |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + true |
| 105 | +} |
| 106 | + |
| 107 | +fn each_value_source_needs_inference( |
| 108 | + cx: &LateContext<'_>, |
| 109 | + e: &Expr<'_>, |
| 110 | + locals_to_check: &mut Vec<HirId>, |
| 111 | + seen_locals: &mut HirIdSet, |
| 112 | +) -> bool { |
| 113 | + for_each_value_source(e, &mut |e| { |
| 114 | + if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) { |
| 115 | + ControlFlow::Continue(()) |
| 116 | + } else { |
| 117 | + ControlFlow::Break(()) |
| 118 | + } |
| 119 | + }) |
| 120 | + .is_continue() |
| 121 | +} |
| 122 | + |
| 123 | +fn needs_inferred_result_ty( |
| 124 | + cx: &LateContext<'_>, |
| 125 | + e: &Expr<'_>, |
| 126 | + locals_to_check: &mut Vec<HirId>, |
| 127 | + seen_locals: &mut HirIdSet, |
| 128 | +) -> bool { |
| 129 | + let (id, args) = match e.kind { |
68 | 130 | ExprKind::Call(
|
69 | 131 | Expr {
|
70 | 132 | kind: ExprKind::Path(ref path),
|
71 | 133 | hir_id,
|
72 | 134 | ..
|
73 | 135 | },
|
74 |
| - _, |
| 136 | + args, |
75 | 137 | ) => match cx.qpath_res(path, *hir_id) {
|
76 |
| - Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id, |
| 138 | + Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args), |
77 | 139 | _ => return false,
|
78 | 140 | },
|
79 |
| - ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) { |
80 |
| - Some(id) => id, |
| 141 | + ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) { |
| 142 | + Some(id) => (id, args), |
81 | 143 | None => return false,
|
82 | 144 | },
|
| 145 | + ExprKind::Path(QPath::Resolved(None, path)) => { |
| 146 | + if let Res::Local(id) = path.res |
| 147 | + && seen_locals.insert(id) |
| 148 | + { |
| 149 | + locals_to_check.push(id); |
| 150 | + } |
| 151 | + return true; |
| 152 | + }, |
83 | 153 | _ => return false,
|
84 | 154 | };
|
85 | 155 | let sig = cx.tcx.fn_sig(id).skip_binder();
|
86 | 156 | if let ty::Param(output_ty) = *sig.output().kind() {
|
87 |
| - sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index)) |
| 157 | + sig.inputs().iter().zip(args).all(|(&ty, arg)| { |
| 158 | + !ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals) |
| 159 | + }) |
88 | 160 | } else {
|
89 | 161 | false
|
90 | 162 | }
|
91 | 163 | }
|
92 |
| - |
93 |
| -fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool { |
94 |
| - struct Visitor(u32); |
95 |
| - impl<'tcx> TypeVisitor<'tcx> for Visitor { |
96 |
| - type BreakTy = (); |
97 |
| - fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { |
98 |
| - if let ty::Param(ty) = *ty.kind() { |
99 |
| - if ty.index == self.0 { |
100 |
| - ControlFlow::BREAK |
101 |
| - } else { |
102 |
| - ControlFlow::CONTINUE |
103 |
| - } |
104 |
| - } else { |
105 |
| - ty.super_visit_with(self) |
106 |
| - } |
107 |
| - } |
108 |
| - } |
109 |
| - ty.visit_with(&mut Visitor(index)).is_break() |
110 |
| -} |
|
0 commit comments