about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/ast.rs1
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs2
-rw-r--r--compiler/rustc_ast/src/visit.rs1
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs6
-rw-r--r--compiler/rustc_ast_lowering/src/item.rs2
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs8
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs2
-rw-r--r--compiler/rustc_const_eval/src/const_eval/fn_queries.rs1
-rw-r--r--compiler/rustc_const_eval/src/lib.rs1
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs20
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/mod.rs13
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs10
-rw-r--r--compiler/rustc_expand/src/build.rs1
-rw-r--r--compiler/rustc_feature/src/active.rs4
-rw-r--r--compiler/rustc_hir/src/hir.rs1
-rw-r--r--compiler/rustc_hir/src/intravisit.rs1
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs14
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs2
-rw-r--r--compiler/rustc_middle/src/hir/map/mod.rs4
-rw-r--r--compiler/rustc_middle/src/traits/select.rs4
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs6
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs8
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs10
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs7
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs27
-rw-r--r--library/core/src/cmp.rs26
-rw-r--r--library/core/src/lib.rs1
-rw-r--r--src/tools/rustfmt/src/closures.rs19
-rw-r--r--src/tools/rustfmt/src/expr.rs1
-rw-r--r--tests/ui-fulldeps/pprust-expr-roundtrip.rs1
-rw-r--r--tests/ui/check-static-values-constraints.stderr1
-rw-r--r--tests/ui/const-generics/issue-93647.stderr1
-rw-r--r--tests/ui/consts/const-fn-error.stderr2
-rw-r--r--tests/ui/consts/const-for.stderr2
-rw-r--r--tests/ui/consts/invalid-inline-const-in-match-arm.stderr1
-rw-r--r--tests/ui/consts/issue-28113.stderr1
-rw-r--r--tests/ui/consts/issue-56164.stderr1
-rw-r--r--tests/ui/consts/issue-68542-closure-in-array-len.stderr1
-rw-r--r--tests/ui/consts/issue-90870.fixed3
-rw-r--r--tests/ui/consts/issue-90870.rs3
-rw-r--r--tests/ui/consts/issue-90870.stderr7
-rw-r--r--tests/ui/never_type/issue-52443.stderr2
-rw-r--r--tests/ui/parser/recover-quantified-closure.rs2
-rw-r--r--tests/ui/parser/recover-quantified-closure.stderr4
-rw-r--r--tests/ui/resolve/issue-39559-2.stderr2
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/call.rs10
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/const_derives/derive-const-non-const-type.stderr1
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/cross-crate.stock.stderr1
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/gate.rs5
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/gate.stderr12
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.rs15
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.stderr11
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/staged-api-user-crate.stderr1
-rw-r--r--tests/ui/rfc-2632-const-trait-impl/std-impl-gate.stock.stderr1
56 files changed, 249 insertions, 48 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index e656fb3740b..7de594719dd 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1307,6 +1307,7 @@ impl Expr {
 pub struct Closure {
     pub binder: ClosureBinder,
     pub capture_clause: CaptureBy,
+    pub constness: Const,
     pub asyncness: Async,
     pub movability: Movability,
     pub fn_decl: P<FnDecl>,
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index c572171e8f4..77f342d1eb3 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1362,6 +1362,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
         ExprKind::Closure(box Closure {
             binder,
             capture_clause: _,
+            constness,
             asyncness,
             movability: _,
             fn_decl,
@@ -1370,6 +1371,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
             fn_arg_span: _,
         }) => {
             vis.visit_closure_binder(binder);
+            visit_constness(constness, vis);
             vis.visit_asyncness(asyncness);
             vis.visit_fn_decl(fn_decl);
             vis.visit_expr(body);
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index df7145a722a..e8823eff83a 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -836,6 +836,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
             binder,
             capture_clause: _,
             asyncness: _,
+            constness: _,
             movability: _,
             fn_decl,
             body,
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 14f082be9ba..c3611b2f522 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -209,6 +209,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 ExprKind::Closure(box Closure {
                     binder,
                     capture_clause,
+                    constness,
                     asyncness,
                     movability,
                     fn_decl,
@@ -233,6 +234,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                             binder,
                             *capture_clause,
                             e.id,
+                            *constness,
                             *movability,
                             fn_decl,
                             body,
@@ -651,6 +653,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 fn_decl_span: self.lower_span(span),
                 fn_arg_span: None,
                 movability: Some(hir::Movability::Static),
+                constness: hir::Constness::NotConst,
             });
 
             hir::ExprKind::Closure(c)
@@ -890,6 +893,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
         binder: &ClosureBinder,
         capture_clause: CaptureBy,
         closure_id: NodeId,
+        constness: Const,
         movability: Movability,
         decl: &FnDecl,
         body: &Expr,
@@ -927,6 +931,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
             fn_decl_span: self.lower_span(fn_decl_span),
             fn_arg_span: Some(self.lower_span(fn_arg_span)),
             movability: generator_option,
+            constness: self.lower_constness(constness),
         });
 
         hir::ExprKind::Closure(c)
@@ -1041,6 +1046,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
             fn_decl_span: self.lower_span(fn_decl_span),
             fn_arg_span: Some(self.lower_span(fn_arg_span)),
             movability: None,
+            constness: hir::Constness::NotConst,
         });
         hir::ExprKind::Closure(c)
     }
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index ea30bed5ace..065779d0670 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -1239,7 +1239,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
         }
     }
 
