use std::sync::Arc; 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>, contract: &rustc_ast::FnContract, ) -> rustc_hir::Expr<'hir> { // The order in which things are lowered is important! I.e to // refer to variables in contract_decls from postcond/precond, // we must lower it first! let contract_decls = self.lower_stmts(&contract.declarations).0; match (&contract.requires, &contract.ensures) { (Some(req), Some(ens)) => { // Lower the fn contract, which turns: // // { body } // // into: // // let __postcond = if contract_checks { // CONTRACT_DECLARATIONS; // contract_check_requires(PRECOND); // Some(|ret_val| POSTCOND) // } else { // None // }; // { // let ret = { body }; // // if contract_checks { // contract_check_ensures(__postcond, ret) // } else { // ret // } // } let precond = self.lower_precond(req); let postcond_checker = self.lower_postcond_checker(ens); let contract_check = self.lower_contract_check_with_postcond( contract_decls, 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 contract_checks { // Some(|ret_val| POSTCOND) // } else { // None // }; // { // let ret = { body }; // // if contract_checks { // CONTRACT_DECLARATIONS; // contract_check_ensures(__postcond, ret) // } else { // ret // } // } let postcond_checker = self.lower_postcond_checker(ens); let contract_check = self.lower_contract_check_with_postcond(contract_decls, 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_checks { // CONTRACT_DECLARATIONS; // contract_requires(PRECOND); // } // body // } let precond = self.lower_precond(req); let precond_check = self.lower_contract_check_just_precond(contract_decls, 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, Some(Arc::clone(&self.allow_contracts)), ); 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, Some(Arc::clone(&self.allow_contracts)), ); 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, 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 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())), 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, contract_decls: &'hir [rustc_hir::Stmt<'hir>], precond: Option>, postcond_checker: &'hir rustc_hir::Expr<'hir>, ) -> &'hir rustc_hir::Expr<'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 contract_check = rustc_hir::ExprKind::If( self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())), 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 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 contract_check = self.expr_call_lang_item_fn_mut( span, rustc_hir::LangItem::ContractCheckEnsures, arena_vec![self; *cond_fn, *ret], ); 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))) } }