Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4dd0bca

Browse files
committedMar 12, 2025
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 6650252 commit 4dd0bca

18 files changed

+205
-35
lines changed
 

‎compiler/rustc_ast_lowering/src/expr.rs

+9-4
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

+1
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ language_item_table! {
434434
// Experimental lang items for implementing contract pre- and post-condition checking.
435435
ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None;
436436
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;
437+
ContractCheckEnsures, sym::contract_check_ensures, contract_check_ensures_fn, Target::Fn, GenericRequirement::None;
437438
}
438439

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

‎compiler/rustc_hir_analysis/src/check/intrinsic.rs

+4-8
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

+6-7
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

+41-6
Original file line numberDiff line numberDiff line change
@@ -3288,20 +3288,55 @@ pub const fn contract_checks() -> bool {
32883288
///
32893289
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
32903290
/// returns false.
3291-
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
3291+
///
3292+
/// Note that this function is a no-op during constant evaluation.
3293+
#[unstable(feature = "contracts_internals", issue = "128044")]
3294+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
32923295
#[lang = "contract_check_requires"]
32933296
#[rustc_intrinsic]
3294-
pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
3295-
if contract_checks() && !cond() {
3296-
// Emit no unwind panic in case this was a safety requirement.
3297-
crate::panicking::panic_nounwind("failed requires check");
3298-
}
3297+
pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
3298+
const_eval_select!(
3299+
@capture[C: Fn() -> bool + Copy] { cond: C } :
3300+
if const {
3301+
// Do nothing
3302+
} else {
3303+
if contract_checks() && !cond() {
3304+
// Emit no unwind panic in case this was a safety requirement.
3305+
crate::panicking::panic_nounwind("failed requires check");
3306+
}
3307+
}
3308+
)
32993309
}
33003310

