about summary refs log tree commit diff
path: root/clippy_utils/src
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2022-06-30 10:27:25 +0200
committerPhilipp Krones <hello@philkrones.com>2022-06-30 10:27:25 +0200
commitf26cf11fe88e1b5bb51d0bd7515941fe51d5739d (patch)
treece08ddacf71e55cae06764b9c57b7d59da08d98f /clippy_utils/src
parentee37029afa62e93ab5ec8b1f2a23f8dbfd2a453f (diff)
parentff3964af1b497f5621f9c1e362cef24aeab4018a (diff)
downloadrust-f26cf11fe88e1b5bb51d0bd7515941fe51d5739d.tar.gz
rust-f26cf11fe88e1b5bb51d0bd7515941fe51d5739d.zip
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_utils/src')
-rw-r--r--clippy_utils/src/diagnostics.rs8
-rw-r--r--clippy_utils/src/hir_utils.rs38
-rw-r--r--clippy_utils/src/lib.rs69
-rw-r--r--clippy_utils/src/msrvs.rs9
-rw-r--r--clippy_utils/src/paths.rs13
-rw-r--r--clippy_utils/src/ty.rs266
-rw-r--r--clippy_utils/src/visitors.rs131
7 files changed, 441 insertions, 93 deletions
diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs
index 7f55db3b31f..ad95369b9ef 100644
--- a/clippy_utils/src/diagnostics.rs
+++ b/clippy_utils/src/diagnostics.rs
@@ -155,13 +155,7 @@ where
     });
 }
 
-pub fn span_lint_hir(
-    cx: &LateContext<'_>,
-    lint: &'static Lint,
-    hir_id: HirId,
-    sp: Span,
-    msg: &str,
-) {
+pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
     cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
         let mut diag = diag.build(msg);
         docs_link(&mut diag, lint);
diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs
index af62c4afd5a..74ef2c1bebb 100644
--- a/clippy_utils/src/hir_utils.rs
+++ b/clippy_utils/src/hir_utils.rs
@@ -1,4 +1,5 @@
 use crate::consts::constant_simple;
+use crate::macros::macro_backtrace;
 use crate::source::snippet_opt;
 use rustc_ast::ast::InlineAsmTemplatePiece;
 use rustc_data_structures::fx::FxHasher;
@@ -12,7 +13,7 @@ use rustc_hir::{
 use rustc_lexer::{tokenize, TokenKind};
 use rustc_lint::LateContext;
 use rustc_middle::ty::TypeckResults;
-use rustc_span::Symbol;
+use rustc_span::{sym, Symbol};
 use std::hash::{Hash, Hasher};
 
 /// Type used to check whether two ast are the same. This is different from the
@@ -121,6 +122,9 @@ impl HirEqInterExpr<'_, '_, '_> {
 
     /// Checks whether two blocks are the same.
     fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+        if self.cannot_be_compared_block(left) || self.cannot_be_compared_block(right) {
+            return false;
+        }
         match (left.stmts, left.expr, right.stmts, right.expr) {
             ([], None, [], None) => {
                 // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
@@ -171,6 +175,38 @@ impl HirEqInterExpr<'_, '_, '_> {
         }
     }
 
+    fn cannot_be_compared_block(&mut self, block: &Block<'_>) -> bool {
+        if block.stmts.last().map_or(false, |stmt| {
+            matches!(
+                stmt.kind,
+                StmtKind::Semi(semi_expr) if self.should_ignore(semi_expr)
+            )
+        }) {
+            return true;
+        }
+
+        if let Some(block_expr) = block.expr
+            && self.should_ignore(block_expr)
+        {
+            return true
+        }
+
+        false
+    }
+
+    fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
+        if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
+            matches!(
+                &self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id),
+                Some(sym::todo_macro | sym::unimplemented_macro)
+            )
+        }) {
+            return true;
+        }
+
+        false
+    }
+
     pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool {
         match (left, right) {
             (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true,
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 5106c39b5c6..9fa28e137f9 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -1539,9 +1539,13 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
     None
 }
 
-/// Returns `true` if the lint is allowed in the current context
+/// Returns `true` if the lint is allowed in the current context. This is useful for
+/// skipping long running code when it's unnecessary
 ///
-/// Useful for skipping long running code when it's unnecessary
+/// This function should check the lint level for the same node, that the lint will
+/// be emitted at. If the information is buffered to be emitted at a later point, please
+/// make sure to use `span_lint_hir` functions to emit the lint. This ensures that
+/// expectations at the checked nodes will be fulfilled.
 pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
     cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
 }
@@ -2058,6 +2062,21 @@ pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
     (e, count)
 }
 
