From 4f22b0aab47cc320e09bdb1fd59472ee703af035 Mon Sep 17 00:00:00 2001 From: Dawid Lachowicz Date: Wed, 24 Dec 2025 10:31:34 +0000 Subject: [PATCH] contracts: allow non-const calls in requires clause HIR lowered contracts are now encapsulated within closures, allowing them to be conditionally executed based on the const context, meaning that contracts on const functions can be non-const themselves. This new implementation also fixes early returns from contracts, and disallows certain undesired side-effect patterns within contracts by placing trait bounds on the constructed contract closures. --- compiler/rustc_ast_lowering/src/contract.rs | 229 ++++++++++++------ compiler/rustc_ast_lowering/src/expr.rs | 42 ++++ compiler/rustc_hir/src/definitions.rs | 6 +- compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_parse/src/parser/expr.rs | 20 -- compiler/rustc_parse/src/parser/generics.rs | 1 - .../src/cfi/typeid/itanium_cxx_abi/encode.rs | 4 +- compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_symbol_mangling/src/v0.rs | 2 +- library/core/src/contracts.rs | 39 ++- ...ntract-captures-via-closure-noncopy.stderr | 16 +- ...d-side-effect-declarations-with-ensures.rs | 18 ++ ...de-effect-declarations-with-ensures.stderr | 28 +++ ...racts-disabled-side-effect-declarations.rs | 2 +- ...s-disabled-side-effect-declarations.stderr | 28 +++ ...abled-side-effect-ensures-with-requires.rs | 18 ++ ...d-side-effect-ensures-with-requires.stderr | 28 +++ .../contracts-disabled-side-effect-ensures.rs | 2 +- ...tracts-disabled-side-effect-ensures.stderr | 28 +++ .../early-return-requires-ensures.rs | 22 ++ .../early-return-requires-ensures.stderr | 21 ++ .../early-return-requires-expr-ensures.rs | 19 ++ tests/ui/contracts/empty-ensures.rs | 1 + tests/ui/contracts/empty-ensures.stderr | 17 +- tests/ui/contracts/explicit-return-ensures.rs | 16 ++ .../ui/contracts/explicit-return-requires.rs | 16 ++ .../internal_machinery/contract-lang-items.rs | 2 +- .../internal-feature-gating.rs | 2 +- .../internal-feature-gating.stderr | 2 +- .../internal_machinery/lowering/basics.rs | 2 +- .../requires-non-const-stmt-with-ensures.rs | 17 ++ tests/ui/contracts/requires-non-const-stmt.rs | 16 ++ ...-tokenstream-for-contracts-issue-140683.rs | 1 + ...enstream-for-contracts-issue-140683.stderr | 21 +- 34 files changed, 555 insertions(+), 133 deletions(-) create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.stderr create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-declarations.stderr create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.rs create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.stderr create mode 100644 tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr create mode 100644 tests/ui/contracts/early-return-requires-ensures.rs create mode 100644 tests/ui/contracts/early-return-requires-ensures.stderr create mode 100644 tests/ui/contracts/early-return-requires-expr-ensures.rs create mode 100644 tests/ui/contracts/explicit-return-ensures.rs create mode 100644 tests/ui/contracts/explicit-return-requires.rs create mode 100644 tests/ui/contracts/requires-non-const-stmt-with-ensures.rs create mode 100644 tests/ui/contracts/requires-non-const-stmt.rs diff --git a/compiler/rustc_ast_lowering/src/contract.rs b/compiler/rustc_ast_lowering/src/contract.rs index 6cffd8e119b7e..f31869906baf1 100644 --- a/compiler/rustc_ast_lowering/src/contract.rs +++ b/compiler/rustc_ast_lowering/src/contract.rs @@ -34,9 +34,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // into: // // let __postcond = if contract_checks { - // CONTRACT_DECLARATIONS; - // contract_check_requires(PRECOND); - // Some(|ret_val| POSTCOND) + // let __ensures_builder = || { + // CONTRACT_DECLARATIONS; + // contract_check_requires(|| PRECOND); + // build_check_ensures(Some(|| { POSTCOND_DECLS; |ret_val| POSTCOND })) + // }; + // contract_check_requires_and_build_ensures(__ensures_builder) // } else { // None // }; @@ -50,7 +53,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // } // } - let precond = self.lower_precond(req); + let lowered_req = self.lower_expr_mut(&req); + let precond = self.lower_precond(lowered_req); let postcond_checker = self.lower_postcond_checker(ens); let contract_check = self.lower_contract_check_with_postcond( @@ -71,7 +75,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // into: // // let __postcond = if contract_checks { - // Some(|ret_val| POSTCOND) + // let __ensures_builder = || { + // CONTRACT_DECLARATIONS; + // build_check_ensures(Some(|| { POSTCOND_DECLS; |ret_val| POSTCOND })) + // }; + // contract_check_requires_and_build_ensures(__ensures_builder) // } else { // None // }; @@ -79,7 +87,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // let ret = { body }; // // if contract_checks { - // CONTRACT_DECLARATIONS; // contract_check_ensures(__postcond, ret) // } else { // ret @@ -102,13 +109,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // // { // if contracts_checks { - // CONTRACT_DECLARATIONS; - // contract_requires(PRECOND); + // contract_requires(|| { CONTRACT_DECLARATIONS; PRECOND }); // } // body // } - let precond = self.lower_precond(req); - let precond_check = self.lower_contract_check_just_precond(contract_decls, precond); + let lowered_req = self.lower_expr(&req); + let precond = self.block_decls_with_precond(contract_decls, lowered_req); + let precond_check = self.lower_contract_check_just_precond(precond); let body = self.arena.alloc(body(self)); @@ -137,17 +144,17 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } /// Lower the precondition check intrinsic. - fn lower_precond(&mut self, req: &Box) -> rustc_hir::Stmt<'hir> { - let lowered_req = self.lower_expr_mut(&req); + fn lower_precond(&mut self, req: rustc_hir::Expr<'hir>) -> rustc_hir::Stmt<'hir> { let req_span = self.mark_span_with_reason( rustc_span::DesugaringKind::Contract, - lowered_req.span, + req.span, Some(Arc::clone(&self.allow_contracts)), ); + let req_closure = self.expr_closure(req_span, req); let precond = self.expr_call_lang_item_fn_mut( req_span, rustc_hir::LangItem::ContractCheckRequires, - &*arena_vec![self; lowered_req], + &*arena_vec![self; req_closure], ); self.stmt_expr(req.span, precond) } @@ -163,33 +170,42 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { Some(Arc::clone(&self.allow_contracts)), ); let lowered_ens = self.lower_expr_mut(&ens); + let ens_closure = self.expr_closure(ens_span, lowered_ens); self.expr_call_lang_item_fn( ens_span, rustc_hir::LangItem::ContractBuildCheckEnsures, - &*arena_vec![self; lowered_ens], + &*arena_vec![self; ens_closure], ) } + fn block_decls_with_precond( + &mut self, + contract_decls: &'hir [rustc_hir::Stmt<'_>], + lowered_req: &'hir rustc_hir::Expr<'_>, + ) -> rustc_hir::Stmt<'hir> { + let req_span = span_of_stmts(contract_decls, lowered_req.span); + + let precond_stmts = self.block_all(req_span, contract_decls, Some(lowered_req)); + let precond_stmts = self.expr_block(precond_stmts); + self.lower_precond(precond_stmts) + } + fn lower_contract_check_just_precond( &mut self, - contract_decls: &'hir [rustc_hir::Stmt<'hir>], precond: rustc_hir::Stmt<'hir>, ) -> rustc_hir::Stmt<'hir> { - let stmts = self - .arena - .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain([precond].into_iter())); - - let then_block_stmts = self.block_all(precond.span, stmts, None); + let span = precond.span; + let then_block_stmts = self.block_all(span, &*arena_vec![self; precond], None); let then_block = self.arena.alloc(self.expr_block(&then_block_stmts)); let precond_check = rustc_hir::ExprKind::If( - self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())), + self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())), then_block, None, ); - let precond_check = self.expr(precond.span, precond_check); - self.stmt_expr(precond.span, precond_check) + let precond_check = self.expr(span, precond_check); + self.stmt_expr(span, precond_check) } fn lower_contract_check_with_postcond( @@ -201,26 +217,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let stmts = self .arena .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain(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 span = self.contract_check_with_postcond_span(stmts, postcond_checker); + + let then_block = self.contract_check_with_postcond_block(stmts, postcond_checker, span); + let else_block = self.option_none_block(span); let contract_check = rustc_hir::ExprKind::If( self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())), @@ -230,32 +231,89 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.arena.alloc(self.expr(span, contract_check)) } + fn contract_check_with_postcond_span( + &mut self, + stmts: &mut [rustc_hir::Stmt<'hir>], + postcond_checker: &rustc_hir::Expr<'_>, + ) -> rustc_span::Span { + // For error diagnostics, span is set to decls + precondition, because + // those will determine the well-typedness of the __ensures_builder + // closure. postcond_checker is already type-checked as part of the + // call to build_check_ensures. + let span = + span_of_stmts(stmts, stmts.last().map(|s| s.span).unwrap_or(postcond_checker.span)); + self.mark_span_with_reason( + rustc_span::DesugaringKind::Contract, + span, + Some(Arc::clone(&self.allow_contracts)), + ) + } + + fn contract_check_with_postcond_block( + &mut self, + stmts: &'hir mut [rustc_hir::Stmt<'hir>], + postcond_checker: &'hir rustc_hir::Expr<'_>, + span: rustc_span::Span, + ) -> &'hir mut rustc_hir::Expr<'hir> { + let (builder_decl, builder_ident_expr) = + self.contract_check_with_postcond_builder(stmts, postcond_checker, span); + + let build_postcond_call = self.expr_call_lang_item_fn( + span, + rustc_hir::LangItem::ContractCheckRequiresAndBuildEnsures, + &*arena_vec![self; *builder_ident_expr], + ); + let block_stmts = + self.block_all(span, arena_vec![self; builder_decl], Some(build_postcond_call)); + self.arena.alloc(self.expr_block(block_stmts)) + } + + fn contract_check_with_postcond_builder( + &mut self, + stmts: &'hir mut [rustc_hir::Stmt<'hir>], + postcond_checker: &'hir rustc_hir::Expr<'_>, + span: rustc_span::Span, + ) -> (rustc_hir::Stmt<'hir>, &'hir rustc_hir::Expr<'hir>) { + let block_closure = + self.contract_check_with_postcond_builder_closure(stmts, postcond_checker, span); + + let (builder_ident, builder_hir_id, builder_decl) = + self.bind_expression(block_closure, span, "__ensures_builder"); + let builder_ident_expr = self.expr_ident(span, builder_ident, builder_hir_id); + + (builder_decl, builder_ident_expr) + } + + fn contract_check_with_postcond_builder_closure( + &mut self, + stmts: &'hir mut [rustc_hir::Stmt<'hir>], + postcond_checker: &'hir rustc_hir::Expr<'_>, + span: rustc_span::Span, + ) -> &'hir mut rustc_hir::Expr<'hir> { + let stmts = self.block_all(span, stmts, Some(postcond_checker)); + let stmts = self.expr_block(stmts); + let closure = self.expr_closure(span, stmts); + self.arena.alloc(closure) + } + + fn option_none_block(&mut self, span: rustc_span::Span) -> &'hir mut rustc_hir::Expr<'hir> { + let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item( + span, + rustc_hir::lang_items::LangItem::OptionNone, + Default::default(), + )); + let else_block = self.block_expr(none_expr); + self.arena.alloc(self.expr_block(else_block)) + } + 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, - ), - ) - }; + let (check_ident, check_hir_id, postcond_decl) = + self.bind_expression(contract_check, postcond_span, "__ensures_checker"); // Install contract_ensures so we will intercept `return` statements, // then lower the body. @@ -274,6 +332,26 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { wrapped_body } + fn bind_expression( + &mut self, + expr: &'hir rustc_hir::Expr<'hir>, + span: rustc_span::Span, + var_name: &str, + ) -> (rustc_span::Ident, rustc_hir::HirId, rustc_hir::Stmt<'hir>) { + let ident = rustc_span::Ident::from_str_and_span(var_name, span); + let (pat, hir_id) = + self.pat_ident_binding_mode_mut(span, ident, rustc_hir::BindingMode::NONE); + + let decl = self.stmt_let_pat( + None, + span, + Some(expr), + self.arena.alloc(pat), + rustc_hir::LocalSource::Contract, + ); + (ident, hir_id, decl) + } + /// 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( @@ -307,20 +385,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // 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_ident, ret_hir_id, ret_stmt) = self.bind_expression(expr, span, "__ret"); let ret = self.expr_ident(span, ret_ident, ret_hir_id); let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id); @@ -355,3 +420,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.arena.alloc(self.expr_block(self.arena.alloc(ret_block))) } } + +fn span_of_stmts<'hir>( + stmts: &'hir [rustc_hir::Stmt<'_>], + default_span: rustc_span::Span, +) -> rustc_span::Span { + match stmts { + [] => default_span, + [first, ..] => first.span.to(default_span), + } +} diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index d9bf6b52f31f7..59df16e2725e2 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2344,6 +2344,48 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::Lit(Spanned { node: LitKind::Bool(val), span })) } + pub(super) fn expr_closure( + &mut self, + span: Span, + body_expr: hir::Expr<'hir>, + ) -> hir::Expr<'hir> { + let closure_node_id = self.next_node_id(); + + let closure_def_id = self.create_def( + closure_node_id, + None, + hir::def::DefKind::Closure, + hir::definitions::DefPathData::LateClosure, + span, + ); + + let hir_id = self.lower_node_id(closure_node_id); + let body_id = self.lower_body(|_| (Default::default(), body_expr)); + + let fn_decl = self.arena.alloc(hir::FnDecl { + inputs: &[], + output: hir::FnRetTy::DefaultReturn(span), + c_variadic: false, + implicit_self: hir::ImplicitSelfKind::None, + lifetime_elision_allowed: true, + }); + + let closure = self.arena.alloc(hir::Closure { + def_id: closure_def_id, + binder: hir::ClosureBinder::Default, + constness: hir::Constness::NotConst, + capture_clause: hir::CaptureBy::Ref, + bound_generic_params: &[], + fn_decl, + body: body_id, + fn_decl_span: span, + fn_arg_span: None, + kind: hir::ClosureKind::Closure, + }); + + hir::Expr { hir_id, kind: hir::ExprKind::Closure(closure), 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) } diff --git a/compiler/rustc_hir/src/definitions.rs b/compiler/rustc_hir/src/definitions.rs index 5e361891f6d04..f577687ccfbfc 100644 --- a/compiler/rustc_hir/src/definitions.rs +++ b/compiler/rustc_hir/src/definitions.rs @@ -296,6 +296,8 @@ pub enum DefPathData { LifetimeNs(Symbol), /// A closure expression. Closure, + /// A closure expression created during AST->HIR lowering. + LateClosure, // Subportions of items: /// Implicit constructor for a unit or tuple-like struct or enum variant. @@ -466,6 +468,7 @@ impl DefPathData { | Use | GlobalAsm | Closure + | LateClosure | Ctor | AnonConst | LateAnonConst @@ -490,6 +493,7 @@ impl DefPathData { | Use | GlobalAsm | Closure + | LateClosure | Ctor | AnonConst | LateAnonConst @@ -510,7 +514,7 @@ impl DefPathData { ForeignMod => DefPathDataName::Anon { namespace: kw::Extern }, Use => DefPathDataName::Anon { namespace: kw::Use }, GlobalAsm => DefPathDataName::Anon { namespace: sym::global_asm }, - Closure => DefPathDataName::Anon { namespace: sym::closure }, + Closure | LateClosure => DefPathDataName::Anon { namespace: sym::closure }, Ctor => DefPathDataName::Anon { namespace: sym::constructor }, AnonConst | LateAnonConst => DefPathDataName::Anon { namespace: sym::constant }, DesugaredAnonymousLifetime => DefPathDataName::Named(kw::UnderscoreLifetime), diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 557f76208bfe6..8054824b585af 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -436,6 +436,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; + ContractCheckRequiresAndBuildEnsures, sym::contract_check_requires_and_build_ensures, contract_check_requires_and_build_ensures_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_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 8bb22c2a831bf..e00d9ee49b324 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -4123,26 +4123,6 @@ impl<'a> Parser<'a> { self.mk_expr(span, ExprKind::Tup(Default::default())) } - pub(crate) fn mk_closure_expr(&self, span: Span, body: Box) -> Box { - self.mk_expr( - span, - ast::ExprKind::Closure(Box::new(ast::Closure { - binder: rustc_ast::ClosureBinder::NotPresent, - constness: rustc_ast::Const::No, - movability: rustc_ast::Movability::Movable, - capture_clause: rustc_ast::CaptureBy::Ref, - coroutine_kind: None, - fn_decl: Box::new(rustc_ast::FnDecl { - inputs: Default::default(), - output: rustc_ast::FnRetTy::Default(span), - }), - fn_arg_span: span, - fn_decl_span: span, - body, - })), - ) - } - /// Create expression span ensuring the span of the parent node /// is larger than the span of lhs and rhs, including the attributes. fn mk_expr_sp(&self, lhs: &Box, lhs_span: Span, op_span: Span, rhs_span: Span) -> Span { diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 8c02092fd6788..3c91f393ee6b1 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -371,7 +371,6 @@ impl<'a> Parser<'a> { }, None => self.mk_unit_expr(decls_and_precond.span), }; - let precond = self.mk_closure_expr(precond.span, precond); let decls = decls_and_precond.stmts; (decls, Some(precond)) } else { diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/encode.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/encode.rs index 5505fe82cea65..c9ce27f48edd5 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/encode.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/encode.rs @@ -680,7 +680,9 @@ fn encode_ty_name(tcx: TyCtxt<'_>, def_id: DefId) -> String { hir::definitions::DefPathData::ForeignMod => "F", // Not specified in v0's hir::definitions::DefPathData::TypeNs(..) => "t", hir::definitions::DefPathData::ValueNs(..) => "v", - hir::definitions::DefPathData::Closure => "C", + hir::definitions::DefPathData::Closure | hir::definitions::DefPathData::LateClosure => { + "C" + } hir::definitions::DefPathData::Ctor => "c", hir::definitions::DefPathData::AnonConst => "K", hir::definitions::DefPathData::LateAnonConst => "k", diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 1915ff0380fda..07f3ef7419f7f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -782,6 +782,7 @@ symbols! { contract_build_check_ensures, contract_check_ensures, contract_check_requires, + contract_check_requires_and_build_ensures, contract_checks, contracts, contracts_ensures, diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 95cbb9e07ebb7..7288233ff6704 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -885,7 +885,7 @@ impl<'tcx> Printer<'tcx> for V0SymbolMangler<'tcx> { // Uppercase categories are more stable than lowercase ones. DefPathData::TypeNs(_) => 't', DefPathData::ValueNs(_) => 'v', - DefPathData::Closure => 'C', + DefPathData::Closure | DefPathData::LateClosure => 'C', DefPathData::Ctor => 'c', DefPathData::AnonConst => 'K', DefPathData::LateAnonConst => 'k', diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs index 495f84bce4bf2..33466e426baef 100644 --- a/library/core/src/contracts.rs +++ b/library/core/src/contracts.rs @@ -1,8 +1,9 @@ //! Unstable module containing the unstable contracts lang items and attribute macros. +use crate::intrinsics::const_eval_select; pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_requires as requires}; -/// This is an identity function used as part of the desugaring of the `#[ensures]` attribute. +/// This function is used as part of the desugaring of the `#[ensures]` attribute. /// /// This is an existing hack to allow users to omit the type of the return value in their ensures /// attribute. @@ -14,11 +15,39 @@ pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_require // Similar to `contract_check_requires`, we need to use the user-facing // `contracts` feature rather than the perma-unstable `contracts_internals`. // Const-checking doesn't honor allow_internal_unstable logic used by contract expansion. -#[rustc_const_unstable(feature = "contracts", issue = "128044")] #[lang = "contract_build_check_ensures"] -pub const fn build_check_ensures(cond: C) -> C +pub fn build_check_ensures(cond: C) -> E where - C: Fn(&Ret) -> bool + Copy + 'static, + C: FnOnce() -> E + Copy, + E: Fn(&Ret) -> bool + Copy + 'static, { - cond + cond() +} + +/// This function is used as part of the contracts HIR lowering (desugaring) to +/// ensure contract code is only executed in a non-const environment, allowing +/// the contract to call non-const functions even when the function being +/// annotated with contracts is const itself. +/// +/// The `contract` closure should execute the necessary requires check via +/// `contract_check_ensures` and return an ensures closure built by +/// `build_check_ensures`. +#[unstable(feature = "contracts_internals", issue = "128044")] +#[rustc_const_unstable(feature = "contracts", issue = "128044")] +#[lang = "contract_check_requires_and_build_ensures"] +pub const fn contract_check_requires_and_build_ensures< + C: FnOnce() -> E + Copy, + E: Fn(&Ret) -> bool + Copy, + Ret, +>( + contract: C, +) -> Option { + const_eval_select!( + @capture[C: FnOnce() -> E + Copy, E: Fn(&Ret) -> bool + Copy, Ret] { contract: C } -> Option: + if const { + None + } else { + Some(contract()) + } + ) } diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr index 20c220e98bcc8..6bd96f14c5b07 100644 --- a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -1,20 +1,18 @@ -error[E0277]: the trait bound `Baz: Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:42: 13:57}` +error[E0277]: the trait bound `Baz: Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:1: 13:82}` --> $DIR/contract-captures-via-closure-noncopy.rs:13:1 | LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------------------------------------^^^^ - | | | - | | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:42: 13:57}` - | | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:13:42}` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | | unsatisfied trait bound - | required by a bound introduced by this call + | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:1: 13:82}` | - = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:42: 13:57}`, the trait `Copy` is not implemented for `Baz` + = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:1: 13:82}`, the trait `Copy` is not implemented for `Baz` note: required because it's used within this closure - --> $DIR/contract-captures-via-closure-noncopy.rs:13:42 + --> $DIR/contract-captures-via-closure-noncopy.rs:13:1 | LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `build_check_ensures` --> $SRC_DIR/core/src/contracts.rs:LL:COL help: consider annotating `Baz` with `#[derive(Copy)]` diff --git a/tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.rs b/tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.rs new file mode 100644 index 0000000000000..9606336c4917d --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.rs @@ -0,0 +1,18 @@ +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::{ensures, requires}; + +#[requires(*x = 0; true)] +//~^ ERROR: the trait bound `&mut &mut u32: Copy` is not satisfied +#[ensures(|_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-declarations-with-ensures.stderr b/tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.stderr new file mode 100644 index 0000000000000..2dd8e40f3caf8 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-declarations-with-ensures.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `&mut &mut u32: Copy` is not satisfied in `{closure@$DIR/contracts-disabled-side-effect-declarations-with-ensures.rs:7:12: 7:24}` + --> $DIR/contracts-disabled-side-effect-declarations-with-ensures.rs:7:12 + | +LL | #[requires(*x = 0; true)] + | ^^^^^^^^^^^^ + | | + | unsatisfied trait bound + | within this `{closure@$DIR/contracts-disabled-side-effect-declarations-with-ensures.rs:7:12: 7:24}` + | + = help: within `{closure@$DIR/contracts-disabled-side-effect-declarations-with-ensures.rs:7:12: 7:24}`, the trait `Copy` is not implemented for `&mut &mut u32` +help: the trait `Copy` is implemented for `u32` + --> $SRC_DIR/core/src/marker.rs:LL:COL + ::: $SRC_DIR/core/src/marker.rs:LL:COL + | + = note: in this macro invocation + = note: `Copy` is implemented for `&&mut u32`, but not for `&mut &mut u32` +note: required because it's used within this closure + --> $DIR/contracts-disabled-side-effect-declarations-with-ensures.rs:7:12 + | +LL | #[requires(*x = 0; true)] + | ^^^^^^^^^^^^ +note: required by a bound in `contract_check_requires_and_build_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + = note: this error originates in the macro `marker_impls` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/contracts-disabled-side-effect-declarations.rs b/tests/ui/contracts/contracts-disabled-side-effect-declarations.rs index 4b2d4a80237fe..324d11d81113c 100644 --- a/tests/ui/contracts/contracts-disabled-side-effect-declarations.rs +++ b/tests/ui/contracts/contracts-disabled-side-effect-declarations.rs @@ -1,4 +1,3 @@ -//@ run-pass #![expect(incomplete_features)] #![feature(contracts)] @@ -6,6 +5,7 @@ extern crate core; use core::contracts::requires; #[requires(*x = 0; true)] +//~^ ERROR: the trait bound `&mut &mut u32: Copy` is not satisfied fn buggy_add(x: &mut u32, y: u32) { *x = *x + y; } diff --git a/tests/ui/contracts/contracts-disabled-side-effect-declarations.stderr b/tests/ui/contracts/contracts-disabled-side-effect-declarations.stderr new file mode 100644 index 0000000000000..12c09abee1a87 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-declarations.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `&mut &mut u32: Copy` is not satisfied in `{closure@$DIR/contracts-disabled-side-effect-declarations.rs:7:12: 7:24}` + --> $DIR/contracts-disabled-side-effect-declarations.rs:7:12 + | +LL | #[requires(*x = 0; true)] + | ^^^^^^^^^^^^ + | | + | unsatisfied trait bound + | within this `{closure@$DIR/contracts-disabled-side-effect-declarations.rs:7:12: 7:24}` + | + = help: within `{closure@$DIR/contracts-disabled-side-effect-declarations.rs:7:12: 7:24}`, the trait `Copy` is not implemented for `&mut &mut u32` +help: the trait `Copy` is implemented for `u32` + --> $SRC_DIR/core/src/marker.rs:LL:COL + ::: $SRC_DIR/core/src/marker.rs:LL:COL + | + = note: in this macro invocation + = note: `Copy` is implemented for `&&mut u32`, but not for `&mut &mut u32` +note: required because it's used within this closure + --> $DIR/contracts-disabled-side-effect-declarations.rs:7:12 + | +LL | #[requires(*x = 0; true)] + | ^^^^^^^^^^^^ +note: required by a bound in `contract_check_requires` + --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL + = note: this error originates in the macro `marker_impls` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.rs b/tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.rs new file mode 100644 index 0000000000000..201c3c7bbc416 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.rs @@ -0,0 +1,18 @@ +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::{ensures, requires}; + +#[requires(true)] +#[ensures(*x = 0; |_ret| true)] +//~^ ERROR: the trait bound `&mut &mut u32: Copy` is not satisfied +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-with-requires.stderr b/tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.stderr new file mode 100644 index 0000000000000..9df76c01db668 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures-with-requires.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `&mut &mut u32: Copy` is not satisfied in `{closure@$DIR/contracts-disabled-side-effect-ensures-with-requires.rs:8:1: 8:32}` + --> $DIR/contracts-disabled-side-effect-ensures-with-requires.rs:8:1 + | +LL | #[ensures(*x = 0; |_ret| true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | unsatisfied trait bound + | within this `{closure@$DIR/contracts-disabled-side-effect-ensures-with-requires.rs:8:1: 8:32}` + | + = help: within `{closure@$DIR/contracts-disabled-side-effect-ensures-with-requires.rs:8:1: 8:32}`, the trait `Copy` is not implemented for `&mut &mut u32` +help: the trait `Copy` is implemented for `u32` + --> $SRC_DIR/core/src/marker.rs:LL:COL + ::: $SRC_DIR/core/src/marker.rs:LL:COL + | + = note: in this macro invocation + = note: `Copy` is implemented for `&&mut u32`, but not for `&mut &mut u32` +note: required because it's used within this closure + --> $DIR/contracts-disabled-side-effect-ensures-with-requires.rs:8:1 + | +LL | #[ensures(*x = 0; |_ret| true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `build_check_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + = note: this error originates in the macro `marker_impls` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs index 1488b8f8d3fd5..e353b653e8ba4 100644 --- a/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.rs @@ -1,4 +1,3 @@ -//@ run-pass #![expect(incomplete_features)] #![feature(contracts)] @@ -6,6 +5,7 @@ extern crate core; use core::contracts::ensures; #[ensures(*x = 0; |_ret| true)] +//~^ ERROR: the trait bound `&mut &mut u32: Copy` is not satisfied fn buggy_add(x: &mut u32, y: u32) { *x = *x + y; } 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..a61a2199e2b69 --- /dev/null +++ b/tests/ui/contracts/contracts-disabled-side-effect-ensures.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `&mut &mut u32: Copy` is not satisfied in `{closure@$DIR/contracts-disabled-side-effect-ensures.rs:7:1: 7:32}` + --> $DIR/contracts-disabled-side-effect-ensures.rs:7:1 + | +LL | #[ensures(*x = 0; |_ret| true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | unsatisfied trait bound + | within this `{closure@$DIR/contracts-disabled-side-effect-ensures.rs:7:1: 7:32}` + | + = help: within `{closure@$DIR/contracts-disabled-side-effect-ensures.rs:7:1: 7:32}`, the trait `Copy` is not implemented for `&mut &mut u32` +help: the trait `Copy` is implemented for `u32` + --> $SRC_DIR/core/src/marker.rs:LL:COL + ::: $SRC_DIR/core/src/marker.rs:LL:COL + | + = note: in this macro invocation + = note: `Copy` is implemented for `&&mut u32`, but not for `&mut &mut u32` +note: required because it's used within this closure + --> $DIR/contracts-disabled-side-effect-ensures.rs:7:1 + | +LL | #[ensures(*x = 0; |_ret| true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `build_check_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + = note: this error originates in the macro `marker_impls` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/early-return-requires-ensures.rs b/tests/ui/contracts/early-return-requires-ensures.rs new file mode 100644 index 0000000000000..ff1ef945de9e0 --- /dev/null +++ b/tests/ui/contracts/early-return-requires-ensures.rs @@ -0,0 +1,22 @@ +//@ dont-require-annotations: NOTE +//@ compile-flags: -Zcontract-checks=yes +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::{ensures, requires}; + +// Early return in requires takes precedence over ensures clause, +// but now we have two different closure types as candidates for the ensures +// closure, which is not allowed. +#[requires(return |_ret| { true }; true)] +#[ensures(|_ret| { false })] +//~^ ERROR: mismatched types [E0308] +//~| NOTE: no two closures, even if identical, have the same type +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/early-return-requires-ensures.stderr b/tests/ui/contracts/early-return-requires-ensures.stderr new file mode 100644 index 0000000000000..4c278f42ff29f --- /dev/null +++ b/tests/ui/contracts/early-return-requires-ensures.stderr @@ -0,0 +1,21 @@ +error[E0308]: mismatched types + --> $DIR/early-return-requires-ensures.rs:13:1 + | +LL | #[requires(return |_ret| { true }; true)] + | ------ the expected closure +LL | #[ensures(|_ret| { false })] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure + | + = note: expected closure `{closure@$DIR/early-return-requires-ensures.rs:12:19: 12:25}` + found closure `{closure@$DIR/early-return-requires-ensures.rs:13:11: 13:17}` + = note: no two closures, even if identical, have the same type + = help: consider boxing your closure and/or using it as a trait object +note: return type inferred to be `{closure@$DIR/early-return-requires-ensures.rs:12:19: 12:25}` here + --> $DIR/early-return-requires-ensures.rs:12:19 + | +LL | #[requires(return |_ret| { true }; true)] + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/contracts/early-return-requires-expr-ensures.rs b/tests/ui/contracts/early-return-requires-expr-ensures.rs new file mode 100644 index 0000000000000..29f7f5b20fe18 --- /dev/null +++ b/tests/ui/contracts/early-return-requires-expr-ensures.rs @@ -0,0 +1,19 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::{ensures, requires}; + +// note that when we wrap requires in a block, the return is scoped just to +// requires, not the entire contract, making this early return valid +#[requires({if true { return true }; true})] +#[ensures(|_ret| { true })] +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/empty-ensures.rs b/tests/ui/contracts/empty-ensures.rs index 0a52391117861..79521ebf07dc5 100644 --- a/tests/ui/contracts/empty-ensures.rs +++ b/tests/ui/contracts/empty-ensures.rs @@ -7,6 +7,7 @@ use core::contracts::ensures; #[ensures()] //~^ ERROR expected a `Fn(&_)` closure, found `()` [E0277] +//~| ERROR expected a `Fn(&_)` closure, found `()` [E0277] fn foo(x: u32) -> u32 { x * 2 } diff --git a/tests/ui/contracts/empty-ensures.stderr b/tests/ui/contracts/empty-ensures.stderr index 6a19d234b52c2..330bbf2065edd 100644 --- a/tests/ui/contracts/empty-ensures.stderr +++ b/tests/ui/contracts/empty-ensures.stderr @@ -2,15 +2,22 @@ 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 + | ^^^^^^^^^^^^ expected an `Fn(&_)` closure, found `()` | = 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 +error[E0277]: expected a `Fn(&_)` closure, found `()` + --> $DIR/empty-ensures.rs:8:1 + | +LL | #[ensures()] + | ^^^^^^^^^^^^ expected an `Fn(&_)` closure, found `()` + | + = help: the trait `for<'a> Fn(&'a _)` is not implemented for `()` +note: required by a bound in `contract_check_requires_and_build_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/explicit-return-ensures.rs b/tests/ui/contracts/explicit-return-ensures.rs new file mode 100644 index 0000000000000..b5572ad3b7d3e --- /dev/null +++ b/tests/ui/contracts/explicit-return-ensures.rs @@ -0,0 +1,16 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::ensures; + +#[ensures(return |_ret| { true })] +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/explicit-return-requires.rs b/tests/ui/contracts/explicit-return-requires.rs new file mode 100644 index 0000000000000..7deda2cf13d40 --- /dev/null +++ b/tests/ui/contracts/explicit-return-requires.rs @@ -0,0 +1,16 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; +use core::contracts::requires; + +#[requires(return true)] +fn foo(x: u32) -> u32 { + x * 2 +} + +fn main() { + foo(1); +} diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index d675601036447..50d35bf2ae121 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -10,7 +10,7 @@ #![feature(contracts_internals)] // to access check_requires lang item #![feature(core_intrinsics)] fn foo(x: Baz) -> i32 { - let injected_checker = Some(core::contracts::build_check_ensures(|ret| *ret > 100)); + 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) diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index 9d3765bc228db..9b02c5ef06882 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -7,7 +7,7 @@ fn main() { core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1); //~^ ERROR use of unstable library feature `contracts_internals` - core::contracts::build_check_ensures(|_: &()| true); + core::contracts::build_check_ensures(|| |_: &()| true); //~^ ERROR use of unstable library feature `contracts_internals` // ast extensions are guarded by contracts_internals feature gate diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 0010fca2f96c5..8ce2a967b26a2 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_ensures(Some(|_: &&u32| true), &1); error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:10:5 | -LL | core::contracts::build_check_ensures(|_: &()| true); +LL | core::contracts::build_check_ensures(|| |_: &()| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #128044 for more information diff --git a/tests/ui/contracts/internal_machinery/lowering/basics.rs b/tests/ui/contracts/internal_machinery/lowering/basics.rs index 7b3a769af8258..db35ffb6dcaba 100644 --- a/tests/ui/contracts/internal_machinery/lowering/basics.rs +++ b/tests/ui/contracts/internal_machinery/lowering/basics.rs @@ -13,7 +13,7 @@ fn foo(x: u32) -> u32 { // call contract_check_requires here to avoid borrow checker issues // with variables declared in contract requires core::intrinsics::contract_check_requires(|| y > 0); - Some(core::contracts::build_check_ensures(move |ret| *ret == y)) + Some(core::contracts::build_check_ensures(|| move |ret| *ret == y)) }; core::intrinsics::contract_check_ensures(post, { 2 * x }) diff --git a/tests/ui/contracts/requires-non-const-stmt-with-ensures.rs b/tests/ui/contracts/requires-non-const-stmt-with-ensures.rs new file mode 100644 index 0000000000000..01349f833ce86 --- /dev/null +++ b/tests/ui/contracts/requires-non-const-stmt-with-ensures.rs @@ -0,0 +1,17 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes + +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; + +use core::contracts::{ensures, requires}; + +pub fn foo() {} + +#[requires(foo(); true)] +#[ensures(|_| { true })] +pub const fn bar() {} + +fn main() {} diff --git a/tests/ui/contracts/requires-non-const-stmt.rs b/tests/ui/contracts/requires-non-const-stmt.rs new file mode 100644 index 0000000000000..e7da8da9aa347 --- /dev/null +++ b/tests/ui/contracts/requires-non-const-stmt.rs @@ -0,0 +1,16 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes + +#![expect(incomplete_features)] +#![feature(contracts)] + +extern crate core; + +use core::contracts::requires; + +pub fn foo() {} + +#[requires(foo(); true)] +pub const fn bar() {} + +fn main() {} diff --git a/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.rs b/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.rs index 68346a00ae1a7..81d733f140cc0 100644 --- a/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.rs +++ b/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.rs @@ -5,6 +5,7 @@ struct T; impl T { #[core::contracts::ensures] //~ ERROR expected a `Fn(&_)` closure, found `()` + //~| ERROR expected a `Fn(&_)` closure, found `()` fn b() {(loop)} //~^ ERROR expected `{`, found `)` //~| ERROR expected `{`, found `)` diff --git a/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.stderr b/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.stderr index f1ffda2a9bee6..f20a4e73b9128 100644 --- a/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.stderr +++ b/tests/ui/macros/ice-in-tokenstream-for-contracts-issue-140683.stderr @@ -1,5 +1,5 @@ error: expected `{`, found `)` - --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:8:18 + --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:9:18 | LL | fn b() {(loop)} | ----^ expected `{` @@ -7,7 +7,7 @@ LL | fn b() {(loop)} | while parsing this `loop` expression error: expected `{`, found `)` - --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:8:18 + --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:9:18 | LL | fn b() {(loop)} | ----^ expected `{` @@ -20,15 +20,22 @@ error[E0277]: expected a `Fn(&_)` closure, found `()` --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:7:5 | LL | #[core::contracts::ensures] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | expected an `Fn(&_)` closure, found `()` - | required by a bound introduced by this call + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `Fn(&_)` closure, found `()` | = 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 3 previous errors +error[E0277]: expected a `Fn(&_)` closure, found `()` + --> $DIR/ice-in-tokenstream-for-contracts-issue-140683.rs:7:5 + | +LL | #[core::contracts::ensures] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `Fn(&_)` closure, found `()` + | + = help: the trait `for<'a> Fn(&'a _)` is not implemented for `()` +note: required by a bound in `contract_check_requires_and_build_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0277`.