Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
e.id,
expr_hir_id,
*coroutine_kind,
*constness,
fn_decl,
body,
*fn_decl_span,
Expand Down Expand Up @@ -1061,7 +1062,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
binder: &ClosureBinder,
capture_clause: CaptureBy,
closure_id: NodeId,
constness: Const,
mut constness: Const,
movability: Movability,
decl: &FnDecl,
body: &Expr,
Expand All @@ -1071,11 +1072,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
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;
Expand Down Expand Up @@ -1158,6 +1166,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
closure_id: NodeId,
closure_hir_id: HirId,
coroutine_kind: CoroutineKind,
constness: Const,
decl: &FnDecl,
body: &Expr,
fn_decl_span: Span,
Expand Down Expand Up @@ -1204,6 +1213,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
let fn_decl =
self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None);

if let Const::Yes(span) = constness {
self.dcx().span_err(span, "const coroutines are not supported");
}

let c = self.arena.alloc(hir::Closure {
def_id: closure_def_id,
binder: binder_clause,
Expand All @@ -1217,7 +1230,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
// knows that a `FnDecl` output type like `-> &str` actually means
// "coroutine that returns &str", rather than directly returning a `&str`.
kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring),
constness: hir::Constness::NotConst,
constness: self.lower_constness(constness),
});
hir::ExprKind::Closure(c)
}
Expand Down
24 changes: 20 additions & 4 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::mem;

use rustc_abi::ExternAbi;
use rustc_ast::visit::AssocCtxt;
use rustc_ast::*;
Expand Down Expand Up @@ -345,6 +347,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
body.as_deref(),
attrs,
contract.as_deref(),
header.constness,
);

let itctx = ImplTraitContext::Universal;
Expand Down Expand Up @@ -1024,6 +1027,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
Some(body),
attrs,
contract.as_deref(),
sig.header.constness,
);
let (generics, sig) = self.lower_method_sig(
generics,
Expand Down Expand Up @@ -1217,6 +1221,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
body.as_deref(),
attrs,
contract.as_deref(),
sig.header.constness,
);
let (generics, sig) = self.lower_method_sig(
generics,
Expand Down Expand Up @@ -1346,11 +1351,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
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
}

Expand All @@ -1369,9 +1376,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
&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)));

