Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit b9754f9

Browse files
committed
Enable contracts for const functions
Use `const_eval_select!()` macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions. This commit also removes one level of indirection for ensures clauses, however, it currently has a spurious warning message when the bottom of the function is unreachable.
1 parent 5337252 commit b9754f9

18 files changed

+206
-35
lines changed

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,12 +397,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
397397
&mut self,
398398
expr: &'hir hir::Expr<'hir>,
399399
span: Span,
400-
check_ident: Ident,
401-
check_hir_id: HirId,
400+
cond_ident: Ident,
401+
cond_hir_id: HirId,
402402
) -> &'hir hir::Expr<'hir> {
403-
let checker_fn = self.expr_ident(span, check_ident, check_hir_id);
403+
let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
404404
let span = self.mark_span_with_reason(DesugaringKind::Contract, span, None);
405-
self.expr_call(span, checker_fn, std::slice::from_ref(expr))
405+
let call_expr = self.expr_call_lang_item_fn_mut(
406+
span,
407+
hir::LangItem::ContractCheckEnsures,
408+
arena_vec![self; *expr, *cond_fn],
409+
);
410+
self.arena.alloc(call_expr)
406411
}
407412

408413
pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {

compiler/rustc_hir/src/lang_items.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,8 @@ language_item_table! {
439439
DefaultTrait3, sym::default_trait3, default_trait3_trait, Target::Trait, GenericRequirement::None;
440440
DefaultTrait2, sym::default_trait2, default_trait2_trait, Target::Trait, GenericRequirement::None;
441441
DefaultTrait1, sym::default_trait1, default_trait1_trait, Target::Trait, GenericRequirement::None;
442+
443+
ContractCheckEnsures, sym::contract_check_ensures, contract_check_ensures_fn, Target::Fn, GenericRequirement::None;
442444
}
443445

444446
/// The requirement imposed on the generics of a lang item

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,11 @@ pub fn check_intrinsic_type(
232232
};
233233
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
234234
} else if intrinsic_name == sym::contract_check_ensures {
235-
// contract_check_ensures::<'a, Ret, C>(&'a Ret, C)
236-
// where C: impl Fn(&'a Ret) -> bool,
235+
// contract_check_ensures::<Ret, C>(Ret, C) -> Ret
236+
// where C: for<'a> Fn(&'a Ret) -> bool,
237237
//
238-
// so: two type params, one lifetime param, 0 const params, two inputs, no return
239-
240-
let p = generics.param_at(0, tcx);
241-
let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data());
242-
let ref_ret = Ty::new_imm_ref(tcx, r, param(1));
243-
(2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe)
238+
// so: two type params, 0 lifetime param, 0 const params, two inputs, no return
239+
(2, 0, 0, vec![param(0), param(1)], param(0), hir::Safety::Safe)
244240
} else {
245241
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
246242
let (n_tps, n_cts, inputs, output) = match intrinsic_name {

library/core/src/contracts.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_require
55
/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }`
66
/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }`
77
/// (including the implicit return of the tail expression, if any).
8+
///
9+
/// This call helps with type inference for the predicate.
810
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
11+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
912
#[lang = "contract_build_check_ensures"]
1013
#[track_caller]
11-
pub fn build_check_ensures<Ret, C>(cond: C) -> impl (Fn(Ret) -> Ret) + Copy
14+
pub const fn build_check_ensures<Ret, C>(cond: C) -> C
1215
where
13-
C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static,
16+
C: Fn(&Ret) -> bool + Copy + 'static,
1417
{
15-
#[track_caller]
16-
move |ret| {
17-
crate::intrinsics::contract_check_ensures(&ret, cond);
18-
ret
19-
}
18+
cond
2019
}

library/core/src/intrinsics/mod.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3450,20 +3450,55 @@ pub const fn contract_checks() -> bool {
34503450
///
34513451
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
34523452
/// returns false.
3453-
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
3453+
///
3454+
/// Note that this function is a no-op during constant evaluation.
3455+
#[unstable(feature = "contracts_internals", issue = "128044")]
3456+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
34543457
#[lang = "contract_check_requires"]
34553458
#[rustc_intrinsic]
3456-
pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
3457-
if contract_checks() && !cond() {
3458-
// Emit no unwind panic in case this was a safety requirement.
3459-
crate::panicking::panic_nounwind("failed requires check");
3460-
}
3459+
pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
3460+
const_eval_select!(
3461+
@capture[C: Fn() -> bool + Copy] { cond: C } :
3462+
if const {
3463+
// Do nothing
3464+
} else {
3465+
if contract_checks() && !cond() {
3466+
// Emit no unwind panic in case this was a safety requirement.
3467+
crate::panicking::panic_nounwind("failed requires check");
3468+
}
3469+
}
3470+
)
34613471
}
34623472

34633473
/// Check if the post-condition `cond` has been met.
34643474
///
34653475
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
34663476
/// returns false.
3477+
///
3478+
/// Note that this function is a no-op during constant evaluation.
3479+
#[cfg(not(bootstrap))]
3480+
#[unstable(feature = "contracts_internals", issue = "128044")]
3481+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
3482+
#[lang = "contract_check_ensures"]
3483+
#[rustc_intrinsic]
3484+
pub const fn contract_check_ensures<Ret, C: Fn(&Ret) -> bool + Copy>(ret: Ret, cond: C) -> Ret {
3485+
const_eval_select!(
3486+
@capture[Ret, C: Fn(&Ret) -> bool + Copy] { ret: Ret, cond: C } -> Ret :
3487+
if const {
3488+
// Do nothing
3489+
ret
3490+
} else {
3491+
if contract_checks() && !cond(&ret) {
3492+
// Emit no unwind panic in case this was a safety requirement.
3493+
crate::panicking::panic_nounwind("failed ensures check");
3494+
}
3495+
ret
3496+
}
3497+
)
3498+
}
3499+
3500+
/// This is the old version of contract_check_ensures kept here for bootstrap only.
3501+
#[cfg(bootstrap)]
34673502
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
34683503
#[rustc_intrinsic]
34693504
pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {

library/core/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@
101101
#![feature(bstr)]
102102
#![feature(bstr_internals)]
103103
#![feature(cfg_match)]
104-
#![feature(closure_track_caller)]
105104
#![feature(const_carrying_mul_add)]
106105
#![feature(const_eval_select)]
107106
#![feature(core_intrinsics)]

tests/ui/contracts/contract-attributes-nest.chk_pass.stderr

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
77
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
88
= note: `#[warn(incomplete_features)]` on by default
99

10-
warning: 1 warning emitted
10+
warning: unreachable expression
11+
--> $DIR/contract-attributes-nest.rs:23:1
12+
|
13+
LL | #[core::contracts::ensures(|ret| *ret > 100)]
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
15+
...
16+
LL | return x.baz + 50;
17+
| ----------------- any code following this expression is unreachable
18+
|
19+
= note: `#[warn(unreachable_code)]` on by default
20+
21+
warning: 2 warnings emitted
1122

tests/ui/contracts/contract-attributes-nest.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#[core::contracts::requires(x.baz > 0)]
2323
#[core::contracts::ensures(|ret| *ret > 100)]
24+
//~^ WARN unreachable expression [unreachable_code]
2425
fn nest(x: Baz) -> i32
2526
{
2627
loop {

tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
77
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
88
= note: `#[warn(incomplete_features)]` on by default
99

10-
warning: 1 warning emitted
10+
warning: unreachable expression
11+
--> $DIR/contract-attributes-nest.rs:23:1
12+
|
13+
LL | #[core::contracts::ensures(|ret| *ret > 100)]
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
15+
...
16+
LL | return x.baz + 50;
17+
| ----------------- any code following this expression is unreachable
18+
|
19+
= note: `#[warn(unreachable_code)]` on by default
20+
21+
warning: 2 warnings emitted
1122

tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
77
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
88
= note: `#[warn(incomplete_features)]` on by default
99

10-
warning: 1 warning emitted
10+
warning: unreachable expression
11+
--> $DIR/contract-attributes-nest.rs:23:1
12+
|
13+
LL | #[core::contracts::ensures(|ret| *ret > 100)]
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
15+
...
16+
LL | return x.baz + 50;
17+
| ----------------- any code following this expression is unreachable
18+
|
19+
= note: `#[warn(unreachable_code)]` on by default
20+
21+
warning: 2 warnings emitted
1122

0 commit comments

Comments
 (0)