+/// Peels off all references on the type. Returns the underlying type and the number of references
+/// removed.
+pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) {
+    let mut count = 0;
+    loop {
+        match &ty.kind {
+            TyKind::Rptr(_, ref_ty) => {
+                ty = ref_ty.ty;
+                count += 1;
+            },
+            _ => break (ty, count),
+        }
+    }
+}
+
 /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
 /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
 pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
@@ -2110,7 +2129,7 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn(
                 }
             }
             names.sort_unstable();
-            f(&*entry.insert(names))
+            f(entry.insert(names))
         },
     }
 }
@@ -2168,6 +2187,50 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
             && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
 }
 
+/// Walks the HIR tree from the given expression, up to the node where the value produced by the
+/// expression is consumed. Calls the function for every node encountered this way until it returns
+/// `Some`.
+///
+/// This allows walking through `if`, `match`, `break`, block expressions to find where the value
+/// produced by the expression is consumed.
+pub fn walk_to_expr_usage<'tcx, T>(
+    cx: &LateContext<'tcx>,
+    e: &Expr<'tcx>,
+    mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>,
+) -> Option<T> {
+    let map = cx.tcx.hir();
+    let mut iter = map.parent_iter(e.hir_id);
+    let mut child_id = e.hir_id;
+
+    while let Some((parent_id, parent)) = iter.next() {
+        if let Some(x) = f(parent, child_id) {
+            return Some(x);
+        }
+        let parent = match parent {
+            Node::Expr(e) => e,
+            Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
+                child_id = parent_id;
+                continue;
+            },
+            Node::Arm(a) if a.body.hir_id == child_id => {
+                child_id = parent_id;
+                continue;
+            },
+            _ => return None,
+        };
+        match parent.kind {
+            ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
+            ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
+                child_id = id;
+                iter = map.parent_iter(id);
+            },
+            ExprKind::Block(..) => child_id = parent_id,
+            _ => return None,
+        }
+    }
+    None
+}
+
 macro_rules! op_utils {
     ($($name:ident $assign:ident)*) => {
         /// Binary operation traits like `LangItem::Add`
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index b9ec2c19fdd..b09c929f76e 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -12,8 +12,8 @@ macro_rules! msrv_aliases {
 
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
-    1,53,0 { OR_PATTERNS, MANUAL_BITS }
-    1,52,0 { STR_SPLIT_ONCE }
+    1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN }
+    1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
     1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
     1,50,0 { BOOL_THEN }
     1,47,0 { TAU }
@@ -23,14 +23,15 @@ msrv_aliases! {
     1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
     1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
     1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
-    1,38,0 { POINTER_CAST }
+    1,38,0 { POINTER_CAST, REM_EUCLID }
     1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
     1,36,0 { ITERATOR_COPIED }
     1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
     1,34,0 { TRY_FROM }
     1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
     1,28,0 { FROM_BOOL }
-    1,26,0 { RANGE_INCLUSIVE }
+    1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
+    1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
     1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
     1,16,0 { STR_REPEAT }
     1,24,0 { IS_ASCII_DIGIT }
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 89789c3d851..6542e77113b 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -21,8 +21,14 @@ pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
 pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
 pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
 pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
+pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"];
 pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
 pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"];
+pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
+pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
+pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
+pub const CORE_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"];
 pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
 pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
 pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
@@ -50,6 +56,7 @@ pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWri
 pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
 pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
 pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"];
 #[cfg(feature = "internal")]
 pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
 #[cfg(feature = "internal")]
@@ -62,6 +69,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
 pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
 pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
 pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"];
+pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
 pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
 #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
 pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
@@ -83,8 +91,6 @@ pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
 pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
 pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
 pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
-pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"];
-pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"];
 pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
 pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
 pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
@@ -144,6 +150,7 @@ pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_p
 pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
 pub const SLICE_GET: [&str; 4] = ["core", "slice", "<impl [T]>", "get"];
 pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_INTO: [&str; 4] = ["core", "slice", "<impl [T]>", "iter"];
 pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
 pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
 pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
@@ -153,6 +160,7 @@ pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_s
 pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
 pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"];
 pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"];
+pub const STR_CHARS: [&str; 4] = ["core", "str", "<impl str>", "chars"];
 pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
 pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
 pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
@@ -178,6 +186,7 @@ pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_wri
 pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
 pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
 pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
 pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
 pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
 pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs
index 227e97d37ec..6ca36eed4e6 100644
--- a/clippy_utils/src/ty.rs
+++ b/clippy_utils/src/ty.rs
@@ -2,19 +2,20 @@
 
 #![allow(clippy::module_name_repetitions)]
 
+use core::ops::ControlFlow;
 use rustc_ast::ast::Mutability;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir as hir;
-use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
 use rustc_hir::def_id::DefId;
-use rustc_hir::{Expr, LangItem, TyKind, Unsafety};
+use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::LateContext;
 use rustc_middle::mir::interpret::{ConstValue, Scalar};
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
 use rustc_middle::ty::{
-    self, AdtDef, Binder, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy,
-    VariantDiscr,
+    self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy,
+    Region, RegionKind, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDef, VariantDiscr,
 };
 use rustc_span::symbol::Ident;
 use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@@ -501,16 +502,46 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(P
 #[derive(Clone, Copy)]
 pub enum ExprFnSig<'tcx> {
     Sig(Binder<'tcx, FnSig<'tcx>>),
-    Closure(Binder<'tcx, FnSig<'tcx>>),
+    Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),
     Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>),
 }
 impl<'tcx> ExprFnSig<'tcx> {
-    /// Gets the argument type at the given offset.
-    pub fn input(self, i: usize) -> Binder<'tcx, Ty<'tcx>> {
+    /// Gets the argument type at the given offset. This will return `None` when the index is out of
+    /// bounds only for variadic functions, otherwise this will panic.
+    pub fn input(self, i: usize) -> Option<Binder<'tcx, Ty<'tcx>>> {
         match self {
-            Self::Sig(sig) => sig.input(i),
-            Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
-            Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_fields()[i]),
+            Self::Sig(sig) => {
+                if sig.c_variadic() {
+                    sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose()
+                } else {
+                    Some(sig.input(i))
+                }
+            },
+            Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])),
+            Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])),
+        }
+    }
+
+    /// Gets the argument type at the given offset. For closures this will also get the type as
+    /// written. This will return `None` when the index is out of bounds only for variadic
+    /// functions, otherwise this will panic.
+    pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> {
+        match self {
+            Self::Sig(sig) => {
+                if sig.c_variadic() {
+                    sig.inputs()
+                        .map_bound(|inputs| inputs.get(i).copied())
+                        .transpose()
+                        .map(|arg| (None, arg))
+                } else {
+                    Some((None, sig.input(i)))
+                }
+            },
+            Self::Closure(decl, sig) => Some((
+                decl.and_then(|decl| decl.inputs.get(i)),
+                sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
+            )),
+            Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))),
         }
     }
 