Expand All @@ -1389,16 +1400,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
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 {
self.lower_body(|this| {
(
&[],
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")),
},
)
Expand All @@ -1417,12 +1432,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
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 {
Expand All @@ -1447,7 +1463,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
};
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| {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ struct LoweringContext<'a, 'hir> {
loop_scope: Option<HirId>,
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,
Expand Down Expand Up @@ -190,6 +191,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
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,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ declare_features! (
/// Allows defining and calling c-variadic functions in const contexts.
(unstable, const_c_variadic, "1.95.0", Some(151787)),
/// Allows `const || {}` closures in const contexts.
(incomplete, const_closures, "1.68.0", Some(106003)),
(unstable, const_closures, "1.68.0", Some(106003)),
/// Allows using `[const] Destruct` bounds and calling drop impls in const contexts.
(unstable, const_destruct, "1.85.0", Some(133214)),
/// Allows `for _ in _` loops in const contexts.
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir_analysis/src/collect/predicates_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,9 @@ pub(super) fn const_conditions<'tcx>(
},
// N.B. Tuple ctors are unconditionally constant.
Node::Ctor(hir::VariantData::Tuple { .. }) => return Default::default(),
Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(_), .. }) => {
(hir::Generics::empty(), None, tcx.is_conditionally_const(tcx.local_parent(def_id)))
}
_ => bug!("const_conditions called on wrong item: {def_id:?}"),
};

Expand Down
10 changes: 6 additions & 4 deletions compiler/rustc_middle/src/hir/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,18 @@ impl<'tcx> TyCtxt<'tcx> {
/// This should only be used for determining the context of a body, a return
/// value of `Some` does not always suggest that the owner of the body is `const`,
/// just that it has to be checked as if it were.
pub fn hir_body_const_context(self, def_id: LocalDefId) -> Option<ConstContext> {
let def_id = def_id.into();
pub fn hir_body_const_context(self, local_def_id: LocalDefId) -> Option<ConstContext> {
let def_id = local_def_id.into();
let ccx = match self.hir_body_owner_kind(def_id) {
BodyOwnerKind::Const { inline } => ConstContext::Const { inline },
BodyOwnerKind::Static(mutability) => ConstContext::Static(mutability),

BodyOwnerKind::Fn if self.is_constructor(def_id) => return None,
BodyOwnerKind::Fn | BodyOwnerKind::Closure if self.is_const_fn(def_id) => {
ConstContext::ConstFn
// Const closures use their parent's const context
BodyOwnerKind::Closure if self.is_const_fn(def_id) => {
return self.hir_body_const_context(self.local_parent(local_def_id));
}
BodyOwnerKind::Fn if self.is_const_fn(def_id) => ConstContext::ConstFn,
BodyOwnerKind::Fn | BodyOwnerKind::Closure | BodyOwnerKind::GlobalAsm => return None,
};

Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/ty/context/impl_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.is_conditionally_const(def_id)
}

fn closure_is_const(self, def_id: DefId) -> bool {
debug_assert_matches!(self.def_kind(def_id), DefKind::Closure);
self.constness(def_id) == hir::Constness::Const
}

fn alias_has_const_conditions(self, def_id: DefId) -> bool {
debug_assert_matches!(self.def_kind(def_id), DefKind::AssocTy | DefKind::OpaqueTy);
self.is_conditionally_const(def_id)
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2118,11 +2118,7 @@ impl<'tcx> TyCtxt<'tcx> {
// FIXME(const_trait_impl): ATPITs could be conditionally const?
hir::OpaqueTyOrigin::TyAlias { .. } => false,
},
DefKind::Closure => {
// Closures and RPITs will eventually have const conditions
// for `[const]` bounds.
false
}
DefKind::Closure => self.constness(def_id) == hir::Constness::Const,
DefKind::Ctor(_, CtorKind::Const)
| DefKind::Mod
| DefKind::Struct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,11 @@ fn coroutine_closure_to_ambiguous_coroutine<I: Interner>(
///
/// Doing so on all calls to `extract_tupled_inputs_and_output_from_callable`
/// would be wasteful.
#[instrument(level = "trace", skip(cx), ret)]
pub(in crate::solve) fn extract_fn_def_from_const_callable<I: Interner>(
cx: I,
self_ty: I::Ty,
) -> Result<(ty::Binder<I, (I::Ty, I::Ty)>, I::FunctionId, I::GenericArgs), NoSolution> {
) -> Result<(ty::Binder<I, (I::Ty, I::Ty)>, I::DefId, I::GenericArgs), NoSolution> {
match self_ty.kind() {
ty::FnDef(def_id, args) => {
let sig = cx.fn_sig(def_id);
Expand All @@ -675,7 +676,7 @@ pub(in crate::solve) fn extract_fn_def_from_const_callable<I: Interner>(
Ok((
sig.instantiate(cx, args)
.map_bound(|sig| (Ty::new_tup(cx, sig.inputs().as_slice()), sig.output())),
def_id,
def_id.into(),
args,
))
} else {
Expand All @@ -686,9 +687,19 @@ pub(in crate::solve) fn extract_fn_def_from_const_callable<I: Interner>(
ty::FnPtr(..) => {
return Err(NoSolution);
}
// `Closure`s are not const for now.
ty::Closure(..) => {
return Err(NoSolution);
ty::Closure(def, args) => {
if cx.closure_is_const(def) {
let closure_args = args.as_closure();
Ok((
closure_args
.sig()
.map_bound(|sig| (sig.inputs().get(0).unwrap(), sig.output())),
def.into(),
args,
))
} else {
return Err(NoSolution);
}
}
// `CoroutineClosure`s are not const for now.
ty::CoroutineClosure(..) => {
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ where
todo!("Fn* are not yet const")
}

#[instrument(level = "trace", skip_all, ret)]
fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
Expand All @@ -289,7 +290,7 @@ where
let output_is_sized_pred =
ty::TraitRef::new(cx, cx.require_trait_lang_item(SolverTraitLangItem::Sized), [output]);
let requirements = cx
.const_conditions(def_id.into())
.const_conditions(def_id)
.iter_instantiated(cx, args)
.map(|trait_ref| {
(
Expand Down
17 changes: 14 additions & 3 deletions compiler/rustc_trait_selection/src/traits/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,21 @@ fn evaluate_host_effect_for_fn_goal<'tcx>(
// We may support function pointers at some point in the future
ty::FnPtr(..) => return Err(EvaluationFailure::NoSolution),

// Closures could implement `[const] Fn`,
// Coroutines could implement `[const] Fn`,
// but they don't really need to right now.
ty::Closure(..) | ty::CoroutineClosure(_, _) => {
return Err(EvaluationFailure::NoSolution);
ty::CoroutineClosure(_, _) => return Err(EvaluationFailure::NoSolution),

ty::Closure(def, args) => {
// For now we limit ourselves to closures without binders. The next solver can handle them.
let sig =
args.as_closure().sig().no_bound_vars().ok_or(EvaluationFailure::NoSolution)?;
(
def,
tcx.mk_args_from_iter(
[ty::GenericArg::from(*sig.inputs().get(0).unwrap()), sig.output().into()]
.into_iter(),
),
)
}

// Everything else needs explicit impls or cannot have an impl
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_type_ir/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ pub trait Interner:

fn impl_is_const(self, def_id: Self::ImplId) -> bool;
fn fn_is_const(self, def_id: Self::FunctionId) -> bool;
fn closure_is_const(self, def_id: Self::ClosureId) -> bool;
fn alias_has_const_conditions(self, def_id: Self::DefId) -> bool;
fn const_conditions(
self,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0277]: the trait bound `(): [const] Bar` is not satisfied
error[E0277]: the trait bound `(): const Bar` is not satisfied
--> $DIR/call-const-closure.rs:16:18
|
LL | (const || ().foo())();
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/traits/const-traits/call-const-closure.old.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0277]: the trait bound `(): const Bar` is not satisfied
--> $DIR/call-const-closure.rs:16:18
|
LL | (const || ().foo())();
| ^^^
|
help: make the `impl` of trait `Bar` `const`
|
LL | impl const Bar for () {
| +++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
7 changes: 3 additions & 4 deletions tests/ui/traits/const-traits/call-const-closure.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//@ compile-flags: -Znext-solver
//@[next] compile-flags: -Znext-solver
//@ revisions: next old
//@ edition:2021

#![feature(const_trait_impl, const_closures)]
#![allow(incomplete_features)]

const trait Bar {
fn foo(&self);
Expand All @@ -14,8 +14,7 @@ impl Bar for () {

const FOO: () = {
(const || ().foo())();
//~^ ERROR the trait bound `(): [const] Bar` is not satisfied
// FIXME(const_trait_impl): The constness environment for const closures is wrong.
//~^ ERROR the trait bound `(): const Bar` is not satisfied
};

fn main() {}
Loading
Loading