]> git.proxmox.com Git - rustc.git/blobdiff - 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
index 27678c8ba3c469af9d7af04de25d90bb3c6639e6..aec028d5c4824b63bfad47af38eca5846a18ab1a 100644 (file)
@@ -1,43 +1,40 @@
 use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::get_parent_node;
 use clippy_utils::source::snippet_with_macro_callsite;
-use clippy_utils::visitors::for_each_value_source;
+use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
-use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
+use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
 use rustc_lint::{LateContext, LintContext};
 use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::{self, Ty, TypeFoldable, TypeSuperFoldable, TypeVisitor};
+use rustc_middle::ty;
 
 use super::LET_UNIT_VALUE;
 
-pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
-    if let StmtKind::Local(local) = stmt.kind
-        && let Some(init) = local.init
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
+    if let Some(init) = local.init
         && !local.pat.span.from_expansion()
-        && !in_external_macro(cx.sess(), stmt.span)
+        && !in_external_macro(cx.sess(), local.span)
         && cx.typeck_results().pat_ty(local.pat).is_unit()
     {
-        let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
-            ControlFlow::Continue(())
-        } else {
-            ControlFlow::Break(())
-        }).is_continue();
-
-        if needs_inferred {
-            if !matches!(local.pat.kind, PatKind::Wild) {
+        if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
+            || matches!(local.pat.kind, PatKind::Tuple([], None)))
+            && expr_needs_inferred_result(cx, init)
+        {
+            if !matches!(local.pat.kind, PatKind::Wild | PatKind::Tuple([], None)) {
                 span_lint_and_then(
                     cx,
                     LET_UNIT_VALUE,
-                    stmt.span,
+                    local.span,
                     "this let-binding has unit value",
                     |diag| {
-                            diag.span_suggestion(
-                                local.pat.span,
-                                "use a wild (`_`) binding",
-                                "_",
-                                Applicability::MaybeIncorrect, // snippet
-                            );
+                        diag.span_suggestion(
+                            local.pat.span,
+                            "use a wild (`_`) binding",
+                            "_",
+                            Applicability::MaybeIncorrect, // snippet
+                        );
                     },
                 );
             }
@@ -45,15 +42,15 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
             span_lint_and_then(
                 cx,
                 LET_UNIT_VALUE,
-                stmt.span,
+                local.span,
                 "this let-binding has unit value",
                 |diag| {
                     if let Some(expr) = &local.init {
                         let snip = snippet_with_macro_callsite(cx, expr.span, "()");
                         diag.span_suggestion(
-                            stmt.span,
+                            local.span,
                             "omit the `let` binding",
-                            format!("{};", snip),
+                            format!("{snip};"),
                             Applicability::MachineApplicable, // snippet
                         );
                     }
@@ -63,48 +60,106 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
     }
 }
 
-fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
-    let id = match e.kind {
+/// Checks sub-expressions which create the value returned by the given expression for whether
+/// return value inference is needed. This checks through locals to see if they also need inference
+/// at this point.
+///
+/// e.g.
+/// ```rust,ignore
+/// let bar = foo();
+/// let x: u32 = if true { baz() } else { bar };
+/// ```
+/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the
+/// initialization of `bar`. If both `foo` and `baz` have a return type which require type
+/// inference then this function would return `true`.
+fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+    // The locals used for initialization which have yet to be checked.
+    let mut locals_to_check = Vec::new();
+    // All the locals which have been added to `locals_to_check`. Needed to prevent cycles.
+    let mut seen_locals = HirIdSet::default();
+    if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
+        return false;
+    }
+    while let Some(id) = locals_to_check.pop() {
+        if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
+            if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
+                return false;
+            }
+            if let Some(e) = l.init {
+                if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
+                    return false;
+                }
+            } else if for_each_local_assignment(cx, id, |e| {
+                if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
+                    ControlFlow::Continue(())
+                } else {
+                    ControlFlow::Break(())
+                }
+            })
+            .is_break()
+            {
+                return false;
+            }
+        }
+    }
+
+    true
+}
+
+fn each_value_source_needs_inference(
+    cx: &LateContext<'_>,
+    e: &Expr<'_>,
+    locals_to_check: &mut Vec<HirId>,
+    seen_locals: &mut HirIdSet,
+) -> bool {
+    for_each_value_source(e, &mut |e| {
+        if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) {
+            ControlFlow::Continue(())
+        } else {
+            ControlFlow::Break(())
+        }
+    })
+    .is_continue()
+}
+
+fn needs_inferred_result_ty(
+    cx: &LateContext<'_>,
+    e: &Expr<'_>,
+    locals_to_check: &mut Vec<HirId>,
+    seen_locals: &mut HirIdSet,
+) -> bool {
+    let (id, args) = match e.kind {
         ExprKind::Call(
             Expr {
                 kind: ExprKind::Path(ref path),
                 hir_id,
                 ..
             },
-            _,
+            args,
         ) => match cx.qpath_res(path, *hir_id) {
-            Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id,
+            Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args),
             _ => return false,
         },
-        ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
-            Some(id) => id,
+        ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
+            Some(id) => (id, args),
             None => return false,
         },
+        ExprKind::Path(QPath::Resolved(None, path)) => {
+            if let Res::Local(id) = path.res
+                && seen_locals.insert(id)
+            {
+                locals_to_check.push(id);
+            }
+            return true;
+        },
         _ => return false,
     };
     let sig = cx.tcx.fn_sig(id).skip_binder();
     if let ty::Param(output_ty) = *sig.output().kind() {
-        sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
+        sig.inputs().iter().zip(args).all(|(&ty, arg)| {
+            !ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals)
+        })
     } else {
         false
     }
 }
-
-fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
-    struct Visitor(u32);
-    impl<'tcx> TypeVisitor<'tcx> for Visitor {
-        type BreakTy = ();
-        fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-            if let ty::Param(ty) = *ty.kind() {
-                if ty.index == self.0 {
-                    ControlFlow::BREAK
-                } else {
-                    ControlFlow::CONTINUE
-                }
-            } else {
-                ty.super_visit_with(self)
-            }
-        }
-    }
-    ty.visit_with(&mut Visitor(index)).is_break()
-}