@@ -518,7 +549,7 @@ impl<'tcx> ExprFnSig<'tcx> {
     /// specified.
     pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
         match self {
-            Self::Sig(sig) | Self::Closure(sig) => Some(sig.output()),
+            Self::Sig(sig) | Self::Closure(_, sig) => Some(sig.output()),
             Self::Trait(_, output) => output,
         }
     }
@@ -529,74 +560,123 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnS
     if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) {
         Some(ExprFnSig::Sig(cx.tcx.fn_sig(id)))
     } else {
-        let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs();
-        match *ty.kind() {
-            ty::Closure(_, subs) => Some(ExprFnSig::Closure(subs.as_closure().sig())),
-            ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))),
-            ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)),
-            ty::Dynamic(bounds, _) => {
-                let lang_items = cx.tcx.lang_items();
-                match bounds.principal() {
-                    Some(bound)
-                        if Some(bound.def_id()) == lang_items.fn_trait()
-                            || Some(bound.def_id()) == lang_items.fn_once_trait()
-                            || Some(bound.def_id()) == lang_items.fn_mut_trait() =>
-                    {
-                        let output = bounds
-                            .projection_bounds()
-                            .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
-                            .map(|p| p.map_bound(|p| p.term.ty().expect("return type was a const")));
-                        Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output))
-                    },
-                    _ => None,
+        ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs())
+    }
+}
+
+fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
+    match *ty.kind() {
+        ty::Closure(id, subs) => {
+            let decl = id
+                .as_local()
+                .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id)));
+            Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
+        },
+        ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))),
+        ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)),
+        ty::Dynamic(bounds, _) => {
+            let lang_items = cx.tcx.lang_items();
+            match bounds.principal() {
+                Some(bound)
+                    if Some(bound.def_id()) == lang_items.fn_trait()
+                        || Some(bound.def_id()) == lang_items.fn_once_trait()
+                        || Some(bound.def_id()) == lang_items.fn_mut_trait() =>
+                {
+                    let output = bounds
+                        .projection_bounds()
+                        .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
+                        .map(|p| p.map_bound(|p| p.term.ty().unwrap()));
+                    Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output))
+                },
+                _ => None,
+            }
+        },
+        ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) {
+            Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty),
+            _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)),
+        },
+        ty::Param(_) => sig_from_bounds(cx, ty),
+        _ => None,
+    }
+}
+
+fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
+    let mut inputs = None;
+    let mut output = None;
+    let lang_items = cx.tcx.lang_items();
+
+    for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) {
+        match pred.kind().skip_binder() {
+            PredicateKind::Trait(p)
+                if (lang_items.fn_trait() == Some(p.def_id())
+                    || lang_items.fn_mut_trait() == Some(p.def_id())
+                    || lang_items.fn_once_trait() == Some(p.def_id()))
+                    && p.self_ty() == ty =>
+            {
+                if inputs.is_some() {
+                    // Multiple different fn trait impls. Is this even allowed?
+                    return None;
                 }
+                inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1)));
             },
