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