From ae4ddd2806b7abcabf33fb6ef61f01c5e675c8b9 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 12 Mar 2026 08:44:28 +0000 Subject: [PATCH] Reject const closures outside const contexts --- compiler/rustc_ast_lowering/src/expr.rs | 11 ++++++-- compiler/rustc_ast_lowering/src/item.rs | 24 +++++++++++++++--- compiler/rustc_ast_lowering/src/lib.rs | 2 ++ .../const-closure-issue-125866-error.rs | 4 +-- .../const-closure-issue-125866-pass.rs | 25 ------------------- ...closure-const_trait_impl-ice-113381.stderr | 7 +++--- tests/ui/traits/const-traits/gate.rs | 6 ++--- tests/ui/traits/const-traits/gate.stderr | 14 +++++------ ...-const-op-const-closure-non-const-outer.rs | 2 +- ...st-op-const-closure-non-const-outer.stderr | 7 +++--- 10 files changed, 50 insertions(+), 52 deletions(-) delete mode 100644 tests/ui/traits/const-traits/const-closure-issue-125866-pass.rs diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index e86a2f33b87b..46b33a481214 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1062,7 +1062,7 @@ fn lower_expr_closure( binder: &ClosureBinder, capture_clause: CaptureBy, closure_id: NodeId, - constness: Const, + mut constness: Const, movability: Movability, decl: &FnDecl, body: &Expr, @@ -1072,11 +1072,18 @@ fn lower_expr_closure( let closure_def_id = self.local_def_id(closure_id); let (binder_clause, generic_params) = self.lower_closure_binder(binder); + if let Const::Yes(span) = constness { + if !self.is_in_const_context { + self.dcx().span_err(span, "cannot use `const` closures outside of const contexts"); + constness = Const::No; + } + } + let (body_id, closure_kind) = self.with_new_scopes(fn_decl_span, move |this| { let mut coroutine_kind = find_attr!(attrs, Coroutine(_) => hir::CoroutineKind::Coroutine(Movability::Movable)); // FIXME(contracts): Support contracts on closures? - let body_id = this.lower_fn_body(decl, None, |this| { + let body_id = this.lower_fn_body(decl, None, constness, |this| { this.coroutine_kind = coroutine_kind; let e = this.lower_expr_mut(body); coroutine_kind = this.coroutine_kind; diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index ed78b77a704f..e2ac38caf295 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1,3 +1,5 @@ +use std::mem; + use rustc_abi::ExternAbi; use rustc_ast::visit::AssocCtxt; use rustc_ast::*; @@ -345,6 +347,7 @@ fn lower_item_kind( body.as_deref(), attrs, contract.as_deref(), + header.constness, ); let itctx = ImplTraitContext::Universal; @@ -1024,6 +1027,7 @@ fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { Some(body), attrs, contract.as_deref(), + sig.header.constness, ); let (generics, sig) = self.lower_method_sig( generics, @@ -1217,6 +1221,7 @@ fn lower_impl_item( body.as_deref(), attrs, contract.as_deref(), + sig.header.constness, ); let (generics, sig) = self.lower_method_sig( generics, @@ -1346,11 +1351,13 @@ pub(super) fn lower_body( f: impl FnOnce(&mut Self) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>), ) -> hir::BodyId { let prev_coroutine_kind = self.coroutine_kind.take(); + let prev_is_in_const_context = mem::take(&mut self.is_in_const_context); let task_context = self.task_context.take(); let (parameters, result) = f(self); let body_id = self.record_body(parameters, result); self.task_context = task_context; self.coroutine_kind = prev_coroutine_kind; + self.is_in_const_context = prev_is_in_const_context; body_id } @@ -1369,9 +1376,13 @@ pub(super) fn lower_fn_body( &mut self, decl: &FnDecl, contract: Option<&FnContract>, + constness: Const, body: impl FnOnce(&mut Self) -> hir::Expr<'hir>, ) -> hir::BodyId { self.lower_body(|this| { + if let Const::Yes(_) = constness { + this.is_in_const_context = true; + } let params = this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))); @@ -1389,8 +1400,9 @@ fn lower_fn_body_block( decl: &FnDecl, body: &Block, contract: Option<&FnContract>, + constness: Const, ) -> hir::BodyId { - self.lower_fn_body(decl, contract, |this| this.lower_block_expr(body)) + self.lower_fn_body(decl, contract, constness, |this| this.lower_block_expr(body)) } pub(super) fn lower_const_body(&mut self, span: Span, expr: Option<&Expr>) -> hir::BodyId { @@ -1398,7 +1410,10 @@ pub(super) fn lower_const_body(&mut self, span: Span, expr: Option<&Expr>) -> hi ( &[], match expr { - Some(expr) => this.lower_expr_mut(expr), + Some(expr) => { + this.is_in_const_context = true; + this.lower_expr_mut(expr) + } None => this.expr_err(span, this.dcx().span_delayed_bug(span, "no block")), }, ) @@ -1417,12 +1432,13 @@ fn lower_maybe_coroutine_body( body: Option<&Block>, attrs: &'hir [hir::Attribute], contract: Option<&FnContract>, + constness: Const, ) -> hir::BodyId { let Some(body) = body else { // Functions without a body are an error, except if this is an intrinsic. For those we // create a fake body so that the entire rest of the compiler doesn't have to deal with // this as a special case. - return self.lower_fn_body(decl, contract, |this| { + return self.lower_fn_body(decl, contract, constness, |this| { if find_attr!(attrs, RustcIntrinsic) || this.tcx.is_sdylib_interface_build() { let span = this.lower_span(span); let empty_block = hir::Block { @@ -1447,7 +1463,7 @@ fn lower_maybe_coroutine_body( }; let Some(coroutine_kind) = coroutine_kind else { // Typical case: not a coroutine. - return self.lower_fn_body_block(decl, body, contract); + return self.lower_fn_body_block(decl, body, contract, constness); }; // FIXME(contracts): Support contracts on async fn. self.lower_body(|this| { diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index c3525d124670..e8092b540ec8 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -129,6 +129,7 @@ struct LoweringContext<'a, 'hir> { loop_scope: Option, is_in_loop_condition: bool, is_in_dyn_type: bool, + is_in_const_context: bool, current_hir_id_owner: hir::OwnerId, item_local_id_counter: hir::ItemLocalId, @@ -190,6 +191,7 @@ fn new( loop_scope: None, is_in_loop_condition: false, is_in_dyn_type: false, + is_in_const_context: false, coroutine_kind: None, task_context: None, current_item: None, diff --git a/tests/ui/traits/const-traits/const-closure-issue-125866-error.rs b/tests/ui/traits/const-traits/const-closure-issue-125866-error.rs index 7a44920bb729..e13ee39631a3 100644 --- a/tests/ui/traits/const-traits/const-closure-issue-125866-error.rs +++ b/tests/ui/traits/const-traits/const-closure-issue-125866-error.rs @@ -16,9 +16,9 @@ } fn main() { - let x = create_array(const |i| 2 * i as u32); + let x = const { create_array(const |i| 2 * i as u32) }; assert_eq!(x, [0, 2, 4, 6, 8]); - let y = create_array(const |i| 2 * i as u32 + 1); + let y = const { create_array(const |i| 2 * i as u32 + 1) }; assert_eq!(y, [1, 3, 5, 7, 9]); } diff --git a/tests/ui/traits/const-traits/const-closure-issue-125866-pass.rs b/tests/ui/traits/const-traits/const-closure-issue-125866-pass.rs deleted file mode 100644 index af7375172e67..000000000000 --- a/tests/ui/traits/const-traits/const-closure-issue-125866-pass.rs +++ /dev/null @@ -1,25 +0,0 @@ -//@ check-pass - -#![allow(incomplete_features)] -#![feature(const_closures, const_trait_impl)] - -const fn create_array(mut f: impl [const] FnMut(usize) -> u32 + Copy) -> [u32; N] { - let mut array = [0; N]; - let mut i = 0; - loop { - array[i] = f(i); - i += 1; - if i == N { - break; - } - } - array -} - -fn main() { - let x = create_array(const |i| 2 * i as u32); - assert_eq!(x, [0, 2, 4, 6, 8]); - - let y = create_array(const |i| 2 * i as u32 + 1); - assert_eq!(y, [1, 3, 5, 7, 9]); -} diff --git a/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr b/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr index dab3f14161fa..bbe736b94b5b 100644 --- a/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr +++ b/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr @@ -1,9 +1,8 @@ -error[E0277]: the trait bound `{closure@$DIR/const_closure-const_trait_impl-ice-113381.rs:15:6: 15:14}: [const] Fn()` is not satisfied - --> $DIR/const_closure-const_trait_impl-ice-113381.rs:15:5 +error: cannot use `const` closures outside of const contexts + --> $DIR/const_closure-const_trait_impl-ice-113381.rs:15:6 | LL | (const || (()).foo())(); - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/const-traits/gate.rs b/tests/ui/traits/const-traits/gate.rs index 3f348c841393..86b42099d289 100644 --- a/tests/ui/traits/const-traits/gate.rs +++ b/tests/ui/traits/const-traits/gate.rs @@ -1,13 +1,13 @@ // gate-test-const_closures fn main() { - (const || {})(); + const { (const || {})() }; //~^ ERROR: const closures are experimental - //~| ERROR: the trait bound `{closure@$DIR/gate.rs:4:6: 4:14}: [const] Fn()` is not satisfied + //~| ERROR: the trait bound `{closure@$DIR/gate.rs:4:14: 4:22}: [const] Fn()` is not satisfied } macro_rules! e { - ($e:expr) => {} + ($e:expr) => {}; } e!((const || {})); diff --git a/tests/ui/traits/const-traits/gate.stderr b/tests/ui/traits/const-traits/gate.stderr index 6bef2c511ce7..b86217943d77 100644 --- a/tests/ui/traits/const-traits/gate.stderr +++ b/tests/ui/traits/const-traits/gate.stderr @@ -1,8 +1,8 @@ error[E0658]: const closures are experimental - --> $DIR/gate.rs:4:6 + --> $DIR/gate.rs:4:14 | -LL | (const || {})(); - | ^^^^^ +LL | const { (const || {})() }; + | ^^^^^ | = note: see issue #106003 for more information = help: add `#![feature(const_closures)]` to the crate attributes to enable @@ -18,11 +18,11 @@ LL | e!((const || {})); = help: add `#![feature(const_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0277]: the trait bound `{closure@$DIR/gate.rs:4:6: 4:14}: [const] Fn()` is not satisfied - --> $DIR/gate.rs:4:5 +error[E0277]: the trait bound `{closure@$DIR/gate.rs:4:14: 4:22}: [const] Fn()` is not satisfied + --> $DIR/gate.rs:4:13 | -LL | (const || {})(); - | ^^^^^^^^^^^^^^^ +LL | const { (const || {})() }; + | ^^^^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs index de5bedf0ace7..a13fab3b21db 100644 --- a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs +++ b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs @@ -13,5 +13,5 @@ fn main() { // #150052 deduplicate diagnostics for const trait supertraits // so we only get one error here (const || { (()).foo() })(); - //~^ ERROR: }: [const] Fn()` is not satisfied + //~^ ERROR: cannot use `const` closures outside of const contexts } diff --git a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr index efbedca1c7e7..43c98ec7c661 100644 --- a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr +++ b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr @@ -1,9 +1,8 @@ -error[E0277]: the trait bound `{closure@$DIR/non-const-op-const-closure-non-const-outer.rs:15:6: 15:14}: [const] Fn()` is not satisfied - --> $DIR/non-const-op-const-closure-non-const-outer.rs:15:5 +error: cannot use `const` closures outside of const contexts + --> $DIR/non-const-op-const-closure-non-const-outer.rs:15:6 | LL | (const || { (()).foo() })(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`.