-            ty::Param(_) | ty::Projection(..) => {
-                let mut inputs = None;
-                let mut output = None;
-                let lang_items = cx.tcx.lang_items();
-
-                for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) {
-                    let mut is_input = false;
-                    if let Some(ty) = pred
-                        .kind()
-                        .map_bound(|pred| match pred {
-                            PredicateKind::Trait(p)
-                                if (lang_items.fn_trait() == Some(p.def_id())
-                                    || lang_items.fn_mut_trait() == Some(p.def_id())
-                                    || lang_items.fn_once_trait() == Some(p.def_id()))
-                                    && p.self_ty() == ty =>
-                            {
-                                is_input = true;
-                                Some(p.trait_ref.substs.type_at(1))
-                            },
-                            PredicateKind::Projection(p)
-                                if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
-                                    && p.projection_ty.self_ty() == ty =>
-                            {
-                                is_input = false;
-                                p.term.ty()
-                            },
-                            _ => None,
-                        })
-                        .transpose()
-                    {
-                        if is_input && inputs.is_none() {
-                            inputs = Some(ty);
-                        } else if !is_input && output.is_none() {
-                            output = Some(ty);
-                        } else {
-                            // Multiple different fn trait impls. Is this even allowed?
-                            return None;
-                        }
-                    }
+            PredicateKind::Projection(p)
+                if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
+                    && p.projection_ty.self_ty() == ty =>
+            {
+                if output.is_some() {
+                    // Multiple different fn trait impls. Is this even allowed?
+                    return None;
                 }
+                output = Some(pred.kind().rebind(p.term.ty().unwrap()));
+            },
+            _ => (),
+        }
+    }
+
+    inputs.map(|ty| ExprFnSig::Trait(ty, output))
+}
+
+fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> {
+    let mut inputs = None;
+    let mut output = None;
+    let lang_items = cx.tcx.lang_items();
 
-                inputs.map(|ty| ExprFnSig::Trait(ty, output))
+    for pred in cx
+        .tcx
+        .bound_explicit_item_bounds(ty.item_def_id)
+        .transpose_iter()
+        .map(|x| x.map_bound(|(p, _)| p))
+    {
+        match pred.0.kind().skip_binder() {
+            PredicateKind::Trait(p)
+                if (lang_items.fn_trait() == Some(p.def_id())
+                    || lang_items.fn_mut_trait() == Some(p.def_id())
+                    || lang_items.fn_once_trait() == Some(p.def_id())) =>
+            {
+                if inputs.is_some() {
+                    // Multiple different fn trait impls. Is this even allowed?
+                    return None;
+                }
+                inputs = Some(
+                    pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
+                        .subst(cx.tcx, ty.substs),
+                );
             },
-            _ => None,
+            PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => {
+                if output.is_some() {
+                    // Multiple different fn trait impls. Is this even allowed?
+                    return None;
+                }
+                output = Some(
+                    pred.map_bound(|pred| pred.kind().rebind(p.term.ty().unwrap()))
+                        .subst(cx.tcx, ty.substs),
+                );
+            },
+            _ => (),
         }
     }
+
+    inputs.map(|ty| ExprFnSig::Trait(ty, output))
 }
 
 #[derive(Clone, Copy)]
