diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 937031324f57f..080449c4fef10 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3246,11 +3246,19 @@ pub struct Impl { pub items: ThinVec<P<AssocItem>>, } +#[derive(Clone, Encodable, Decodable, Debug, Default)] +pub struct FnContract { + pub requires: Option<P<Expr>>, + pub captures: Vec<(Ident, P<Expr>)>, + pub ensures: Option<P<Expr>>, +} + #[derive(Clone, Encodable, Decodable, Debug)] pub struct Fn { pub defaultness: Defaultness, pub generics: Generics, pub sig: FnSig, + pub contract: Option<P<FnContract>>, pub body: Option<P<Block>>, } @@ -3548,7 +3556,7 @@ mod size_asserts { static_assert_size!(Block, 32); static_assert_size!(Expr, 72); static_assert_size!(ExprKind, 40); - static_assert_size!(Fn, 160); + static_assert_size!(Fn, 168); static_assert_size!(ForeignItem, 88); static_assert_size!(ForeignItemKind, 16); static_assert_size!(GenericArg, 24); diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 1a7da46913d7e..90cfc17b52232 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -119,6 +119,10 @@ pub trait MutVisitor: Sized { walk_flat_map_item(self, i) } + fn visit_contract(&mut self, c: &mut P<FnContract>) { + walk_contract(self, c); + } + fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) { walk_fn_decl(self, d); } @@ -898,6 +902,20 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) { } } +fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) { + let FnContract { requires, captures, ensures } = contract.deref_mut(); + if let Some(pred) = requires { + vis.visit_expr(pred); + } + for (ident, expr) in captures.iter_mut() { + vis.visit_ident(ident); + vis.visit_expr(expr); + } + if let Some(pred) = ensures { + vis.visit_expr(pred); + } +} + fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) { let FnDecl { inputs, output } = decl.deref_mut(); inputs.flat_map_in_place(|param| vis.flat_map_param(param)); @@ -1100,8 +1118,9 @@ impl WalkItemKind for ItemKind { ItemKind::Const(item) => { visit_const_item(item, vis); } - ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => { + ItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => { visit_defaultness(vis, defaultness); + if let Some(contract) = contract { vis.visit_contract(contract)}; vis.visit_fn(FnKind::Fn(sig, generics, body), span, id); } ItemKind::Mod(safety, mod_kind) => { @@ -1206,8 +1225,9 @@ impl WalkItemKind for AssocItemKind { AssocItemKind::Const(item) => { visit_const_item(item, visitor); } - AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => { + AssocItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => { visit_defaultness(visitor, defaultness); + if let Some(contract) = contract { visitor.visit_contract(contract); } visitor.visit_fn(FnKind::Fn(sig, generics, body), span, id); } AssocItemKind::Type(box TyAlias { @@ -1311,8 +1331,9 @@ impl WalkItemKind for ForeignItemKind { visitor.visit_ty(ty); visit_opt(expr, |expr| visitor.visit_expr(expr)); } - ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => { + ForeignItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => { visit_defaultness(visitor, defaultness); + if let Some(contract) = contract { visitor.visit_contract(contract); } visitor.visit_fn(FnKind::Fn(sig, generics, body), span, id); } ForeignItemKind::TyAlias(box TyAlias { diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 4dcadb8517eb4..06ab8654344c9 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -66,7 +66,7 @@ impl BoundKind { #[derive(Copy, Clone, Debug)] pub enum FnKind<'a> { /// E.g., `fn foo()`, `fn foo(&self)`, or `extern "Abi" fn foo()`. - Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, &'a Generics, Option<&'a Block>), + Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, &'a Generics, Option<&'a FnContract>, Option<&'a Block>), /// E.g., `|x, y| body`. Closure(&'a ClosureBinder, &'a Option<CoroutineKind>, &'a FnDecl, &'a Expr), @@ -75,7 +75,7 @@ pub enum FnKind<'a> { impl<'a> FnKind<'a> { pub fn header(&self) -> Option<&'a FnHeader> { match *self { - FnKind::Fn(_, _, sig, _, _, _) => Some(&sig.header), + FnKind::Fn(_, _, sig, _, _, _, _) => Some(&sig.header), FnKind::Closure(..) => None, } } @@ -89,7 +89,7 @@ impl<'a> FnKind<'a> { pub fn decl(&self) -> &'a FnDecl { match self { - FnKind::Fn(_, _, sig, _, _, _) => &sig.decl, + FnKind::Fn(_, _, sig, _, _, _, _) => &sig.decl, FnKind::Closure(_, _, decl, _) => decl, } } @@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized { fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result { walk_closure_binder(self, b) } + fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result { + walk_contract(self, c) + } fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result { walk_where_predicate(self, p) } @@ -359,8 +362,10 @@ impl WalkItemKind for ItemKind { try_visit!(visitor.visit_ty(ty)); visit_opt!(visitor, visit_expr, expr); } - ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => { - let kind = FnKind::Fn(FnCtxt::Free, *ident, sig, vis, generics, body.as_deref()); + ItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => + +{ + let kind = FnKind::Fn(FnCtxt::Free, *ident, sig, vis, generics, contract.as_deref(), body.as_deref()); try_visit!(visitor.visit_fn(kind, *span, *id)); } ItemKind::Mod(_unsafety, mod_kind) => match mod_kind { @@ -695,8 +700,8 @@ impl WalkItemKind for ForeignItemKind { try_visit!(visitor.visit_ty(ty)); visit_opt!(visitor, visit_expr, expr); } - ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => { - let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body.as_deref()); + ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => { + let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, contract.as_deref(), body.as_deref()); try_visit!(visitor.visit_fn(kind, span, id)); } ForeignItemKind::TyAlias(box TyAlias { @@ -784,6 +789,21 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>( V::Result::output() } +pub fn walk_contract<'a, V:Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result { + let FnContract { requires, captures, ensures } = c; + if let Some(pred) = requires { + visitor.visit_expr(pred); + } + for (ident, expr) in captures { + visitor.visit_ident(*ident); + visitor.visit_expr(expr); + } + if let Some(pred) = ensures { + visitor.visit_expr(pred); + } + V::Result::output() +} + pub fn walk_where_predicate<'a, V: Visitor<'a>>( visitor: &mut V, predicate: &'a WherePredicate, @@ -829,11 +849,12 @@ pub fn walk_fn_decl<'a, V: Visitor<'a>>( pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Result { match kind { - FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, body) => { + FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, contract, body) => { // Identifier and visibility are visited as a part of the item. try_visit!(visitor.visit_fn_header(header)); try_visit!(visitor.visit_generics(generics)); try_visit!(walk_fn_decl(visitor, decl)); + visit_opt!(visitor, visit_contract, contract); visit_opt!(visitor, visit_block, body); } FnKind::Closure(binder, _coroutine_kind, decl, body) => { @@ -859,9 +880,9 @@ impl WalkItemKind for AssocItemKind { try_visit!(visitor.visit_ty(ty)); visit_opt!(visitor, visit_expr, expr); } - AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => { + AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => { let kind = - FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body.as_deref()); + FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, contract.as_deref(), body.as_deref()); try_visit!(visitor.visit_fn(kind, span, id)); } AssocItemKind::Type(box TyAlias { diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 3b85f1737bdd9..5199b2205bfe9 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -205,7 +205,7 @@ impl<'hir> LoweringContext<'_, 'hir> { abi: sig.abi, } }; - hir::FnSig { decl, header, span } + hir::FnSig { decl, header, span, contract_info: None } } fn generate_param(&mut self, span: Span) -> (hir::Param<'hir>, NodeId) { @@ -376,7 +376,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }); let header = self.generate_header_error(); - let sig = hir::FnSig { decl, header, span }; + let sig = hir::FnSig { decl, header, span, contract_info: None }; let body_id = self.lower_body(|this| (&[], this.mk_expr(hir::ExprKind::Err(err), span))); DelegationResults { generics, body_id, sig } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 52372bbf991c9..4fe924aad8a2c 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2035,7 +2035,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.arena.alloc(self.expr_call_mut(span, e, args)) } - fn expr_call_lang_item_fn_mut( + pub(super) fn expr_call_lang_item_fn_mut( &mut self, span: Span, lang_item: hir::LangItem, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 1273b50dff816..c1e28660408d1 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -207,9 +207,159 @@ impl<'hir> LoweringContext<'_, 'hir> { sig: FnSig { decl, header, span: fn_sig_span }, generics, body, + contract: _contract, .. }) => { - self.with_new_scopes(*fn_sig_span, |this| { + + // `fn foo<G, ...>(a: A, ...) -> RET requires(PRE) captures(o = OLD) ensures(|ret| POST) { ... }` + // + // ==> + // + // fn foo_with_contract<G, ...>(a: A, ...) -> RET { + // check_requires(|| PRE); let o = OLD; let r = foo::<G, ...>(a, ...); check_ensures((|ret| POST)(&r)); r + // } + // fn foo<G, ...>(a: A, ...) -> RET { ... } + let _contract_ids = _contract + .as_ref() + .map(|_contract| { + let contract_node_id = self.next_node_id(); + let contract_def_id = self.create_def( + self.local_def_id(id), + contract_node_id, + kw::Empty, + DefKind::Fn, + *fn_sig_span, + ); + let _lit_true = |this: &mut LoweringContext<'_, 'hir>| { + this.expr(*fn_sig_span, hir::ExprKind::Lit(this.arena.alloc(hir::Lit { + span: *fn_sig_span, + node: ast::LitKind::Bool(true), + }))) + }; + let lit_unit = |this: &mut LoweringContext<'_, 'hir>| { + this.expr(*fn_sig_span, hir::ExprKind::Tup(&[])) + }; + + let contract_ast_fn_decl = { + let inputs = decl.inputs.clone(); + FnDecl { + inputs, + // FIXME: at some point the wrapper should call + // to the wrapped function (perhaps passed as + // a parameter) and return its result. But + // lets hold off on that for now. + output: FnRetTy::Default(*fn_sig_span), + } + }; + + let contract_hir_fn_decl = self.lower_fn_decl( + &contract_ast_fn_decl, + id, + *fn_sig_span, + FnDeclKind::Fn, + None, + ); + + let contract_body_id = self.lower_fn_body(&contract_ast_fn_decl, |this| { + // rustc_contract_requires(|| PRECOND); + // let o = OLD; + // let r = the_real_thing(); + // rustc_contract_ensures(o, r, |o, r| POSTCOND); + // r + + // lit_true(this) + lit_unit(this) + }); + (contract_def_id, contract_hir_fn_decl, contract_body_id) + }); + +/* + let _contract_id = _contract.as_ref().map(|contract| { + let body_id = self.with_new_scopes(*fn_sig_span, |this| { + this.lower_fn_body(decl, |this| { + let lit_true = |this: &mut LoweringContext<'_, 'hir>| { + this.expr(*fn_sig_span, hir::ExprKind::Lit(this.arena.alloc(hir::Lit { + span: *fn_sig_span, + node: ast::LitKind::Bool(true), + }))) + }; + let req = if let Some(req) = &contract.requires { + this.lower_expr_mut(req) + } else { + lit_true(this) + }; + let _precond = this.arena.alloc(this.expr_call_lang_item_fn_mut( + req.span, + hir::LangItem::ContractCheckRequires, + arena_vec![this; req], // FIXME: needs to build closure around `req` + )); + + // FIXME: need to construct `let o = OLD` and the inner call still. + + // FIXME: need to construct `let r = foo::<G, ...>(a, ...);` + + let _postcond = { + let ens = if let Some(ens) = &contract.ensures { + this.lower_expr_mut(ens) + } else { + lit_true(this) // FIXME: needs to build closure of `|old, ret| true` instead + }; + this.arena.alloc(this.expr_call_lang_item_fn_mut( + ens.span, + hir::LangItem::ContractCheckEnsures, + arena_vec![self; ens], + )) + }; + + let contract_span = Span::new( + std::cmp::min(_precond.span.lo(), _postcond.span.lo()), + std::cmp::max(_precond.span.hi(), _postcond.span.hi()), + // FIXME: should this computed/inherited from the + // contexts attached to precond and postcond? + rustc_span::SyntaxContext::root(), + None, + ); + + tracing::debug!("_precond: {:?}", &_precond); + tracing::debug!("_postcond: {:?}", &_postcond); + tracing::debug!("contract_span: {:?}", &contract_span); + let _precond_span = _precond.span; + let _precond = hir::StmtKind::Expr(_precond); + let _postcond_span = _postcond.span; + let _postcond = hir::StmtKind::Expr(_postcond); + + let stmts = + arena_vec![ + this; + this.stmt(_precond_span, _precond), + this.stmt(_postcond_span, _postcond) + ]; + + let block = this.block_all( + contract_span, + stmts, + None, // FIXME: this needs to be returning the let-bound `r` that holds the value returned by the wrapped call. + ); + let block = hir::ExprKind::Block(block, None); + let block = this.expr(contract_span, block); + block + }) + + // let contract_body: hir::Body = unimplemented!(); + + // this.lower_maybe_coroutine_body(*fn_sig_span, , contract_span, fn_id, decl.clone(), None, body) + + }); + + tracing::debug!("body_id: {:?}", body_id); + body_id + }); + */ + + tracing::debug!("_contract_ids: {:?}", _contract_ids); + + + let fn_item = self.with_new_scopes(*fn_sig_span, |this| { // Note: we don't need to change the return type from `T` to // `impl Future<Output = T>` here because lower_body // only cares about the input argument patterns in the function @@ -235,14 +385,18 @@ impl<'hir> LoweringContext<'_, 'hir> { coroutine_kind, ) }); + let contract_info: Option<hir::FnContractInfo<'_>> = _contract_ids.map(|(_, wrapper_decl, wrapper_body_id)| hir::FnContractInfo { wrapper_decl, wrapper_body_id } ); let sig = hir::FnSig { decl, header: this.lower_fn_header(*header, hir::Safety::Safe), span: this.lower_span(*fn_sig_span), + contract_info, }; hir::ItemKind::Fn(sig, generics, body_id) - }) - } + }); + + fn_item + } ItemKind::Mod(_, mod_kind) => match mod_kind { ModKind::Loaded(items, _, spans) => { hir::ItemKind::Mod(self.lower_mod(items, spans)) @@ -253,7 +407,7 @@ impl<'hir> LoweringContext<'_, 'hir> { abi: fm.abi.map_or(abi::Abi::FALLBACK, |abi| self.lower_abi(abi)), items: self .arena - .alloc_from_iter(fm.items.iter().map(|x| self.lower_foreign_item_ref(x))), + .alloc_from_iter(fm.items.iter().map(|x| self.lower_foreign_item_ref(x))), }, ItemKind::GlobalAsm(asm) => hir::ItemKind::GlobalAsm(self.lower_inline_asm(span, asm)), ItemKind::TyAlias(box TyAlias { generics, where_clauses, ty, .. }) => { @@ -681,7 +835,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe); hir::ForeignItemKind::Fn( - hir::FnSig { header, decl, span: self.lower_span(sig.span) }, + hir::FnSig { header, decl, span: self.lower_span(sig.span), opt_contract_id: None }, fn_args, generics, ) @@ -1382,7 +1536,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_generics(generics, constness, kind == FnDeclKind::Impl, id, itctx, |this| { this.lower_fn_decl(&sig.decl, id, sig.span, kind, coroutine_kind) }); - (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span) }) + (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span), opt_contract_id: None }) } pub(super) fn lower_fn_header( diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 87e3a6871b9e1..5b6de03972920 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -961,7 +961,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { walk_list!(self, visit_attribute, &item.attrs); return; // Avoid visiting again. } - ItemKind::Fn(box Fn { defaultness, sig, generics, body }) => { + ItemKind::Fn(box Fn { defaultness, sig, generics, contract, body }) => { self.check_defaultness(item.span, *defaultness); if body.is_none() { @@ -990,7 +990,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { self.visit_vis(&item.vis); self.visit_ident(item.ident); let kind = - FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, body.as_deref()); + FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, contract.as_deref(), body.as_deref()); self.visit_fn(kind, item.span, item.id); walk_list!(self, visit_attribute, &item.attrs); return; // Avoid visiting again. @@ -1396,13 +1396,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> { _, _, _, + _, ) = fk { self.maybe_lint_missing_abi(*sig_span, id); } // Functions without bodies cannot have patterns. - if let FnKind::Fn(ctxt, _, sig, _, _, None) = fk { + if let FnKind::Fn(ctxt, _, sig, _, _, _, None) = fk { Self::check_decl_no_pat(&sig.decl, |span, ident, mut_ident| { if mut_ident && matches!(ctxt, FnCtxt::Assoc(_)) { if let Some(ident) = ident { @@ -1436,7 +1437,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { .is_some(); let disallowed = (!tilde_const_allowed).then(|| match fk { - FnKind::Fn(_, ident, _, _, _, _) => TildeConstReason::Function { ident: ident.span }, + FnKind::Fn(_, ident, _, _, _, _, _) => TildeConstReason::Function { ident: ident.span }, FnKind::Closure(..) => TildeConstReason::Closure, }); self.with_tilde_const(disallowed, |this| visit::walk_fn(this, fk)); @@ -1512,7 +1513,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { self.outer_trait_or_trait_impl.as_ref().and_then(TraitOrTraitImpl::constness).is_some(); match &item.kind { - AssocItemKind::Fn(box Fn { sig, generics, body, .. }) + AssocItemKind::Fn(box Fn { sig, generics, contract, body, .. }) if parent_is_const || ctxt == AssocCtxt::Trait || matches!(sig.header.constness, Const::Yes(_)) => @@ -1525,6 +1526,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { sig, &item.vis, generics, + contract.as_deref(), body.as_deref(), ); walk_list!(self, visit_attribute, &item.attrs); diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 8217b6df5b470..8ae62da7df0eb 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -34,8 +34,8 @@ impl<'a> State<'a> { self.maybe_print_comment(span.lo()); self.print_outer_attributes(attrs); match kind { - ast::ForeignItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => { - self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs); + ast::ForeignItemKind::Fn(box ast::Fn { defaultness, sig, generics, contract, body }) => { + self.print_fn_full(sig, ident, generics, vis, *defaultness, contract.as_deref(), body.as_deref(), attrs); } ast::ForeignItemKind::Static(box ast::StaticItem { ty, mutability, expr, safety }) => { self.print_safety(*safety); @@ -195,13 +195,14 @@ impl<'a> State<'a> { *defaultness, ); } - ast::ItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => { + ast::ItemKind::Fn(box ast::Fn { defaultness, sig, generics, contract, body }) => { self.print_fn_full( sig, item.ident, generics, &item.vis, *defaultness, + contract.as_deref(), body.as_deref(), &item.attrs, ); @@ -538,8 +539,8 @@ impl<'a> State<'a> { self.maybe_print_comment(span.lo()); self.print_outer_attributes(attrs); match kind { - ast::AssocItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => { - self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs); + ast::AssocItemKind::Fn(box ast::Fn { defaultness, sig, generics, contract, body }) => { + self.print_fn_full(sig, ident, generics, vis, *defaultness, contract.as_deref(), body.as_deref(), attrs); } ast::AssocItemKind::Const(box ast::ConstItem { defaultness, generics, ty, expr }) => { self.print_item_const( @@ -653,6 +654,7 @@ impl<'a> State<'a> { generics: &ast::Generics, vis: &ast::Visibility, defaultness: ast::Defaultness, + contract: Option<&ast::FnContract>, body: Option<&ast::Block>, attrs: &[ast::Attribute], ) { @@ -662,6 +664,10 @@ impl<'a> State<'a> { self.print_visibility(vis); self.print_defaultness(defaultness); self.print_fn(&sig.decl, sig.header, Some(name), generics); + if let Some(contract) = contract { + self.nbsp(); + self.print_contract(contract); + } if let Some(body) = body { self.nbsp(); self.print_block_with_attrs(body, attrs); @@ -670,6 +676,29 @@ impl<'a> State<'a> { } } + fn print_contract(&mut self, contract: &ast::FnContract) { + if let Some(pred) = &contract.requires { + self.word("rustc_requires"); + self.popen(); + self.print_expr(pred, FixupContext::default()); + self.pclose(); + } + for (ident, expr) in &contract.captures { + self.word("rustc_captures"); + self.popen(); + self.print_ident(*ident); + self.word_space("="); + self.print_expr(expr, FixupContext::default()); + self.pclose(); + } + if let Some(pred) = &contract.ensures { + self.word("rustc_ensures"); + self.popen(); + self.print_expr(pred, FixupContext::default()); + self.pclose(); + } + } + pub(crate) fn print_fn( &mut self, decl: &ast::FnDecl, diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs index 0caad997b9df1..ba9730d72edde 100644 --- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -86,6 +86,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span defaultness: ast::Defaultness::Final, sig, generics: Generics::default(), + contract: None, body, })); diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs new file mode 100644 index 0000000000000..8dc0579e7dd5d --- /dev/null +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -0,0 +1,156 @@ +#![allow(unused_imports, unused_variables)] + +use rustc_ast::token; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_errors::ErrorGuaranteed; +use rustc_expand::base::{AttrProcMacro, ExtCtxt}; +use rustc_span::symbol::{kw, Ident, sym, Symbol}; +use rustc_span::Span; + +pub(crate) struct ExpandRequires; + +pub(crate) struct ExpandCaptures; + +pub(crate) struct ExpandEnsures; + +impl AttrProcMacro for ExpandRequires { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result<TokenStream, ErrorGuaranteed> { + expand_requires_tts(ecx, span, annotation, annotated) + } +} + +impl AttrProcMacro for ExpandCaptures { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result<TokenStream, ErrorGuaranteed> { + todo!() + } +} + +impl AttrProcMacro for ExpandEnsures { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result<TokenStream, ErrorGuaranteed> { + todo!() + } +} + +fn expand_injecting_circa_where_clause( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotated: TokenStream, + inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>, +) -> Result<TokenStream, ErrorGuaranteed> { + let mut new_tts = Vec::with_capacity(annotated.len()); + let mut cursor = annotated.into_trees(); + + // Find the `fn name<G,...>(x:X,...)` and inject the AST contract forms right after + // the formal parameters (and return type if any). + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + if let TokenTree::Token(tok, _) = tt && tok.is_ident_named(kw::Fn) { + break; + } + } + + // Found the `fn` keyword, now find the formal parameters. + // + // FIXME: can this fail if you have parentheticals in a generics list, like `fn foo<F: Fn(X) -> Y>` ? + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + + if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt { + break; + } + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt { + panic!("contract attribute applied to fn without parameter list."); + } + } + + // There *might* be a return type declaration (and figuring out where that ends would require parsing an arbitrary type expression, e.g. `-> Foo<args ...>` + // + // Instead of trying to figure that out, scan ahead and look for the first occurence of a `where`, a `{ ... }`, or a `;`. + // + // FIXME: this might still fall into a trap for something like `-> Ctor<T, const { 0 }>`. I + // *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form + // prefixed by e.g. `const`, so we should still be able to filter them out without having to + // parse the type expression itself. But rather than try to fix things with hacks like that, + // time might be better spent extending the attribute expander to suport tt-annotation atop + // ast-annotated, which would be an elegant way to sidestep all of this. + let mut opt_next_tt = cursor.next_ref(); + while let Some(next_tt) = opt_next_tt { + if let TokenTree::Token(tok, _) = next_tt && tok.is_ident_named(kw::Where) { + break; + } + if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt { + break; + } + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt { + break; + } + + // for anything else, transcribe the tt and keep looking. + new_tts.push(next_tt.clone()); + opt_next_tt = cursor.next_ref(); + continue; + } + + // At this point, we've transcribed everything from the `fn` through the formal parameter + // list and return type declaration, (if any), but `tt` itself has *not* been transcribed. + // + // Now inject the AST contract form. + // + // FIXME: this kind of manual token tree munging does not have significant precedent among rustc + // builtin macros, probably because most builtin macros use direct AST manipulation to accomplish + // similar goals. But since our attributes need to take arbitrary expressions, and our attribute + // infrastructure does not yet support mixing a token-tree annotation with an AST annotated, we + // end up doing token tree manipulation. + inject(&mut new_tts)?; + + // Above we injected the internal AST requires contruct. Now copy over all the other token trees. + if let Some(tt) = opt_next_tt { + new_tts.push(tt.clone()); + } + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + } + + Ok(TokenStream::new(new_tts)) +} + +fn expand_requires_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result<TokenStream, ErrorGuaranteed> { + expand_injecting_circa_where_clause( + _ecx, + attr_span, + annotated, + |new_tts| { + new_tts.push(TokenTree::Token(token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)), + Spacing::Joint)); + new_tts.push(TokenTree::Delimited(DelimSpan::from_single(attr_span), + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), + token::Delimiter::Parenthesis, + annotation)); + Ok(()) + }, + ) +} diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 82baaca9a4641..a34f39ac2b204 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -1043,6 +1043,7 @@ impl<'a> MethodDef<'a> { defaultness, sig, generics: fn_generics, + contract: None, body: Some(body_block), })), tokens: None, diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index b4b18409a18eb..fd9ea73bcec64 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -82,6 +82,7 @@ impl AllocFnFactory<'_, '_> { defaultness: ast::Defaultness::Final, sig, generics: Generics::default(), + contract: None, body, })); let item = self.cx.item( diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index ebe5e2b544292..3adc5805c6c71 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -36,6 +36,7 @@ mod compile_error; mod concat; mod concat_bytes; mod concat_idents; +mod contracts; mod derive; mod deriving; mod edition_panic; @@ -135,4 +136,10 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote); register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client }))); + let requires = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandRequires)); + register(sym::contracts_requires, requires); + let captures = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandCaptures)); + register(sym::contracts_captures, captures); + let ensures = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandEnsures)); + register(sym::contracts_ensures, ensures); } diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 953484533087c..733a81e622b7d 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -346,6 +346,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> { defaultness, sig, generics: ast::Generics::default(), + contract: None, body: Some(main_body), })); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2ef6fa53f4edd..fb8bc4bf04284 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2306,6 +2306,12 @@ pub struct MutTy<'hir> { pub mutbl: Mutability, } +#[derive(Debug, Clone, Copy, HashStable_Generic)] +pub struct FnContractInfo<'hir> { + pub wrapper_decl: &'hir FnDecl<'hir>, + pub wrapper_body_id: BodyId, +} + /// Represents a function's signature in a trait declaration, /// trait implementation, or a free function. #[derive(Debug, Clone, Copy, HashStable_Generic)] @@ -2313,6 +2319,8 @@ pub struct FnSig<'hir> { pub header: FnHeader, pub decl: &'hir FnDecl<'hir>, pub span: Span, + // FIXME: turn this into a reference to save space + pub contract_info: Option<FnContractInfo<'hir>>, } // The bodies for items are stored "out of line", in a separate @@ -4082,16 +4090,16 @@ mod size_asserts { static_assert_size!(Expr<'_>, 64); static_assert_size!(ExprKind<'_>, 48); static_assert_size!(FnDecl<'_>, 40); - static_assert_size!(ForeignItem<'_>, 88); - static_assert_size!(ForeignItemKind<'_>, 56); + static_assert_size!(ForeignItem<'_>, 104); + static_assert_size!(ForeignItemKind<'_>, 72); static_assert_size!(GenericArg<'_>, 16); static_assert_size!(GenericBound<'_>, 48); static_assert_size!(Generics<'_>, 56); static_assert_size!(Impl<'_>, 80); - static_assert_size!(ImplItem<'_>, 88); - static_assert_size!(ImplItemKind<'_>, 40); - static_assert_size!(Item<'_>, 88); - static_assert_size!(ItemKind<'_>, 56); + static_assert_size!(ImplItem<'_>, 104); + static_assert_size!(ImplItemKind<'_>, 56); + static_assert_size!(Item<'_>, 96); + static_assert_size!(ItemKind<'_>, 64); static_assert_size!(LetStmt<'_>, 64); static_assert_size!(Param<'_>, 32); static_assert_size!(Pat<'_>, 72); @@ -4102,8 +4110,8 @@ mod size_asserts { static_assert_size!(Res, 12); static_assert_size!(Stmt<'_>, 32); static_assert_size!(StmtKind<'_>, 16); - static_assert_size!(TraitItem<'_>, 88); - static_assert_size!(TraitItemKind<'_>, 48); + static_assert_size!(TraitItem<'_>, 104); + static_assert_size!(TraitItemKind<'_>, 64); static_assert_size!(Ty<'_>, 48); static_assert_size!(TyKind<'_>, 32); // tidy-alphabetical-end diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 58916d0586593..85ddd3ec4745a 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -514,6 +514,9 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V:: try_visit!(visitor.visit_nested_body(body)); } ItemKind::Fn(ref sig, ref generics, body_id) => { + if let Some(contract_info) = sig.contract_info { + try_visit!(visitor.visit_nested_body(contract_info.wrapper_body_id)); + } try_visit!(visitor.visit_id(item.hir_id())); try_visit!(visitor.visit_fn( FnKind::ItemFn(item.ident, generics, sig.header), diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index b161e6ba0fa51..d204980a423ac 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -423,6 +423,10 @@ language_item_table! { EffectsIntersectionOutput, sym::EffectsIntersectionOutput, effects_intersection_output, Target::AssocTy, GenericRequirement::None; EffectsCompat, sym::EffectsCompat, effects_compat, Target::Trait, GenericRequirement::Exact(1); EffectsTyCompat, sym::EffectsTyCompat, effects_ty_compat, Target::Trait, GenericRequirement::Exact(1); + + ContractCheckCaptures, sym::contract_check_captures, contract_check_captures_fn, Target::Fn, GenericRequirement::None; + ContractCheckEnsures, sym::contract_check_ensures, contract_check_ensures_fn, Target::Fn, GenericRequirement::None; + ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None; } pub enum GenericRequirement { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 06317a3b3049c..5570e4752af7e 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -2,7 +2,7 @@ //! intrinsics that the compiler exposes. use rustc_errors::codes::*; -use rustc_errors::{DiagMessage, struct_span_code_err}; +use rustc_errors::{struct_span_code_err, DiagMessage}; use rustc_hir as hir; use rustc_middle::bug; use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; @@ -145,6 +145,10 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - | sym::fmul_algebraic | sym::fdiv_algebraic | sym::frem_algebraic + | sym::contract_check_requires + | sym::contract_check_ensures + | sym::contract_check_requires_2 + | sym::contract_check_ensures_2 | sym::const_eval_select => hir::Safety::Safe, _ => hir::Safety::Unsafe, }; @@ -164,15 +168,24 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - /// Remember to add all intrinsics here, in `compiler/rustc_codegen_llvm/src/intrinsic.rs`, /// and in `library/core/src/intrinsics.rs`. -pub fn check_intrinsic_type( - tcx: TyCtxt<'_>, +pub fn check_intrinsic_type<'tcx>( + tcx: TyCtxt<'tcx>, intrinsic_id: LocalDefId, span: Span, intrinsic_name: Symbol, abi: Abi, ) { + // Note: generics_of will include the early-bound lifetimes + // (e.g. lifetimes that are referenced in where-clauses), but not + // late-bound lifetimes. let generics = tcx.generics_of(intrinsic_id); - let param = |n| { + + let ref_early_lt_param = #[track_caller] |n: u32, payload_ty: Ty<'tcx>| { + let p = generics.param_at(n as usize, tcx); + let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data()); + Ty::new_imm_ref(tcx, r, payload_ty) + }; + let param = #[track_caller] |n| { if let &ty::GenericParamDef { name, kind: ty::GenericParamDefKind::Type { .. }, .. } = generics.param_at(n as usize, tcx) { @@ -226,6 +239,43 @@ pub fn check_intrinsic_type( } }; (n_tps, 0, 0, inputs, output, hir::Safety::Unsafe) + } else if name_str.starts_with("contract_check") { + let (n_tps, n_lts, inputs, output) = match intrinsic_name { + // `requires::<C>(c)`, where c is `|| pred()` + sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), + // `requires2::<Old, C, O>(c, o)`, where c is `|| pred()` and o is `|| make_old()` + sym::contract_check_requires_2 => (3, 0, vec![param(1), param(2)], param(0)), + // `ensures::<Ret, C>(ret, c)` where ret: &Ret and c is `|ret| pred(ret)`. + sym::contract_check_ensures => { + // variant with late bound lifetime + let br = ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BrAnon }; + let ref_ret = Ty::new_imm_ref(tcx, ty::Region::new_bound(tcx, ty::INNERMOST, br), param(0)); + (2, 0, vec![ref_ret, param(1)], tcx.types.unit) + } + // `ensures::<'a, Ret, C>(ret, c)` where ret: &'a Ret and c is `|ret| pred(ret)`. + sym::contract_check_ensures_lt => { + // variant with early bound lifetime + let ref_ret = ref_early_lt_param(0, param(1)); + (2, 1, vec![ref_ret, param(2)], tcx.types.unit) + } + // `ensures2::<'r, Old, Ret, C>(old, ret, c)` where old: Old, ret: &'r Ret and c is `|old, ret| pred(old, ret)` + sym::contract_check_ensures_2 => { + // variant with late bound lifetime + let br = ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BrAnon }; + let ref_ret = Ty::new_imm_ref(tcx, ty::Region::new_bound(tcx, ty::INNERMOST, br), param(1)); + (3, 0, vec![param(0), ref_ret, param(2)], tcx.types.unit) + } + sym::contract_check_ensures_lt_2 => { + // variant with early bound lifetime + let ref_ret = ref_early_lt_param(0, param(2)); + (3, 1, vec![param(1), ref_ret, param(3)], tcx.types.unit) + } + other => { + tcx.dcx().emit_err(UnrecognizedIntrinsicFunction { span, name: other }); + return; + } + }; + (n_tps, n_lts, 0, inputs, output, hir::Safety::Safe) } else { let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let (n_tps, n_cts, inputs, output) = match intrinsic_name { diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index f63e2d40e3906..105ecf6096bdc 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1338,7 +1338,7 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_, ty::PolyFn } TraitItem(hir::TraitItem { - kind: TraitItemKind::Fn(FnSig { header, decl, span: _ }, _), + kind: TraitItemKind::Fn(FnSig { header, decl, span: _, opt_contract_id: _ }, _), generics, .. }) => { diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index 71ee77f8f6172..55f8d0ba44bb6 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -62,12 +62,14 @@ This API is completely unstable and subject to change. #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(rust_logo)] #![feature(assert_matches)] +#![feature(closure_track_caller)] #![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(never_type)] #![feature(rustdoc_internals)] #![feature(slice_partition_dedup)] +#![feature(stmt_expr_attributes)] #![feature(try_blocks)] #![feature(unwrap_infallible)] #![warn(unreachable_pub)] diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 6b0a897faba94..2bf3b4aa7258e 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -148,7 +148,7 @@ fn typeck_with_fallback<'tcx>( } let mut fcx = FnCtxt::new(&root_ctxt, param_env, def_id); - if let Some(hir::FnSig { header, decl, .. }) = node.fn_sig() { + if let Some(hir::FnSig { header, decl, opt_contract_id, .. }) = node.fn_sig() { let fn_sig = if decl.output.get_infer_ret_ty().is_some() { fcx.lowerer().lower_fn_ty(id, header.safety, header.abi, decl, None, None) } else { @@ -161,6 +161,13 @@ fn typeck_with_fallback<'tcx>( let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); let fn_sig = fcx.normalize(body.value.span, fn_sig); + if let Some(contract_id) = opt_contract_id { + let contract_body = tcx.hir().body(*contract_id); + // FIXME probably need to create a distinct fn_sig. + check_fn(&mut fcx, fn_sig, None, decl, def_id, contract_body, tcx.features().unsized_fn_params); + } + + check_fn(&mut fcx, fn_sig, None, decl, def_id, body, tcx.features().unsized_fn_params); } else { let expected_type = infer_type_if_missing(&fcx, node); diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 8bd9c899a6286..fd127d57eb6b6 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -349,6 +349,7 @@ impl EarlyLintPass for UnsafeCode { ast::FnSig { header: ast::FnHeader { safety: ast::Safety::Unsafe(_), .. }, .. }, _, _, + _, body, ) = fk { diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 2285877c9ef26..3f8f5e7ef8bde 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -157,7 +157,7 @@ impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> // Explicitly check for lints associated with 'closure_id', since // it does not have a corresponding AST node - if let ast_visit::FnKind::Fn(_, _, sig, _, _, _) = fk { + if let ast_visit::FnKind::Fn(_, _, sig, _, _, _, _) = fk { if let Some(coroutine_kind) = sig.header.coroutine_kind { self.check_id(coroutine_kind.closure_id()); } diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index 660686f4aa280..a8872be6264a9 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -228,9 +228,14 @@ impl<'tcx> Generics { } /// Returns the `GenericParamDef` with the given index. + #[track_caller] pub fn param_at(&'tcx self, param_index: usize, tcx: TyCtxt<'tcx>) -> &'tcx GenericParamDef { if let Some(index) = param_index.checked_sub(self.parent_count) { - &self.own_params[index] + &self.own_params.get(index) + .unwrap_or_else(#[track_caller] || { + panic!("expected generic at own_params[index] but index is {index} and own_params are {:?}", + &self.own_params) + }) } else { tcx.generics_of(self.parent.expect("parent_count > 0 but no parent?")) .param_at(param_index, tcx) diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 5aebe716b0a10..e8a8a3dd87f9f 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -295,6 +295,36 @@ impl<'a> Parser<'a> { }) } + /// Parses an optional fn contract (`requires(WWW) captures(X = YYY) ensures(ZZZ)`) + pub(super) fn parse_contract(&mut self) -> PResult<'a, Option<rustc_ast::ptr::P<ast::FnContract>>> { + let pre_cond = if self.eat_keyword(kw::RustcContractRequires) { + Some(self.parse_expr()?) + } else { + None + }; + let captures = if self.eat_keyword(kw::RustcContractCaptures) { + let ident = self.parse_ident()?; + if self.eat(&token::Eq) { + vec![(ident, self.parse_expr()?)] + } else { + // FIXME: replace this with a proper parse error. + panic!("Malformed captures clause on contract"); + } + } else { + vec![] + }; + let post_cond = if self.eat_keyword(kw::RustcContractEnsures) { + Some(self.parse_expr()?) + } else { + None + }; + if pre_cond.is_none() && captures.is_empty() && post_cond.is_none() { + Ok(None) + } else { + Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires: pre_cond, captures, ensures: post_cond }))) + } + } + /// Parses an optional where-clause. /// /// ```ignore (only-for-syntax-highlight) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 36733726564fb..b9ac5fb0ee1d3 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -197,9 +197,9 @@ impl<'a> Parser<'a> { self.parse_use_item()? } else if self.check_fn_front_matter(check_pub, case) { // FUNCTION ITEM - let (ident, sig, generics, body) = + let (ident, sig, generics, contract, body) = self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?; - (ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body }))) + (ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, contract, body }))) } else if self.eat_keyword(kw::Extern) { if self.eat_keyword(kw::Crate) { // EXTERN CRATE @@ -2312,7 +2312,7 @@ impl<'a> Parser<'a> { sig_lo: Span, vis: &Visibility, case: Case, - ) -> PResult<'a, (Ident, FnSig, Generics, Option<P<Block>>)> { + ) -> PResult<'a, (Ident, FnSig, Generics, Option<P<FnContract>>, Option<P<Block>>)> { let fn_span = self.token.span; let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn` let ident = self.parse_ident()?; // `foo` @@ -2338,6 +2338,8 @@ impl<'a> Parser<'a> { // inside `parse_fn_body()`. let fn_params_end = self.prev_token.span.shrink_to_hi(); + let contract = self.parse_contract()?; + generics.where_clause = self.parse_where_clause()?; // `where T: Ord` // `fn_params_end` is needed only when it's followed by a where clause. @@ -2349,7 +2351,7 @@ impl<'a> Parser<'a> { let body = self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?; let fn_sig_span = sig_lo.to(sig_hi); - Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, body)) + Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body)) } /// Provide diagnostics when function body is not found diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs index f3174e7dea2d8..4ce683a750561 100644 --- a/compiler/rustc_parse/src/validate_attr.rs +++ b/compiler/rustc_parse/src/validate_attr.rs @@ -31,7 +31,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) { // Check input tokens for built-in and key-value attributes. match attr_info { // `rustc_dummy` doesn't have any restrictions specific to built-in attributes. - Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => { + Some(BuiltinAttribute { name, template, .. }) if input_restricted_by_parser(*name) => { match parse_meta(psess, attr) { // Don't check safety again, we just did that Ok(meta) => { @@ -56,6 +56,10 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) { } } +fn input_restricted_by_parser(name: Symbol) -> bool { + name != sym::rustc_dummy && name != sym::contracts_requires +} + pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> { let item = attr.get_normal_item(); Ok(MetaItem { diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 0047f2c4b51e9..dd5bde59dc0d3 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -240,11 +240,12 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { fn visit_fn(&mut self, fn_kind: FnKind<'a>, span: Span, _: NodeId) { match fn_kind { - FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, body) + FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, contract, body) if let Some(coroutine_kind) = header.coroutine_kind => { self.visit_fn_header(header); self.visit_generics(generics); + if let Some(contract) = contract { self.visit_contract(contract); } // For async functions, we need to create their inner defs inside of a // closure to match their desugared representation. Besides that, diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 66c1ff93ce1ce..8aefb3d4afa0f 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -961,8 +961,8 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r match fn_kind { // Bail if the function is foreign, and thus cannot validly have // a body, or if there's no body for some other reason. - FnKind::Fn(FnCtxt::Foreign, _, sig, _, generics, _) - | FnKind::Fn(_, _, sig, _, generics, None) => { + FnKind::Fn(FnCtxt::Foreign, _, sig, _, generics, _, _) + | FnKind::Fn(_, _, sig, _, generics, _, None) => { self.visit_fn_header(&sig.header); self.visit_generics(generics); self.with_lifetime_rib( @@ -1002,7 +1002,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r // Create a label rib for the function. this.with_label_rib(RibKind::FnOrCoroutine, |this| { match fn_kind { - FnKind::Fn(_, _, sig, _, generics, body) => { + FnKind::Fn(_, _, sig, _, generics, contract, body) => { this.visit_generics(generics); let declaration = &sig.decl; @@ -1033,6 +1033,8 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r }, ); + if let Some(contract) = contract { this.visit_contract(contract); } + if let Some(body) = body { // Ignore errors in function bodies if this is rustdoc // Be sure not to set this until the function signature has been resolved. diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index fce5ec36c661b..c6ffdd3b94fa8 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3169,7 +3169,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { { let pre = if lt.kind == MissingLifetimeKind::Ampersand && let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, sig, _, _, _) = kind + && let FnKind::Fn(_, _, sig, _, _, _, _) = kind && !sig.decl.inputs.is_empty() && let sugg = sig .decl @@ -3210,7 +3210,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else if (lt.kind == MissingLifetimeKind::Ampersand || lt.kind == MissingLifetimeKind::Underscore) && let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, sig, _, _, _) = kind + && let FnKind::Fn(_, _, sig, _, _, _, _) = kind && let ast::FnRetTy::Ty(ret_ty) = &sig.decl.output && !sig.decl.inputs.is_empty() && let arg_refs = sig @@ -3270,7 +3270,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let mut owned_sugg = lt.kind == MissingLifetimeKind::Ampersand; let mut sugg = vec![(lt.span, String::new())]; if let Some((kind, _span)) = self.diag_metadata.current_function - && let FnKind::Fn(_, _, sig, _, _, _) = kind + && let FnKind::Fn(_, _, sig, _, _, _, _) = kind && let ast::FnRetTy::Ty(ty) = &sig.decl.output { let mut lt_finder = diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cc3bda99a117b..70ef0d5179b14 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -103,6 +103,9 @@ symbols! { MacroRules: "macro_rules", Raw: "raw", Reuse: "reuse", + RustcContractCaptures: "rustc_contract_captures", + RustcContractEnsures: "rustc_contract_ensures", + RustcContractRequires: "rustc_contract_requires", Safe: "safe", Union: "union", Yeet: "yeet", @@ -650,6 +653,16 @@ symbols! { const_try, constant, constructor, + contract_check_captures, // FIXME we do not need this, do we? + contract_check_ensures, + contract_check_ensures_2, + contract_check_ensures_lt, + contract_check_ensures_lt_2, + contract_check_requires, + contract_check_requires_2, + contracts_captures, + contracts_ensures, + contracts_requires, convert_identity, copy, copy_closures, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index e3b4628e1846a..71c365d5ed157 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -3048,6 +3048,48 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) // Runtime NOP } +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_requires<C: FnOnce() -> bool>(c: C) { + assert!(c()); +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_requires_2<Old, C: FnOnce() -> bool, O: FnOnce() -> Old>(c: C, o: O) -> Old { + assert!(c()); o() +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures<Ret, C: for<'a> FnOnce(&'a Ret) -> bool>(ret: &Ret, c: C) { + assert!(c(ret)); +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures_lt<'r, Ret, C: FnOnce(&'r Ret) -> bool>(ret: &'r Ret, c: C) { + assert!(c(ret)); +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures_2<Old, Ret, C: for<'a> FnOnce(Old, &'a Ret) -> bool>(old: Old, ret: &Ret, c: C) { + assert!(c(old, ret)); +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures_lt_2<'r, Old, Ret, C: FnOnce(Old, &'r Ret) -> bool>(old: Old, ret: &'r Ret, c: C) { + assert!(c(old, ret)); +} + /// The intrinsic will return the size stored in that vtable. /// /// # Safety diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 08c0d6e34cd02..fe1c381d3cfe2 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -270,6 +270,15 @@ pub mod assert_matches { pub use crate::macros::{assert_matches, debug_assert_matches}; } +#[cfg(not(bootstrap))] +/// Unstable module containin the unstable contracts attribute macros. +#[unstable(feature = "rustc_contracts", issue = "none")] +pub mod contracts { + pub use crate::macros::builtin::contracts_ensures; + pub use crate::macros::builtin::contracts_captures; + pub use crate::macros::builtin::contracts_requires; +} + #[unstable(feature = "cfg_match", issue = "115585")] pub use crate::macros::cfg_match; diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index aa0646846e43e..3f1839c507d76 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1695,6 +1695,45 @@ pub(crate) mod builtin { /* compiler built-in */ } + #[cfg(not(bootstrap))] + /// Attribute macro applied to a function to compute and name state at entry that will be retained for evaluating the postcondition at exit. + /// + /// This one is not prefixed with rustc_ because pnkfelix is trying to + /// debug whether that name is ignored specially which can make things + /// hard to track down when adding a new feature like this. + #[unstable(feature = "rustc_contracts", issue = "none")] + #[allow_internal_unstable(core_intrinsics)] + #[rustc_builtin_macro] + pub macro contracts_captures($item:item) { + /* compiler built-in */ + } + + #[cfg(not(bootstrap))] + /// Attribute macro applied to a function to give it a postcondition. + /// + /// This one is not prefixed with rustc_ because pnkfelix is trying to + /// debug whether that name is ignored specially which can make things + /// hard to track down when adding a new feature like this. + #[unstable(feature = "rustc_contracts", issue = "none")] + #[allow_internal_unstable(core_intrinsics)] + #[rustc_builtin_macro] + pub macro contracts_ensures($item:item) { + /* compiler built-in */ + } + + #[cfg(not(bootstrap))] + /// Attribute macro applied to a function to give it a precondition. + /// + /// This one is not prefixed with rustc_ because pnkfelix is trying to + /// debug whether that name is ignored specially which can make things + /// hard to track down when adding a new feature like this. + #[unstable(feature = "rustc_contracts", issue = "none")] + #[allow_internal_unstable(core_intrinsics)] + #[rustc_builtin_macro] + pub macro contracts_requires($item:item) { + /* compiler built-in */ + } + /// Attribute macro applied to a function to register it as a handler for allocation failure. /// /// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html). diff --git a/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs b/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs index d276e29bacecf..c76ffe862e703 100644 --- a/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs +++ b/src/tools/clippy/clippy_lints/src/multiple_bound_locations.rs @@ -39,7 +39,7 @@ declare_lint_pass!(MultipleBoundLocations => [MULTIPLE_BOUND_LOCATIONS]); impl EarlyLintPass for MultipleBoundLocations { fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) { - if let FnKind::Fn(_, _, _, _, generics, _) = kind + if let FnKind::Fn(_, _, _, _, generics, _, _) = kind && !generics.params.is_empty() && !generics.where_clause.predicates.is_empty() { diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index 68f74e52ed7b7..3e02ad11b6e8e 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -291,7 +291,8 @@ pub fn eq_block(l: &Block, r: &Block) -> bool { } pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool { - use StmtKind::*; + +use StmtKind::*; match (&l.kind, &r.kind) { (Let(l), Let(r)) => { eq_pat(&l.pat, &r.pat) @@ -362,18 +363,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && both(lc.as_ref(), rc.as_ref(), |l, r| eq_contract(l, r)) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, (Mod(lu, lmk), Mod(ru, rmk)) => { @@ -497,18 +501,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && both(lc.as_ref(), rc.as_ref(), |l, r| eq_contract(l, r)) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -559,18 +566,21 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { defaultness: ld, sig: lf, generics: lg, + contract: lc, body: lb, }), Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, + contract: rc, body: rb, }), ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) + && both(lc.as_ref(), rc.as_ref(), |l, r| eq_contract(l, r)) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -677,6 +687,34 @@ pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool { } } +pub fn eq_contract(l: &FnContract, r: &FnContract) -> bool { + let req_cmp = match (&l.requires, &r.requires) { + (Some(l_expr), Some(r_expr)) => eq_expr(l_expr, r_expr), + (None, None) => true, + _ => return false, + }; + { + let mut lcap = l.captures.iter(); + let mut rcap = r.captures.iter(); + // if control breaks out of loop, then l and r have equal captures. + loop { + let (li, le, ri, re) = match (lcap.next(), rcap.next()) { + (None, None) => break, + (Some((li, le)), Some((ri, re))) => (li, le, ri, re), + (Some(_), None) | (None, Some(_)) => return false, + }; + if li != ri { return false; } + if !eq_expr(le, re) { return false; } + } + }; + let ens_cmp = match (&l.ensures, &r.ensures) { + (Some(l_expr), Some(r_expr)) => eq_expr(l_expr, r_expr), + (None, None) => true, + _ => return false, + }; + req_cmp && ens_cmp +} + pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool { eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind) } diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index fc043a697e039..f9475372d4f90 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -333,12 +333,12 @@ impl<'a> FnSig<'a> { defaultness: ast::Defaultness, ) -> FnSig<'a> { match *fn_kind { - visit::FnKind::Fn(visit::FnCtxt::Assoc(..), _, fn_sig, vis, generics, _) => { + visit::FnKind::Fn(visit::FnCtxt::Assoc(..), _, fn_sig, vis, generics, _, _) => { let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis); fn_sig.defaultness = defaultness; fn_sig } - visit::FnKind::Fn(_, _, fn_sig, vis, generics, _) => FnSig { + visit::FnKind::Fn(_, _, fn_sig, vis, generics, _, _) => FnSig { decl, generics, ext: fn_sig.header.ext, @@ -3432,6 +3432,7 @@ impl Rewrite for ast::ForeignItem { defaultness, ref sig, ref generics, + ref contract, ref body, } = **fn_kind; if let Some(ref body) = body { @@ -3447,6 +3448,8 @@ impl Rewrite for ast::ForeignItem { sig, &self.vis, generics, + contract.as_deref(), + // contract.as_ref().map(|c: &ptr::P<ast::FnContract>| -> &ast::FnContract { &*c }), Some(body), ), &sig.decl, diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index 8102fe7ad8f29..6fde2582ab890 100644 --- a/src/tools/rustfmt/src/visitor.rs +++ b/src/tools/rustfmt/src/visitor.rs @@ -386,7 +386,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { let indent = self.block_indent; let block; let rewrite = match fk { - visit::FnKind::Fn(_, ident, _, _, _, Some(ref b)) => { + visit::FnKind::Fn(_, ident, _, _, _, _, Some(ref b)) => { block = b; self.rewrite_fn_before_block( indent, @@ -538,6 +538,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { defaultness, ref sig, ref generics, + ref contract, ref body, } = **fn_kind; if let Some(ref body) = body { @@ -553,6 +554,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { sig, &item.vis, generics, + contract.as_deref(), Some(body), ), &sig.decl, @@ -646,13 +648,14 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { defaultness, ref sig, ref generics, + ref contract, ref body, } = **fn_kind; if let Some(ref body) = body { let inner_attrs = inner_attributes(&ai.attrs); let fn_ctxt = visit::FnCtxt::Assoc(assoc_ctxt); self.visit_fn( - visit::FnKind::Fn(fn_ctxt, ai.ident, sig, &ai.vis, generics, Some(body)), + visit::FnKind::Fn(fn_ctxt, ai.ident, sig, &ai.vis, generics, contract.as_deref(), Some(body)), &sig.decl, ai.span, defaultness,