Skip to content

Commit 380ad1b

Browse files
authored
Rollup merge of #138374 - celinval:issue-136925-const-contract, r=compiler-errors,oli-obk,RalfJung
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 since we no longer build a closure around the ensures predicate. Resolves #136925 **Call-out:** This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before. r? ```@compiler-errors```
2 parents 2da29db + 13f1c84 commit 380ad1b

16 files changed

+183
-39
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
399399
&mut self,
400400
expr: &'hir hir::Expr<'hir>,
401401
span: Span,
402-
check_ident: Ident,
403-
check_hir_id: HirId,
402+
cond_ident: Ident,
403+
cond_hir_id: HirId,
404404
) -> &'hir hir::Expr<'hir> {
405-
let checker_fn = self.expr_ident(span, check_ident, check_hir_id);
406-
let span = self.mark_span_with_reason(DesugaringKind::Contract, span, None);
407-
self.expr_call(span, checker_fn, std::slice::from_ref(expr))
405+
let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
406+
let call_expr = self.expr_call_lang_item_fn_mut(
407+
span,
408+
hir::LangItem::ContractCheckEnsures,
409+
arena_vec![self; *cond_fn, *expr],
410+
);
411+
self.arena.alloc(call_expr)
408412
}
409413

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

compiler/rustc_ast_lowering/src/item.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1206,8 +1206,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
12061206
let precond = if let Some(req) = &contract.requires {
12071207
// Lower the precondition check intrinsic.
12081208
let lowered_req = this.lower_expr_mut(&req);
1209+
let req_span = this.mark_span_with_reason(
1210+
DesugaringKind::Contract,
1211+
lowered_req.span,
1212+
None,
1213+
);
12091214
let precond = this.expr_call_lang_item_fn_mut(
1210-
req.span,
1215+
req_span,
12111216
hir::LangItem::ContractCheckRequires,
12121217
&*arena_vec![this; lowered_req],
12131218
);
@@ -1217,6 +1222,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
12171222
};
12181223
let (postcond, body) = if let Some(ens) = &contract.ensures {
12191224
let ens_span = this.lower_span(ens.span);
1225+
let ens_span =
1226+
this.mark_span_with_reason(DesugaringKind::Contract, ens_span, None);
12201227
// Set up the postcondition `let` statement.
12211228
let check_ident: Ident =
12221229
Ident::from_str_and_span("__ensures_checker", ens_span);

compiler/rustc_hir/src/lang_items.rs

+2
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ language_item_table! {
442442
DefaultTrait3, sym::default_trait3, default_trait3_trait, Target::Trait, GenericRequirement::None;
443443
DefaultTrait2, sym::default_trait2, default_trait2_trait, Target::Trait, GenericRequirement::None;
444444
DefaultTrait1, sym::default_trait1, default_trait1_trait, Target::Trait, GenericRequirement::None;
445+
446+
ContractCheckEnsures, sym::contract_check_ensures, contract_check_ensures_fn, Target::Fn, GenericRequirement::None;
445447
}
446448

447449
/// 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
@@ -217,15 +217,11 @@ pub(crate) fn check_intrinsic_type(
217217
};
218218
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
219219
} else if intrinsic_name == sym::contract_check_ensures {
220-
// contract_check_ensures::<'a, Ret, C>(&'a Ret, C)
221-
// where C: impl Fn(&'a Ret) -> bool,
220+
// contract_check_ensures::<Ret, C>(Ret, C) -> Ret
221+
// where C: for<'a> Fn(&'a Ret) -> bool,
222222
//
223-
// so: two type params, one lifetime param, 0 const params, two inputs, no return
224-
225-
let p = generics.param_at(0, tcx);
226-
let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data());
227-
let ref_ret = Ty::new_imm_ref(tcx, r, param(1));
228-
(2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe)
223+
// so: two type params, 0 lifetime param, 0 const params, two inputs, no return
224+
(2, 0, 0, vec![param(0), param(1)], param(1), hir::Safety::Safe)
229225
} else {
230226
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
231227
let (n_tps, n_cts, inputs, output) = match intrinsic_name {

library/core/src/contracts.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22
33
pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_requires as requires};
44

5-
/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }`
6-
/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }`
7-
/// (including the implicit return of the tail expression, if any).
5+
/// This is an identity function used as part of the desugaring of the `#[ensures]` attribute.
6+
///
7+
/// This is an existing hack to allow users to omit the type of the return value in their ensures
8+
/// attribute.
9+
///
10+
/// Ideally, rustc should be able to generate the type annotation.
11+
/// The existing lowering logic makes it rather hard to add the explicit type annotation,
12+
/// while the function call is fairly straight forward.
813
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
14+
// Similar to `contract_check_requires`, we need to use the user-facing
15+
// `contracts` feature rather than the perma-unstable `contracts_internals`.
16+
// Const-checking doesn't honor allow_internal_unstable logic used by contract expansion.
17+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
918
#[lang = "contract_build_check_ensures"]
10-
#[track_caller]
11-
pub fn build_check_ensures<Ret, C>(cond: C) -> impl (Fn(Ret) -> Ret) + Copy
19+
pub const fn build_check_ensures<Ret, C>(cond: C) -> C
1220
where
13-
C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static,
21+
C: Fn(&Ret) -> bool + Copy + 'static,
1422
{
15-
#[track_caller]
16-
move |ret| {
17-
crate::intrinsics::contract_check_ensures(&ret, cond);
18-
ret
19-
}
23+
cond
2024
}