@@ -667,3 +747,45 @@ pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
         false
     }
 }
+
+pub fn for_each_top_level_late_bound_region<B>(
+    ty: Ty<'_>,
+    f: impl FnMut(BoundRegion) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+    struct V<F> {
+        index: u32,
+        f: F,
+    }
+    impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> {
+        type BreakTy = B;
+        fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+            if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index {
+                (self.f)(bound)
+            } else {
+                ControlFlow::Continue(())
+            }
+        }
+        fn visit_binder<T: TypeFoldable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> {
+            self.index += 1;
+            let res = t.super_visit_with(self);
+            self.index -= 1;
+            res
+        }
+    }
+    ty.visit_with(&mut V { index: 0, f })
+}
+
+/// Gets the struct or enum variant from the given `Res`
+pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> {
+    match res {
+        Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()),
+        Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)),
+        Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()),
+        Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => {
+            let var_id = cx.tcx.parent(id);
+            Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id))
+        },
+        Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()),
+        _ => None,
+    }
+}
diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs
index b6c8f1d516e..68cfa8c1aa8 100644
--- a/clippy_utils/src/visitors.rs
+++ b/clippy_utils/src/visitors.rs
@@ -1,16 +1,18 @@
+use crate::ty::needs_ordered_drop;
 use crate::{get_enclosing_block, path_to_local_id};
 use core::ops::ControlFlow;
 use rustc_hir as hir;
-use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
 use rustc_hir::{
-    Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource,
-    Unsafety,
+    Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp,
+    UnsafeSource, Unsafety,
 };
 use rustc_lint::LateContext;
 use rustc_middle::hir::map::Map;
 use rustc_middle::hir::nested_filter;
-use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::{self, Ty, TypeckResults};
 
 /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
 /// bodies (i.e. closures) are visited.
@@ -494,3 +496,124 @@ pub fn for_each_local_use_after_expr<'tcx, B>(
         ControlFlow::Continue(())
     }
 }
