about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCelina G. Val <celinval@amazon.com>2025-02-25 13:24:07 -0800
committerCelina G. Val <celinval@amazon.com>2025-04-07 11:17:33 -0700
commitb9754f9e7bfe2d8eed780962b550a25a87118ce4 (patch)
tree1d8ef4bffc26b6918e8698db3bcfbebfb9f03d7f
parent5337252b9952fdd9482ed6a4add17254e5bd2c40 (diff)
downloadrust-b9754f9e7bfe2d8eed780962b550a25a87118ce4.tar.gz
rust-b9754f9e7bfe2d8eed780962b550a25a87118ce4.zip
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.
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs13
-rw-r--r--compiler/rustc_hir/src/lang_items.rs2
-rw-r--r--compiler/rustc_hir_analysis/src/check/intrinsic.rs12
-rw-r--r--library/core/src/contracts.rs13
-rw-r--r--library/core/src/intrinsics/mod.rs47
-rw-r--r--library/core/src/lib.rs1
-rw-r--r--tests/ui/contracts/contract-attributes-nest.chk_pass.stderr13
-rw-r--r--tests/ui/contracts/contract-attributes-nest.rs1
-rw-r--r--tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr13
-rw-r--r--tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr13
-rw-r--r--tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr13
-rw-r--r--tests/ui/contracts/contract-const-fn.all_pass.stderr11
-rw-r--r--tests/ui/contracts/contract-const-fn.rs56
-rw-r--r--tests/ui/contracts/contract-const-fn.runtime_fail_post.stderr11
-rw-r--r--tests/ui/contracts/contract-const-fn.runtime_fail_pre.stderr11
-rw-r--r--tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs1
-rw-r--r--tests/ui/contracts/internal_machinery/contract-intrinsics.rs6
-rw-r--r--tests/ui/contracts/internal_machinery/contract-lang-items.rs4
18 files changed, 206 insertions, 35 deletions
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 80bb1e8fc41..0b7a7630431 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -397,12 +397,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
         &mut self,
         expr: &'hir hir::Expr<'hir>,
         span: Span,
-        check_ident: Ident,
-        check_hir_id: HirId,
+        cond_ident: Ident,
+        cond_hir_id: HirId,
     ) -> &'hir hir::Expr<'hir> {
-        let checker_fn = self.expr_ident(span, check_ident, check_hir_id);
+        let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
         let span = self.mark_span_with_reason(DesugaringKind::Contract, span, None);
-        self.expr_call(span, checker_fn, std::slice::from_ref(expr))
+        let call_expr = self.expr_call_lang_item_fn_mut(
+            span,
+            hir::LangItem::ContractCheckEnsures,
+            arena_vec![self; *expr, *cond_fn],
+        );
+        self.arena.alloc(call_expr)
     }
 
     pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs
index 90fab01ba2d..fd15d054317 100644
--- a/compiler/rustc_hir/src/lang_items.rs
+++ b/compiler/rustc_hir/src/lang_items.rs
@@ -439,6 +439,8 @@ language_item_table! {
     DefaultTrait3,           sym::default_trait3,      default_trait3_trait,       Target::Trait,          GenericRequirement::None;
     DefaultTrait2,           sym::default_trait2,      default_trait2_trait,       Target::Trait,          GenericRequirement::None;
     DefaultTrait1,           sym::default_trait1,      default_trait1_trait,       Target::Trait,          GenericRequirement::None;
+
+    ContractCheckEnsures,     sym::contract_check_ensures,      contract_check_ensures_fn,      Target::Fn, GenericRequirement::None;
 }
 
 /// The requirement imposed on the generics of a lang item
diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
index 42d785c8dd0..290e47b42b5 100644
--- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs
+++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
@@ -232,15 +232,11 @@ pub fn check_intrinsic_type(
         };
         (n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
     } else if intrinsic_name == sym::contract_check_ensures {
-        // contract_check_ensures::<'a, Ret, C>(&'a Ret, C)
-        // where C: impl Fn(&'a Ret) -> bool,
+        // contract_check_ensures::<Ret, C>(Ret, C) -> Ret
+        // where C: for<'a> Fn(&'a Ret) -> bool,
         //
-        // so: two type params, one lifetime param, 0 const params, two inputs, no return
-
-        let p = generics.param_at(0, tcx);
-        let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data());
-        let ref_ret = Ty::new_imm_ref(tcx, r, param(1));
-        (2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe)
+        // so: two type params, 0 lifetime param, 0 const params, two inputs, no return
+        (2, 0, 0, vec![param(0), param(1)], param(0), hir::Safety::Safe)
     } else {
         let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
         let (n_tps, n_cts, inputs, output) = match intrinsic_name {
diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs
index 8b79a3a7eba..82922642018 100644
--- a/library/core/src/contracts.rs
+++ b/library/core/src/contracts.rs
@@ -5,16 +5,15 @@ pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_require
 /// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }`
 /// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }`
 /// (including the implicit return of the tail expression, if any).
+///
+/// This call helps with type inference for the predicate.
 #[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
+#[rustc_const_unstable(feature = "contracts", issue = "128044")]
 #[lang = "contract_build_check_ensures"]
 #[track_caller]
-pub fn build_check_ensures<Ret, C>(cond: C) -> impl (Fn(Ret) -> Ret) + Copy
+pub const fn build_check_ensures<Ret, C>(cond: C) -> C
 where
-    C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static,
+    C: Fn(&Ret) -> bool + Copy + 'static,
 {
-    #[track_caller]
-    move |ret| {
-        crate::intrinsics::contract_check_ensures(&ret, cond);
-        ret
-    }
+    cond
 }
diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs
index 81e59a1f349..8812cb66526 100644
--- a/library/core/src/intrinsics/mod.rs
+++ b/library/core/src/intrinsics/mod.rs
@@ -3450,20 +3450,55 @@ pub const fn contract_checks() -> bool {
 ///
 /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
 /// returns false.
-#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
+///
+/// Note that this function is a no-op during constant evaluation.
+#[unstable(feature = "contracts_internals", issue = "128044")]
+#[rustc_const_unstable(feature = "contracts", issue = "128044")]
 #[lang = "contract_check_requires"]
 #[rustc_intrinsic]
-pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
-    if contract_checks() && !cond() {
-        // Emit no unwind panic in case this was a safety requirement.
-        crate::panicking::panic_nounwind("failed requires check");
-    }
+pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
+    const_eval_select!(
+        @capture[C: Fn() -> bool + Copy] { cond: C } :
+        if const {
+                // Do nothing
+        } else {
+            if contract_checks() && !cond() {
+                // Emit no unwind panic in case this was a safety requirement.
+                crate::panicking::panic_nounwind("failed requires check");
+            }
+        }
+    )
 }
 
 /// Check if the post-condition `cond` has been met.
 ///
 /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
 /// returns false.
+///
+/// Note that this function is a no-op during constant evaluation.
+#[cfg(not(bootstrap))]
+#[unstable(feature = "contracts_internals", issue = "128044")]
+#[rustc_const_unstable(feature = "contracts", issue = "128044")]
+#[lang = "contract_check_ensures"]
+#[rustc_intrinsic]
+pub const fn contract_check_ensures<Ret, C: Fn(&Ret) -> bool + Copy>(ret: Ret, cond: C) -> Ret {
+    const_eval_select!(
+        @capture[Ret, C: Fn(&Ret) -> bool + Copy] { ret: Ret, cond: C } -> Ret :
+        if const {
+            // Do nothing
+            ret
+        } else {
+            if contract_checks() && !cond(&ret) {
+                // Emit no unwind panic in case this was a safety requirement.
+                crate::panicking::panic_nounwind("failed ensures check");
+            }
+            ret
+        }
+    )
+}
+
+/// This is the old version of contract_check_ensures kept here for bootstrap only.
+#[cfg(bootstrap)]
 #[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
 #[rustc_intrinsic]
 pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index dc06aa4c38d..8c68e57897c 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -101,7 +101,6 @@
 #![feature(bstr)]
 #![feature(bstr_internals)]
 #![feature(cfg_match)]
-#![feature(closure_track_caller)]
 #![feature(const_carrying_mul_add)]
 #![feature(const_eval_select)]
 #![feature(core_intrinsics)]
diff --git a/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr b/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
index 9ca95b8bb01..e7c42ad98a5 100644
--- a/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
+++ b/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
    = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
    = note: `#[warn(incomplete_features)]` on by default
 
-warning: 1 warning emitted
+warning: unreachable expression
+  --> $DIR/contract-attributes-nest.rs:23:1
+   |
+LL | #[core::contracts::ensures(|ret| *ret > 100)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
+...
+LL |         return x.baz + 50;
+   |         ----------------- any code following this expression is unreachable
+   |
+   = note: `#[warn(unreachable_code)]` on by default
+
+warning: 2 warnings emitted
 
diff --git a/tests/ui/contracts/contract-attributes-nest.rs b/tests/ui/contracts/contract-attributes-nest.rs
index e1e61b88f28..7c35e54c18b 100644
--- a/tests/ui/contracts/contract-attributes-nest.rs
+++ b/tests/ui/contracts/contract-attributes-nest.rs
@@ -21,6 +21,7 @@
 
 #[core::contracts::requires(x.baz > 0)]
 #[core::contracts::ensures(|ret| *ret > 100)]
+//~^ WARN unreachable expression [unreachable_code]
 fn nest(x: Baz) -> i32
 {
     loop {
diff --git a/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr
index 9ca95b8bb01..e7c42ad98a5 100644
--- a/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr
+++ b/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
    = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
    = note: `#[warn(incomplete_features)]` on by default
 
-warning: 1 warning emitted
+warning: unreachable expression
+  --> $DIR/contract-attributes-nest.rs:23:1
+   |
+LL | #[core::contracts::ensures(|ret| *ret > 100)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
+...
+LL |         return x.baz + 50;
+   |         ----------------- any code following this expression is unreachable
+   |
+   = note: `#[warn(unreachable_code)]` on by default
+
+warning: 2 warnings emitted
 
diff --git a/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr
index 9ca95b8bb01..e7c42ad98a5 100644
--- a/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr
+++ b/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
    = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
    = note: `#[warn(incomplete_features)]` on by default
 
-warning: 1 warning emitted
+warning: unreachable expression
+  --> $DIR/contract-attributes-nest.rs:23:1
+   |
+LL | #[core::contracts::ensures(|ret| *ret > 100)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
+...
+LL |         return x.baz + 50;
+   |         ----------------- any code following this expression is unreachable
+   |
+   = note: `#[warn(unreachable_code)]` on by default
+
+warning: 2 warnings emitted
 
diff --git a/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr
index 9ca95b8bb01..e7c42ad98a5 100644
--- a/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr
+++ b/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr
@@ -7,5 +7,16 @@ LL | #![feature(contracts)]
    = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
    = note: `#[warn(incomplete_features)]` on by default
 
-warning: 1 warning emitted
+warning: unreachable expression
+  --> $DIR/contract-attributes-nest.rs:23:1
+   |
+LL | #[core::contracts::ensures(|ret| *ret > 100)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
+...
+LL |         return x.baz + 50;
+   |         ----------------- any code following this expression is unreachable
+   |
+   = note: `#[warn(unreachable_code)]` on by default
+
+warning: 2 warnings emitted
 
diff --git a/tests/ui/contracts/contract-const-fn.all_pass.stderr b/tests/ui/contracts/contract-const-fn.all_pass.stderr
new file mode 100644
index 00000000000..e5b1df65582
--- /dev/null
+++ b/tests/ui/contracts/contract-const-fn.all_pass.stderr
@@ -0,0 +1,11 @@
+warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/contract-const-fn.rs:17:12
+   |
+LL | #![feature(contracts)]
+   |            ^^^^^^^^^
+   |
+   = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
+   = note: `#[warn(incomplete_features)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/contracts/contract-const-fn.rs b/tests/ui/contracts/contract-const-fn.rs
new file mode 100644
index 00000000000..733a06ae570
--- /dev/null
+++ b/tests/ui/contracts/contract-const-fn.rs
@@ -0,0 +1,56 @@
+//! Check if we can annotate a constant function with contracts.
+//!
+//! The contract is only checked at runtime, and it will not fail if evaluated statically.
+//! This is an existing limitation due to the existing architecture and the lack of constant
+//! closures.
+//!
+//@ revisions: all_pass runtime_fail_pre runtime_fail_post
+//
+//@ [all_pass] run-pass
+//
+//@ [runtime_fail_pre] run-fail
+//@ [runtime_fail_post] run-fail
+//
+//@ [all_pass] compile-flags: -Zcontract-checks=yes
+//@ [runtime_fail_pre] compile-flags: -Zcontract-checks=yes
+//@ [runtime_fail_post] compile-flags: -Zcontract-checks=yes
+#![feature(contracts)]
+//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
+
+extern crate core;
+use core::contracts::*;
+
+#[requires(x < 100)]
+const fn less_than_100(x: u8) -> u8 {
+    x
+}
+
+// This is wrong on purpose.
+#[ensures(|ret| *ret)]
+const fn always_true(b: bool) -> bool {
+    b
+}
+
+const ZERO: u8 = less_than_100(0);
+// This is no-op because the contract cannot be checked at compilation time.
+const TWO_HUNDRED: u8 = less_than_100(200);
+
+/// Example from <https://github.com/rust-lang/rust/issues/136925>.
+#[ensures(move |ret: &u32| *ret > x)]
+const fn broken_sum(x: u32, y: u32) -> u32 {
+    x + y
+}
+
+fn main() {
+    assert_eq!(ZERO, 0);
+    assert_eq!(TWO_HUNDRED, 200);
+    assert_eq!(broken_sum(0, 1), 1);
+    assert_eq!(always_true(true), true);
+
+    #[cfg(runtime_fail_post)]
+    let _ok = always_true(false);
+
+    // Runtime check should fail.
+    #[cfg(runtime_fail_pre)]
+    let _200 = less_than_100(200);
+}
diff --git a/tests/ui/contracts/contract-const-fn.runtime_fail_post.stderr b/tests/ui/contracts/contract-const-fn.runtime_fail_post.stderr
new file mode 100644
index 00000000000..e5b1df65582
--- /dev/null
+++ b/tests/ui/contracts/contract-const-fn.runtime_fail_post.stderr
@@ -0,0 +1,11 @@
+warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/contract-const-fn.rs:17:12
+   |
+LL | #![feature(contracts)]
+   |            ^^^^^^^^^
+   |
+   = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
+   = note: `#[warn(incomplete_features)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/contracts/contract-const-fn.runtime_fail_pre.stderr b/tests/ui/contracts/contract-const-fn.runtime_fail_pre.stderr
new file mode 100644
index 00000000000..e5b1df65582
--- /dev/null
+++ b/tests/ui/contracts/contract-const-fn.runtime_fail_pre.stderr
@@ -0,0 +1,11 @@
+warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/contract-const-fn.rs:17:12
+   |
+LL | #![feature(contracts)]
+   |            ^^^^^^^^^
+   |
+   = note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
+   = note: `#[warn(incomplete_features)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs
index 6d8cd3949ee..7f9c3fe28ce 100644
--- a/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs
+++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs
@@ -21,6 +21,7 @@
 fn nest(x: Baz) -> i32
     contract_requires(|| x.baz > 0)
     contract_ensures(|ret| *ret > 100)
+    //~^ WARN unreachable expression [unreachable_code]
 {
     loop {
         return x.baz + 50;
diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs
index ae692afd146..f94dfbde75f 100644
--- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs
+++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs
@@ -26,11 +26,11 @@ fn main() {
     #[cfg(any(default, unchk_pass, chk_fail_requires))]
     core::intrinsics::contract_check_requires(|| false);
 
-    let doubles_to_two = { let old = 2; move |ret| ret + ret == old };
+    let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old };
     // Always pass
-    core::intrinsics::contract_check_ensures(&1, doubles_to_two);
+    core::intrinsics::contract_check_ensures(1, doubles_to_two);
 
     // Fail if enabled
     #[cfg(any(default, unchk_pass, chk_fail_ensures))]
-    core::intrinsics::contract_check_ensures(&2, doubles_to_two);
+    core::intrinsics::contract_check_ensures(2, doubles_to_two);
 }
diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs
index e91bbed294d..26042cf688f 100644
--- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs
+++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs
@@ -15,14 +15,14 @@
 #![feature(contracts)] // to access core::contracts
 //~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
 #![feature(contracts_internals)] // to access check_requires lang item
-
+#![feature(core_intrinsics)]
 fn foo(x: Baz) -> i32 {
     let injected_checker = {
         core::contracts::build_check_ensures(|ret| *ret > 100)
     };
 
     let ret = x.baz + 50;
-    injected_checker(ret)
+    core::intrinsics::contract_check_ensures(ret, injected_checker)
 }
 
 struct Baz { baz: i32 }