about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src
diff options
context:
space:
mode:
authorDeadbeef <ent3rm4n@gmail.com>2021-12-09 22:42:17 +0800
committerDeadbeef <ent3rm4n@gmail.com>2022-02-12 19:24:08 +1100
commitf7f0f843b70f1b6c4ac9e22789d93d7bf5569dc3 (patch)
treef725f2399e03cccf9f4faf4babd7593705a21222 /compiler/rustc_const_eval/src
parent1b0dcdc341ea3659fd8d91ff85605069098ca34b (diff)
downloadrust-f7f0f843b70f1b6c4ac9e22789d93d7bf5569dc3.tar.gz
rust-f7f0f843b70f1b6c4ac9e22789d93d7bf5569dc3.zip
Improve error messages even more
Diffstat (limited to 'compiler/rustc_const_eval/src')
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs30
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs337
-rw-r--r--compiler/rustc_const_eval/src/util/call_kind.rs145
-rw-r--r--compiler/rustc_const_eval/src/util/mod.rs2
4 files changed, 388 insertions, 126 deletions
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index ec96064222b..bd16a8853a6 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -293,13 +293,13 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
     }
 
     /// Emits an error if an expression cannot be evaluated in the current context.
-    pub fn check_op(&mut self, op: impl NonConstOp) {
+    pub fn check_op(&mut self, op: impl NonConstOp<'tcx>) {
         self.check_op_spanned(op, self.span);
     }
 
     /// Emits an error at the given `span` if an expression cannot be evaluated in the current
     /// context.
-    pub fn check_op_spanned<O: NonConstOp>(&mut self, op: O, span: Span) {
+    pub fn check_op_spanned<O: NonConstOp<'tcx>>(&mut self, op: O, span: Span) {
         let gate = match op.status_in_item(self.ccx) {
             Status::Allowed => return,
 
@@ -773,7 +773,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
         self.super_terminator(terminator, location);
 
         match &terminator.kind {
-            TerminatorKind::Call { func, args, .. } => {
+            TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
                 let ConstCx { tcx, body, param_env, .. } = *self.ccx;
                 let caller = self.def_id().to_def_id();
 
@@ -797,7 +797,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                 if let Some(trait_id) = tcx.trait_of_item(callee) {
                     trace!("attempting to call a trait method");
                     if !self.tcx.features().const_trait_impl {
-                        self.check_op(ops::FnCallNonConst(callee, substs));
+                        self.check_op(ops::FnCallNonConst {
+                            caller,
+                            callee,
+                            substs,
+                            span: *fn_span,
+                            from_hir_call: *from_hir_call,
+                        });
                         return;
                     }
 
@@ -856,7 +862,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             }
 
                             if !nonconst_call_permission {
-                                self.check_op(ops::FnCallNonConst(callee, substs));
+                                self.check_op(ops::FnCallNonConst {
+                                    caller,
+                                    callee,
+                                    substs,
+                                    span: *fn_span,
+                                    from_hir_call: *from_hir_call,
+                                });
                                 return;
                             }
                         }
@@ -925,7 +937,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     }
 
                     if !nonconst_call_permission {
-                        self.check_op(ops::FnCallNonConst(callee, substs));
+                        self.check_op(ops::FnCallNonConst {
+                            caller,
+                            callee,
+                            substs,
+                            span: *fn_span,
+                            from_hir_call: *from_hir_call,
+                        });
                         return;
                     }
                 }
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index a6da3ebba6e..c26b1e550ba 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -3,14 +3,20 @@
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
+use rustc_middle::mir;
+use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
-use rustc_middle::{mir, ty::AssocKind};
+use rustc_middle::ty::{suggest_constraining_type_param, Adt, Param, TraitPredicate, Ty};
+use rustc_middle::ty::{Binder, BoundConstness, ImplPolarity, TraitRef};
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::sym;
-use rustc_span::{symbol::Ident, Span, Symbol};
-use rustc_span::{BytePos, Pos};
+use rustc_span::{BytePos, Pos, Span, Symbol};
+use rustc_trait_selection::traits::SelectionContext;
 
 use super::ConstCx;
+use crate::util::{call_kind, CallDesugaringKind, CallKind};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Status {
@@ -29,9 +35,9 @@ pub enum DiagnosticImportance {
 }
 
 /// An operation that is not *always* allowed in a const context.
-pub trait NonConstOp: std::fmt::Debug {
+pub trait NonConstOp<'tcx>: std::fmt::Debug {
     /// Returns an enum indicating whether this operation is allowed within the given item.
-    fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
+    fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
         Status::Forbidden
     }
 
@@ -39,13 +45,13 @@ pub trait NonConstOp: std::fmt::Debug {
         DiagnosticImportance::Primary
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx>;
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx>;
 }
 
 #[derive(Debug)]
 pub struct FloatingPointOp;
-impl NonConstOp for FloatingPointOp {
-    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for FloatingPointOp {
+    fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
         if ccx.const_kind() == hir::ConstContext::ConstFn {
             Status::Unstable(sym::const_fn_floating_point_arithmetic)
         } else {
@@ -53,7 +59,7 @@ impl NonConstOp for FloatingPointOp {
         }
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         feature_err(
             &ccx.tcx.sess.parse_sess,
             sym::const_fn_floating_point_arithmetic,
@@ -66,40 +72,120 @@ impl NonConstOp for FloatingPointOp {
 /// A function call where the callee is a pointer.
 #[derive(Debug)]
 pub struct FnCallIndirect;
-impl NonConstOp for FnCallIndirect {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         ccx.tcx.sess.struct_span_err(span, "function pointers are not allowed in const fn")
     }
 }
 
 /// A function call where the callee is not marked as `const`.
-#[derive(Debug)]
-pub struct FnCallNonConst<'tcx>(pub DefId, pub SubstsRef<'tcx>);
-impl<'a> NonConstOp for FnCallNonConst<'a> {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
-        let FnCallNonConst(def_id, substs) = *self;
-        let mut err = struct_span_err!(
-            ccx.tcx.sess,
-            span,
-            E0015,
-            "cannot call non-const fn `{}` in {}s",
-            ccx.tcx.def_path_str_with_substs(def_id, substs),
-            ccx.const_kind()
-        );
-        err.note(&format!(
-            "calls in {}s are limited to constant functions, \
-             tuple structs and tuple variants",
-            ccx.const_kind(),
-        ));
+#[derive(Debug, Clone, Copy)]
+pub struct FnCallNonConst<'tcx> {
+    pub caller: DefId,
+    pub callee: DefId,
+    pub substs: SubstsRef<'tcx>,
+    pub span: Span,
+    pub from_hir_call: bool,
+}
 
-        if let Some(trait_def_id) = ccx.tcx.lang_items().eq_trait() {
-            if let Some(eq_item) = ccx.tcx.associated_items(trait_def_id).find_by_name_and_kind(
-                ccx.tcx,
-                Ident::with_dummy_span(sym::eq),
-                AssocKind::Fn,
-                trait_def_id,
-            ) {
-                if callee == eq_item.def_id && substs.len() == 2 {
+impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, _: Span) -> DiagnosticBuilder<'tcx> {
+        let FnCallNonConst { caller, callee, substs, span, from_hir_call } = *self;
+        let ConstCx { tcx, param_env, .. } = *ccx;
+
+        let diag_trait = |mut err, self_ty: Ty<'_>, trait_id| {
+            let trait_ref = TraitRef::from_method(tcx, trait_id, substs);
+
+            match self_ty.kind() {
+                Param(param_ty) => {
+                    debug!(?param_ty);
+                    if let Some(generics) = caller
+                        .as_local()
+                        .map(|id| tcx.hir().local_def_id_to_hir_id(id))
+                        .map(|id| tcx.hir().get(id))
+                        .as_ref()
+                        .and_then(|node| node.generics())
+                    {
+                        let constraint = with_no_trimmed_paths(|| {
+                            format!("~const {}", trait_ref.print_only_trait_path())
+                        });
+                        suggest_constraining_type_param(
+                            tcx,
+                            generics,
+                            &mut err,
+                            &param_ty.name.as_str(),
+                            &constraint,
+                            None,
+                        );
+                    }
+                }
+                Adt(..) => {
+                    let obligation = Obligation::new(
+                        ObligationCause::dummy(),
+                        param_env,
+                        Binder::dummy(TraitPredicate {
+                            trait_ref,
+                            constness: BoundConstness::ConstIfConst,
+                            polarity: ImplPolarity::Positive,
+                        }),
+                    );
+
+                    let implsrc = tcx.infer_ctxt().enter(|infcx| {
+                        let mut selcx = SelectionContext::new(&infcx);
+                        selcx.select(&obligation)
+                    });
+
+                    if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
+                        let span =
+                            tcx.sess.source_map().guess_head_span(tcx.def_span(data.impl_def_id));
+                        err.span_note(span, "impl defined here, but it is not `const`");
+                    }
+                }
+                _ => {}
+            }
+
+            err
+        };
+
+        let call_kind = call_kind(tcx, ccx.param_env, callee, substs, span, from_hir_call, None);
+
+        debug!(?call_kind);
+
+        let mut err = match call_kind {
+            CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
+                macro_rules! error {
+                    ($fmt:literal) => {
+                        struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind(),)
+                    };
+                }
+
+                let err = match kind {
+                    CallDesugaringKind::ForLoopIntoIter => {
+                        error!("cannot convert `{}` into an iterator in {}s")
+                    }
+                    CallDesugaringKind::QuestionBranch => {
+                        error!("`?` cannot determine the branch of `{}` in {}s")
+                    }
+                    CallDesugaringKind::QuestionFromResidual => {
+                        error!("`?` cannot convert from residual of `{}` in {}s")
+                    }
+                    CallDesugaringKind::TryBlockFromOutput => {
+                        error!("`try` block cannot convert `{}` to the result in {}s")
+                    }
+                };
+
+                diag_trait(err, self_ty, kind.trait_def_id(tcx))
+            }
+            CallKind::Operator { trait_id, self_ty, .. } => {
+                let mut err = struct_span_err!(
+                    tcx.sess,
+                    span,
+                    E0015,
+                    "cannot call non-const operator in {}s",
+                    ccx.const_kind()
+                );
+
+                if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
                     match (substs[0].unpack(), substs[1].unpack()) {
                         (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty))
                             if self_ty == rhs_ty
@@ -137,8 +223,43 @@ impl<'a> NonConstOp for FnCallNonConst<'a> {
                         _ => {}
                     }
                 }
+
+                diag_trait(err, self_ty, trait_id)
             }
-        }
+            CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
+                let mut err = struct_span_err!(
+                    tcx.sess,
+                    span,
+                    E0015,
+                    "cannot perform deref coercion on `{}` in {}s",
+                    self_ty,
+                    ccx.const_kind()
+                );
+
+                err.note(&format!("attempting to deref into `{}`", deref_target_ty));
+
+                // Check first whether the source is accessible (issue #87060)
+                if tcx.sess.source_map().span_to_snippet(deref_target).is_ok() {
+                    err.span_note(deref_target, "deref defined here");
+                }
+
+                diag_trait(err, self_ty, tcx.lang_items().deref_trait().unwrap())
+            }
+            _ => struct_span_err!(
+                ccx.tcx.sess,
+                span,
+                E0015,
+                "cannot call non-const fn `{}` in {}s",
+                ccx.tcx.def_path_str_with_substs(callee, substs),
+                ccx.const_kind(),
+            ),
+        };
+
+        err.note(&format!(
+            "calls in {}s are limited to constant functions, \
+             tuple structs and tuple variants",
+            ccx.const_kind(),
+        ));
 
         err
     }