33013311
/// Check if the post-condition `cond` has been met.
33023312
///
33033313
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
33043314
/// returns false.
3315+
///
3316+
/// Note that this function is a no-op during constant evaluation.
3317+
#[cfg(not(bootstrap))]
3318+
#[unstable(feature = "contracts_internals", issue = "128044")]
3319+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
3320+
#[lang = "contract_check_ensures"]
3321+
#[rustc_intrinsic]
3322+
pub const fn contract_check_ensures<Ret, C: Fn(&Ret) -> bool + Copy>(ret: Ret, cond: C) -> Ret {
3323+
const_eval_select!(
3324+
@capture[Ret, C: Fn(&Ret) -> bool + Copy] { ret: Ret, cond: C } -> Ret :
3325+
if const {
3326+
// Do nothing
3327+
ret
3328+
} else {
3329+
if contract_checks() && !cond(&ret) {
3330+
// Emit no unwind panic in case this was a safety requirement.
3331+
crate::panicking::panic_nounwind("failed ensures check");
3332+
}
3333+
ret
3334+
}
3335+
)
3336+
}
3337+
3338+
/// This is the old version of contract_check_ensures kept here for bootstrap only.
3339+
#[cfg(bootstrap)]
33053340
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
33063341
#[rustc_intrinsic]
33073342
pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {

‎library/core/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
#![feature(bigint_helper_methods)]
114114
#![feature(bstr)]
115115
#![feature(bstr_internals)]
116-
#![feature(closure_track_caller)]
117116
#![feature(const_carrying_mul_add)]
118117
#![feature(const_eval_select)]
119118
#![feature(core_intrinsics)]

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

+12-1
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

+1
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

+12-1
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

+12-1
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_pass.stderr

+12-1
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/contract-const-fn.rs:17:12
3+
|
4+
LL | #![feature(contracts)]
5+
| ^^^^^^^^^
6+
|
7+
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
8+
= note: `#[warn(incomplete_features)]` on by default
9+
10+
warning: 1 warning emitted
11+
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! Check if we can annotate a constant function with contracts.
2+
//!
3+
//! The contract is only checked at runtime, and it will not fail if evaluated statically.
4+
//! This is an existing limitation due to the existing architecture and the lack of constant
5+
//! closures.
6+
//!
7+
//@ revisions: all_pass runtime_fail_pre runtime_fail_post
8+
//
9+
//@ [all_pass] run-pass
10+
//
11+
//@ [runtime_fail_pre] run-fail
12+
//@ [runtime_fail_post] run-fail
13+
//
14+
//@ [all_pass] compile-flags: -Zcontract-checks=yes
15+
//@ [runtime_fail_pre] compile-flags: -Zcontract-checks=yes
16+
//@ [runtime_fail_post] compile-flags: -Zcontract-checks=yes
17+
#![feature(contracts)]
18+
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
19+
20+
extern crate core;
21+
use core::contracts::*;
22+
23+
#[requires(x < 100)]
24+
const fn less_than_100(x: u8) -> u8 {
25+
x
26+
}
27+
28+
// This is wrong on purpose.
29+
#[ensures(|ret| *ret)]
30+
const fn always_true(b: bool) -> bool {
31+
b
32+
}
33+
34+
const ZERO: u8 = less_than_100(0);
35+
// This is no-op because the contract cannot be checked at compilation time.
36+
const TWO_HUNDRED: u8 = less_than_100(200);
37+
38+
/// Example from <https://github.com/rust-lang/rust/issues/136925>.
39+
#[ensures(move |ret: &u32| *ret > x)]
40+
const fn broken_sum(x: u32, y: u32) -> u32 {
41+
x + y
42+
}
43+
44+
fn main() {
45+
assert_eq!(ZERO, 0);
46+
assert_eq!(TWO_HUNDRED, 200);
47+
assert_eq!(broken_sum(0, 1), 1);
48+
assert_eq!(always_true(true), true);
49+
50+
#[cfg(runtime_fail_post)]
51+
let _ok = always_true(false);
52+
53+
// Runtime check should fail.
54+
#[cfg(runtime_fail_pre)]
55+
let _200 = less_than_100(200);
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/contract-const-fn.rs:17:12
3+
|
4+
LL | #![feature(contracts)]
5+
| ^^^^^^^^^
6+
|
7+
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
8+
= note: `#[warn(incomplete_features)]` on by default
9+
10+
warning: 1 warning emitted
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/contract-const-fn.rs:17:12
3+
|
4+
LL | #![feature(contracts)]
5+
| ^^^^^^^^^
6+
|
7+
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
8+
= note: `#[warn(incomplete_features)]` on by default
9+
10+
warning: 1 warning emitted
11+

‎tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
fn nest(x: Baz) -> i32
2222
contract_requires(|| x.baz > 0)
2323
contract_ensures(|ret| *ret > 100)
24+
//~^ WARN unreachable expression [unreachable_code]
2425
{
2526
loop {
2627
return x.baz + 50;

‎tests/ui/contracts/internal_machinery/contract-intrinsics.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ fn main() {
2626
#[cfg(any(default, unchk_pass, chk_fail_requires))]
2727
core::intrinsics::contract_check_requires(|| false);
2828

29-
let doubles_to_two = { let old = 2; move |ret| ret + ret == old };
29+
let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old };
3030
// Always pass
31-
core::intrinsics::contract_check_ensures(&1, doubles_to_two);
31+
core::intrinsics::contract_check_ensures(1, doubles_to_two);
3232

3333
// Fail if enabled
3434
#[cfg(any(default, unchk_pass, chk_fail_ensures))]
35-
core::intrinsics::contract_check_ensures(&2, doubles_to_two);
35+
core::intrinsics::contract_check_ensures(2, doubles_to_two);
3636
}

‎tests/ui/contracts/internal_machinery/contract-lang-items.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
#![feature(contracts)] // to access core::contracts
1616
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
1717
#![feature(contracts_internals)] // to access check_requires lang item
18-
18+
#![feature(core_intrinsics)]
1919
fn foo(x: Baz) -> i32 {
2020
let injected_checker = {
2121
core::contracts::build_check_ensures(|ret| *ret > 100)
2222
};
2323

2424
let ret = x.baz + 50;
25-
injected_checker(ret)
25+
core::intrinsics::contract_check_ensures(ret, injected_checker)
2626
}
2727

2828
struct Baz { baz: i32 }

0 commit comments

Comments
 (0)
Failed to load comments.