-    fn lower_constness(&mut self, c: Const) -> hir::Constness {
+    pub(super) fn lower_constness(&mut self, c: Const) -> hir::Constness {
         match c {
             Const::Yes(_) => hir::Constness::Const,
             Const::No => hir::Constness::NotConst,
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 039338f543c..89ba6f936d1 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -385,6 +385,14 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
             ast::ExprKind::TryBlock(_) => {
                 gate_feature_post!(&self, try_blocks, e.span, "`try` expression is experimental");
             }
+            ast::ExprKind::Closure(box ast::Closure { constness: ast::Const::Yes(_), .. }) => {
+                gate_feature_post!(
+                    &self,
+                    const_closures,
+                    e.span,
+                    "const closures are experimental"
+                );
+            }
             _ => {}
         }
         visit::walk_expr(self, e)
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 3b17f6dd627..b125c6407d0 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -399,6 +399,7 @@ impl<'a> State<'a> {
             ast::ExprKind::Closure(box ast::Closure {
                 binder,
                 capture_clause,
+                constness,
                 asyncness,
                 movability,
                 fn_decl,
@@ -407,6 +408,7 @@ impl<'a> State<'a> {
                 fn_arg_span: _,
             }) => {
                 self.print_closure_binder(binder);
+                self.print_constness(*constness);
                 self.print_movability(*movability);
                 self.print_asyncness(*asyncness);
                 self.print_capture_clause(*capture_clause);
diff --git a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
index f1674d04f8d..351c701305a 100644
--- a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
@@ -41,6 +41,7 @@ fn constness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::Constness {
             };
             if is_const { hir::Constness::Const } else { hir::Constness::NotConst }
         }
+        hir::Node::Expr(e) if let hir::ExprKind::Closure(c) = e.kind => c.constness,
         _ => {
             if let Some(fn_kind) = node.fn_kind() {
                 if fn_kind.constness() == hir::Constness::Const {
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 443c01fdb90..46e7b09a55e 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -20,6 +20,7 @@ Rust MIR: a lowered representation of Rust.
 #![feature(trusted_step)]
 #![feature(try_blocks)]
 #![feature(yeet_expr)]
+#![feature(if_let_guard)]
 #![feature(is_some_and)]
 #![recursion_limit = "256"]
 
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 54213d55a2d..d4c75cd55ce 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -242,7 +242,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
             // impl trait is gone in MIR, so check the return type of a const fn by its signature
             // instead of the type of the return place.
             self.span = body.local_decls[RETURN_PLACE].source_info.span;
-            let return_ty = tcx.fn_sig(def_id).output();
+            let return_ty = self.ccx.fn_sig().output();
             self.check_local_or_return_ty(return_ty.skip_binder(), RETURN_PLACE);
         }
 
@@ -730,6 +730,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             substs,
                             span: *fn_span,
                             from_hir_call: *from_hir_call,
+                            feature: Some(sym::const_trait_impl),
                         });
                         return;
                     }
@@ -782,6 +783,20 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             );
                             return;
                         }
+                        Ok(Some(ImplSource::Closure(data))) => {
+                            if !tcx.is_const_fn_raw(data.closure_def_id) {
+                                self.check_op(ops::FnCallNonConst {
+                                    caller,
+                                    callee,
+                                    substs,
+                                    span: *fn_span,
+                                    from_hir_call: *from_hir_call,
+                                    feature: None,
+                                });
+
+                                return;
+                            }
+                        }
                         Ok(Some(ImplSource::UserDefined(data))) => {
                             let callee_name = tcx.item_name(callee);
                             if let Some(&did) = tcx
@@ -802,6 +817,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                                     substs,
                                     span: *fn_span,
                                     from_hir_call: *from_hir_call,
+                                    feature: None,
                                 });
                                 return;
                             }
@@ -844,6 +860,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                                     substs,
                                     span: *fn_span,
                                     from_hir_call: *from_hir_call,
+                                    feature: None,
                                 });
                                 return;
                             }
@@ -903,6 +920,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             substs,
                             span: *fn_span,
                             from_hir_call: *from_hir_call,
+                            feature: None,
                         });
                         return;
                     }
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
index 0a90572d39e..54868e418c4 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
@@ -8,7 +8,7 @@ use rustc_attr as attr;
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::mir;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_middle::ty::{self, PolyFnSig, TyCtxt};
 use rustc_span::Symbol;
 
 pub use self::qualifs::Qualif;
@@ -64,6 +64,17 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
     fn is_async(&self) -> bool {
         self.tcx.asyncness(self.def_id()).is_async()
     }
+
+    pub fn fn_sig(&self) -> PolyFnSig<'tcx> {
+        let did = self.def_id().to_def_id();
+        if self.tcx.is_closure(did) {
+            let ty = self.tcx.type_of(did);
+            let ty::Closure(_, substs) = ty.kind() else { bug!("type_of closure not ty::Closure") };
+            substs.as_closure().sig()
+        } else {
+            self.tcx.fn_sig(did)
+        }
+    }
 }
 
 pub fn rustc_allow_const_fn_unstable(
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 b19d270e610..0cb5d2ff8c7 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -111,6 +111,7 @@ pub struct FnCallNonConst<'tcx> {
     pub substs: SubstsRef<'tcx>,
     pub span: Span,
     pub from_hir_call: bool,
+    pub feature: Option<Symbol>,
 }
 
 impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
@@ -119,7 +120,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
         ccx: &ConstCx<'_, 'tcx>,
         _: Span,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
-        let FnCallNonConst { caller, callee, substs, span, from_hir_call } = *self;
+        let FnCallNonConst { caller, callee, substs, span, from_hir_call, feature } = *self;
         let ConstCx { tcx, param_env, .. } = *ccx;
 
         let diag_trait = |err, self_ty: Ty<'_>, trait_id| {
@@ -318,6 +319,13 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
             ccx.const_kind(),
         ));
 