library/core/src/intrinsics/mod.rs

+48-6
Original file line numberDiff line numberDiff line change
@@ -3402,20 +3402,62 @@ pub const fn contract_checks() -> bool {
34023402
///
34033403
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
34043404
/// returns false.
3405-
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
3405+
///
3406+
/// Note that this function is a no-op during constant evaluation.
3407+
#[unstable(feature = "contracts_internals", issue = "128044")]
3408+
// Calls to this function get inserted by an AST expansion pass, which uses the equivalent of
3409+
// `#[allow_internal_unstable]` to allow using `contracts_internals` functions. Const-checking
3410+
// doesn't honor `#[allow_internal_unstable]`, so for the const feature gate we use the user-facing
3411+
// `contracts` feature rather than the perma-unstable `contracts_internals`
3412+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
34063413
#[lang = "contract_check_requires"]
34073414
#[rustc_intrinsic]
3408-
pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
3409-
if contract_checks() && !cond() {
3410-
// Emit no unwind panic in case this was a safety requirement.
3411-
crate::panicking::panic_nounwind("failed requires check");
3412-
}
3415+
pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
3416+
const_eval_select!(
3417+
@capture[C: Fn() -> bool + Copy] { cond: C } :
3418+
if const {
3419+
// Do nothing
3420+
} else {
3421+
if contract_checks() && !cond() {
3422+
// Emit no unwind panic in case this was a safety requirement.
3423+
crate::panicking::panic_nounwind("failed requires check");
3424+
}
3425+
}
3426+
)
34133427
}
34143428

34153429
/// Check if the post-condition `cond` has been met.
34163430
///
34173431
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
34183432
/// returns false.
3433+
///
3434+
/// Note that this function is a no-op during constant evaluation.
3435+
#[cfg(not(bootstrap))]
3436+
#[unstable(feature = "contracts_internals", issue = "128044")]
3437+
// Similar to `contract_check_requires`, we need to use the user-facing
3438+
// `contracts` feature rather than the perma-unstable `contracts_internals`.
3439+
// Const-checking doesn't honor allow_internal_unstable logic used by contract expansion.
3440+
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
3441+
#[lang = "contract_check_ensures"]
3442+
#[rustc_intrinsic]
3443+
pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(cond: C, ret: Ret) -> Ret {
3444+
const_eval_select!(
3445+
@capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: C, ret: Ret } -> Ret :
3446+
if const {
3447+
// Do nothing
3448+
ret
3449+
} else {
3450+
if contract_checks() && !cond(&ret) {
3451+
// Emit no unwind panic in case this was a safety requirement.
3452+
crate::panicking::panic_nounwind("failed ensures check");
3453+
}
3454+
ret
3455+
}
3456+
)
3457+
}
3458+
3459+
/// This is the old version of contract_check_ensures kept here for bootstrap only.
3460+
#[cfg(bootstrap)]
34193461
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
34203462
#[rustc_intrinsic]
34213463
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
@@ -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-captures-via-closure-noncopy.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz
1616
| | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`
1717
| | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:12:42}`
1818
| unsatisfied trait bound
19+
| required by a bound introduced by this call
1920
|
2021
= help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`, the trait `std::marker::Copy` is not implemented for `Baz`
2122
note: required because it's used within this closure
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-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(doubles_to_two, 1);
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(doubles_to_two, 2);
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(injected_checker, ret)
2626
}
2727

2828
struct Baz { baz: i32 }

tests/ui/contracts/internal_machinery/internal-feature-gating.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn main() {
66
//~^ ERROR use of unstable library feature `contracts_internals`
77
core::intrinsics::contract_check_requires(|| true);
88
//~^ ERROR use of unstable library feature `contracts_internals`
9-
core::intrinsics::contract_check_ensures(&1, |_|true);
9+
core::intrinsics::contract_check_ensures( |_|true, &1);
1010
//~^ ERROR use of unstable library feature `contracts_internals`
1111

1212
core::contracts::build_check_ensures(|_: &()| true);

tests/ui/contracts/internal_machinery/internal-feature-gating.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ LL | core::intrinsics::contract_check_requires(|| true);
4141
error[E0658]: use of unstable library feature `contracts_internals`
4242
--> $DIR/internal-feature-gating.rs:9:5
4343
|
44-
LL | core::intrinsics::contract_check_ensures(&1, |_|true);
44+
LL | core::intrinsics::contract_check_ensures( |_|true, &1);
4545
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4646
|
4747
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information

0 commit comments

Comments
 (0)