From 3e563368a7ad63b00e2edf35e00f85c01a1ab33c Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Fri, 25 Jul 2025 07:50:46 +0100 Subject: [PATCH 1/4] Guard HIR lowered contracts with contract_checks Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled. The call to contract_checks is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled. --- compiler/rustc_ast_lowering/src/contract.rs | 260 ++++++++++++++++++ compiler/rustc_ast_lowering/src/expr.rs | 86 +++--- compiler/rustc_ast_lowering/src/item.rs | 71 +---- compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_analysis/src/check/intrinsic.rs | 4 +- compiler/rustc_middle/src/ty/sty.rs | 6 + library/core/src/intrinsics/mod.rs | 23 +- .../contracts-disabled-side-effect-ensures.rs | 17 ++ ...tracts-disabled-side-effect-ensures.stderr | 11 + tests/ui/contracts/empty-ensures.rs | 16 ++ tests/ui/contracts/empty-ensures.stderr | 25 ++ tests/ui/contracts/empty-requires.rs | 18 ++ tests/ui/contracts/empty-requires.stderr | 18 ++ .../internal_machinery/contract-intrinsics.rs | 12 +- .../internal_machinery/contract-lang-items.rs | 6 +- .../internal-feature-gating.rs | 2 +- .../internal-feature-gating.stderr | 2 +- 18 files changed, 459 insertions(+), 120 deletions(-) create mode 100644 compiler/rustc_ast_lowering/src/contract.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr create mode 100644 tests/ui/contracts/empty-ensures.rs create mode 100644 tests/ui/contracts/empty-ensures.stderr create mode 100644 tests/ui/contracts/empty-requires.rs create mode 100644 tests/ui/contracts/empty-requires.stderr diff --git a/compiler/rustc_ast_lowering/src/contract.rs b/compiler/rustc_ast_lowering/src/contract.rs new file mode 100644 index 0000000000000..ad49cd2a42423 --- /dev/null +++ b/compiler/rustc_ast_lowering/src/contract.rs @@ -0,0 +1,260 @@ +use crate::LoweringContext; + +impl<'a, 'hir> LoweringContext<'a, 'hir> { + pub(super) fn lower_contract( + &mut self, + body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, + contract: &rustc_ast::FnContract, + ) -> rustc_hir::Expr<'hir> { + match (&contract.requires, &contract.ensures) { + (Some(req), Some(ens)) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // let __postcond = if contracts_checks() { + // contract_check_requires(PRECOND); + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; + // contract_check_ensures(__postcond, { body }) + // } + + let precond = self.lower_precond(req); + let postcond_checker = self.lower_postcond_checker(ens); + + let contract_check = + self.lower_contract_check_with_postcond(Some(precond), postcond_checker); + + let wrapped_body = + self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span); + self.expr_block(wrapped_body) + } + (None, Some(ens)) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // let __postcond = if contracts_check() { + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; + // __postcond({ body }) + // } + + let postcond_checker = self.lower_postcond_checker(ens); + let contract_check = + self.lower_contract_check_with_postcond(None, postcond_checker); + + let wrapped_body = + self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span); + self.expr_block(wrapped_body) + } + (Some(req), None) => { + // Lower the fn contract, which turns: + // + // { body } + // + // into: + // + // { + // if contracts_check() { + // contract_requires(PRECOND); + // } + // body + // } + let precond = self.lower_precond(req); + let precond_check = self.lower_contract_check_just_precond(precond); + + let body = self.arena.alloc(body(self)); + + // Flatten the body into precond check, then body. + let wrapped_body = self.block_all( + body.span, + self.arena.alloc_from_iter([precond_check].into_iter()), + Some(body), + ); + self.expr_block(wrapped_body) + } + (None, None) => body(self), + } + } + + /// Lower the precondition check intrinsic. + fn lower_precond(&mut self, req: &Box) -> rustc_hir::Stmt<'hir> { + let lowered_req = self.lower_expr_mut(&req); + let req_span = self.mark_span_with_reason( + rustc_span::DesugaringKind::Contract, + lowered_req.span, + None, + ); + let precond = self.expr_call_lang_item_fn_mut( + req_span, + rustc_hir::LangItem::ContractCheckRequires, + &*arena_vec![self; lowered_req], + ); + self.stmt_expr(req.span, precond) + } + + fn lower_postcond_checker( + &mut self, + ens: &Box, + ) -> &'hir rustc_hir::Expr<'hir> { + let ens_span = self.lower_span(ens.span); + let ens_span = + self.mark_span_with_reason(rustc_span::DesugaringKind::Contract, ens_span, None); + let lowered_ens = self.lower_expr_mut(&ens); + self.expr_call_lang_item_fn( + ens_span, + rustc_hir::LangItem::ContractBuildCheckEnsures, + &*arena_vec![self; lowered_ens], + ) + } + + fn lower_contract_check_just_precond( + &mut self, + precond: rustc_hir::Stmt<'hir>, + ) -> rustc_hir::Stmt<'hir> { + let stmts = self.arena.alloc_from_iter([precond].into_iter()); + + let then_block_stmts = self.block_all(precond.span, stmts, None); + let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); + + let precond_check = rustc_hir::ExprKind::If( + self.expr_call_lang_item_fn( + precond.span, + rustc_hir::LangItem::ContractChecks, + Default::default(), + ), + then_block, + None, + ); + + let precond_check = self.expr(precond.span, precond_check); + self.stmt_expr(precond.span, precond_check) + } + + fn lower_contract_check_with_postcond( + &mut self, + precond: Option>, + postcond_checker: &'hir rustc_hir::Expr<'hir>, + ) -> &'hir rustc_hir::Expr<'hir> { + let stmts = self.arena.alloc_from_iter(precond.into_iter()); + let span = match precond { + Some(precond) => precond.span, + None => postcond_checker.span, + }; + + let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item( + postcond_checker.span, + rustc_hir::lang_items::LangItem::OptionSome, + &*arena_vec![self; *postcond_checker], + )); + let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker)); + let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); + + let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item( + postcond_checker.span, + rustc_hir::lang_items::LangItem::OptionNone, + Default::default(), + )); + let else_block = self.block_expr(none_expr); + let else_block = self.arena.alloc(self.expr_block(else_block)); + + let contract_check = rustc_hir::ExprKind::If( + self.expr_call_lang_item_fn( + span, + rustc_hir::LangItem::ContractChecks, + Default::default(), + ), + then_block, + Some(else_block), + ); + self.arena.alloc(self.expr(span, contract_check)) + } + + fn wrap_body_with_contract_check( + &mut self, + body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, + contract_check: &'hir rustc_hir::Expr<'hir>, + postcond_span: rustc_span::Span, + ) -> &'hir rustc_hir::Block<'hir> { + let check_ident: rustc_span::Ident = + rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span); + let (check_hir_id, postcond_decl) = { + // Set up the postcondition `let` statement. + let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut( + postcond_span, + check_ident, + rustc_hir::BindingMode::NONE, + ); + ( + check_hir_id, + self.stmt_let_pat( + None, + postcond_span, + Some(contract_check), + self.arena.alloc(checker_pat), + rustc_hir::LocalSource::Contract, + ), + ) + }; + + // Install contract_ensures so we will intercept `return` statements, + // then lower the body. + self.contract_ensures = Some((postcond_span, check_ident, check_hir_id)); + let body = self.arena.alloc(body(self)); + + // Finally, inject an ensures check on the implicit return of the body. + let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id); + + // Flatten the body into precond, then postcond, then wrapped body. + let wrapped_body = self.block_all( + body.span, + self.arena.alloc_from_iter([postcond_decl].into_iter()), + Some(body), + ); + wrapped_body + } + + /// Create an `ExprKind::Ret` that is optionally wrapped by a call to check + /// a contract ensures clause, if it exists. + pub(super) fn checked_return( + &mut self, + opt_expr: Option<&'hir rustc_hir::Expr<'hir>>, + ) -> rustc_hir::ExprKind<'hir> { + let checked_ret = + if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures { + let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span)); + Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id)) + } else { + opt_expr + }; + rustc_hir::ExprKind::Ret(checked_ret) + } + + /// Wraps an expression with a call to the ensures check before it gets returned. + pub(super) fn inject_ensures_check( + &mut self, + expr: &'hir rustc_hir::Expr<'hir>, + span: rustc_span::Span, + cond_ident: rustc_span::Ident, + cond_hir_id: rustc_hir::HirId, + ) -> &'hir rustc_hir::Expr<'hir> { + let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); + let call_expr = self.expr_call_lang_item_fn_mut( + span, + rustc_hir::LangItem::ContractCheckEnsures, + arena_vec![self; *cond_fn, *expr], + ); + self.arena.alloc(call_expr) + } +} diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index bb6b25baf013d..d1ce3b169d75f 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -383,36 +383,6 @@ impl<'hir> LoweringContext<'_, 'hir> { }) } - /// Create an `ExprKind::Ret` that is optionally wrapped by a call to check - /// a contract ensures clause, if it exists. - fn checked_return(&mut self, opt_expr: Option<&'hir hir::Expr<'hir>>) -> hir::ExprKind<'hir> { - let checked_ret = - if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures { - let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span)); - Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id)) - } else { - opt_expr - }; - hir::ExprKind::Ret(checked_ret) - } - - /// Wraps an expression with a call to the ensures check before it gets returned. - pub(crate) fn inject_ensures_check( - &mut self, - expr: &'hir hir::Expr<'hir>, - span: Span, - cond_ident: Ident, - cond_hir_id: HirId, - ) -> &'hir hir::Expr<'hir> { - let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); - let call_expr = self.expr_call_lang_item_fn_mut( - span, - hir::LangItem::ContractCheckEnsures, - arena_vec![self; *cond_fn, *expr], - ); - self.arena.alloc(call_expr) - } - pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); @@ -2120,7 +2090,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e)) } - fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> { + pub(super) fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> { self.arena.alloc(self.expr(sp, hir::ExprKind::Tup(&[]))) } @@ -2161,6 +2131,43 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::Call(e, args)) } + pub(super) fn expr_struct( + &mut self, + span: Span, + path: &'hir hir::QPath<'hir>, + fields: &'hir [hir::ExprField<'hir>], + ) -> hir::Expr<'hir> { + self.expr(span, hir::ExprKind::Struct(path, fields, rustc_hir::StructTailExpr::None)) + } + + pub(super) fn expr_enum_variant( + &mut self, + span: Span, + path: &'hir hir::QPath<'hir>, + fields: &'hir [hir::Expr<'hir>], + ) -> hir::Expr<'hir> { + let fields = self.arena.alloc_from_iter(fields.into_iter().enumerate().map(|(i, f)| { + hir::ExprField { + hir_id: self.next_id(), + ident: Ident::from_str(&i.to_string()), + expr: f, + span: f.span, + is_shorthand: false, + } + })); + self.expr_struct(span, path, fields) + } + + pub(super) fn expr_enum_variant_lang_item( + &mut self, + span: Span, + lang_item: hir::LangItem, + fields: &'hir [hir::Expr<'hir>], + ) -> hir::Expr<'hir> { + let path = self.arena.alloc(self.lang_item_path(span, lang_item)); + self.expr_enum_variant(span, path, fields) + } + pub(super) fn expr_call( &mut self, span: Span, @@ -2189,8 +2196,21 @@ impl<'hir> LoweringContext<'_, 'hir> { self.arena.alloc(self.expr_call_lang_item_fn_mut(span, lang_item, args)) } - fn expr_lang_item_path(&mut self, span: Span, lang_item: hir::LangItem) -> hir::Expr<'hir> { - self.expr(span, hir::ExprKind::Path(hir::QPath::LangItem(lang_item, self.lower_span(span)))) + pub(super) fn expr_lang_item_path( + &mut self, + span: Span, + lang_item: hir::LangItem, + ) -> hir::Expr<'hir> { + let path = self.lang_item_path(span, lang_item); + self.expr(span, hir::ExprKind::Path(path)) + } + + pub(super) fn lang_item_path( + &mut self, + span: Span, + lang_item: hir::LangItem, + ) -> hir::QPath<'hir> { + hir::QPath::LangItem(lang_item, self.lower_span(span)) } /// `::name` diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 53351f91c46bc..d11b7f798b6db 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1214,76 +1214,9 @@ impl<'hir> LoweringContext<'_, 'hir> { let params = this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))); - // Optionally lower the fn contract, which turns: - // - // { body } - // - // into: - // - // { contract_requires(PRECOND); let __postcond = |ret_val| POSTCOND; postcond({ body }) } + // Optionally lower the fn contract if let Some(contract) = contract { - let precond = if let Some(req) = &contract.requires { - // Lower the precondition check intrinsic. - let lowered_req = this.lower_expr_mut(&req); - let req_span = this.mark_span_with_reason( - DesugaringKind::Contract, - lowered_req.span, - None, - ); - let precond = this.expr_call_lang_item_fn_mut( - req_span, - hir::LangItem::ContractCheckRequires, - &*arena_vec![this; lowered_req], - ); - Some(this.stmt_expr(req.span, precond)) - } else { - None - }; - let (postcond, body) = if let Some(ens) = &contract.ensures { - let ens_span = this.lower_span(ens.span); - let ens_span = - this.mark_span_with_reason(DesugaringKind::Contract, ens_span, None); - // Set up the postcondition `let` statement. - let check_ident: Ident = - Ident::from_str_and_span("__ensures_checker", ens_span); - let (checker_pat, check_hir_id) = this.pat_ident_binding_mode_mut( - ens_span, - check_ident, - hir::BindingMode::NONE, - ); - let lowered_ens = this.lower_expr_mut(&ens); - let postcond_checker = this.expr_call_lang_item_fn( - ens_span, - hir::LangItem::ContractBuildCheckEnsures, - &*arena_vec![this; lowered_ens], - ); - let postcond = this.stmt_let_pat( - None, - ens_span, - Some(postcond_checker), - this.arena.alloc(checker_pat), - hir::LocalSource::Contract, - ); - - // Install contract_ensures so we will intercept `return` statements, - // then lower the body. - this.contract_ensures = Some((ens_span, check_ident, check_hir_id)); - let body = this.arena.alloc(body(this)); - - // Finally, inject an ensures check on the implicit return of the body. - let body = this.inject_ensures_check(body, ens_span, check_ident, check_hir_id); - (Some(postcond), body) - } else { - let body = &*this.arena.alloc(body(this)); - (None, body) - }; - // Flatten the body into precond, then postcond, then wrapped body. - let wrapped_body = this.block_all( - body.span, - this.arena.alloc_from_iter([precond, postcond].into_iter().flatten()), - Some(body), - ); - (params, this.expr_block(wrapped_body)) + (params, this.lower_contract(body, contract)) } else { (params, body(this)) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4e2243e87873c..282a62ca44310 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -77,6 +77,7 @@ macro_rules! arena_vec { mod asm; mod block; +mod contract; mod delegation; mod errors; mod expr; diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 2e099a97b65be..d62ec5482b430 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -429,6 +429,7 @@ language_item_table! { // Experimental lang items for implementing contract pre- and post-condition checking. ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None; ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None; + ContractChecks, sym::contract_checks, contract_checks_fn, Target::Fn, GenericRequirement::None; // Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727) DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index bc3448be5823e..5fd71fd1cd76c 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -651,7 +651,9 @@ pub(crate) fn check_intrinsic_type( sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), - sym::contract_check_ensures => (2, 0, vec![param(0), param(1)], param(1)), + sym::contract_check_ensures => { + (2, 0, vec![Ty::new_option(tcx, param(0)), param(1)], param(1)) + } sym::simd_eq | sym::simd_ne | sym::simd_lt | sym::simd_le | sym::simd_gt | sym::simd_ge => { (2, 0, vec![param(0), param(0)], param(1)) diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index de35e5e847c81..7f44217971833 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -903,6 +903,12 @@ impl<'tcx> Ty<'tcx> { Ty::new_generic_adt(tcx, def_id, ty) } + #[inline] + pub fn new_option(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let def_id = tcx.require_lang_item(LangItem::Option, DUMMY_SP); + Ty::new_generic_adt(tcx, def_id, ty) + } + #[inline] pub fn new_maybe_uninit(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { let def_id = tcx.require_lang_item(LangItem::MaybeUninit, DUMMY_SP); diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index cef700be9ea1f..d35eca015ed01 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2637,9 +2637,10 @@ pub const unsafe fn const_make_global(ptr: *mut u8) -> *const u8 { /// of not prematurely committing at compile-time to whether contract /// checking is turned on, so that we can specify contracts in libstd /// and let an end user opt into turning them on. -#[rustc_const_unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] #[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[rustc_const_unstable(feature = "contracts", issue = "128044")] #[inline(always)] +#[lang = "contract_checks"] #[rustc_intrinsic] pub const fn contract_checks() -> bool { // FIXME: should this be `false` or `cfg!(contract_checks)`? @@ -2668,7 +2669,7 @@ pub const fn contract_check_requires bool + Copy>(cond: C) { if const { // Do nothing } else { - if contract_checks() && !cond() { + if !cond() { // Emit no unwind panic in case this was a safety requirement. crate::panicking::panic_nounwind("failed requires check"); } @@ -2689,16 +2690,24 @@ pub const fn contract_check_requires bool + Copy>(cond: C) { #[rustc_const_unstable(feature = "contracts", issue = "128044")] #[lang = "contract_check_ensures"] #[rustc_intrinsic] -pub const fn contract_check_ensures bool + Copy, Ret>(cond: C, ret: Ret) -> Ret { +pub const fn contract_check_ensures bool + Copy, Ret>( + cond: Option, + ret: Ret, +) -> Ret { const_eval_select!( - @capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: C, ret: Ret } -> Ret : + @capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: Option, ret: Ret } -> 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"); + match cond { + crate::option::Option::Some(cond) => { + if !cond(&ret) { + // Emit no unwind panic in case this was a safety requirement. + crate::panicking::panic_nounwind("failed ensures check"); + } + }, + crate::option::Option::None => {}, } ret } diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs new file mode 100644 index 0000000000000..d234acb8268a1 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs @@ -0,0 +1,17 @@ +//@ run-pass +#![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::ensures; + +#[ensures({*x = 0; |_ret| true})] +fn buggy_add(x: &mut u32, y: u32) { + *x = *x + y; +} + +fn main() { + let mut x = 10; + buggy_add(&mut x, 100); + assert_eq!(x, 110); +} diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr b/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr new file mode 100644 index 0000000000000..dd9ebe9bd3556 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-disabled-side-effect-ensures.rs:2:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/empty-ensures.rs b/tests/ui/contracts/empty-ensures.rs new file mode 100644 index 0000000000000..d897f27bf6c94 --- /dev/null +++ b/tests/ui/contracts/empty-ensures.rs @@ -0,0 +1,16 @@ +//@ 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::ensures; + +#[ensures()] +//~^ ERROR expected a `Fn(&_)` closure, found `()` [E0277] +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/empty-ensures.stderr b/tests/ui/contracts/empty-ensures.stderr new file mode 100644 index 0000000000000..407a253bd8565 --- /dev/null +++ b/tests/ui/contracts/empty-ensures.stderr @@ -0,0 +1,25 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/empty-ensures.rs:2:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0277]: expected a `Fn(&_)` closure, found `()` + --> $DIR/empty-ensures.rs:8:1 + | +LL | #[ensures()] + | ^^^^^^^^^^^^ + | | + | expected an `Fn(&_)` closure, found `()` + | required by a bound introduced by this call + | + = help: the trait `for<'a> Fn(&'a _)` is not implemented for `()` +note: required by a bound in `build_check_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/empty-requires.rs b/tests/ui/contracts/empty-requires.rs new file mode 100644 index 0000000000000..e3c72dcd66a17 --- /dev/null +++ b/tests/ui/contracts/empty-requires.rs @@ -0,0 +1,18 @@ +//@ dont-require-annotations: NOTE +//@ 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; + +#[requires()] +//~^ ERROR mismatched types [E0308] +//~| NOTE expected `bool`, found `()` +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/empty-requires.stderr b/tests/ui/contracts/empty-requires.stderr new file mode 100644 index 0000000000000..b48e547b8cda7 --- /dev/null +++ b/tests/ui/contracts/empty-requires.stderr @@ -0,0 +1,18 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/empty-requires.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0308]: mismatched types + --> $DIR/empty-requires.rs:9:1 + | +LL | #[requires()] + | ^^^^^^^^^^^^^ expected `bool`, found `()` + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 6e613b53fc9b1..5caea2b23d93c 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -22,15 +22,15 @@ fn main() { // always pass core::intrinsics::contract_check_requires(|| true); - // fail if enabled - #[cfg(any(default, unchk_pass, chk_fail_requires))] + // always fail + #[cfg(any(chk_fail_requires))] core::intrinsics::contract_check_requires(|| false); let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old }; // Always pass - core::intrinsics::contract_check_ensures(doubles_to_two, 1); + core::intrinsics::contract_check_ensures(Some(doubles_to_two), 1); - // Fail if enabled - #[cfg(any(default, unchk_pass, chk_fail_ensures))] - core::intrinsics::contract_check_ensures(doubles_to_two, 2); + // always fail + #[cfg(any(chk_fail_ensures))] + core::intrinsics::contract_check_ensures(Some(doubles_to_two), 2); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index ac72d233bf6b4..50cf592ac564d 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -17,8 +17,10 @@ #![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 injected_checker = if core::intrinsics::contract_checks() { + Some(core::contracts::build_check_ensures(|ret| *ret > 100)) + } else { + None }; let ret = x.baz + 50; diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index 6e5a7a3f95005..d101ab33547eb 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -6,7 +6,7 @@ fn main() { //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_requires(|| true); //~^ ERROR use of unstable library feature `contracts_internals` - core::intrinsics::contract_check_ensures( |_|true, &1); + core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); //~^ ERROR use of unstable library feature `contracts_internals` core::contracts::build_check_ensures(|_: &()| true); diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 1e39bd62e245b..738b7a3e09ff1 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -41,7 +41,7 @@ LL | core::intrinsics::contract_check_requires(|| true); error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:9:5 | -LL | core::intrinsics::contract_check_ensures( |_|true, &1); +LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #128044 for more information From 4aa3affd050583f56ecce23624417fe05dc62ac6 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Sat, 16 Aug 2025 15:37:53 +0100 Subject: [PATCH 2/4] Guard HIR contracts based on compiler flag rather than lang_item This allows the optimiser to properly eliminate contract code when runtime contract checks are disabled. It comes at the cost of having to recompile upstream crates (e.g. std) to enable contracts in them. However, this trade off is acceptable if it means disabled runtime contract checks do not affect the runtime performance of the functions they annotate. With the proper elimination of contract code, which this change introduces, the runtime performance of annotated functions should be the same as the original unannotated function. --- compiler/rustc_ast_lowering/src/contract.rs | 118 ++++++++++++++---- compiler/rustc_ast_lowering/src/expr.rs | 39 ++++-- ...contract-lang-items.unchk_fail_post.stderr | 11 -- 3 files changed, 120 insertions(+), 48 deletions(-) delete mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr diff --git a/compiler/rustc_ast_lowering/src/contract.rs b/compiler/rustc_ast_lowering/src/contract.rs index ad49cd2a42423..2f1c3d66d4e70 100644 --- a/compiler/rustc_ast_lowering/src/contract.rs +++ b/compiler/rustc_ast_lowering/src/contract.rs @@ -1,6 +1,18 @@ +use thin_vec::thin_vec; + use crate::LoweringContext; impl<'a, 'hir> LoweringContext<'a, 'hir> { + /// Lowered contracts are guarded with the `contract_checks` compiler flag, + /// i.e. the flag turns into a boolean guard in the lowered HIR. The reason + /// for not eliminating the contract code entirely when the `contract_checks` + /// flag is disabled is so that contracts can be type checked, even when + /// they are disabled, which avoids them becoming stale (i.e. out of sync + /// with the codebase) over time. + /// + /// The optimiser should be able to eliminate all contract code guarded + /// by `if false`, leaving the original body intact when runtime contract + /// checks are disabled. pub(super) fn lower_contract( &mut self, body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>, @@ -14,14 +26,20 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // // into: // + // let __postcond = if contract_checks { + // contract_check_requires(PRECOND); + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; // { - // let __postcond = if contracts_checks() { - // contract_check_requires(PRECOND); - // Some(|ret_val| POSTCOND) - // } else { - // None - // }; - // contract_check_ensures(__postcond, { body }) + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } // } let precond = self.lower_precond(req); @@ -41,13 +59,19 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // // into: // + // let __postcond = if contract_checks { + // Some(|ret_val| POSTCOND) + // } else { + // None + // }; // { - // let __postcond = if contracts_check() { - // Some(|ret_val| POSTCOND) - // } else { - // None - // }; - // __postcond({ body }) + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } // } let postcond_checker = self.lower_postcond_checker(ens); @@ -66,7 +90,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // into: // // { - // if contracts_check() { + // if contracts_checks { // contract_requires(PRECOND); // } // body @@ -129,11 +153,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); let precond_check = rustc_hir::ExprKind::If( - self.expr_call_lang_item_fn( - precond.span, - rustc_hir::LangItem::ContractChecks, - Default::default(), - ), + self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())), then_block, None, ); @@ -170,11 +190,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let else_block = self.arena.alloc(self.expr_block(else_block)); let contract_check = rustc_hir::ExprKind::If( - self.expr_call_lang_item_fn( - span, - rustc_hir::LangItem::ContractChecks, - Default::default(), - ), + self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())), then_block, Some(else_block), ); @@ -249,12 +265,60 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { cond_ident: rustc_span::Ident, cond_hir_id: rustc_hir::HirId, ) -> &'hir rustc_hir::Expr<'hir> { + // { + // let ret = { body }; + // + // if contract_checks { + // contract_check_ensures(__postcond, ret) + // } else { + // ret + // } + // } + let ret_ident: rustc_span::Ident = rustc_span::Ident::from_str_and_span("__ret", span); + + // Set up the return `let` statement. + let (ret_pat, ret_hir_id) = + self.pat_ident_binding_mode_mut(span, ret_ident, rustc_hir::BindingMode::NONE); + + let ret_stmt = self.stmt_let_pat( + None, + span, + Some(expr), + self.arena.alloc(ret_pat), + rustc_hir::LocalSource::Contract, + ); + + let ret = self.expr_ident(span, ret_ident, ret_hir_id); + let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); - let call_expr = self.expr_call_lang_item_fn_mut( + let contract_check = self.expr_call_lang_item_fn_mut( span, rustc_hir::LangItem::ContractCheckEnsures, - arena_vec![self; *cond_fn, *expr], + arena_vec![self; *cond_fn, *ret], ); - self.arena.alloc(call_expr) + let contract_check = self.arena.alloc(contract_check); + let call_expr = self.block_expr_block(contract_check); + + // same ident can't be used in 2 places, so we create a new one for the + // else branch + let ret = self.expr_ident(span, ret_ident, ret_hir_id); + let ret_block = self.block_expr_block(ret); + + let contracts_enabled: rustc_hir::Expr<'_> = + self.expr_bool_literal(span, self.tcx.sess.contract_checks()); + let contract_check = self.arena.alloc(self.expr( + span, + rustc_hir::ExprKind::If( + self.arena.alloc(contracts_enabled), + call_expr, + Some(ret_block), + ), + )); + + let attrs: rustc_ast::AttrVec = thin_vec![self.unreachable_code_attr(span)]; + self.lower_attrs(contract_check.hir_id, &attrs, span, rustc_hir::Target::Expression); + + let ret_block = self.block_all(span, arena_vec![self; ret_stmt], Some(contract_check)); + self.arena.alloc(self.expr_block(self.arena.alloc(ret_block))) } } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index d1ce3b169d75f..4a9b9f544b53d 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1941,16 +1941,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }; - // `#[allow(unreachable_code)]` - let attr = attr::mk_attr_nested_word( - &self.tcx.sess.psess.attr_id_generator, - AttrStyle::Outer, - Safety::Default, - sym::allow, - sym::unreachable_code, - try_span, - ); - let attrs: AttrVec = thin_vec![attr]; + let attrs: AttrVec = thin_vec![self.unreachable_code_attr(try_span)]; // `ControlFlow::Continue(val) => #[allow(unreachable_code)] val,` let continue_arm = { @@ -2290,6 +2281,17 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(b.span, hir::ExprKind::Block(b, None)) } + /// Wrap an expression in a block, and wrap that block in an expression again. + /// Useful for constructing if-expressions, which require expressions of + /// kind block. + pub(super) fn block_expr_block( + &mut self, + expr: &'hir hir::Expr<'hir>, + ) -> &'hir hir::Expr<'hir> { + let b = self.block_expr(expr); + self.arena.alloc(self.expr_block(b)) + } + pub(super) fn expr_array_ref( &mut self, span: Span, @@ -2303,6 +2305,10 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr)) } + pub(super) fn expr_bool_literal(&mut self, span: Span, val: bool) -> hir::Expr<'hir> { + self.expr(span, hir::ExprKind::Lit(Spanned { node: LitKind::Bool(val), span })) + } + pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> { let hir_id = self.next_id(); hir::Expr { hir_id, kind, span: self.lower_span(span) } @@ -2336,6 +2342,19 @@ impl<'hir> LoweringContext<'_, 'hir> { body: expr, } } + + /// `#[allow(unreachable_code)]` + pub(super) fn unreachable_code_attr(&mut self, span: Span) -> Attribute { + let attr = attr::mk_attr_nested_word( + &self.tcx.sess.psess.attr_id_generator, + AttrStyle::Outer, + Safety::Default, + sym::allow, + sym::unreachable_code, + span, + ); + attr + } } /// Used by [`LoweringContext::make_lowered_await`] to customize the desugaring based on what kind diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr deleted file mode 100644 index a60ce1602659b..0000000000000 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr +++ /dev/null @@ -1,11 +0,0 @@ -warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 - | -LL | #![feature(contracts)] // to access core::contracts - | ^^^^^^^^^ - | - = note: see issue #128044 for more information - = note: `#[warn(incomplete_features)]` on by default - -warning: 1 warning emitted - From 0a30c39c43d2e496d49461991b59e2e846d50b7a Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Thu, 4 Sep 2025 08:24:00 +0100 Subject: [PATCH 3/4] Remove no longer used contract_checks intrinsic The contract_checks compiler flag is now used to determine if runtime contract checks should be enabled, as opposed to the compiler intrinsic as previously. --- compiler/rustc_hir/src/lang_items.rs | 1 - .../rustc_hir_analysis/src/check/intrinsic.rs | 2 -- .../src/lower_intrinsics.rs | 11 ---------- library/core/src/intrinsics/mod.rs | 18 ---------------- .../internal_machinery/contract-intrinsics.rs | 21 ++++--------------- .../contract-lang-items.chk_fail_post.stderr | 2 +- .../contract-lang-items.chk_pass.stderr | 2 +- .../internal_machinery/contract-lang-items.rs | 19 ++++------------- .../contract-lang-items.unchk_pass.stderr | 2 +- .../internal-feature-gating.rs | 2 -- .../internal-feature-gating.stderr | 20 +++++------------- 11 files changed, 16 insertions(+), 84 deletions(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index d62ec5482b430..2e099a97b65be 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -429,7 +429,6 @@ language_item_table! { // Experimental lang items for implementing contract pre- and post-condition checking. ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None; ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None; - ContractChecks, sym::contract_checks, contract_checks_fn, Target::Fn, GenericRequirement::None; // Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727) DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 5fd71fd1cd76c..a6659912e3fb9 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -647,8 +647,6 @@ pub(crate) fn check_intrinsic_type( sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))), - // contract_checks() -> bool - sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), sym::contract_check_ensures => { diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 8dadce0d448d3..fc8092fd5832b 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -34,17 +34,6 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { )); terminator.kind = TerminatorKind::Goto { target }; } - sym::contract_checks => { - let target = target.unwrap(); - block.statements.push(Statement::new( - terminator.source_info, - StatementKind::Assign(Box::new(( - *destination, - Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool), - ))), - )); - terminator.kind = TerminatorKind::Goto { target }; - } sym::forget => { let target = target.unwrap(); block.statements.push(Statement::new( diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index d35eca015ed01..7886148bb81e9 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2631,24 +2631,6 @@ pub const unsafe fn const_make_global(ptr: *mut u8) -> *const u8 { ptr } -/// Returns whether we should perform contract-checking at runtime. -/// -/// This is meant to be similar to the ub_checks intrinsic, in terms -/// of not prematurely committing at compile-time to whether contract -/// checking is turned on, so that we can specify contracts in libstd -/// and let an end user opt into turning them on. -#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] -#[rustc_const_unstable(feature = "contracts", issue = "128044")] -#[inline(always)] -#[lang = "contract_checks"] -#[rustc_intrinsic] -pub const fn contract_checks() -> bool { - // FIXME: should this be `false` or `cfg!(contract_checks)`? - - // cfg!(contract_checks) - false -} - /// Check if the pre-condition `cond` has been met. /// /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 5caea2b23d93c..9dd5167c9e117 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -1,29 +1,16 @@ -//@ revisions: default unchk_pass chk_pass chk_fail_ensures chk_fail_requires +//@ revisions: default chk_fail_ensures chk_fail_requires // //@ [default] run-pass -//@ [unchk_pass] run-pass -//@ [chk_pass] run-pass //@ [chk_fail_requires] run-crash //@ [chk_fail_ensures] run-crash -// -//@ [unchk_pass] compile-flags: -Zcontract-checks=no -//@ [chk_pass] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_requires] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_ensures] compile-flags: -Zcontract-checks=yes -#![feature(cfg_contract_checks, contracts_internals, core_intrinsics)] +#![feature(contracts_internals, core_intrinsics)] fn main() { - #[cfg(any(default, unchk_pass))] // default: disabled - assert_eq!(core::intrinsics::contract_checks(), false); - - #[cfg(chk_pass)] // explicitly enabled - assert_eq!(core::intrinsics::contract_checks(), true); - // always pass core::intrinsics::contract_check_requires(|| true); // always fail - #[cfg(any(chk_fail_requires))] + #[cfg(chk_fail_requires)] core::intrinsics::contract_check_requires(|| false); let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old }; @@ -31,6 +18,6 @@ fn main() { core::intrinsics::contract_check_ensures(Some(doubles_to_two), 1); // always fail - #[cfg(any(chk_fail_ensures))] + #[cfg(chk_fail_ensures)] core::intrinsics::contract_check_ensures(Some(doubles_to_two), 2); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index 50cf592ac564d..ad88ebfe22e3b 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -1,27 +1,16 @@ -//@ revisions: unchk_pass unchk_fail_post chk_pass chk_fail_post +//@ revisions: unchk_pass chk_pass chk_fail_post // //@ [unchk_pass] run-pass -//@ [unchk_fail_post] run-pass //@ [chk_pass] run-pass // //@ [chk_fail_post] run-crash -// -//@ [unchk_pass] compile-flags: -Zcontract-checks=no -//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no -// -//@ [chk_pass] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes #![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 = if core::intrinsics::contract_checks() { - Some(core::contracts::build_check_ensures(|ret| *ret > 100)) - } else { - None - }; + let injected_checker = Some(core::contracts::build_check_ensures(|ret| *ret > 100)); let ret = x.baz + 50; core::intrinsics::contract_check_ensures(injected_checker, ret) @@ -31,11 +20,11 @@ struct Baz { baz: i32 } const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; -#[cfg(any(unchk_fail_post, chk_fail_post))] +#[cfg(chk_fail_post)] const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; fn main() { assert_eq!(foo(BAZ_PASS_PRE_POST), 150); - #[cfg(any(unchk_fail_post, chk_fail_post))] + #[cfg(chk_fail_post)] foo(BAZ_FAIL_POST); } diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr index a60ce1602659b..acce6b1fbc729 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr @@ -1,5 +1,5 @@ warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/contract-lang-items.rs:15:12 + --> $DIR/contract-lang-items.rs:8:12 | LL | #![feature(contracts)] // to access core::contracts | ^^^^^^^^^ diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index d101ab33547eb..48bd376594f22 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -2,8 +2,6 @@ fn main() { // intrinsics are guarded by contracts_internals feature gate. - core::intrinsics::contract_checks(); - //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_requires(|| true); //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 738b7a3e09ff1..c1318fc15a78d 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -1,5 +1,5 @@ error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:16:28 + --> $DIR/internal-feature-gating.rs:14:28 | LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } | ^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:18:28 + --> $DIR/internal-feature-gating.rs:16:28 | LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } | ^^^^^^^^^^^^^^^^ @@ -21,16 +21,6 @@ LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:5:5 | -LL | core::intrinsics::contract_checks(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #128044 for more information - = help: add `#![feature(contracts_internals)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:7:5 - | LL | core::intrinsics::contract_check_requires(|| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | @@ -39,7 +29,7 @@ LL | core::intrinsics::contract_check_requires(|| true); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:9:5 + --> $DIR/internal-feature-gating.rs:7:5 | LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +39,7 @@ LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `contracts_internals` - --> $DIR/internal-feature-gating.rs:12:5 + --> $DIR/internal-feature-gating.rs:10:5 | LL | core::contracts::build_check_ensures(|_: &()| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -58,6 +48,6 @@ LL | core::contracts::build_check_ensures(|_: &()| true); = help: add `#![feature(contracts_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0658`. From 085ca0e122467255f9281cb0c69948cfd21189d9 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Thu, 4 Sep 2025 09:01:14 +0100 Subject: [PATCH 4/4] Wrap contract clauses in brances instead of parenthesis The compiler complained about uncecessary parenthesis on contract clauses, which were insterted by the contract macros. This commit changes the macro to use braces as the delimiter instead, fixing the issue. --- compiler/rustc_builtin_macros/src/contracts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index 6a24af361fe78..85fbe934124f5 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -141,7 +141,7 @@ fn expand_requires_tts( new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), - token::Delimiter::Parenthesis, + token::Delimiter::Brace, annotation, )); Ok(()) @@ -163,7 +163,7 @@ fn expand_ensures_tts( new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), - token::Delimiter::Parenthesis, + token::Delimiter::Brace, annotation, )); Ok(())