@@ -150,8 +271,8 @@ impl<'a> NonConstOp for FnCallNonConst<'a> {
 #[derive(Debug)]
 pub struct FnCallUnstable(pub DefId, pub Option<Symbol>);
 
-impl NonConstOp for FnCallUnstable {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let FnCallUnstable(def_id, feature) = *self;
 
         let mut err = ccx.tcx.sess.struct_span_err(
@@ -176,8 +297,8 @@ impl NonConstOp for FnCallUnstable {
 
 #[derive(Debug)]
 pub struct FnPtrCast;
-impl NonConstOp for FnPtrCast {
-    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for FnPtrCast {
+    fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
         if ccx.const_kind() != hir::ConstContext::ConstFn {
             Status::Allowed
         } else {
@@ -185,7 +306,7 @@ impl NonConstOp for FnPtrCast {
         }
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         feature_err(
             &ccx.tcx.sess.parse_sess,
             sym::const_fn_fn_ptr_basics,
@@ -197,8 +318,8 @@ impl NonConstOp for FnPtrCast {
 
 #[derive(Debug)]
 pub struct Generator(pub hir::GeneratorKind);
-impl NonConstOp for Generator {
-    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for Generator {
+    fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
         if let hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Block) = self.0 {
             Status::Unstable(sym::const_async_blocks)
         } else {
@@ -206,7 +327,7 @@ impl NonConstOp for Generator {
         }
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let msg = format!("{}s are not allowed in {}s", self.0, ccx.const_kind());
         if let hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Block) = self.0 {
             feature_err(&ccx.tcx.sess.parse_sess, sym::const_async_blocks, span, &msg)
@@ -218,8 +339,8 @@ impl NonConstOp for Generator {
 
 #[derive(Debug)]
 pub struct HeapAllocation;
-impl NonConstOp for HeapAllocation {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for HeapAllocation {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -242,8 +363,8 @@ impl NonConstOp for HeapAllocation {
 
 #[derive(Debug)]
 pub struct InlineAsm;
-impl NonConstOp for InlineAsm {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for InlineAsm {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -258,8 +379,8 @@ impl NonConstOp for InlineAsm {
 pub struct LiveDrop {
     pub dropped_at: Option<Span>,
 }
-impl NonConstOp for LiveDrop {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for LiveDrop {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -278,8 +399,8 @@ impl NonConstOp for LiveDrop {
 /// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow never escapes to
 /// the final value of the constant.
 pub struct TransientCellBorrow;
-impl NonConstOp for TransientCellBorrow {
-    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for TransientCellBorrow {
+    fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
         Status::Unstable(sym::const_refs_to_cell)
     }
     fn importance(&self) -> DiagnosticImportance {
@@ -287,7 +408,7 @@ impl NonConstOp for TransientCellBorrow {
         // not additionally emit a feature gate error if activating the feature gate won't work.
         DiagnosticImportance::Secondary
     }
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         feature_err(
             &ccx.tcx.sess.parse_sess,
             sym::const_refs_to_cell,
@@ -302,8 +423,8 @@ impl NonConstOp for TransientCellBorrow {
 /// the final value of the constant, and thus we cannot allow this (for now). We may allow
 /// it in the future for static items.
 pub struct CellBorrow;
-impl NonConstOp for CellBorrow {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for CellBorrow {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -339,8 +460,8 @@ impl NonConstOp for CellBorrow {
 /// static or const items.
 pub struct MutBorrow(pub hir::BorrowKind);
 
-impl NonConstOp for MutBorrow {
-    fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for MutBorrow {
+    fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
         Status::Forbidden
     }
 
@@ -350,7 +471,7 @@ impl NonConstOp for MutBorrow {
         DiagnosticImportance::Secondary
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let raw = match self.0 {
             hir::BorrowKind::Raw => "raw ",
             hir::BorrowKind::Ref => "",
@@ -384,12 +505,12 @@ impl NonConstOp for MutBorrow {
 #[derive(Debug)]
 pub struct TransientMutBorrow(pub hir::BorrowKind);
 
-impl NonConstOp for TransientMutBorrow {
-    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for TransientMutBorrow {
+    fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
         Status::Unstable(sym::const_mut_refs)
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let raw = match self.0 {
             hir::BorrowKind::Raw => "raw ",
             hir::BorrowKind::Ref => "",
@@ -406,8 +527,8 @@ impl NonConstOp for TransientMutBorrow {
 
 #[derive(Debug)]
 pub struct MutDeref;
-impl NonConstOp for MutDeref {
-    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for MutDeref {
+    fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
         Status::Unstable(sym::const_mut_refs)
     }
 
@@ -416,7 +537,7 @@ impl NonConstOp for MutDeref {
         DiagnosticImportance::Secondary
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         feature_err(
             &ccx.tcx.sess.parse_sess,
             sym::const_mut_refs,
@@ -429,8 +550,8 @@ impl NonConstOp for MutDeref {
 /// A call to a `panic()` lang item where the first argument is _not_ a `&str`.
 #[derive(Debug)]
 pub struct PanicNonStr;
-impl NonConstOp for PanicNonStr {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for PanicNonStr {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         ccx.tcx.sess.struct_span_err(
             span,
             "argument to `panic!()` in a const context must have type `&str`",
@@ -443,8 +564,8 @@ impl NonConstOp for PanicNonStr {
 /// allocation base addresses that are not known at compile-time.
 #[derive(Debug)]
 pub struct RawPtrComparison;
-impl NonConstOp for RawPtrComparison {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for RawPtrComparison {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = ccx
             .tcx
             .sess
@@ -459,12 +580,12 @@ impl NonConstOp for RawPtrComparison {
 
 #[derive(Debug)]
 pub struct RawMutPtrDeref;
-impl NonConstOp for RawMutPtrDeref {
+impl<'tcx> NonConstOp<'tcx> for RawMutPtrDeref {
     fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
         Status::Unstable(sym::const_mut_refs)
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         feature_err(
             &ccx.tcx.sess.parse_sess,
             sym::const_mut_refs,
@@ -479,8 +600,8 @@ impl NonConstOp for RawMutPtrDeref {
 /// allocation base addresses that are not known at compile-time.
 #[derive(Debug)]
 pub struct RawPtrToIntCast;
-impl NonConstOp for RawPtrToIntCast {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for RawPtrToIntCast {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = ccx
             .tcx
             .sess
@@ -496,8 +617,8 @@ impl NonConstOp for RawPtrToIntCast {
 /// An access to a (non-thread-local) `static`.
 #[derive(Debug)]
 pub struct StaticAccess;
-impl NonConstOp for StaticAccess {
-    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+impl<'tcx> NonConstOp<'tcx> for StaticAccess {
+    fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
         if let hir::ConstContext::Static(_) = ccx.const_kind() {
             Status::Allowed
         } else {
@@ -505,7 +626,7 @@ impl NonConstOp for StaticAccess {
         }
     }
 
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         let mut err = struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -530,8 +651,8 @@ impl NonConstOp for StaticAccess {
 /// An access to a thread-local `static`.
 #[derive(Debug)]
 pub struct ThreadLocalAccess;
-impl NonConstOp for ThreadLocalAccess {
-    fn build_error<'tcx>(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
+impl<'tcx> NonConstOp<'tcx> for ThreadLocalAccess {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
         struct_span_err!(
             ccx.tcx.sess,
             span,
@@ -548,8 +669,8 @@ pub mod ty {
 
     #[derive(Debug)]
     pub struct MutRef(pub mir::LocalKind);
-    impl NonConstOp for MutRef {
-        fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
+    impl<'tcx> NonConstOp<'tcx> for MutRef {
+        fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
             Status::Unstable(sym::const_mut_refs)
         }
 
@@ -562,11 +683,7 @@ pub mod ty {
             }
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_mut_refs,
@@ -578,7 +695,7 @@ pub mod ty {
 
     #[derive(Debug)]
     pub struct FnPtr(pub mir::LocalKind);
-    impl NonConstOp for FnPtr {
+    impl<'tcx> NonConstOp<'tcx> for FnPtr {
         fn importance(&self) -> DiagnosticImportance {
             match self.0 {
                 mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
@@ -588,7 +705,7 @@ pub mod ty {
             }
         }
 
-        fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+        fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
             if ccx.const_kind() != hir::ConstContext::ConstFn {
                 Status::Allowed
             } else {
@@ -596,11 +713,7 @@ pub mod ty {
             }
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_fn_fn_ptr_basics,
@@ -612,16 +725,12 @@ pub mod ty {
 
     #[derive(Debug)]
     pub struct ImplTrait;
-    impl NonConstOp for ImplTrait {
+    impl<'tcx> NonConstOp<'tcx> for ImplTrait {
         fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
             Status::Unstable(sym::const_impl_trait)
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_impl_trait,
@@ -633,7 +742,7 @@ pub mod ty {
 
     #[derive(Debug)]
     pub struct TraitBound(pub mir::LocalKind);
-    impl NonConstOp for TraitBound {
+    impl<'tcx> NonConstOp<'tcx> for TraitBound {
         fn importance(&self) -> DiagnosticImportance {
             match self.0 {
                 mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
@@ -643,7 +752,7 @@ pub mod ty {
             }
         }
 
-        fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+        fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
             if ccx.const_kind() != hir::ConstContext::ConstFn {
                 Status::Allowed
             } else {
@@ -651,11 +760,7 @@ pub mod ty {
             }
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             let mut err = feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_fn_trait_bound,
@@ -676,7 +781,7 @@ pub mod ty {
 
     #[derive(Debug)]
     pub struct DynTrait(pub mir::LocalKind);
-    impl NonConstOp for DynTrait {
+    impl<'tcx> NonConstOp<'tcx> for DynTrait {
         fn importance(&self) -> DiagnosticImportance {
             match self.0 {
                 mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
@@ -686,7 +791,7 @@ pub mod ty {
             }
         }
 
-        fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
+        fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
             if ccx.const_kind() != hir::ConstContext::ConstFn {
                 Status::Allowed
             } else {
@@ -694,11 +799,7 @@ pub mod ty {
             }
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             let mut err = feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_fn_trait_bound,
@@ -720,16 +821,12 @@ pub mod ty {
     /// A trait bound with the `?const Trait` opt-out
     #[derive(Debug)]
     pub struct TraitBoundNotConst;
-    impl NonConstOp for TraitBoundNotConst {
-        fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+    impl<'tcx> NonConstOp<'tcx> for TraitBoundNotConst {
+        fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
             Status::Unstable(sym::const_trait_bound_opt_out)
         }
 
-        fn build_error<'tcx>(
-            &self,
-            ccx: &ConstCx<'_, 'tcx>,
-            span: Span,
-        ) -> DiagnosticBuilder<'tcx> {
+        fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
             feature_err(
                 &ccx.tcx.sess.parse_sess,
                 sym::const_trait_bound_opt_out,
diff --git a/compiler/rustc_const_eval/src/util/call_kind.rs b/compiler/rustc_const_eval/src/util/call_kind.rs
new file mode 100644
index 00000000000..34925692664
--- /dev/null
+++ b/compiler/rustc_const_eval/src/util/call_kind.rs
@@ -0,0 +1,145 @@
+//! Common logic for borrowck use-after-move errors when moved into a `fn(self)`,
+//! as well as errors when attempting to call a non-const function in a const
+//! context.
+
+use rustc_hir::def_id::DefId;
+use rustc_hir::lang_items::LangItemGroup;
+use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::{self, AssocItemContainer, DefIdTree, Instance, ParamEnv, Ty, TyCtxt};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, DesugaringKind, Span};
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum CallDesugaringKind {
+    /// for _ in x {} calls x.into_iter()
+    ForLoopIntoIter,
+    /// x? calls x.branch()
+    QuestionBranch,
+    /// x? calls type_of(x)::from_residual()
+    QuestionFromResidual,
+    /// try { ..; x } calls type_of(x)::from_output(x)
+    TryBlockFromOutput,
+}
+
+impl CallDesugaringKind {
+    pub fn trait_def_id(self, tcx: TyCtxt<'_>) -> DefId {
+        match self {
+            Self::ForLoopIntoIter => tcx.get_diagnostic_item(sym::IntoIterator).unwrap(),
+            Self::QuestionBranch | Self::TryBlockFromOutput => {
+                tcx.lang_items().try_trait().unwrap()
+            }
+            Self::QuestionFromResidual => tcx.get_diagnostic_item(sym::FromResidual).unwrap(),
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum CallKind<'tcx> {
+    /// A normal method call of the form `receiver.foo(a, b, c)`
+    Normal {
+        self_arg: Option<Ident>,
+        desugaring: Option<(CallDesugaringKind, Ty<'tcx>)>,
+        /// Whether the self type of the method call has an `.as_ref()` method.
+        /// Used for better diagnostics.
+        is_option_or_result: bool,
+    },
+    /// A call to `Fn(..)::call(..)`, desugared from `my_closure(a, b, c)`
+    FnCall(DefId),
+    /// A call to an operator trait, desuraged from operator syntax (e.g. `a << b`)
+    Operator { self_arg: Option<Ident>, trait_id: DefId, self_ty: Ty<'tcx> },
+    DerefCoercion {
+        /// The `Span` of the `Target` associated type
+        /// in the `Deref` impl we are using.
+        deref_target: Span,
+        /// The type `T::Deref` we are dereferencing to
+        deref_target_ty: Ty<'tcx>,
+        self_ty: Ty<'tcx>,
+    },
+}
+
+pub fn call_kind<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ParamEnv<'tcx>,
+    method_did: DefId,
+    method_substs: SubstsRef<'tcx>,
+    fn_call_span: Span,
+    from_hir_call: bool,
+    self_arg: Option<Ident>,
+) -> CallKind<'tcx> {
+    let parent = tcx.opt_associated_item(method_did).and_then(|assoc| match assoc.container {
+        AssocItemContainer::ImplContainer(impl_did) => tcx.trait_id_of_impl(impl_did),
+        AssocItemContainer::TraitContainer(trait_did) => Some(trait_did),
+    });
+
+    let fn_call = (!from_hir_call)
+        .then(|| parent)
+        .flatten()
+        .and_then(|p| tcx.lang_items().group(LangItemGroup::Fn).iter().find(|did| **did == p));
+
+    let operator = (!from_hir_call)
+        .then(|| parent)
+        .flatten()
+        .and_then(|p| tcx.lang_items().group(LangItemGroup::Op).iter().find(|did| **did == p));
+
+    let is_deref = !from_hir_call && tcx.is_diagnostic_item(sym::deref_method, method_did);
+
+    // Check for a 'special' use of 'self' -
+    // an FnOnce call, an operator (e.g. `<<`), or a
+    // deref coercion.
+    let kind = if let Some(&trait_id) = fn_call {
+        Some(CallKind::FnCall(trait_id))
+    } else if let Some(&trait_id) = operator {
+        Some(CallKind::Operator { self_arg, trait_id, self_ty: method_substs.type_at(0) })
+    } else if is_deref {
+        let deref_target = tcx.get_diagnostic_item(sym::deref_target).and_then(|deref_target| {
+            Instance::resolve(tcx, param_env, deref_target, method_substs).transpose()
+        });
+        if let Some(Ok(instance)) = deref_target {
+            let deref_target_ty = instance.ty(tcx, param_env);
+            Some(CallKind::DerefCoercion {
+                deref_target: tcx.def_span(instance.def_id()),
+                deref_target_ty,
+                self_ty: method_substs.type_at(0),
+            })
+        } else {
+            None
+        }
+    } else {
+        None
+    };
+
+    kind.unwrap_or_else(|| {
+        // This isn't a 'special' use of `self`
+        debug!(?method_did, ?fn_call_span);
+        let desugaring = if Some(method_did) == tcx.lang_items().into_iter_fn()
+            && fn_call_span.desugaring_kind() == Some(DesugaringKind::ForLoop)
+        {
+            Some((CallDesugaringKind::ForLoopIntoIter, method_substs.type_at(0)))
+        } else if fn_call_span.desugaring_kind() == Some(DesugaringKind::QuestionMark) {
+            if Some(method_did) == tcx.lang_items().branch_fn() {
+                Some((CallDesugaringKind::QuestionBranch, method_substs.type_at(0)))
+            } else if Some(method_did) == tcx.lang_items().from_residual_fn() {
+                Some((CallDesugaringKind::QuestionFromResidual, method_substs.type_at(0)))
+            } else {
+                None
+            }
+        } else if Some(method_did) == tcx.lang_items().from_output_fn()
+            && fn_call_span.desugaring_kind() == Some(DesugaringKind::TryBlock)
+        {
+            Some((CallDesugaringKind::TryBlockFromOutput, method_substs.type_at(0)))
+        } else {
+            None
+        };
+        let parent_self_ty = tcx
+            .parent(method_did)
+            .filter(|did| tcx.def_kind(*did) == rustc_hir::def::DefKind::Impl)
+            .and_then(|did| match tcx.type_of(did).kind() {
+                ty::Adt(def, ..) => Some(def.did),
+                _ => None,
+            });
+        let is_option_or_result = parent_self_ty.map_or(false, |def_id| {
+            matches!(tcx.get_diagnostic_name(def_id), Some(sym::Option | sym::Result))
+        });
+        CallKind::Normal { self_arg, desugaring, is_option_or_result }
+    })
+}
diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs
index 4a406f8bfd0..a1876bed83e 100644
--- a/compiler/rustc_const_eval/src/util/mod.rs
+++ b/compiler/rustc_const_eval/src/util/mod.rs
@@ -1,8 +1,10 @@
 pub mod aggregate;
 mod alignment;
+mod call_kind;
 pub mod collect_writes;
 mod find_self_call;
 
 pub use self::aggregate::expand_aggregate;
 pub use self::alignment::is_disaligned;
+pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind};
 pub use self::find_self_call::find_self_call;