+        if let Some(feature) = feature && ccx.tcx.sess.is_nightly_build() {
+            err.help(&format!(
+                "add `#![feature({})]` to the crate attributes to enable",
+                feature,
+            ));
+        }
+
         if let ConstContext::Static(_) = ccx.const_kind() {
             err.note("consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell");
         }
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 93b3af4ab97..9b16e79d49a 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -533,6 +533,7 @@ impl<'a> ExtCtxt<'a> {
             ast::ExprKind::Closure(Box::new(ast::Closure {
                 binder: ast::ClosureBinder::NotPresent,
                 capture_clause: ast::CaptureBy::Ref,
+                constness: ast::Const::No,
                 asyncness: ast::Async::No,
                 movability: ast::Movability::Movable,
                 fn_decl,
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 531ddcc1fa6..196c31302a0 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -339,7 +339,9 @@ declare_features! (
     (active, collapse_debuginfo, "1.65.0", Some(100758), None),
     /// Allows `async {}` expressions in const contexts.
     (active, const_async_blocks, "1.53.0", Some(85368), None),
-    // Allows limiting the evaluation steps of const expressions
+    /// Allows `const || {}` closures in const contexts.
+    (incomplete, const_closures, "CURRENT_RUSTC_VERSION", Some(106003), None),
+    /// Allows limiting the evaluation steps of const expressions
     (active, const_eval_limit, "1.43.0", Some(67217), None),
     /// Allows the definition of `const extern fn` and `const unsafe extern fn`.
     (active, const_extern_fn, "1.40.0", Some(64926), None),
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index bc897ed8112..60f5b79de10 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -938,6 +938,7 @@ pub struct Crate<'hir> {
 pub struct Closure<'hir> {
     pub def_id: LocalDefId,
     pub binder: ClosureBinder,
+    pub constness: Constness,
     pub capture_clause: CaptureBy,
     pub bound_generic_params: &'hir [GenericParam<'hir>],
     pub fn_decl: &'hir FnDecl<'hir>,
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index 6c475b659eb..02641b7cf8f 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -742,6 +742,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
             fn_decl_span: _,
             fn_arg_span: _,
             movability: _,
+            constness: _,
         }) => {
             walk_list!(visitor, visit_generic_param, bound_generic_params);
             visitor.visit_fn(FnKind::Closure, fn_decl, body, expression.span, expression.hir_id)
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index 3e3af8395a1..f74c551a45b 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -1464,6 +1464,7 @@ impl<'a> State<'a> {
             }
             hir::ExprKind::Closure(&hir::Closure {
                 binder,
+                constness,
                 capture_clause,
                 bound_generic_params,
                 fn_decl,
@@ -1474,6 +1475,7 @@ impl<'a> State<'a> {
                 def_id: _,
             }) => {
                 self.print_closure_binder(binder, bound_generic_params);
+                self.print_constness(constness);
                 self.print_capture_clause(capture_clause);
 
                 self.print_closure_params(fn_decl, body);
@@ -2272,10 +2274,7 @@ impl<'a> State<'a> {
     }
 
     pub fn print_fn_header_info(&mut self, header: hir::FnHeader) {
-        match header.constness {
-            hir::Constness::NotConst => {}
-            hir::Constness::Const => self.word_nbsp("const"),
-        }
+        self.print_constness(header.constness);
 
         match header.asyncness {
             hir::IsAsync::NotAsync => {}
@@ -2292,6 +2291,13 @@ impl<'a> State<'a> {
         self.word("fn")
     }
 
+    pub fn print_constness(&mut self, s: hir::Constness) {
+        match s {
+            hir::Constness::NotConst => {}
+            hir::Constness::Const => self.word_nbsp("const"),
+        }
+    }
+
     pub fn print_unsafety(&mut self, s: hir::Unsafety) {
         match s {
             hir::Unsafety::Normal => {}
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index bdc4ae391f0..030328d1e26 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -1686,6 +1686,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             }
 
             ty::Closure(_, substs) => {
+                let constness = self.tcx.constness(def_id.to_def_id());
+                self.tables.constness.set(def_id.to_def_id().index, constness);
                 record!(self.tables.fn_sig[def_id.to_def_id()] <- substs.as_closure().sig());
             }
 
diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs
index 883554f959c..48bae7a2d4e 100644
--- a/compiler/rustc_middle/src/hir/map/mod.rs
+++ b/compiler/rustc_middle/src/hir/map/mod.rs
@@ -485,7 +485,9 @@ impl<'hir> Map<'hir> {
             BodyOwnerKind::Static(mt) => ConstContext::Static(mt),
 
             BodyOwnerKind::Fn if self.tcx.is_constructor(def_id.to_def_id()) => return None,
-            BodyOwnerKind::Fn if self.tcx.is_const_fn_raw(def_id.to_def_id()) => {
+            BodyOwnerKind::Fn | BodyOwnerKind::Closure
+                if self.tcx.is_const_fn_raw(def_id.to_def_id()) =>
+            {
                 ConstContext::ConstFn
             }
             BodyOwnerKind::Fn if self.tcx.is_const_default_method(def_id.to_def_id()) => {
diff --git a/compiler/rustc_middle/src/traits/select.rs b/compiler/rustc_middle/src/traits/select.rs
index ec69864c951..1cc9fd526b4 100644
--- a/compiler/rustc_middle/src/traits/select.rs
+++ b/compiler/rustc_middle/src/traits/select.rs
@@ -131,7 +131,9 @@ pub enum SelectionCandidate<'tcx> {
 
     /// Implementation of a `Fn`-family trait by one of the anonymous types
     /// generated for an `||` expression.
-    ClosureCandidate,
+    ClosureCandidate {
+        is_const: bool,
+    },
 
     /// Implementation of a `Generator` trait by one of the anonymous types
     /// generated for a generator.
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index fa571d480b6..993e95b3514 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -2465,8 +2465,10 @@ impl<'tcx> TyCtxt<'tcx> {
 
     #[inline]
     pub fn is_const_fn_raw(self, def_id: DefId) -> bool {
-        matches!(self.def_kind(def_id), DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..))
-            && self.constness(def_id) == hir::Constness::Const
+        matches!(
+            self.def_kind(def_id),
+            DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) | DefKind::Closure
+        ) && self.constness(def_id) == hir::Constness::Const
     }
 
     #[inline]
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index f5093fb02a8..dd2b03988c3 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1325,7 +1325,10 @@ impl<'a> Parser<'a> {
             self.parse_array_or_repeat_expr(Delimiter::Bracket)
         } else if self.check_path() {
             self.parse_path_start_expr()
-        } else if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) {
+        } else if self.check_keyword(kw::Move)
+            || self.check_keyword(kw::Static)
+            || self.check_const_closure()
+        {
             self.parse_closure_expr()
         } else if self.eat_keyword(kw::If) {
             self.parse_if_expr()
@@ -2065,6 +2068,8 @@ impl<'a> Parser<'a> {
             ClosureBinder::NotPresent
         };
 
+        let constness = self.parse_constness(Case::Sensitive);
+
         let movability =
             if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
 
@@ -2111,6 +2116,7 @@ impl<'a> Parser<'a> {
             ExprKind::Closure(Box::new(ast::Closure {
                 binder,
                 capture_clause,
+                constness,
                 asyncness,
                 movability,
                 fn_decl,
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 49d31981539..2fd2a4e5154 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -736,6 +736,16 @@ impl<'a> Parser<'a> {
         self.check_or_expected(self.token.can_begin_const_arg(), TokenType::Const)
     }
 
+    fn check_const_closure(&self) -> bool {
+        self.is_keyword_ahead(0, &[kw::Const])
+            && self.look_ahead(1, |t| match &t.kind {
+                token::Ident(kw::Move | kw::Static | kw::Async, _)
+                | token::OrOr
+                | token::BinOp(token::Or) => true,
+                _ => false,
+            })
+    }
+
     fn check_inline_const(&self, dist: usize) -> bool {
         self.is_keyword_ahead(dist, &[kw::Const])
             && self.look_ahead(dist + 1, |t| match &t.kind {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index fbb12701d96..706002f79b1 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -498,6 +498,7 @@ symbols! {
         console,
         const_allocate,
         const_async_blocks,
+        const_closures,
         const_compare_raw_pointers,
         const_constructor,
         const_deallocate,
diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
index 170c1673dbd..8c291d1595d 100644
--- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
@@ -255,18 +255,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
         // touch bound regions, they just capture the in-scope
         // type/region parameters
         match *obligation.self_ty().skip_binder().kind() {
-            ty::Closure(_, closure_substs) => {
+            ty::Closure(def_id, closure_substs) => {
+                let is_const = self.tcx().is_const_fn_raw(def_id);
                 debug!(?kind, ?obligation, "assemble_unboxed_candidates");
                 match self.infcx.closure_kind(closure_substs) {
                     Some(closure_kind) => {
                         debug!(?closure_kind, "assemble_unboxed_candidates");
                         if closure_kind.extends(kind) {
-                            candidates.vec.push(ClosureCandidate);
+                            candidates.vec.push(ClosureCandidate { is_const });
                         }
                     }
                     None => {
                         debug!("assemble_unboxed_candidates: closure_kind not yet known");
-                        candidates.vec.push(ClosureCandidate);
+                        candidates.vec.push(ClosureCandidate { is_const });
                     }
                 }
             }
diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
index 15526b34ed2..a41d10f1043 100644
--- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
@@ -84,7 +84,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 ImplSource::Object(data)
             }
 
-            ClosureCandidate => {
+            ClosureCandidate { .. } => {
                 let vtable_closure = self.confirm_closure_candidate(obligation)?;
                 ImplSource::Closure(vtable_closure)
             }
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 2615e262282..305902af7c8 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -1365,15 +1365,18 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                     // const param
                     ParamCandidate(trait_pred) if trait_pred.is_const_if_const() => {}
                     // const projection
-                    ProjectionCandidate(_, ty::BoundConstness::ConstIfConst) => {}
+                    ProjectionCandidate(_, ty::BoundConstness::ConstIfConst)
                     // auto trait impl
-                    AutoImplCandidate => {}
+                    | AutoImplCandidate
                     // generator / future, this will raise error in other places
                     // or ignore error with const_async_blocks feature
-                    GeneratorCandidate => {}
-                    FutureCandidate => {}
+                    | GeneratorCandidate
+                    | FutureCandidate
                     // FnDef where the function is const
-                    FnPointerCandidate { is_const: true } => {}
+                    | FnPointerCandidate { is_const: true }
+                    | ConstDestructCandidate(_)
+                    | ClosureCandidate { is_const: true } => {}
+
                     FnPointerCandidate { is_const: false } => {
                         if let ty::FnDef(def_id, _) = obligation.self_ty().skip_binder().kind() && tcx.trait_of_item(*def_id).is_some() {
                             // Trait methods are not seen as const unless the trait is implemented as const.
@@ -1382,7 +1385,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                             continue
                         }
                     }
-                    ConstDestructCandidate(_) => {}
+
                     _ => {
                         // reject all other types of candidates
                         continue;
@@ -1844,7 +1847,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
             (
                 ParamCandidate(ref cand),
                 ImplCandidate(..)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
@@ -1863,7 +1866,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
             }
             (
                 ImplCandidate(_)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
@@ -1894,7 +1897,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
             (
                 ObjectCandidate(_) | ProjectionCandidate(..),
                 ImplCandidate(..)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
@@ -1907,7 +1910,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
 
             (
                 ImplCandidate(..)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
@@ -1989,7 +1992,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
             // Everything else is ambiguous
             (
                 ImplCandidate(_)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
@@ -1999,7 +2002,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                 | BuiltinCandidate { has_nested: true }
                 | TraitAliasCandidate,
                 ImplCandidate(_)
-                | ClosureCandidate
+                | ClosureCandidate { .. }
                 | GeneratorCandidate
                 | FutureCandidate
                 | FnPointerCandidate { .. }
diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs
index ebf5baa3c02..a7d6fec7d3d 100644
--- a/library/core/src/cmp.rs
+++ b/library/core/src/cmp.rs
@@ -1234,17 +1234,23 @@ where
     F: ~const Destruct,
     K: ~const Destruct,
 {
-    const fn imp<T, F: ~const FnMut(&T) -> K, K: ~const Ord>(
-        f: &mut F,
-        (v1, v2): (&T, &T),
-    ) -> Ordering
-    where
-        T: ~const Destruct,
-        K: ~const Destruct,
-    {
-        f(v1).cmp(&f(v2))
+    cfg_if! {
+        if #[cfg(bootstrap)] {
+            const fn imp<T, F: ~const FnMut(&T) -> K, K: ~const Ord>(
+                f: &mut F,
+                (v1, v2): (&T, &T),
+            ) -> Ordering
+            where
+                T: ~const Destruct,
+                K: ~const Destruct,
+            {
+                f(v1).cmp(&f(v2))
+            }
+            min_by(v1, v2, ConstFnMutClosure::new(&mut f, imp))
+        } else {
+            min_by(v1, v2, const |v1, v2| f(v1).cmp(&f(v2)))
+        }
     }
-    min_by(v1, v2, ConstFnMutClosure::new(&mut f, imp))
 }
 
 /// Compares and returns the maximum of two values.
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index e898bca88e4..8790649abe6 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -191,6 +191,7 @@
 #![feature(cfg_sanitize)]
 #![feature(cfg_target_has_atomic)]
 #![feature(cfg_target_has_atomic_equal_alignment)]
+#![cfg_attr(not(bootstrap), feature(const_closures))]
 #![feature(const_fn_floating_point_arithmetic)]
 #![feature(const_mut_refs)]
 #![feature(const_precise_live_drops)]
diff --git a/src/tools/rustfmt/src/closures.rs b/src/tools/rustfmt/src/closures.rs
index 244d4427c56..8fd0fcf8f5c 100644
--- a/src/tools/rustfmt/src/closures.rs
+++ b/src/tools/rustfmt/src/closures.rs
@@ -26,6 +26,7 @@ use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt};
 
 pub(crate) fn rewrite_closure(
     binder: &ast::ClosureBinder,
+    constness: ast::Const,
     capture: ast::CaptureBy,
     is_async: &ast::Async,
     movability: ast::Movability,
@@ -38,7 +39,7 @@ pub(crate) fn rewrite_closure(
     debug!("rewrite_closure {:?}", body);
 
     let (prefix, extra_offset) = rewrite_closure_fn_decl(
-        binder, capture, is_async, movability, fn_decl, body, span, context, shape,
+        binder, constness, capture, is_async, movability, fn_decl, body, span, context, shape,
     )?;
     // 1 = space between `|...|` and body.
     let body_shape = shape.offset_left(extra_offset)?;
@@ -230,6 +231,7 @@ fn rewrite_closure_block(
 // Return type is (prefix, extra_offset)
 fn rewrite_closure_fn_decl(
     binder: &ast::ClosureBinder,
+    constness: ast::Const,
     capture: ast::CaptureBy,
     asyncness: &ast::Async,
     movability: ast::Movability,
@@ -250,6 +252,12 @@ fn rewrite_closure_fn_decl(
         ast::ClosureBinder::NotPresent => "".to_owned(),
     };
 
+    let const_ = if matches!(constness, ast::Const::Yes(_)) {
+        "const "
+    } else {
+        ""
+    };
+
     let immovable = if movability == ast::Movability::Static {
         "static "
     } else {
@@ -264,7 +272,7 @@ fn rewrite_closure_fn_decl(
     // 4 = "|| {".len(), which is overconservative when the closure consists of
     // a single expression.
     let nested_shape = shape
-        .shrink_left(binder.len() + immovable.len() + is_async.len() + mover.len())?
+        .shrink_left(binder.len() + const_.len() + immovable.len() + is_async.len() + mover.len())?
         .sub_width(4)?;
 
     // 1 = |
@@ -302,7 +310,10 @@ fn rewrite_closure_fn_decl(
         .tactic(tactic)
         .preserve_newline(true);
     let list_str = write_list(&item_vec, &fmt)?;
-    let mut prefix = format!("{}{}{}{}|{}|", binder, immovable, is_async, mover, list_str);
+    let mut prefix = format!(
+        "{}{}{}{}{}|{}|",
+        binder, const_, immovable, is_async, mover, list_str
+    );
 
     if !ret_str.is_empty() {
         if prefix.contains('\n') {
@@ -329,6 +340,7 @@ pub(crate) fn rewrite_last_closure(
     if let ast::ExprKind::Closure(ref closure) = expr.kind {
         let ast::Closure {
             ref binder,
+            constness,
             capture_clause,
             ref asyncness,
             movability,
@@ -349,6 +361,7 @@ pub(crate) fn rewrite_last_closure(
         };
         let (prefix, extra_offset) = rewrite_closure_fn_decl(
             binder,
+            constness,
             capture_clause,
             asyncness,
             movability,
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index 414e767690b..868ff045ab7 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -205,6 +205,7 @@ pub(crate) fn format_expr(
         }
         ast::ExprKind::Closure(ref cl) => closures::rewrite_closure(
             &cl.binder,
+            cl.constness,
             cl.capture_clause,
             &cl.asyncness,
             cl.movability,
diff --git a/tests/ui-fulldeps/pprust-expr-roundtrip.rs b/tests/ui-fulldeps/pprust-expr-roundtrip.rs
index 6dbabc8eb34..7a91dcf0dad 100644
--- a/tests/ui-fulldeps/pprust-expr-roundtrip.rs
+++ b/tests/ui-fulldeps/pprust-expr-roundtrip.rs
@@ -126,6 +126,7 @@ fn iter_exprs(depth: usize, f: &mut dyn FnMut(P<Expr>)) {
                     g(ExprKind::Closure(Box::new(Closure {
                         binder: ClosureBinder::NotPresent,
                         capture_clause: CaptureBy::Value,
+                        constness: Const::No,
                         asyncness: Async::No,
                         movability: Movability::Movable,
                         fn_decl: decl.clone(),
diff --git a/tests/ui/check-static-values-constraints.stderr b/tests/ui/check-static-values-constraints.stderr
index b13700a4ea5..49056678448 100644
--- a/tests/ui/check-static-values-constraints.stderr
+++ b/tests/ui/check-static-values-constraints.stderr
@@ -22,6 +22,7 @@ LL |     field2: SafeEnum::Variant4("str".to_string())
    |                                      ^^^^^^^^^^^
    |
    = note: calls in statics are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
    = note: consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell
 
 error[E0010]: allocations are not allowed in statics
diff --git a/tests/ui/const-generics/issue-93647.stderr b/tests/ui/const-generics/issue-93647.stderr
index e2048ecd60f..18370eea571 100644
--- a/tests/ui/const-generics/issue-93647.stderr
+++ b/tests/ui/const-generics/issue-93647.stderr
@@ -6,6 +6,7 @@ LL |     (||1usize)()
    |
    = note: closures need an RFC before allowed to be called in constants
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/consts/const-fn-error.stderr b/tests/ui/consts/const-fn-error.stderr
index f6b532fb658..f735b3d53ce 100644
--- a/tests/ui/consts/const-fn-error.stderr
+++ b/tests/ui/consts/const-fn-error.stderr
@@ -22,6 +22,7 @@ LL |     for i in 0..x {
 note: impl defined here, but it is not `const`
   --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error[E0658]: mutable references are not allowed in constant functions
   --> $DIR/const-fn-error.rs:5:14
@@ -39,6 +40,7 @@ LL |     for i in 0..x {
    |              ^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to 4 previous errors
 
diff --git a/tests/ui/consts/const-for.stderr b/tests/ui/consts/const-for.stderr
index 294ea627d85..3fb9787c0d8 100644
--- a/tests/ui/consts/const-for.stderr
+++ b/tests/ui/consts/const-for.stderr
@@ -7,6 +7,7 @@ LL |     for _ in 0..5 {}
 note: impl defined here, but it is not `const`
   --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error[E0015]: cannot call non-const fn `<std::ops::Range<i32> as Iterator>::next` in constants
   --> $DIR/const-for.rs:5:14
@@ -15,6 +16,7 @@ LL |     for _ in 0..5 {}
    |              ^^^^
    |
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/consts/invalid-inline-const-in-match-arm.stderr b/tests/ui/consts/invalid-inline-const-in-match-arm.stderr
index ab594c921f9..257ecd7f3cf 100644
--- a/tests/ui/consts/invalid-inline-const-in-match-arm.stderr
+++ b/tests/ui/consts/invalid-inline-const-in-match-arm.stderr
@@ -6,6 +6,7 @@ LL |         const { (|| {})() } => {}
    |
    = note: closures need an RFC before allowed to be called in constants
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/consts/issue-28113.stderr b/tests/ui/consts/issue-28113.stderr
index 7ad1f752eb0..1294cc99bf7 100644
--- a/tests/ui/consts/issue-28113.stderr
+++ b/tests/ui/consts/issue-28113.stderr
@@ -6,6 +6,7 @@ LL |     || -> u8 { 5 }()
    |
    = note: closures need an RFC before allowed to be called in constants
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/consts/issue-56164.stderr b/tests/ui/consts/issue-56164.stderr
index 2579b3e7827..845b23d5d87 100644
--- a/tests/ui/consts/issue-56164.stderr
+++ b/tests/ui/consts/issue-56164.stderr
@@ -6,6 +6,7 @@ LL | const fn foo() { (||{})() }
    |
    = note: closures need an RFC before allowed to be called in constant functions
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: function pointer calls are not allowed in constant functions
   --> $DIR/issue-56164.rs:5:5
diff --git a/tests/ui/consts/issue-68542-closure-in-array-len.stderr b/tests/ui/consts/issue-68542-closure-in-array-len.stderr
index 74fbbc680f7..d23513ed7ff 100644
--- a/tests/ui/consts/issue-68542-closure-in-array-len.stderr
+++ b/tests/ui/consts/issue-68542-closure-in-array-len.stderr
@@ -6,6 +6,7 @@ LL |     a: [(); (|| { 0 })()]
    |
    = note: closures need an RFC before allowed to be called in constants
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/consts/issue-90870.fixed b/tests/ui/consts/issue-90870.fixed
index 0d28e06e532..df44689efed 100644
--- a/tests/ui/consts/issue-90870.fixed
+++ b/tests/ui/consts/issue-90870.fixed
@@ -8,12 +8,14 @@ const fn f(a: &u8, b: &u8) -> bool {
     *a == *b
     //~^ ERROR: cannot call non-const operator in constant functions [E0015]
     //~| HELP: consider dereferencing here
+    //~| HELP: add `#![feature(const_trait_impl)]`
 }
 
 const fn g(a: &&&&i64, b: &&&&i64) -> bool {
     ****a == ****b
     //~^ ERROR: cannot call non-const operator in constant functions [E0015]
     //~| HELP: consider dereferencing here
+    //~| HELP: add `#![feature(const_trait_impl)]`
 }
 
 const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
@@ -21,6 +23,7 @@ const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
         if *l == *r {
         //~^ ERROR: cannot call non-const operator in constant functions [E0015]
         //~| HELP: consider dereferencing here
+        //~| HELP: add `#![feature(const_trait_impl)]`
             a = at;
             b = bt;
         } else {
diff --git a/tests/ui/consts/issue-90870.rs b/tests/ui/consts/issue-90870.rs
index c6bfffd2c5c..676ac73c64d 100644
--- a/tests/ui/consts/issue-90870.rs
+++ b/tests/ui/consts/issue-90870.rs
@@ -8,12 +8,14 @@ const fn f(a: &u8, b: &u8) -> bool {
     a == b
     //~^ ERROR: cannot call non-const operator in constant functions [E0015]
     //~| HELP: consider dereferencing here
+    //~| HELP: add `#![feature(const_trait_impl)]`
 }
 
 const fn g(a: &&&&i64, b: &&&&i64) -> bool {
     a == b
     //~^ ERROR: cannot call non-const operator in constant functions [E0015]
     //~| HELP: consider dereferencing here
+    //~| HELP: add `#![feature(const_trait_impl)]`
 }
 
 const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
@@ -21,6 +23,7 @@ const fn h(mut a: &[u8], mut b: &[u8]) -> bool {
         if l == r {
         //~^ ERROR: cannot call non-const operator in constant functions [E0015]
         //~| HELP: consider dereferencing here
+        //~| HELP: add `#![feature(const_trait_impl)]`
             a = at;
             b = bt;
         } else {
diff --git a/tests/ui/consts/issue-90870.stderr b/tests/ui/consts/issue-90870.stderr
index 478445cfb39..8825efd1449 100644
--- a/tests/ui/consts/issue-90870.stderr
+++ b/tests/ui/consts/issue-90870.stderr
@@ -5,30 +5,33 @@ LL |     a == b
    |     ^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 help: consider dereferencing here
    |
 LL |     *a == *b
    |     +     +
 
 error[E0015]: cannot call non-const operator in constant functions
-  --> $DIR/issue-90870.rs:14:5
+  --> $DIR/issue-90870.rs:15:5
    |
 LL |     a == b
    |     ^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 help: consider dereferencing here
    |
 LL |     ****a == ****b
    |     ++++     ++++
 
 error[E0015]: cannot call non-const operator in constant functions
-  --> $DIR/issue-90870.rs:21:12
+  --> $DIR/issue-90870.rs:23:12
    |
 LL |         if l == r {
    |            ^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 help: consider dereferencing here
    |
 LL |         if *l == *r {
diff --git a/tests/ui/never_type/issue-52443.stderr b/tests/ui/never_type/issue-52443.stderr
index de5c9c56016..33b7a9185d0 100644
--- a/tests/ui/never_type/issue-52443.stderr
+++ b/tests/ui/never_type/issue-52443.stderr
@@ -47,6 +47,7 @@ LL |     [(); { for _ in 0usize.. {}; 0}];
 note: impl defined here, but it is not `const`
   --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error[E0658]: mutable references are not allowed in constants
   --> $DIR/issue-52443.rs:9:21
@@ -64,6 +65,7 @@ LL |     [(); { for _ in 0usize.. {}; 0}];
    |                     ^^^^^^^^
    |
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to 6 previous errors; 1 warning emitted
 
diff --git a/tests/ui/parser/recover-quantified-closure.rs b/tests/ui/parser/recover-quantified-closure.rs
index 10af39b7007..df22f5e065c 100644
--- a/tests/ui/parser/recover-quantified-closure.rs
+++ b/tests/ui/parser/recover-quantified-closure.rs
@@ -7,6 +7,6 @@ fn main() {
 enum Foo { Bar }
 fn foo(x: impl Iterator<Item = Foo>) {
     for <Foo>::Bar in x {}
-    //~^ ERROR expected one of `move`, `static`, `|`
+    //~^ ERROR expected one of `const`, `move`, `static`, `|`
     //~^^ ERROR `for<...>` binders for closures are experimental
 }
diff --git a/tests/ui/parser/recover-quantified-closure.stderr b/tests/ui/parser/recover-quantified-closure.stderr
index 39eec80f658..9ec4d2c034d 100644
--- a/tests/ui/parser/recover-quantified-closure.stderr
+++ b/tests/ui/parser/recover-quantified-closure.stderr
@@ -1,8 +1,8 @@
-error: expected one of `move`, `static`, `|`, or `||`, found `::`
+error: expected one of `const`, `move`, `static`, `|`, or `||`, found `::`
   --> $DIR/recover-quantified-closure.rs:9:14
    |
 LL |     for <Foo>::Bar in x {}
-   |              ^^ expected one of `move`, `static`, `|`, or `||`
+   |              ^^ expected one of `const`, `move`, `static`, `|`, or `||`
 
 error[E0658]: `for<...>` binders for closures are experimental
   --> $DIR/recover-quantified-closure.rs:2:5
diff --git a/tests/ui/resolve/issue-39559-2.stderr b/tests/ui/resolve/issue-39559-2.stderr
index ea27e7bd250..e9d8eb0835b 100644
--- a/tests/ui/resolve/issue-39559-2.stderr
+++ b/tests/ui/resolve/issue-39559-2.stderr
@@ -5,6 +5,7 @@ LL |     let array: [usize; Dim3::dim()]
    |                        ^^^^^^^^^^^
    |
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error[E0015]: cannot call non-const fn `<Dim3 as Dim>::dim` in constants
   --> $DIR/issue-39559-2.rs:16:15
@@ -13,6 +14,7 @@ LL |         = [0; Dim3::dim()];
    |               ^^^^^^^^^^^
    |
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/rfc-2632-const-trait-impl/call.rs b/tests/ui/rfc-2632-const-trait-impl/call.rs
new file mode 100644
index 00000000000..5f48c235373
--- /dev/null
+++ b/tests/ui/rfc-2632-const-trait-impl/call.rs
@@ -0,0 +1,10 @@
+// check-pass
+
+#![feature(const_closures, const_trait_impl)]
+#![allow(incomplete_features)]
+
+pub const _: () = {
+    assert!((const || true)());
+};
+
+fn main() {}
diff --git a/tests/ui/rfc-2632-const-trait-impl/const_derives/derive-const-non-const-type.stderr b/tests/ui/rfc-2632-const-trait-impl/const_derives/derive-const-non-const-type.stderr
index d463c774e28..96e0c78b9c7 100644
--- a/tests/ui/rfc-2632-const-trait-impl/const_derives/derive-const-non-const-type.stderr
+++ b/tests/ui/rfc-2632-const-trait-impl/const_derives/derive-const-non-const-type.stderr
@@ -7,6 +7,7 @@ LL | pub struct S(A);
    |              ^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
    = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: aborting due to previous error
diff --git a/tests/ui/rfc-2632-const-trait-impl/cross-crate.stock.stderr b/tests/ui/rfc-2632-const-trait-impl/cross-crate.stock.stderr
index 086547542bb..22f13a7416e 100644
--- a/tests/ui/rfc-2632-const-trait-impl/cross-crate.stock.stderr
+++ b/tests/ui/rfc-2632-const-trait-impl/cross-crate.stock.stderr
@@ -5,6 +5,7 @@ LL |     Const.func();
    |           ^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/rfc-2632-const-trait-impl/gate.rs b/tests/ui/rfc-2632-const-trait-impl/gate.rs
new file mode 100644
index 00000000000..f2cd26c91b6
--- /dev/null
+++ b/tests/ui/rfc-2632-const-trait-impl/gate.rs
@@ -0,0 +1,5 @@
+// gate-test-const_closures
+fn main() {
+    (const || {})();
+    //~^ ERROR: const closures are experimental
+}
diff --git a/tests/ui/rfc-2632-const-trait-impl/gate.stderr b/tests/ui/rfc-2632-const-trait-impl/gate.stderr
new file mode 100644
index 00000000000..30edc4127e1
--- /dev/null
+++ b/tests/ui/rfc-2632-const-trait-impl/gate.stderr
@@ -0,0 +1,12 @@
+error[E0658]: const closures are experimental
+  --> $DIR/gate.rs:3:6
+   |
+LL |     (const || {})();
+   |      ^^^^^^^^^^^
+   |
+   = note: see issue #106003 <https://github.com/rust-lang/rust/issues/106003> for more information
+   = help: add `#![feature(const_closures)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.rs b/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.rs
new file mode 100644
index 00000000000..cd8bb5963ad
--- /dev/null
+++ b/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.rs
@@ -0,0 +1,15 @@
+#![feature(const_closures, const_trait_impl)]
+#![allow(incomplete_features)]
+
+trait Foo {
+    fn foo(&self);
+}
+
+impl Foo for () {
+    fn foo(&self) {}
+}
+
+fn main() {
+    (const || { (()).foo() })();
+    //~^ ERROR: cannot call non-const fn
+}
diff --git a/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.stderr b/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.stderr
new file mode 100644
index 00000000000..979d7febbca
--- /dev/null
+++ b/tests/ui/rfc-2632-const-trait-impl/non-const-op-const-closure-non-const-outer.stderr
@@ -0,0 +1,11 @@
+error[E0015]: cannot call non-const fn `<() as Foo>::foo` in constant functions
+  --> $DIR/non-const-op-const-closure-non-const-outer.rs:13:22
+   |
+LL |     (const || { (()).foo() })();
+   |                      ^^^^^
+   |
+   = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0015`.
diff --git a/tests/ui/rfc-2632-const-trait-impl/staged-api-user-crate.stderr b/tests/ui/rfc-2632-const-trait-impl/staged-api-user-crate.stderr
index 61f9840e0d0..d7aa0d95cfc 100644
--- a/tests/ui/rfc-2632-const-trait-impl/staged-api-user-crate.stderr
+++ b/tests/ui/rfc-2632-const-trait-impl/staged-api-user-crate.stderr
@@ -5,6 +5,7 @@ LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error
 
diff --git a/tests/ui/rfc-2632-const-trait-impl/std-impl-gate.stock.stderr b/tests/ui/rfc-2632-const-trait-impl/std-impl-gate.stock.stderr
index 0b450a94742..6a3396401d2 100644
--- a/tests/ui/rfc-2632-const-trait-impl/std-impl-gate.stock.stderr
+++ b/tests/ui/rfc-2632-const-trait-impl/std-impl-gate.stock.stderr
@@ -5,6 +5,7 @@ LL |     Default::default()
    |     ^^^^^^^^^^^^^^^^^^
    |
    = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+   = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
 
 error: aborting due to previous error