+
+// Calls the given function for every unconsumed temporary created by the expression. Note the
+// function is only guaranteed to be called for types which need to be dropped, but it may be called
+// for other types.
+pub fn for_each_unconsumed_temporary<'tcx, B>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx Expr<'tcx>,
+    mut f: impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+    // Todo: Handle partially consumed values.
+    fn helper<'tcx, B>(
+        typeck: &'tcx TypeckResults<'tcx>,
+        consume: bool,
+        e: &'tcx Expr<'tcx>,
+        f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
+    ) -> ControlFlow<B> {
+        if !consume
+            || matches!(
+                typeck.expr_adjustments(e),
+                [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_))
+            )
+        {
+            match e.kind {
+                ExprKind::Path(QPath::Resolved(None, p))
+                    if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) =>
+                {
+                    f(typeck.expr_ty(e))?;
+                },
+                ExprKind::Path(_)
+                | ExprKind::Unary(UnOp::Deref, _)
+                | ExprKind::Index(..)
+                | ExprKind::Field(..)
+                | ExprKind::AddrOf(..) => (),
+                _ => f(typeck.expr_ty(e))?,
+            }
+        }
+        match e.kind {
+            ExprKind::AddrOf(_, _, e)
+            | ExprKind::Field(e, _)
+            | ExprKind::Unary(UnOp::Deref, e)
+            | ExprKind::Match(e, ..)
+            | ExprKind::Let(&Let { init: e, .. }) => {
+                helper(typeck, false, e, f)?;
+            },
+            ExprKind::Block(&Block { expr: Some(e), .. }, _)
+            | ExprKind::Box(e)
+            | ExprKind::Cast(e, _)
+            | ExprKind::Unary(_, e) => {
+                helper(typeck, true, e, f)?;
+            },
+            ExprKind::Call(callee, args) => {
+                helper(typeck, true, callee, f)?;
+                for arg in args {
+                    helper(typeck, true, arg, f)?;
+                }
+            },
+            ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => {
+                for arg in args {
+                    helper(typeck, true, arg, f)?;
+                }
+            },
+            ExprKind::Index(borrowed, consumed)
+            | ExprKind::Assign(borrowed, consumed, _)
+            | ExprKind::AssignOp(_, borrowed, consumed) => {
+                helper(typeck, false, borrowed, f)?;
+                helper(typeck, true, consumed, f)?;
+            },
+            ExprKind::Binary(_, lhs, rhs) => {
+                helper(typeck, true, lhs, f)?;
+                helper(typeck, true, rhs, f)?;
+            },
+            ExprKind::Struct(_, fields, default) => {
+                for field in fields {
+                    helper(typeck, true, field.expr, f)?;
+                }
+                if let Some(default) = default {
+                    helper(typeck, false, default, f)?;
+                }
+            },
+            ExprKind::If(cond, then, else_expr) => {
+                helper(typeck, true, cond, f)?;
+                helper(typeck, true, then, f)?;
+                if let Some(else_expr) = else_expr {
+                    helper(typeck, true, else_expr, f)?;
+                }
+            },
+            ExprKind::Type(e, _) => {
+                helper(typeck, consume, e, f)?;
+            },
+
+            // Either drops temporaries, jumps out of the current expression, or has no sub expression.
+            ExprKind::DropTemps(_)
+            | ExprKind::Ret(_)
+            | ExprKind::Break(..)
+            | ExprKind::Yield(..)
+            | ExprKind::Block(..)
+            | ExprKind::Loop(..)
+            | ExprKind::Repeat(..)
+            | ExprKind::Lit(_)
+            | ExprKind::ConstBlock(_)
+            | ExprKind::Closure { .. }
+            | ExprKind::Path(_)
+            | ExprKind::Continue(_)
+            | ExprKind::InlineAsm(_)
+            | ExprKind::Err => (),
+        }
+        ControlFlow::Continue(())
+    }
+    helper(cx.typeck_results(), true, e, &mut f)
+}
+
+pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
+    for_each_unconsumed_temporary(cx, e, |ty| {
+        if needs_ordered_drop(cx, ty) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
+        }
+    })
+    .is_break()
+}