diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 8e73df63ef54f..757026bc1c006 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3316,11 +3316,18 @@ pub struct Impl { pub items: ThinVec>, } +#[derive(Clone, Encodable, Decodable, Debug, Default)] +pub struct FnContract { + pub requires: Option>, + pub ensures: Option>, +} + #[derive(Clone, Encodable, Decodable, Debug)] pub struct Fn { pub defaultness: Defaultness, pub generics: Generics, pub sig: FnSig, + pub contract: Option>, pub body: Option>, } @@ -3618,7 +3625,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 aa88e8369d5b6..e4721b2c8a120 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -143,6 +143,10 @@ pub trait MutVisitor: Sized { walk_flat_map_assoc_item(self, i, ctxt) } + fn visit_contract(&mut self, c: &mut P) { + walk_contract(self, c); + } + fn visit_fn_decl(&mut self, d: &mut P) { walk_fn_decl(self, d); } @@ -973,6 +977,16 @@ fn walk_fn(vis: &mut T, kind: FnKind<'_>) { } } +fn walk_contract(vis: &mut T, contract: &mut P) { + let FnContract { requires, ensures } = contract.deref_mut(); + if let Some(pred) = requires { + vis.visit_expr(pred); + } + if let Some(pred) = ensures { + vis.visit_expr(pred); + } +} + fn walk_fn_decl(vis: &mut T, decl: &mut P) { let FnDecl { inputs, output } = decl.deref_mut(); inputs.flat_map_in_place(|param| vis.flat_map_param(param)); @@ -1205,8 +1219,11 @@ 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(FnCtxt::Free, ident, sig, visibility, generics, body), span, @@ -1329,8 +1346,11 @@ 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(FnCtxt::Assoc(ctxt), ident, sig, visibility, generics, body), span, @@ -1476,8 +1496,11 @@ 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(FnCtxt::Foreign, ident, sig, visibility, generics, body), span, diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 1d6d7330757de..c8e35816a79cc 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -65,7 +65,15 @@ impl BoundKind { #[derive(Copy, Clone, Debug)] pub enum FnKind<'a> { /// E.g., `fn foo()`, `fn foo(&self)`, or `extern "Abi" fn foo()`. - Fn(FnCtxt, &'a Ident, &'a FnSig, &'a Visibility, &'a Generics, &'a Option>), + Fn( + FnCtxt, + &'a Ident, + &'a FnSig, + &'a Visibility, + &'a Generics, + &'a Option>, + &'a Option>, + ), /// E.g., `|x, y| body`. Closure(&'a ClosureBinder, &'a Option, &'a FnDecl, &'a Expr), @@ -74,7 +82,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, } } @@ -88,7 +96,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 +196,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) } @@ -374,8 +385,8 @@ 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); + ItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => { + let kind = FnKind::Fn(FnCtxt::Free, ident, sig, vis, generics, contract, body); try_visit!(visitor.visit_fn(kind, span, id)); } ItemKind::Mod(_unsafety, mod_kind) => match mod_kind { @@ -715,8 +726,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); + ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => { + let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, contract, body); try_visit!(visitor.visit_fn(kind, span, id)); } ForeignItemKind::TyAlias(box TyAlias { @@ -800,6 +811,17 @@ 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, ensures } = c; + if let Some(pred) = requires { + visitor.visit_expr(pred); + } + 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, @@ -858,11 +880,20 @@ 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!(visitor.visit_fn_decl(decl)); + visit_opt!(visitor, visit_contract, contract); visit_opt!(visitor, visit_block, body); } FnKind::Closure(binder, coroutine_kind, decl, body) => { @@ -892,8 +923,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 }) => { - let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body); + AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => { + let kind = + FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, contract, body); try_visit!(visitor.visit_fn(kind, span, id)); } AssocItemKind::Type(box TyAlias { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index a76ca6772e52e..9ccadc74fc333 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -314,7 +314,20 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label)) } ExprKind::Ret(e) => { - let e = e.as_ref().map(|x| self.lower_expr(x)); + let mut e = e.as_ref().map(|x| self.lower_expr(x)); + if let Some(Some((span, fresh_ident))) = self + .contract + .as_ref() + .map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident))) + { + let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2); + let args = if let Some(e) = e { + std::slice::from_ref(e) + } else { + std::slice::from_ref(self.expr_unit(span)) + }; + e = Some(self.expr_call(span, checker_fn, args)); + } hir::ExprKind::Ret(e) } ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), @@ -2125,7 +2138,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, @@ -2135,7 +2148,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr_call_mut(span, path, args) } - fn expr_call_lang_item_fn( + pub(super) fn expr_call_lang_item_fn( &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 6fce911693863..7bc0cecde9d01 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -207,9 +207,42 @@ impl<'hir> LoweringContext<'_, 'hir> { sig: FnSig { decl, header, span: fn_sig_span }, generics, body, + contract, .. }) => { self.with_new_scopes(*fn_sig_span, |this| { + assert!(this.contract.is_none()); + if let Some(contract) = contract { + let requires = contract.requires.clone(); + let ensures = contract.ensures.clone(); + let ensures = if let Some(ens) = ensures { + // FIXME: this needs to be a fresh (or illegal) identifier to prevent + // accidental capture of a parameter or global variable. + let checker_ident: Ident = + Ident::from_str_and_span("__ensures_checker", ens.span); + let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut( + ens.span, + checker_ident, + hir::BindingMode::NONE, + ); + + Some(crate::FnContractLoweringEnsures { + expr: ens, + fresh_ident: (checker_ident, checker_pat, checker_hir_id), + }) + } else { + None + }; + + // Note: `with_new_scopes` will reinstall the outer + // item's contract (if any) after its callback finishes. + this.contract.replace(crate::FnContractLoweringInfo { + span, + requires, + ensures, + }); + } + // Note: we don't need to change the return type from `T` to // `impl Future` here because lower_body // only cares about the input argument patterns in the function @@ -1051,10 +1084,82 @@ impl<'hir> LoweringContext<'_, 'hir> { body: impl FnOnce(&mut Self) -> hir::Expr<'hir>, ) -> hir::BodyId { self.lower_body(|this| { - ( - this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))), - body(this), - ) + let params = + this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))); + let result = body(this); + + let contract = this.contract.take(); + + // { body } + // ==> + // { rustc_contract_requires(PRECOND); { body } } + let result: hir::Expr<'hir> = if let Some(_contract) = contract { + let lit_unit = |this: &mut LoweringContext<'_, 'hir>| { + this.expr(_contract.span, hir::ExprKind::Tup(&[])) + }; + + let precond: hir::Stmt<'hir> = if let Some(req) = _contract.requires { + let lowered_req = this.lower_expr_mut(&req); + let precond = this.expr_call_lang_item_fn_mut( + req.span, + hir::LangItem::ContractCheckRequires, + &*arena_vec![this; lowered_req], + ); + this.stmt_expr(req.span, precond) + } else { + let u = lit_unit(this); + this.stmt_expr(_contract.span, u) + }; + + let (postcond_checker, _opt_ident, result) = if let Some(ens) = _contract.ensures { + let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens; + let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens); + let postcond_checker = this.expr_call_lang_item_fn( + ens.span, + hir::LangItem::ContractBuildCheckEnsures, + &*arena_vec![this; lowered_ens], + ); + let checker_binding_pat = fresh_ident.1; + ( + this.stmt_let_pat( + None, + ens.span, + Some(postcond_checker), + this.arena.alloc(checker_binding_pat), + hir::LocalSource::Contract, + ), + Some((fresh_ident.0, fresh_ident.2)), + { + let checker_fn = + this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2); + let span = this.mark_span_with_reason( + DesugaringKind::Contract, + ens.span, + None, + ); + this.expr_call_mut( + span, + checker_fn, + std::slice::from_ref(this.arena.alloc(result)), + ) + }, + ) + } else { + let u = lit_unit(this); + (this.stmt_expr(_contract.span, u), None, result) + }; + + let block = this.block_all( + _contract.span, + arena_vec![this; precond, postcond_checker], + Some(this.arena.alloc(result)), + ); + this.expr_block(block) + } else { + result + }; + + (params, result) }) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 0e28590bd6609..cf15fa59d49c4 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -85,6 +85,19 @@ mod path; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } +#[derive(Debug, Clone)] +struct FnContractLoweringInfo<'hir> { + pub span: Span, + pub requires: Option>, + pub ensures: Option>, +} + +#[derive(Debug, Clone)] +struct FnContractLoweringEnsures<'hir> { + expr: ast::ptr::P, + fresh_ident: (Ident, hir::Pat<'hir>, HirId), +} + struct LoweringContext<'a, 'hir> { tcx: TyCtxt<'hir>, resolver: &'a mut ResolverAstLowering, @@ -99,6 +112,8 @@ struct LoweringContext<'a, 'hir> { /// Collect items that were created by lowering the current owner. children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>, + contract: Option>, + coroutine_kind: Option, /// When inside an `async` context, this is the `HirId` of the @@ -147,6 +162,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { bodies: Vec::new(), attrs: SortedMap::default(), children: Vec::default(), + contract: None, current_hir_id_owner: hir::CRATE_OWNER_ID, item_local_id_counter: hir::ItemLocalId::ZERO, ident_and_label_to_local_id: Default::default(), @@ -833,12 +849,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let was_in_loop_condition = self.is_in_loop_condition; self.is_in_loop_condition = false; + let old_contract = self.contract.take(); + let catch_scope = self.catch_scope.take(); let loop_scope = self.loop_scope.take(); let ret = f(self); self.catch_scope = catch_scope; self.loop_scope = loop_scope; + self.contract = old_contract; + self.is_in_loop_condition = was_in_loop_condition; self.current_item = current_item; diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 236ca7ba70357..0f63468985492 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -917,7 +917,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); let is_intrinsic = @@ -947,7 +947,8 @@ 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); + let kind = + FnKind::Fn(FnCtxt::Free, &item.ident, sig, &item.vis, generics, contract, body); self.visit_fn(kind, item.span, item.id); walk_list!(self, visit_attribute, &item.attrs); return; // Avoid visiting again. @@ -1354,13 +1355,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> { _, _, _, + _, ) = fk { self.maybe_lint_missing_abi(*extern_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 { @@ -1394,7 +1396,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)); @@ -1470,15 +1472,22 @@ 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(_)) => { self.visit_vis(&item.vis); self.visit_ident(&item.ident); - let kind = - FnKind::Fn(FnCtxt::Assoc(ctxt), &item.ident, sig, &item.vis, generics, body); + let kind = FnKind::Fn( + FnCtxt::Assoc(ctxt), + &item.ident, + sig, + &item.vis, + generics, + contract, + body, + ); walk_list!(self, visit_attribute, &item.attrs); self.visit_fn(kind, item.span, item.id); } diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 94746212138dc..9e01911fd3c40 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -560,6 +560,8 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(unsafe_fields, "`unsafe` fields are experimental"); gate_all!(unsafe_binders, "unsafe binder types are experimental"); + gate_all!(rustc_contracts, "contracts are experimental"); + gate_all!(rustc_contracts_internals, "contract internal machinery is for internal use only"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 897c275d850c2..9312bfcce13b2 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -34,8 +34,23 @@ 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_item_const( @@ -199,13 +214,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, ); @@ -542,8 +558,17 @@ 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( @@ -658,6 +683,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], ) { @@ -667,6 +693,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); @@ -675,6 +705,21 @@ 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(); + } + 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_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index a1979c8b8aba1..dbbc9614c02a7 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1631,6 +1631,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::SizedBound, ); } + &Rvalue::NullaryOp(NullOp::ContractChecks, _) => {} &Rvalue::NullaryOp(NullOp::UbChecks, _) => {} Rvalue::ShallowInitBox(operand, ty) => { diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs index d2b4e1ca824fd..cffc497860133 100644 --- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -85,6 +85,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..be7f276cdaa93 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -0,0 +1,176 @@ +#![allow(unused_imports, unused_variables)] + +use rustc_ast::token; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_errors::ErrorGuaranteed; +use rustc_expand::base::{AttrProcMacro, ExtCtxt}; +use rustc_span::Span; +use rustc_span::symbol::{Ident, Symbol, kw, sym}; + +pub(crate) struct ExpandRequires; + +pub(crate) struct ExpandEnsures; + +impl AttrProcMacro for ExpandRequires { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result { + expand_requires_tts(ecx, span, annotation, annotated) + } +} + +impl AttrProcMacro for ExpandEnsures { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result { + expand_ensures_tts(ecx, span, annotation, annotated) + } +} + +fn expand_injecting_circa_where_clause( + ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotated: TokenStream, + inject: impl FnOnce(&mut Vec) -> Result<(), ErrorGuaranteed>, +) -> Result { + let mut new_tts = Vec::with_capacity(annotated.len()); + let mut cursor = annotated.iter(); + + // Find the `fn name(x:X,...)` and inject the AST contract forms right after + // the formal parameters (and return type if any). + while let Some(tt) = cursor.next() { + 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 Y>` ? + while let Some(tt) = cursor.next() { + 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` + // + // 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`. 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(); + 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(); + } + + // 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/ensures 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() { + new_tts.push(tt.clone()); + } + + // Record the span as a contract attribute expansion. + // This is used later to stop users from using the extended syntax directly + // which is gated via `rustc_contracts_internals`. + ecx.psess().contract_attribute_spans.push(attr_span); + + Ok(TokenStream::new(new_tts)) +} + +fn expand_requires_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result { + 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::Token( + token::Token::new(token::TokenKind::OrOr, attr_span), + Spacing::Alone, + )); + new_tts.push(TokenTree::Delimited( + DelimSpan::from_single(attr_span), + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), + token::Delimiter::Parenthesis, + annotation, + )); + Ok(()) + }) +} + +fn expand_ensures_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result { + expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push(TokenTree::Token( + token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, 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 f0a5e44e066ee..59b01f1146614 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -1034,6 +1034,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 8388e9dcafb8c..8fdbbf8e704a9 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -81,6 +81,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 6071d36f8ebbc..af4c0eca0e5ae 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -54,6 +54,7 @@ mod trace_macros; pub mod asm; pub mod cmdline_attrs; +pub mod contracts; pub mod proc_macro_harness; pub mod standard_library_imports; pub mod test_harness; @@ -138,4 +139,8 @@ 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 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 4644659894368..84df54a1a8437 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -345,6 +345,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P { defaultness, sig, generics: ast::Generics::default(), + contract: None, body: Some(main_body), })); diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 956a024fa4dc3..b8d14a98d6b82 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -858,6 +858,15 @@ fn codegen_stmt<'tcx>( lval.write_cvalue(fx, val); return; } + NullOp::ContractChecks => { + let val = fx.tcx.sess.contract_checks(); + let val = CValue::by_val( + fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()), + fx.layout_of(fx.tcx.types.bool), + ); + lval.write_cvalue(fx, val); + return; + } }; let val = CValue::by_val( fx.bcx.ins().iconst(fx.pointer_type, i64::try_from(val).unwrap()), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 3b62148abb789..d39510c9229e3 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -707,6 +707,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let val = bx.tcx().sess.ub_checks(); bx.cx().const_bool(val) } + mir::NullOp::ContractChecks => { + let val = bx.tcx().sess.contract_checks(); + bx.cx().const_bool(val) + } }; let tcx = self.cx.tcx(); OperandRef { diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 015e16716821a..aa8dc5e5f4bbd 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -567,7 +567,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, + NullOp::SizeOf + | NullOp::AlignOf + | NullOp::OffsetOf(_) + | NullOp::UbChecks + | NullOp::ContractChecks, _, ) => {} Rvalue::ShallowInitBox(_, _) => {} diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 36e5a2ff750ae..888e141759da3 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -287,6 +287,9 @@ pub trait Machine<'tcx>: Sized { /// Determines the result of a `NullaryOp::UbChecks` invocation. fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Determines the result of a `NullaryOp::ContractChecks` invocation. + fn contract_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction. /// You can use this to detect long or endlessly running programs. #[inline] @@ -673,6 +676,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) { interp_ok(true) } + #[inline(always)] + fn contract_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> { + // We can't look at `tcx.sess` here as that can differ across crates, which can lead to + // unsound differences in evaluating the same constant at different instantiation sites. + interp_ok(true) + } + #[inline(always)] fn adjust_global_allocation<'b>( _ecx: &InterpCx<$tcx, Self>, diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index 5fa632fc57aaf..899670aeb62da 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ImmTy::from_uint(val, usize_layout()) } UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx), + ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx), }) } } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 3c3bbc8c31d86..423e1792591c5 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -19,6 +19,7 @@ const GATED_CFGS: &[GatedCfg] = &[ // (name in cfg, feature, function to check if the feature is enabled) (sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks), (sym::ub_checks, sym::cfg_ub_checks, Features::cfg_ub_checks), + (sym::contract_checks, sym::cfg_contract_checks, Features::cfg_contract_checks), (sym::target_thread_local, sym::cfg_target_thread_local, Features::cfg_target_thread_local), ( sym::target_has_atomic_equal_alignment, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index fb83487c9394b..9719c57040c67 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -403,6 +403,8 @@ declare_features! ( (unstable, c_variadic, "1.34.0", Some(44930)), /// Allows the use of `#[cfg()]`. (unstable, cfg_boolean_literals, "1.83.0", Some(131204)), + /// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled. + (unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(133866)), /// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour. (unstable, cfg_overflow_checks, "1.71.0", Some(111466)), /// Provides the relocation model information as cfg entry @@ -604,6 +606,10 @@ declare_features! ( (unstable, return_type_notation, "1.70.0", Some(109417)), /// Allows `extern "rust-cold"`. (unstable, rust_cold_cc, "1.63.0", Some(97544)), + /// Allows use of contracts attributes. + (unstable, rustc_contracts, "CURRENT_RUSTC_VERSION", Some(133866)), + /// Allows access to internal machinery used to implement contracts. + (unstable, rustc_contracts_internals, "CURRENT_RUSTC_VERSION", Some(133866)), /// Allows use of x86 SHA512, SM3 and SM4 target-features and intrinsics (unstable, sha512_sm_x86, "1.82.0", Some(126624)), /// Allows the use of SIMD types in functions declared in `extern` blocks. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index dd96b30fefc36..2b4d8d9c2813a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2544,6 +2544,8 @@ pub enum LocalSource { /// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression. /// The span is that of the `=` sign. AssignDesugar(Span), + /// A contract `#[ensures(..)]` attribute injects a let binding for the check that runs at point of return. + Contract, } /// Hints at the original code for a `match _ { .. }`. diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index fae3b778d7bdd..b171861065050 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -413,6 +413,10 @@ language_item_table! { String, sym::String, string, Target::Struct, GenericRequirement::None; CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None; + + // 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; } pub enum GenericRequirement { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 8aa95d1c1d516..c80e6d58252ff 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -132,6 +132,9 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - | sym::aggregate_raw_ptr | sym::ptr_metadata | sym::ub_checks + | sym::contract_checks + | sym::contract_check_requires + | sym::contract_check_ensures | sym::fadd_algebraic | sym::fsub_algebraic | sym::fmul_algebraic @@ -218,6 +221,18 @@ pub fn check_intrinsic_type( } }; (n_tps, 0, 0, inputs, output, hir::Safety::Unsafe) + } else if intrinsic_name == sym::contract_check_ensures { + // contract_check_ensures::<'a, Ret, C>(&'a Ret, C) -> bool + // where C: impl Fn(&'a Ret) -> bool, + // + // so: two type params, one lifetime param, 0 const params, two inputs, returns boolean + + let p = generics.param_at(0, tcx); + let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data()); + let ref_ret = Ty::new_imm_ref(tcx, r, param(1)); + // 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, 1, 0, vec![ref_ret, param(2)], tcx.types.bool, hir::Safety::Safe) } else { let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let (n_tps, n_cts, inputs, output) = match intrinsic_name { @@ -609,6 +624,11 @@ pub fn check_intrinsic_type( sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))), + // contract_checks() -> bool + sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), + // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool + sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.bool), + sym::simd_eq | sym::simd_ne | sym::simd_lt diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 3bb518e7f9711..fb03ee756f66a 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // diverging expression (e.g. it arose from desugaring of `try { return }`), // we skip issuing a warning because it is autogenerated code. ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {} + // Likewise, do not lint unreachable code injected via contracts desugaring. + ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::Contract) => {} ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"), ExprKind::MethodCall(segment, ..) => { self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call") diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index fb3cf5afad038..0fb661c271a92 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -341,6 +341,7 @@ impl EarlyLintPass for UnsafeCode { ast::FnSig { header: ast::FnHeader { safety: ast::Safety::Unsafe(_), .. }, .. }, _, _, + _, body, ) = fk { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 47522f00bb1b3..f7d5950c931bf 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1081,6 +1081,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { NullOp::AlignOf => write!(fmt, "AlignOf({t})"), NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), NullOp::UbChecks => write!(fmt, "UbChecks()"), + NullOp::ContractChecks => write!(fmt, "ContractChecks()"), } } ThreadLocalRef(did) => ty::tls::with(|tcx| { diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index bbbaffc5a35cc..1af09d105e602 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1513,6 +1513,9 @@ pub enum NullOp<'tcx> { /// Returns whether we should perform some UB-checking at runtime. /// See the `ub_checks` intrinsic docs for details. UbChecks, + /// Returns whether we should perform contract-checking at runtime. + /// See the `contract_checks` intrinsic docs for details. + ContractChecks, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index cbb26b83c79cf..22dc9f855cb18 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -224,7 +224,8 @@ impl<'tcx> Rvalue<'tcx> { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { tcx.types.usize } - Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool, + Rvalue::NullaryOp(NullOp::ContractChecks, _) + | Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool, Rvalue::Aggregate(ref ak, ref ops) => match **ak { AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64), AggregateKind::Tuple => { diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 80875f32e4f66..c1bc613bf9f4d 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -414,7 +414,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { | Rvalue::RawPtr(..) | Rvalue::Discriminant(..) | Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks, + NullOp::SizeOf + | NullOp::AlignOf + | NullOp::OffsetOf(..) + | NullOp::UbChecks + | NullOp::ContractChecks, _, ) => {} } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 71bec38c405d8..c25146304a914 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -527,6 +527,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { .offset_of_subfield(self.typing_env(), layout, fields.iter()) .bytes(), NullOp::UbChecks => return None, + NullOp::ContractChecks => return None, }; let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap(); let imm = ImmTy::from_uint(val, usize_layout); diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index f1705d0c831c2..a9322b0252207 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -611,6 +611,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { .offset_of_subfield(self.typing_env, op_layout, fields.iter()) .bytes(), NullOp::UbChecks => return None, + NullOp::ContractChecks => return None, }; ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into() } diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 942c6144ea69c..901604912fa16 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -34,6 +34,17 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { }); terminator.kind = TerminatorKind::Goto { target }; } + sym::contract_checks => { + let target = target.unwrap(); + block.statements.push(Statement { + source_info: terminator.source_info, + kind: StatementKind::Assign(Box::new(( + *destination, + Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool), + ))), + }); + terminator.kind = TerminatorKind::Goto { target }; + } sym::forget => { let target = target.unwrap(); block.statements.push(Statement { diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 7451f4193042b..946498d924413 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -448,6 +448,7 @@ impl<'tcx> Validator<'_, 'tcx> { NullOp::AlignOf => {} NullOp::OffsetOf(_) => {} NullOp::UbChecks => {} + NullOp::ContractChecks => {} }, Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index a670da94fcc9e..2cc6472342470 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1339,7 +1339,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { Rvalue::Repeat(_, _) | Rvalue::ThreadLocalRef(_) | Rvalue::RawPtr(_, _) - | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _) + | Rvalue::NullaryOp( + NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks, + _, + ) | Rvalue::Discriminant(_) => {} } self.super_rvalue(rvalue, location); diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index b1b84b0b7011e..14b949dbc3d57 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -4,7 +4,7 @@ use rustc_ast::{ WhereClause, token, }; use rustc_errors::{Applicability, PResult}; -use rustc_span::{Ident, Span, kw}; +use rustc_span::{Ident, Span, kw, sym}; use thin_vec::ThinVec; use super::{ForceCollect, Parser, Trailing, UsePreAttrPos}; @@ -297,6 +297,42 @@ impl<'a> Parser<'a> { }) } + /// Parses a rustc-internal fn contract + /// (`rustc_contract_requires(WWW) rustc_contract_ensures(ZZZ)`) + pub(super) fn parse_contract( + &mut self, + ) -> PResult<'a, Option>> { + let gate = |span| { + if self.psess.contract_attribute_spans.contains(span) { + // span was generated via a builtin contracts attribute, so gate as end-user visible + self.psess.gated_spans.gate(sym::rustc_contracts, span); + } else { + // span was not generated via a builtin contracts attribute, so gate as internal machinery + self.psess.gated_spans.gate(sym::rustc_contracts_internals, span); + } + }; + + let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) { + let precond = self.parse_expr()?; + gate(precond.span); + Some(precond) + } else { + None + }; + let ensures = if self.eat_keyword_noexpect(exp!(RustcContractEnsures).kw) { + let postcond = self.parse_expr()?; + gate(postcond.span); + Some(postcond) + } else { + None + }; + if requires.is_none() && ensures.is_none() { + Ok(None) + } else { + Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures }))) + } + } + /// 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 f3e56be9f6e8b..dbdc31f06a29d 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -213,9 +213,12 @@ 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(exp!(Extern)) { if self.eat_keyword(exp!(Crate)) { // EXTERN CRATE @@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> { sig_lo: Span, vis: &Visibility, case: Case, - ) -> PResult<'a, (Ident, FnSig, Generics, Option>)> { + ) -> PResult<'a, (Ident, FnSig, Generics, Option>, Option>)> { let fn_span = self.token.span; let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn` let ident = self.parse_ident()?; // `foo` @@ -2398,6 +2401,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. @@ -2409,7 +2414,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/parser/token_type.rs b/compiler/rustc_parse/src/parser/token_type.rs index 73f3ac001c8ae..50f03e72f82dc 100644 --- a/compiler/rustc_parse/src/parser/token_type.rs +++ b/compiler/rustc_parse/src/parser/token_type.rs @@ -108,6 +108,8 @@ pub enum TokenType { KwRef, KwReturn, KwReuse, + KwRustcContractEnsures, + KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -242,6 +244,8 @@ impl TokenType { KwRef, KwReturn, KwReuse, + KwRustcContractEnsures, + KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -314,6 +318,8 @@ impl TokenType { TokenType::KwRef => Some(kw::Ref), TokenType::KwReturn => Some(kw::Return), TokenType::KwReuse => Some(kw::Reuse), + TokenType::KwRustcContractEnsures => Some(kw::RustcContractEnsures), + TokenType::KwRustcContractRequires => Some(kw::RustcContractRequires), TokenType::KwSafe => Some(kw::Safe), TokenType::KwSelfUpper => Some(kw::SelfUpper), TokenType::KwStatic => Some(kw::Static), @@ -544,6 +550,8 @@ macro_rules! exp { (Ref) => { exp!(@kw, Ref, KwRef) }; (Return) => { exp!(@kw, Return, KwReturn) }; (Reuse) => { exp!(@kw, Reuse, KwReuse) }; + (RustcContractEnsures) => { exp!(@kw, RustcContractEnsures, KwRustcContractEnsures) }; + (RustcContractRequires) => { exp!(@kw, RustcContractRequires, KwRustcContractRequires) }; (Safe) => { exp!(@kw, Safe, KwSafe) }; (SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) }; (Static) => { exp!(@kw, Static, KwStatic) }; diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 87024c487df71..ff7c8d992d161 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -170,11 +170,20 @@ 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) - if let Some(coroutine_kind) = header.coroutine_kind => - { + 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 7324d3fe7862d..1505781eba640 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -990,8 +990,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( @@ -1023,7 +1023,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; @@ -1050,6 +1050,10 @@ 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 2936d722aa7f0..2b0e2d1eca1ef 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3250,7 +3250,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 @@ -3291,7 +3291,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 @@ -3351,7 +3351,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_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index d586f913335e0..52920e0372e2b 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -119,6 +119,7 @@ pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) { (sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"), (sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"), (sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"), + (sym::contract_checks, None) => disallow(cfg, "-Z contract-checks"), (sym::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"), ( sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers, @@ -300,6 +301,11 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { if sess.is_nightly_build() && sess.opts.unstable_opts.emscripten_wasm_eh { ins_none!(sym::emscripten_wasm_eh); } + + if sess.contract_checks() { + ins_none!(sym::contract_checks); + } + ret } @@ -464,6 +470,7 @@ impl CheckCfg { ins!(sym::target_thread_local, no_values); ins!(sym::ub_checks, no_values); + ins!(sym::contract_checks, no_values); ins!(sym::unix, no_values); ins!(sym::windows, no_values); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index c1fba4c513da5..7d9ffbe5d64a0 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1737,6 +1737,8 @@ options! { "the backend to use"), combine_cgu: bool = (false, parse_bool, [TRACKED], "combine CGUs into a single one"), + contract_checks: Option = (None, parse_opt_bool, [TRACKED], + "emit runtime checks for contract pre- and post-conditions (default: no)"), coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED], "control details of coverage instrumentation"), crate_attr: Vec = (Vec::new(), parse_string_push, [TRACKED], diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 90361efed844b..ebc565d291abd 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -207,6 +207,10 @@ pub struct ParseSess { pub config: Cfg, pub check_config: CheckCfg, pub edition: Edition, + /// Places where contract attributes were expanded into unstable AST forms. + /// This is used to allowlist those spans (so that we only check them against the feature + /// gate for the externally visible interface, and not internal implmentation machinery). + pub contract_attribute_spans: AppendOnlyVec, /// Places where raw identifiers were used. This is used to avoid complaining about idents /// clashing with keywords in new editions. pub raw_identifier_spans: AppendOnlyVec, @@ -255,6 +259,7 @@ impl ParseSess { config: Cfg::default(), check_config: CheckCfg::default(), edition: ExpnId::root().expn_data().edition, + contract_attribute_spans: Default::default(), raw_identifier_spans: Default::default(), bad_unicode_identifiers: Lock::new(Default::default()), source_map, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 60f1154dc6d02..eb7972aff1bb8 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -709,6 +709,10 @@ impl Session { self.opts.unstable_opts.ub_checks.unwrap_or(self.opts.debug_assertions) } + pub fn contract_checks(&self) -> bool { + self.opts.unstable_opts.contract_checks.unwrap_or(false) + } + pub fn relocation_model(&self) -> RelocModel { self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model) } diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index de933952c6a7b..d286bad8cc378 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -277,6 +277,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> { indices.iter().map(|idx| idx.stable(tables)).collect(), ), UbChecks => stable_mir::mir::NullOp::UbChecks, + ContractChecks => stable_mir::mir::NullOp::ContractChecks, } } } diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 3cdae437b7d46..35859ef8cc8d7 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1166,6 +1166,8 @@ pub enum DesugaringKind { /// Marks a `&raw const *_1` needed as part of getting the length of a mutable /// slice for the bounds check, so that MIRI's retag handling can recognize it. IndexBoundsCheckReborrow, + /// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond) + Contract, } impl DesugaringKind { @@ -1183,6 +1185,7 @@ impl DesugaringKind { DesugaringKind::WhileLoop => "`while` loop", DesugaringKind::BoundModifier => "trait bound modifier", DesugaringKind::IndexBoundsCheckReborrow => "slice indexing", + DesugaringKind::Contract => "contract check", } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ef1e2c20978c4..8b486e22a9d89 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -118,6 +118,8 @@ symbols! { MacroRules: "macro_rules", Raw: "raw", Reuse: "reuse", + RustcContractEnsures: "rustc_contract_ensures", + RustcContractRequires: "rustc_contract_requires", Safe: "safe", Union: "union", Yeet: "yeet", @@ -569,6 +571,7 @@ symbols! { cfg_attr_multi, cfg_autodiff_fallback, cfg_boolean_literals, + cfg_contract_checks, cfg_doctest, cfg_emscripten_wasm_eh, cfg_eval, @@ -678,6 +681,12 @@ symbols! { const_ty_placeholder: "", constant, constructor, + contract_build_check_ensures, + contract_check_ensures, + contract_check_requires, + contract_checks, + contracts_ensures, + contracts_requires, convert_identity, copy, copy_closures, @@ -1708,6 +1717,8 @@ symbols! { rustc_const_stable, rustc_const_stable_indirect, rustc_const_unstable, + rustc_contracts, + rustc_contracts_internals, rustc_conversion_suggestion, rustc_deallocator, rustc_def_path, diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index dfd090b39563f..a2ec123372a35 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -606,7 +606,8 @@ impl Rvalue { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { Ok(Ty::usize_ty()) } - Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()), + Rvalue::NullaryOp(NullOp::ContractChecks, _) + | Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()), Rvalue::Aggregate(ak, ops) => match *ak { AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64), AggregateKind::Tuple => Ok(Ty::new_tuple( @@ -987,6 +988,8 @@ pub enum NullOp { OffsetOf(Vec<(VariantIdx, FieldIdx)>), /// cfg!(ub_checks), but at codegen time UbChecks, + /// cfg!(contract_checks), but at codegen time + ContractChecks, } impl Operand { diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs new file mode 100644 index 0000000000000..b155dbc213ee8 --- /dev/null +++ b/library/core/src/contracts.rs @@ -0,0 +1,38 @@ +//! Unstable module containing the unstable contracts lang items and attribute macros. + +#[cfg(not(bootstrap))] +pub use crate::macros::builtin::contracts_ensures as ensures; +#[cfg(not(bootstrap))] +pub use crate::macros::builtin::contracts_requires as requires; + +/// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }` +/// into: `fn foo(x: X) { check_requires(|| PRED) ... }` +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[lang = "contract_check_requires"] +#[track_caller] +pub fn check_requires bool>(c: C) { + if core::intrinsics::contract_checks() { + assert!(core::intrinsics::contract_check_requires(c), "failed requires check"); + } +} + +/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }` +/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }` +/// (including the implicit return of the tail expression, if any). +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[lang = "contract_build_check_ensures"] +#[track_caller] +pub fn build_check_ensures(c: C) -> impl (FnOnce(Ret) -> Ret) + Copy +where + C: for<'a> FnOnce(&'a Ret) -> bool + Copy + 'static, +{ + #[track_caller] + move |ret| { + if core::intrinsics::contract_checks() { + assert!(core::intrinsics::contract_check_ensures(&ret, c), "failed ensures check"); + } + ret + } +} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 7b31bbec7547c..b764c21f119eb 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4035,6 +4035,38 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) // Runtime NOP } +/// Returns whether we should perform contract-checking at runtime. +/// +/// This is meant to be similar to the ub_checks intrinsic, in terms +/// of not prematurely commiting at compile-time to whether contract +/// checking is turned on, so that we can specify contracts in libstd +/// and let an end user opt into turning them on. +#[cfg(not(bootstrap))] +#[rustc_const_unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[inline(always)] +#[rustc_intrinsic] +pub const fn contract_checks() -> bool { + // FIXME: should this be `false` or `cfg!(contract_checks)`? + + // cfg!(contract_checks) + false +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_requires bool>(c: C) -> bool { + c() +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures<'a, Ret, C: FnOnce(&'a Ret) -> bool>(ret: &'a Ret, c: C) -> bool { + c(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 e845bb34426c4..1f3adcef62038 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -110,6 +110,7 @@ #![feature(array_ptr_get)] #![feature(asm_experimental_arch)] #![feature(bigint_helper_methods)] +#![feature(closure_track_caller)] #![feature(const_carrying_mul_add)] #![feature(const_eval_select)] #![feature(core_intrinsics)] @@ -243,6 +244,10 @@ pub mod autodiff { pub use crate::macros::builtin::autodiff; } +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "133866")] +pub mod contracts; + #[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 402b436d28e68..11da5b53d36f0 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1714,6 +1714,32 @@ pub(crate) mod builtin { /* compiler built-in */ } + /// Attribute macro applied to a function to give it a post-condition. + /// + /// The attribute carries an argument token-tree which is + /// eventually parsed as a unary closure expression that is + /// invoked on a reference to the return value. + #[cfg(not(bootstrap))] + #[unstable(feature = "rustc_contracts", issue = "133866")] + #[allow_internal_unstable(rustc_contracts_internals)] + #[rustc_builtin_macro] + pub macro contracts_ensures($item:item) { + /* compiler built-in */ + } + + /// Attribute macro applied to a function to give it a precondition. + /// + /// The attribute carries an argument token-tree which is + /// eventually parsed as an boolean expression with access to the + /// function's formal parameters + #[cfg(not(bootstrap))] + #[unstable(feature = "rustc_contracts", issue = "133866")] + #[allow_internal_unstable(rustc_contracts_internals)] + #[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 882ab2dda7aaa..cb86582da4f68 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/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 2eb09bac8d881..d2e94b118940d 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -362,18 +362,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) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, (Mod(lu, lmk), Mod(ru, rmk)) => { @@ -497,18 +500,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) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -559,18 +565,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) + && eq_opt_fn_contract(lc, rc) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) }, ( @@ -653,6 +662,17 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool { && eq_ext(&l.ext, &r.ext) } +pub fn eq_opt_fn_contract(l: &Option>, r: &Option>) -> bool { + match (l, r) { + (Some(l), Some(r)) => { + eq_expr_opt(l.requires.as_ref(), r.requires.as_ref()) + && eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref()) + } + (None, None) => true, + (Some(_), None) | (None, Some(_)) => false, + } +} + pub fn eq_generics(l: &Generics, r: &Generics) -> bool { over(&l.params, &r.params, eq_generic_param) && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| { diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 428b40c5771c8..af2e1d016c1ed 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -177,7 +177,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _) + Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 5e8f616a37ea0..a17af7f516b29 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1143,6 +1143,10 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { interp_ok(ecx.tcx.sess.ub_checks()) } + fn contract_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> { + interp_ok(ecx.tcx.sess.contract_checks()) + } + fn thread_local_static_pointer( ecx: &mut MiriInterpCx<'tcx>, def_id: DefId, diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index e7d0fba048b43..8a58ca27345c8 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, @@ -3452,6 +3452,7 @@ impl Rewrite for ast::ForeignItem { defaultness, ref sig, ref generics, + ref contract, ref body, } = **fn_kind; if body.is_some() { @@ -3461,7 +3462,15 @@ impl Rewrite for ast::ForeignItem { let inner_attrs = inner_attributes(&self.attrs); let fn_ctxt = visit::FnCtxt::Foreign; visitor.visit_fn( - visit::FnKind::Fn(fn_ctxt, &self.ident, sig, &self.vis, generics, body), + visit::FnKind::Fn( + fn_ctxt, + &self.ident, + sig, + &self.vis, + generics, + contract, + body, + ), &sig.decl, self.span, defaultness, diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index 805e13b780367..f334ebe6a10ce 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 body.is_some() { @@ -547,7 +548,15 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { _ => visit::FnCtxt::Foreign, }; self.visit_fn( - visit::FnKind::Fn(fn_ctxt, &item.ident, sig, &item.vis, generics, body), + visit::FnKind::Fn( + fn_ctxt, + &item.ident, + sig, + &item.vis, + generics, + contract, + body, + ), &sig.decl, item.span, defaultness, @@ -639,13 +648,16 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { defaultness, ref sig, ref generics, + ref contract, ref body, } = **fn_kind; if body.is_some() { 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, body), + visit::FnKind::Fn( + fn_ctxt, &ai.ident, sig, &ai.vis, generics, contract, body, + ), &sig.decl, ai.span, defaultness, diff --git a/tests/ui/check-cfg/allow-same-level.stderr b/tests/ui/check-cfg/allow-same-level.stderr index 5d74b2116543e..81aeed21c859b 100644 --- a/tests/ui/check-cfg/allow-same-level.stderr +++ b/tests/ui/check-cfg/allow-same-level.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `FALSE` LL | #[cfg(FALSE)] | ^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(FALSE)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/cargo-build-script.stderr b/tests/ui/check-cfg/cargo-build-script.stderr index fb48751bc1dbc..f00b5db40e4fd 100644 --- a/tests/ui/check-cfg/cargo-build-script.stderr +++ b/tests/ui/check-cfg/cargo-build-script.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `has_foo` LL | #[cfg(has_foo)] | ^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `has_bar`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `has_bar`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: consider using a Cargo feature instead = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: [lints.rust] diff --git a/tests/ui/check-cfg/cargo-feature.none.stderr b/tests/ui/check-cfg/cargo-feature.none.stderr index aa2a1ab8fb2f4..b7577fb2a33c1 100644 --- a/tests/ui/check-cfg/cargo-feature.none.stderr +++ b/tests/ui/check-cfg/cargo-feature.none.stderr @@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable` LL | #[cfg(tokio_unstable)] | ^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: consider using a Cargo feature instead = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: [lints.rust] diff --git a/tests/ui/check-cfg/cargo-feature.some.stderr b/tests/ui/check-cfg/cargo-feature.some.stderr index c3ba123985b17..4397c7ca0c399 100644 --- a/tests/ui/check-cfg/cargo-feature.some.stderr +++ b/tests/ui/check-cfg/cargo-feature.some.stderr @@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable` LL | #[cfg(tokio_unstable)] | ^^^^^^^^^^^^^^ | - = help: expected names are: `CONFIG_NVME`, `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `CONFIG_NVME`, `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: consider using a Cargo feature instead = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: [lints.rust] diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr index b7dc27f9ba9da..0b908e082001a 100644 --- a/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr +++ b/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `value` LL | #[cfg(value)] | ^^^^^ | - = help: expected names are: `bar`, `bee`, `clippy`, `cow`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `bar`, `bee`, `clippy`, `contract_checks`, `cow`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(value)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr index d2af81d7787c6..caac93133fc9d 100644 --- a/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr +++ b/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_value` LL | #[cfg(my_value)] | ^^^^^^^^ | - = help: expected names are: `bar`, `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `bar`, `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(my_value)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr index 85bf66eb10cb8..e2edf7f1fc74b 100644 --- a/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr +++ b/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `linux` LL | #[cfg(linux)] | ^^^^^ help: found config with similar value: `target_os = "linux"` | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(linux)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr index ef0a413bd0d7e..8c257deb1cbef 100644 --- a/tests/ui/check-cfg/compact-names.stderr +++ b/tests/ui/check-cfg/compact-names.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `target_architecture` LL | #[cfg(target(os = "linux", architecture = "arm"))] | ^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(target_architecture, values("arm"))` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr index 23b6edacce77c..aee9926da65b3 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key` LL | #[cfg(unknown_key = "value")] | ^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr index 804d7fb9163d7..0d6fe69d5edb5 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key` LL | #[cfg(unknown_key = "value")] | ^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/exhaustive-names-values.full.stderr b/tests/ui/check-cfg/exhaustive-names-values.full.stderr index 804d7fb9163d7..0d6fe69d5edb5 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.full.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.full.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key` LL | #[cfg(unknown_key = "value")] | ^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/exhaustive-names.stderr b/tests/ui/check-cfg/exhaustive-names.stderr index d134cfcfd29bd..341a125ae9e4d 100644 --- a/tests/ui/check-cfg/exhaustive-names.stderr +++ b/tests/ui/check-cfg/exhaustive-names.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key` LL | #[cfg(unknown_key = "value")] | ^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr index 76c7befd6d336..dbbcb22a7cb2d 100644 --- a/tests/ui/check-cfg/mix.stderr +++ b/tests/ui/check-cfg/mix.stderr @@ -44,7 +44,7 @@ warning: unexpected `cfg` condition name: `uu` LL | #[cfg_attr(uu, unix)] | ^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(uu)` = note: see for more information about checking conditional configuration diff --git a/tests/ui/check-cfg/raw-keywords.edition2015.stderr b/tests/ui/check-cfg/raw-keywords.edition2015.stderr index 3ad8ebac959b7..75dc79471d7e0 100644 --- a/tests/ui/check-cfg/raw-keywords.edition2015.stderr +++ b/tests/ui/check-cfg/raw-keywords.edition2015.stderr @@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false` LL | #[cfg(r#false)] | ^^^^^^^ | - = help: expected names are: `async`, `clippy`, `debug_assertions`, `doc`, `doctest`, `edition2015`, `edition2021`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `r#true`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `async`, `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `edition2015`, `edition2021`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `r#true`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(r#false)` = note: see for more information about checking conditional configuration diff --git a/tests/ui/check-cfg/raw-keywords.edition2021.stderr b/tests/ui/check-cfg/raw-keywords.edition2021.stderr index ff43a3326979c..875437b6ac012 100644 --- a/tests/ui/check-cfg/raw-keywords.edition2021.stderr +++ b/tests/ui/check-cfg/raw-keywords.edition2021.stderr @@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false` LL | #[cfg(r#false)] | ^^^^^^^ | - = help: expected names are: `r#async`, `clippy`, `debug_assertions`, `doc`, `doctest`, `edition2015`, `edition2021`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `r#true`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `r#async`, `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `edition2015`, `edition2021`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `r#true`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(r#false)` = note: see for more information about checking conditional configuration diff --git a/tests/ui/check-cfg/report-in-external-macros.cargo.stderr b/tests/ui/check-cfg/report-in-external-macros.cargo.stderr index b82a09917f4ed..fd9ea84fd41ac 100644 --- a/tests/ui/check-cfg/report-in-external-macros.cargo.stderr +++ b/tests/ui/check-cfg/report-in-external-macros.cargo.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg` LL | cfg_macro::my_lib_macro!(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate = help: try referring to `cfg_macro::my_lib_macro` crate for guidance on how handle this unexpected cfg = help: the macro `cfg_macro::my_lib_macro` may come from an old version of the `cfg_macro` crate, try updating your dependency with `cargo update -p cfg_macro` diff --git a/tests/ui/check-cfg/report-in-external-macros.rustc.stderr b/tests/ui/check-cfg/report-in-external-macros.rustc.stderr index 85d84a1e1ee31..0ad96f2363884 100644 --- a/tests/ui/check-cfg/report-in-external-macros.rustc.stderr +++ b/tests/ui/check-cfg/report-in-external-macros.rustc.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg` LL | cfg_macro::my_lib_macro!(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `feature`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate = help: try referring to `cfg_macro::my_lib_macro` crate for guidance on how handle this unexpected cfg = help: to expect this configuration use `--check-cfg=cfg(my_lib_cfg)` diff --git a/tests/ui/check-cfg/stmt-no-ice.stderr b/tests/ui/check-cfg/stmt-no-ice.stderr index d8c6b0f3cec8f..70d45e2af2b6b 100644 --- a/tests/ui/check-cfg/stmt-no-ice.stderr +++ b/tests/ui/check-cfg/stmt-no-ice.stderr @@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `crossbeam_loom` LL | #[cfg(crossbeam_loom)] | ^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(crossbeam_loom)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/unexpected-cfg-name.stderr b/tests/ui/check-cfg/unexpected-cfg-name.stderr index 4ca7209cc07b3..798bb444f012a 100644 --- a/tests/ui/check-cfg/unexpected-cfg-name.stderr +++ b/tests/ui/check-cfg/unexpected-cfg-name.stderr @@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `test` LL | #[cfg(test)] | ^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(test)` = note: see for more information about checking conditional configuration diff --git a/tests/ui/check-cfg/well-known-names.stderr b/tests/ui/check-cfg/well-known-names.stderr index 61d518627baea..d9ab90775adf1 100644 --- a/tests/ui/check-cfg/well-known-names.stderr +++ b/tests/ui/check-cfg/well-known-names.stderr @@ -18,7 +18,7 @@ warning: unexpected `cfg` condition name: `features` LL | #[cfg(features = "foo")] | ^^^^^^^^^^^^^^^^ | - = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` + = help: expected names are: `clippy`, `contract_checks`, `debug_assertions`, `doc`, `doctest`, `fmt_debug`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `ub_checks`, `unix`, and `windows` = help: to expect this configuration use `--check-cfg=cfg(features, values("foo"))` = note: see for more information about checking conditional configuration diff --git a/tests/ui/contracts/contract-attributes-nest.rs b/tests/ui/contracts/contract-attributes-nest.rs new file mode 100644 index 0000000000000..1cda21f10d7ec --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.rs @@ -0,0 +1,44 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures(|ret| *ret > 100)] +fn nest(x: Baz) -> i32 +{ + loop { + return x.baz + 50; + } +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(nest(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + nest(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + nest(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contract-attributes-tail.rs b/tests/ui/contracts/contract-attributes-tail.rs new file mode 100644 index 0000000000000..26855bfa82ac4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.rs @@ -0,0 +1,42 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures(|ret| *ret > 100)] +fn tail(x: Baz) -> i32 +{ + x.baz + 50 +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(tail(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + tail(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + tail(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contract-captures-via-closure-copy.rs b/tests/ui/contracts/contract-captures-via-closure-copy.rs new file mode 100644 index 0000000000000..742895ab0ad80 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-copy.rs @@ -0,0 +1,25 @@ +//@ run-fail +//@ compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +struct Baz { + baz: i32 +} + +#[track_caller] +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures({let old = x.baz; move |ret:&Baz| ret.baz == old*2 })] +// Relevant thing is this: ^^^^^^^^^^^^^^^ +// because we are capturing state that is Copy +fn doubler(x: Baz) -> Baz { + Baz { baz: x.baz + 10 } +} + +fn main() { + assert_eq!(doubler(Baz { baz: 10 }).baz, 20); + assert_eq!(doubler(Baz { baz: 100 }).baz, 200); + // This is a *run-fail* test because it is still exercising the + // contract machinery, specifically because this second invocation + // of `doubler` shows how the code does not meet its contract. +} diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.rs b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs new file mode 100644 index 0000000000000..8d7f2fd200e2c --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs @@ -0,0 +1,22 @@ +//@ compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +struct Baz { + baz: i32 +} + +#[track_caller] +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] +// Relevant thing is this: ^^^^^^^^^^^ +// because we are capturing state that is non-Copy. +//~^^^ ERROR trait bound `Baz: std::marker::Copy` is not satisfied +fn doubler(x: Baz) -> Baz { + Baz { baz: x.baz + 10 } +} + +fn main() { + assert_eq!(doubler(Baz { baz: 10 }).baz, 20); + assert_eq!(doubler(Baz { baz: 100 }).baz, 200); +} diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr new file mode 100644 index 0000000000000..b53809827f987 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}` + --> $DIR/contract-captures-via-closure-noncopy.rs:11: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:11:42: 11:57}` + | | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:11:42}` + | unsatisfied trait bound + | + = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}`, the trait `std::marker::Copy` is not implemented for `Baz` +note: required because it's used within this closure + --> $DIR/contract-captures-via-closure-noncopy.rs:11:42 + | +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)]` + | +LL + #[derive(Copy)] +LL | struct Baz { + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..9872fdb1a1852 --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +#[core::contracts::ensures(|ret| *ret > 0)] +fn outer() -> i32 { + let inner_closure = || -> i32 { 0 }; + inner_closure(); + 10 +} + +fn main() { + outer(); +} diff --git a/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..75124259b0d72 --- /dev/null +++ b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs @@ -0,0 +1,16 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +struct Outer { outer: std::cell::Cell } + +#[core::contracts::requires(x.outer.get() > 0)] +fn outer(x: Outer) { + let inner_closure = || { }; + x.outer.set(0); + inner_closure(); +} + +fn main() { + outer(Outer { outer: 1.into() }); +} diff --git a/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs new file mode 100644 index 0000000000000..d95ccd4f7b9b6 --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs @@ -0,0 +1,44 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts_internals)] + +fn nest(x: Baz) -> i32 + rustc_contract_requires(|| x.baz > 0) + rustc_contract_ensures(|ret| *ret > 100) +{ + loop { + return x.baz + 50; + } +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(nest(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + nest(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + nest(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs new file mode 100644 index 0000000000000..636a595e06adb --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs @@ -0,0 +1,42 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts_internals)] + +fn tail(x: Baz) -> i32 + rustc_contract_requires(|| x.baz > 0) + rustc_contract_ensures(|ret| *ret > 100) +{ + x.baz + 50 +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(tail(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + tail(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + tail(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs new file mode 100644 index 0000000000000..2e1be01e7cabc --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -0,0 +1,23 @@ +//@ run-pass +//@ revisions: yes no none +//@ [yes] compile-flags: -Zcontract-checks=yes +//@ [no] compile-flags: -Zcontract-checks=no +#![feature(cfg_contract_checks, rustc_contracts_internals, core_intrinsics)] + +fn main() { + #[cfg(none)] // default: disabled + assert_eq!(core::intrinsics::contract_checks(), false); + + #[cfg(yes)] // explicitly enabled + assert_eq!(core::intrinsics::contract_checks(), true); + + #[cfg(no)] // explicitly disabled + assert_eq!(core::intrinsics::contract_checks(), false); + + assert_eq!(core::intrinsics::contract_check_requires(|| true), true); + assert_eq!(core::intrinsics::contract_check_requires(|| false), false); + + let doubles_to_two = { let old = 2; move |ret| ret + ret == old }; + assert_eq!(core::intrinsics::contract_check_ensures(&1, doubles_to_two), true); + assert_eq!(core::intrinsics::contract_check_ensures(&2, doubles_to_two), false); +} diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs new file mode 100644 index 0000000000000..5af467485b10e --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -0,0 +1,48 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] // to access core::contracts +#![feature(rustc_contracts_internals)] // to access check_requires lang item + +fn foo(x: Baz) -> i32 { + core::contracts::check_requires(|| x.baz > 0); + + let injected_checker = { + core::contracts::build_check_ensures(|ret| *ret > 100) + }; + + let ret = x.baz + 50; + injected_checker(ret) +} + +struct Baz { baz: i32 } + + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(foo(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + foo(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + foo(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs b/tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..0b0151c6df742 --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs @@ -0,0 +1,15 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts_internals)] + +fn outer() -> i32 + rustc_contract_ensures(|ret| *ret > 0) +{ + let inner_closure = || -> i32 { 0 }; + inner_closure(); + 10 +} + +fn main() { + outer(); +} diff --git a/tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs b/tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..79c50a18f7056 --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs @@ -0,0 +1,17 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts_internals)] + +struct Outer { outer: std::cell::Cell } + +fn outer(x: Outer) + rustc_contract_requires(|| x.outer.get() > 0) +{ + let inner_closure = || { }; + x.outer.set(0); + inner_closure(); +} + +fn main() { + outer(Outer { outer: 1.into() }); +} diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs new file mode 100644 index 0000000000000..7b5f17679421f --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -0,0 +1,23 @@ +// gate-test-rustc_contracts_internals + +fn main() { + // intrinsics are guarded by rustc_contracts_internals feature gate. + core::intrinsics::contract_checks(); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::intrinsics::contract_check_requires(|| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::intrinsics::contract_check_ensures(&1, |_|true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + + // lang items are guarded by rustc_contracts_internals feature gate. + core::contracts::check_requires(|| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::contracts::build_check_ensures(|_: &()| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + + // ast extensions are guarded by rustc_contracts_internals feature gate + fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } + //~^ ERROR contract internal machinery is for internal use only + fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } + //~^ ERROR contract internal machinery is for internal use only +} diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr new file mode 100644 index 0000000000000..2acd03b9a358f --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -0,0 +1,73 @@ +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:19:51 + | +LL | fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } + | ^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:21:50 + | +LL | fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } + | ^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:5:5 + | +LL | core::intrinsics::contract_checks(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:7:5 + | +LL | core::intrinsics::contract_check_requires(|| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:9:5 + | +LL | core::intrinsics::contract_check_ensures(&1, |_|true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:13:5 + | +LL | core::contracts::check_requires(|| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:15:5 + | +LL | core::contracts::build_check_ensures(|_: &()| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs new file mode 100644 index 0000000000000..cd9bf12b5e773 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs @@ -0,0 +1,5 @@ +#![crate_type = "lib"] + +pub fn contract_checks_are_enabled() -> bool { + cfg!(contract_checks) //~ ERROR `cfg(contract_checks)` is experimental +} diff --git a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr new file mode 100644 index 0000000000000..af4e605e57098 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr @@ -0,0 +1,13 @@ +error[E0658]: `cfg(contract_checks)` is experimental and subject to change + --> $DIR/feature-gate-cfg-contract-checks.rs:4:10 + | +LL | cfg!(contract_checks) + | ^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(cfg_contract_checks)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-rustc-contracts.rs b/tests/ui/feature-gates/feature-gate-rustc-contracts.rs new file mode 100644 index 0000000000000..d4249c252cda3 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-rustc-contracts.rs @@ -0,0 +1,11 @@ +#![crate_type = "lib"] + +#[core::contracts::requires(x > 0)] +pub fn requires_needs_it(x: i32) { } +//~^^ ERROR use of unstable library feature `rustc_contracts` +//~^^^ ERROR contracts are experimental + +#[core::contracts::ensures(|ret| *ret > 0)] +pub fn ensures_needs_it() -> i32 { 10 } +//~^^ ERROR use of unstable library feature `rustc_contracts` +//~^^^ ERROR contracts are experimental diff --git a/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr b/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr new file mode 100644 index 0000000000000..eb7777a4a5179 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr @@ -0,0 +1,43 @@ +error[E0658]: use of unstable library feature `rustc_contracts` + --> $DIR/feature-gate-rustc-contracts.rs:3:3 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts` + --> $DIR/feature-gate-rustc-contracts.rs:8:3 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contracts are experimental + --> $DIR/feature-gate-rustc-contracts.rs:3:1 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contracts are experimental + --> $DIR/feature-gate-rustc-contracts.rs:8:1 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0658`.