diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index b6f331d316cc9..08621c1c56a14 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3348,11 +3348,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>, } @@ -3650,7 +3657,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 7caf7c4c35687..70616fe87691b 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); } @@ -958,13 +962,16 @@ fn walk_fn(vis: &mut T, kind: FnKind<'_>) { _ctxt, _ident, _vis, - Fn { defaultness, generics, body, sig: FnSig { header, decl, span } }, + Fn { defaultness, generics, contract, body, sig: FnSig { header, decl, span } }, ) => { // Identifier and visibility are visited as a part of the item. visit_defaultness(vis, defaultness); vis.visit_fn_header(header); vis.visit_generics(generics); vis.visit_fn_decl(decl); + if let Some(contract) = contract { + vis.visit_contract(contract); + } if let Some(body) = body { vis.visit_block(body); } @@ -979,6 +986,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)); diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 232fd546de9a3..714b074f930c2 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized { fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result { walk_closure_binder(self, b) } + fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result { + walk_contract(self, c) + } fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result { walk_where_predicate(self, p) } @@ -800,6 +803,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, @@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu _ctxt, _ident, _vis, - Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, body }, + Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, 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) => { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 1267281f73ebe..8125da361b814 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -314,8 +314,8 @@ 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)); - hir::ExprKind::Ret(e) + let expr = e.as_ref().map(|x| self.lower_expr(x)); + self.checked_return(expr) } ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), ExprKind::Become(sub_expr) => { @@ -382,6 +382,32 @@ impl<'hir> LoweringContext<'_, 'hir> { }) } + /// Create an `ExprKind::Ret` that is preceded by a call to check contract ensures clause. + fn checked_return(&mut self, opt_expr: Option<&'hir hir::Expr<'hir>>) -> hir::ExprKind<'hir> { + let checked_ret = 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 expr = opt_expr.unwrap_or_else(|| self.expr_unit(span)); + Some(self.inject_ensures_check(expr, span, fresh_ident.0, fresh_ident.2)) + } else { + opt_expr + }; + hir::ExprKind::Ret(checked_ret) + } + + /// Wraps an expression with a call to the ensures check before it gets returned. + pub(crate) fn inject_ensures_check( + &mut self, + expr: &'hir hir::Expr<'hir>, + span: Span, + check_ident: Ident, + check_hir_id: HirId, + ) -> &'hir hir::Expr<'hir> { + let checker_fn = self.expr_ident(span, check_ident, check_hir_id); + let span = self.mark_span_with_reason(DesugaringKind::Contract, span, None); + self.expr_call(span, checker_fn, std::slice::from_ref(expr)) + } + pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); @@ -1970,7 +1996,8 @@ impl<'hir> LoweringContext<'_, 'hir> { ), )) } else { - self.arena.alloc(self.expr(try_span, hir::ExprKind::Ret(Some(from_residual_expr)))) + let ret_expr = self.checked_return(Some(from_residual_expr)); + self.arena.alloc(self.expr(try_span, ret_expr)) }; self.lower_attrs(ret_expr.hir_id, &attrs); @@ -2019,7 +2046,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let target_id = Ok(catch_id); hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr)) } else { - hir::ExprKind::Ret(Some(from_yeet_expr)) + self.checked_return(Some(from_yeet_expr)) } } diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 74870d741504c..c96110fee619b 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -207,9 +207,40 @@ 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 = ensures.map(|ens| { + // 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, + ); + + crate::FnContractLoweringEnsures { + expr: ens, + fresh_ident: (checker_ident, checker_pat, checker_hir_id), + } + }); + + // 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 @@ -1054,10 +1085,64 @@ 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 opt_contract = this.contract.take(); + + // { body } + // ==> + // { contract_requires(PRECOND); { body } } + let Some(contract) = opt_contract else { return (params, result) }; + let result_ref = this.arena.alloc(result); + 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, 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, + ), + this.inject_ensures_check(result_ref, ens.span, fresh_ident.0, fresh_ident.2), + ) + } else { + let u = lit_unit(this); + (this.stmt_expr(contract.span, u), &*result_ref) + }; + + let block = this.block_all( + contract.span, + arena_vec![this; precond, postcond_checker], + Some(result), + ); + (params, this.expr_block(block)) }) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 893da93085533..2715b3d621522 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -86,6 +86,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, @@ -100,6 +113,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 @@ -148,6 +163,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(), @@ -834,12 +850,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 ea1f4a6559ac8..0049c5b4823cb 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(func @ box Fn { defaultness, generics: _, sig, body }) => { + ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, contract: _, body }) => { self.check_defaultness(item.span, *defaultness); let is_intrinsic = diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 80b99f9448567..62e451fa8764f 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -548,6 +548,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!(contracts, "contracts are incomplete"); + gate_all!(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 4cfcaa95233da..c10b5ad34e102 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -650,13 +650,17 @@ impl<'a> State<'a> { attrs: &[ast::Attribute], func: &ast::Fn, ) { - let ast::Fn { defaultness, generics, sig, body } = func; + let ast::Fn { defaultness, generics, sig, contract, body } = func; if body.is_some() { self.head(""); } 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); @@ -665,6 +669,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 92492bfdb8d31..b727616ebceb9 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1650,6 +1650,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..85a30f7bdc9b4 --- /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) + } +} + +/// Expand the function signature to include the contract clause. +/// +/// The contracts clause will be injected before the function body and the optional where clause. +/// For that, we search for the body / where token, and invoke the `inject` callback to generate the +/// contract clause in the right place. +/// +// 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. +fn expand_contract_clause( + ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotated: TokenStream, + inject: impl FnOnce(&mut TokenStream) -> Result<(), ErrorGuaranteed>, +) -> Result { + let mut new_tts = TokenStream::default(); + let mut cursor = annotated.iter(); + + let is_kw = |tt: &TokenTree, sym: Symbol| { + if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false } + }; + + // Find the `fn` keyword to check if this is a function. + if cursor + .find(|tt| { + new_tts.push_tree((*tt).clone()); + is_kw(tt, kw::Fn) + }) + .is_none() + { + return Err(ecx + .sess + .dcx() + .span_err(attr_span, "contract annotations can only be used on functions")); + } + + // Found the `fn` keyword, now find either the `where` token or the function body. + let next_tt = loop { + let Some(tt) = cursor.next() else { + return Err(ecx.sess.dcx().span_err( + attr_span, + "contract annotations is only supported in functions with bodies", + )); + }; + // If `tt` is the last element. Check if it is the function body. + if cursor.peek().is_none() { + if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt { + break tt; + } else { + return Err(ecx.sess.dcx().span_err( + attr_span, + "contract annotations is only supported in functions with bodies", + )); + } + } + + if is_kw(tt, kw::Where) { + break tt; + } + new_tts.push_tree(tt.clone()); + }; + + // 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. + // + inject(&mut new_tts)?; + + // Above we injected the internal AST requires/ensures construct. Now copy over all the other + // token trees. + new_tts.push_tree(next_tt.clone()); + while let Some(tt) = cursor.next() { + new_tts.push_tree(tt.clone()); + if cursor.peek().is_none() + && !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _)) + { + return Err(ecx.sess.dcx().span_err( + attr_span, + "contract annotations is only supported in functions with bodies", + )); + } + } + + // 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 `contracts_internals`. + ecx.psess().contract_attribute_spans.push(attr_span); + + Ok(new_tts) +} + +fn expand_requires_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result { + expand_contract_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push_tree(TokenTree::Token( + token::Token::from_ast_ident(Ident::new(kw::ContractRequires, attr_span)), + Spacing::Joint, + )); + new_tts.push_tree(TokenTree::Token( + token::Token::new(token::TokenKind::OrOr, attr_span), + Spacing::Alone, + )); + new_tts.push_tree(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_contract_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push_tree(TokenTree::Token( + token::Token::from_ast_ident(Ident::new(kw::ContractEnsures, attr_span)), + Spacing::Joint, + )); + new_tts.push_tree(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 755a733286cea..0631c5a80fc5e 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 6987ae95980a6..ca16583a45de7 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -55,6 +55,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; @@ -137,4 +138,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 31b068bd33dae..472e16e62d5b0 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -344,6 +344,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 7a40d236b9288..a2b9e5712e50b 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -868,7 +868,16 @@ fn codegen_stmt<'tcx>( NullOp::UbChecks => { let val = fx.tcx.sess.ub_checks(); let val = CValue::by_val( - fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()), + fx.bcx.ins().iconst(types::I8, i64::from(val)), + fx.layout_of(fx.tcx.types.bool), + ); + 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::from(val)), fx.layout_of(fx.tcx.types.bool), ); lval.write_cvalue(fx, val); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 85de3238b3e77..27cb7883b9a6c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -741,6 +741,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 e8052a3c83a1f..d0ce027ec2b71 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -675,7 +675,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 8f6b15b8df012..1a799f5dea5b5 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -293,6 +293,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] @@ -679,6 +682,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 e0543977e98d8..67eb96e4d56ad 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 1a216ebf117c6..5699d4ce3b9b6 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(128044)), /// 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 @@ -445,6 +447,10 @@ declare_features! ( (unstable, const_trait_impl, "1.42.0", Some(67792)), /// Allows the `?` operator in const contexts. (unstable, const_try, "1.56.0", Some(74935)), + /// Allows use of contracts attributes. + (incomplete, contracts, "CURRENT_RUSTC_VERSION", Some(128044)), + /// Allows access to internal machinery used to implement contracts. + (internal, contracts_internals, "CURRENT_RUSTC_VERSION", Some(128044)), /// Allows coroutines to be cloned. (unstable, coroutine_clone, "1.65.0", Some(95360)), /// Allows defining coroutines. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index af2f86b67e007..8bc09f631cf1e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2598,6 +2598,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 d9759580e8fdc..75898cbec14b8 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -418,6 +418,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 cf3d489730425..e641fb0fb62e6 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 @@ -219,6 +222,16 @@ 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) + // where C: impl Fn(&'a Ret) -> bool, + // + // so: two type params, one lifetime param, 0 const params, two inputs, no return + + 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)); + (2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe) } else { let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let (n_tps, n_cts, inputs, output) = match intrinsic_name { @@ -610,6 +623,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.unit), + 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 a519e177fbccd..f7bc21cc5260d 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_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 09d7e60e19941..f53b4f3def259 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1103,6 +1103,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 2da25f480c65f..9cec8d832dd14 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1591,6 +1591,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 49449426fa408..af23c8b2ea76d 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -230,7 +230,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 6e00e427a46c7..b6c259aa4e0ab 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -417,7 +417,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { | Rvalue::Discriminant(..) | Rvalue::Len(..) | 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 c261e25100d38..d2ffd26f0a06d 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -545,6 +545,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 2864cc0b9fe0c..e43254ba089e5 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -629,6 +629,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 9a9f66ed4fd7a..9c21bcfc0d26a 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 9101c9bfc9aeb..4dbbcae1756b7 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -457,6 +457,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 e282eaf761c10..b7a3770fc6b1d 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1379,7 +1379,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(_) => {} Rvalue::WrapUnsafeBinder(op, ty) => { diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index b1b84b0b7011e..86816819be275 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 an experimental fn contract + /// (`contract_requires(WWW) 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::contracts, span); + } else { + // span was not generated via a builtin contracts attribute, so gate as internal machinery + self.psess.gated_spans.gate(sym::contracts_internals, span); + } + }; + + let requires = if self.eat_keyword_noexpect(exp!(ContractRequires).kw) { + let precond = self.parse_expr()?; + gate(precond.span); + Some(precond) + } else { + None + }; + let ensures = if self.eat_keyword_noexpect(exp!(ContractEnsures).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..40631d9154d37 100644 --- a/compiler/rustc_parse/src/parser/token_type.rs +++ b/compiler/rustc_parse/src/parser/token_type.rs @@ -83,6 +83,8 @@ pub enum TokenType { KwCatch, KwConst, KwContinue, + KwContractEnsures, + KwContractRequires, KwCrate, KwDefault, KwDyn, @@ -217,6 +219,8 @@ impl TokenType { KwCatch, KwConst, KwContinue, + KwContractEnsures, + KwContractRequires, KwCrate, KwDefault, KwDyn, @@ -289,6 +293,8 @@ impl TokenType { TokenType::KwCatch => Some(kw::Catch), TokenType::KwConst => Some(kw::Const), TokenType::KwContinue => Some(kw::Continue), + TokenType::KwContractEnsures => Some(kw::ContractEnsures), + TokenType::KwContractRequires => Some(kw::ContractRequires), TokenType::KwCrate => Some(kw::Crate), TokenType::KwDefault => Some(kw::Default), TokenType::KwDyn => Some(kw::Dyn), @@ -519,6 +525,8 @@ macro_rules! exp { (Catch) => { exp!(@kw, Catch, KwCatch) }; (Const) => { exp!(@kw, Const, KwConst) }; (Continue) => { exp!(@kw, Continue, KwContinue) }; + (ContractEnsures) => { exp!(@kw, ContractEnsures, KwContractEnsures) }; + (ContractRequires) => { exp!(@kw, ContractRequires, KwContractRequires) }; (Crate) => { exp!(@kw, Crate, KwCrate) }; (Default) => { exp!(@kw, Default, KwDefault) }; (Dyn) => { exp!(@kw, Dyn, KwDyn) }; diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 16c0a345f8791..db607dbb41963 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { _ctxt, _ident, _vis, - Fn { sig: FnSig { header, decl, span: _ }, generics, body, .. }, + Fn { sig: FnSig { header, decl, span: _ }, 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 4842cbd556c37..e37e7e98ee7aa 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -1019,7 +1019,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(_, _, _, Fn { sig, generics, body, .. }) => { + FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => { this.visit_generics(generics); let declaration = &sig.decl; @@ -1046,6 +1046,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_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 4ed5499d32c6d..35819f896c5bd 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2114,6 +2114,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 81ae06602cdb9..abfd3efc6117c 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 2b79081a26e72..c9bb42bdfa1ec 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 4a0420cc60311..bdd6e16a7c171 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -291,6 +291,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 a5826137181db..dbbbb5077cb5c 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1163,6 +1163,8 @@ pub enum DesugaringKind { WhileLoop, /// `async Fn()` bound modifier BoundModifier, + /// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond) + Contract, } impl DesugaringKind { @@ -1179,6 +1181,7 @@ impl DesugaringKind { DesugaringKind::ForLoop => "`for` loop", DesugaringKind::WhileLoop => "`while` loop", DesugaringKind::BoundModifier => "trait bound modifier", + DesugaringKind::Contract => "contract check", } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b23cc90991154..529dfc6ff7a03 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", + ContractEnsures: "contract_ensures", + ContractRequires: "contract_requires", Safe: "safe", Union: "union", Yeet: "yeet", @@ -566,6 +568,7 @@ symbols! { cfg_attr, cfg_attr_multi, cfg_boolean_literals, + cfg_contract_checks, cfg_doctest, cfg_emscripten_wasm_eh, cfg_eval, @@ -675,6 +678,14 @@ symbols! { const_ty_placeholder: "", constant, constructor, + contract_build_check_ensures, + contract_check_ensures, + contract_check_requires, + contract_checks, + contracts, + contracts_ensures, + contracts_internals, + contracts_requires, convert_identity, copy, copy_closures, diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index eec6cd8d49ba3..a6406e9db8e35 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -608,7 +608,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( @@ -1007,6 +1008,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..c769e219e4d49 --- /dev/null +++ b/library/core/src/contracts.rs @@ -0,0 +1,21 @@ +//! Unstable module containing the unstable contracts lang items and attribute macros. +#![cfg(not(bootstrap))] + +pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_requires as requires}; + +/// 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). +#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[lang = "contract_build_check_ensures"] +#[track_caller] +pub fn build_check_ensures(cond: C) -> impl (Fn(Ret) -> Ret) + Copy +where + C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static, +{ + #[track_caller] + move |ret| { + crate::intrinsics::contract_check_ensures(&ret, cond); + ret + } +} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index bf07632d9928b..1e4dc12f9b617 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4044,6 +4044,52 @@ 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 = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[unstable(feature = "contracts_internals", issue = "128044" /* 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 +} + +/// Check if the pre-condition `cond` has been met. +/// +/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition +/// returns false. +#[cfg(not(bootstrap))] +#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[lang = "contract_check_requires"] +#[rustc_intrinsic] +pub fn contract_check_requires bool>(cond: C) { + if contract_checks() && !cond() { + // Emit no unwind panic in case this was a safety requirement. + crate::panicking::panic_nounwind("failed requires check"); + } +} + +/// Check if the post-condition `cond` has been met. +/// +/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition +/// returns false. +#[cfg(not(bootstrap))] +#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) { + if contract_checks() && !cond(ret) { + crate::panicking::panic_nounwind("failed ensures check"); + } +} + /// 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 c18e0405f7293..de8e85f7b36ed 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -113,6 +113,7 @@ #![feature(bigint_helper_methods)] #![feature(bstr)] #![feature(bstr_internals)] +#![feature(closure_track_caller)] #![feature(const_carrying_mul_add)] #![feature(const_eval_select)] #![feature(core_intrinsics)] @@ -246,6 +247,10 @@ pub mod autodiff { pub use crate::macros::builtin::autodiff; } +#[cfg(not(bootstrap))] +#[unstable(feature = "contracts", issue = "128044")] +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 3669051ce82d3..4c6fd196bd31c 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1777,6 +1777,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 = "contracts", issue = "128044")] + #[allow_internal_unstable(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 = "contracts", issue = "128044")] + #[allow_internal_unstable(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_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 2eb09bac8d881..798f4575c2e10 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 0aaef91e48a6d..5a3a3d0cedc42 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 @@ -179,7 +179,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 4735db48e81f8..6bd1076a8a848 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1150,6 +1150,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { interp_ok(ecx.tcx.sess.ub_checks()) } + #[inline(always)] + fn contract_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> { + interp_ok(ecx.tcx.sess.contract_checks()) + } + #[inline(always)] fn thread_local_static_pointer( ecx: &mut MiriInterpCx<'tcx>, diff --git a/tests/ui/check-cfg/cargo-build-script.stderr b/tests/ui/check-cfg/cargo-build-script.stderr index df0bc47571c72..03a7156a4d69e 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: `has_bar` and 30 more + = help: expected names are: `has_bar` and 31 more = 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 58813a1f6770c..b83d1794984de 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: `docsrs`, `feature`, and `test` and 30 more + = help: expected names are: `docsrs`, `feature`, and `test` and 31 more = 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 5a12be8133871..2cddcbbcd7f9e 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`, `docsrs`, `feature`, and `test` and 30 more + = help: expected names are: `CONFIG_NVME`, `docsrs`, `feature`, and `test` and 31 more = 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 7c276c581707e..68e1259dbb842 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`, `cow`, and `foo` and 30 more + = help: expected names are: `bar`, `bee`, `cow`, and `foo` and 31 more = 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 9687a043e8322..138c7fc758494 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` and `foo` and 30 more + = help: expected names are: `bar` and `foo` and 31 more = 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/exhaustive-names-values.feature.stderr b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr index 10302f0a7e46a..af66cbd818946 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: `feature` and 30 more + = help: expected names are: `feature` and 31 more = 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 10302f0a7e46a..af66cbd818946 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: `feature` and 30 more + = help: expected names are: `feature` and 31 more = 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 033aaef848f74..be4d7c7727636 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: `feature` and 30 more + = help: expected names are: `feature` and 31 more = 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 f19ded9cb6715..8ca33e088fc94 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`, `edition2015`, `edition2021`, and `r#true` and 30 more + = help: expected names are: `async`, `edition2015`, `edition2021`, and `r#true` and 31 more = 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 6096148a259b0..cce55720bdd18 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`, `edition2015`, `edition2021`, and `r#true` and 30 more + = help: expected names are: `r#async`, `edition2015`, `edition2021`, and `r#true` and 31 more = 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 a6584d777a3a6..989a01f224412 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: `feature` and 30 more + = help: expected names are: `feature` and 31 more = 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 914b5a0efe366..95d10e014f33b 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: `feature` and 30 more + = help: expected names are: `feature` and 31 more = 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/well-known-names.stderr b/tests/ui/check-cfg/well-known-names.stderr index 4ff90261158bc..000315443f810 100644 --- a/tests/ui/check-cfg/well-known-names.stderr +++ b/tests/ui/check-cfg/well-known-names.stderr @@ -5,6 +5,7 @@ LL | #[cfg(list_all_well_known_cfgs)] | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: expected names are: `clippy` +`contract_checks` `debug_assertions` `doc` `doctest` diff --git a/tests/ui/contracts/contract-annotation-limitations.rs b/tests/ui/contracts/contract-annotation-limitations.rs new file mode 100644 index 0000000000000..10b3bacab5cfa --- /dev/null +++ b/tests/ui/contracts/contract-annotation-limitations.rs @@ -0,0 +1,28 @@ +//! Test for some of the existing limitations and the current error messages. +//! Some of these limitations may be removed in the future. + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] +#![allow(dead_code)] + +/// Represent a 5-star system. +struct Stars(u8); + +impl Stars { + fn is_valid(&self) -> bool { + self.0 <= 5 + } +} + +trait ParseStars { + #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] + //~^ ERROR contract annotations is only supported in functions with bodies + fn parse_string(input: String) -> Option; + + #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] + //~^ ERROR contract annotations is only supported in functions with bodies + fn parse(input: T) -> Option where T: for<'a> Into<&'a str>; +} + +fn main() { +} diff --git a/tests/ui/contracts/contract-annotation-limitations.stderr b/tests/ui/contracts/contract-annotation-limitations.stderr new file mode 100644 index 0000000000000..14338cf4b8687 --- /dev/null +++ b/tests/ui/contracts/contract-annotation-limitations.stderr @@ -0,0 +1,23 @@ +error: contract annotations is only supported in functions with bodies + --> $DIR/contract-annotation-limitations.rs:18:5 + | +LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations is only supported in functions with bodies + --> $DIR/contract-annotation-limitations.rs:22:5 + | +LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-annotation-limitations.rs:4:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: aborting due to 2 previous errors; 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-generics.chk_const_fail.stderr b/tests/ui/contracts/contract-attributes-generics.chk_const_fail.stderr new file mode 100644 index 0000000000000..0630811d4f7ea --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.chk_const_fail.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-generics.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-generics.chk_fail_post.stderr b/tests/ui/contracts/contract-attributes-generics.chk_fail_post.stderr new file mode 100644 index 0000000000000..0630811d4f7ea --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.chk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-generics.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-generics.chk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-generics.chk_fail_pre.stderr new file mode 100644 index 0000000000000..0630811d4f7ea --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.chk_fail_pre.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-generics.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-generics.chk_pass.stderr b/tests/ui/contracts/contract-attributes-generics.chk_pass.stderr new file mode 100644 index 0000000000000..0630811d4f7ea --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.chk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-generics.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-generics.rs b/tests/ui/contracts/contract-attributes-generics.rs new file mode 100644 index 0000000000000..fd79c6abedd85 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.rs @@ -0,0 +1,71 @@ +//! Test that contracts can be applied to generic functions. + +//@ revisions: unchk_pass chk_pass chk_fail_pre chk_fail_post chk_const_fail +// +//@ [unchk_pass] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +//@ [chk_const_fail] run-fail +// +//@ [unchk_pass] 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 +//@ [chk_const_fail] compile-flags: -Zcontract-checks=yes + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +use std::ops::Sub; + +/// Dummy fn contract that precondition fails for val < 0, and post-condition fail for val == 1 +#[core::contracts::requires(val > 0u8.into())] +#[core::contracts::ensures(|ret| *ret > 0u8.into())] +fn decrement(val: T) -> T +where T: PartialOrd + Sub + From +{ + val - 1u8.into() +} + +/// Create a structure that takes a constant parameter. +#[allow(dead_code)] +struct Capped(usize); + +/// Now declare a function to create stars which shouldn't exceed 5 stars. +// Add redundant braces to ensure the built-in macro can handle this syntax. +#[allow(unused_braces)] +#[core::contracts::requires(num <= 5)] +unsafe fn stars_unchecked(num: usize) -> Capped<{ 5 }> { + Capped(num) +} + + +fn main() { + check_decrement(); + check_stars(); +} + +fn check_stars() { + // This should always pass. + let _ = unsafe { stars_unchecked(3) }; + + // This violates the contract. + #[cfg(any(unchk_pass, chk_const_fail))] + let _ = unsafe { stars_unchecked(10) }; +} + +fn check_decrement() { + // This should always pass + assert_eq!(decrement(10u8), 9u8); + + // This should fail requires but pass with no contract check. + #[cfg(any(unchk_pass, chk_fail_pre))] + assert_eq!(decrement(-2i128), -3i128); + + // This should fail ensures but pass with no contract check. + #[cfg(any(unchk_pass, chk_fail_post))] + assert_eq!(decrement(1i32), 0i32); +} diff --git a/tests/ui/contracts/contract-attributes-generics.unchk_pass.stderr b/tests/ui/contracts/contract-attributes-generics.unchk_pass.stderr new file mode 100644 index 0000000000000..0630811d4f7ea --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.unchk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-generics.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.chk_fail_post.stderr b/tests/ui/contracts/contract-attributes-nest.chk_fail_post.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.chk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.chk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-nest.chk_fail_pre.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.chk_fail_pre.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr b/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.chk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.rs b/tests/ui/contracts/contract-attributes-nest.rs new file mode 100644 index 0000000000000..e1e61b88f282e --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.rs @@ -0,0 +1,45 @@ +//@ 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(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +#[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-nest.unchk_fail_post.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr b/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr new file mode 100644 index 0000000000000..9ca95b8bb01a4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-nest.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.chk_fail_post.stderr b/tests/ui/contracts/contract-attributes-tail.chk_fail_post.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.chk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.chk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-tail.chk_fail_pre.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.chk_fail_pre.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.chk_pass.stderr b/tests/ui/contracts/contract-attributes-tail.chk_pass.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.chk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.rs b/tests/ui/contracts/contract-attributes-tail.rs new file mode 100644 index 0000000000000..ce4a6be5b82f7 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.rs @@ -0,0 +1,43 @@ +//@ 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(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +#[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-attributes-tail.unchk_fail_post.stderr b/tests/ui/contracts/contract-attributes-tail.unchk_fail_post.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.unchk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.unchk_fail_pre.stderr b/tests/ui/contracts/contract-attributes-tail.unchk_fail_pre.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.unchk_fail_pre.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-attributes-tail.unchk_pass.stderr b/tests/ui/contracts/contract-attributes-tail.unchk_pass.stderr new file mode 100644 index 0000000000000..f87e7e19fa3db --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.unchk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-attributes-tail.rs:19:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-captures-via-closure-copy.rs b/tests/ui/contracts/contract-captures-via-closure-copy.rs new file mode 100644 index 0000000000000..32c6d2bf4fe17 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-copy.rs @@ -0,0 +1,26 @@ +//@ run-fail +//@ compile-flags: -Zcontract-checks=yes + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +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-copy.stderr b/tests/ui/contracts/contract-captures-via-closure-copy.stderr new file mode 100644 index 0000000000000..d92db601608f5 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-copy.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-captures-via-closure-copy.rs:4:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.rs b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs new file mode 100644 index 0000000000000..976f64c7fd911 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs @@ -0,0 +1,23 @@ +//@ compile-flags: -Zcontract-checks=yes + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +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..4a47671fee191 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -0,0 +1,36 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-captures-via-closure-noncopy.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}` + --> $DIR/contract-captures-via-closure-noncopy.rs:12: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:12:42: 12:57}` + | | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:12:42}` + | unsatisfied trait bound + | + = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12: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:12: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; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_ret.stderr b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_ret.stderr new file mode 100644 index 0000000000000..d693fad446a4d --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_ret.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-early-fn-exit.rs:16:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_try.stderr b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_try.stderr new file mode 100644 index 0000000000000..d693fad446a4d --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_try.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-early-fn-exit.rs:16:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_yeet.stderr b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_yeet.stderr new file mode 100644 index 0000000000000..d693fad446a4d --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_yeet.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-early-fn-exit.rs:16:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_pass.stderr b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_pass.stderr new file mode 100644 index 0000000000000..d693fad446a4d --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.chk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-early-fn-exit.rs:16:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.rs b/tests/ui/contracts/contracts-ensures-early-fn-exit.rs new file mode 100644 index 0000000000000..034cead3b4e9f --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.rs @@ -0,0 +1,49 @@ +//@ revisions: unchk_pass chk_pass chk_fail_try chk_fail_ret chk_fail_yeet +// +//@ [unchk_pass] run-pass +//@ [chk_pass] run-pass +//@ [chk_fail_try] run-fail +//@ [chk_fail_ret] run-fail +//@ [chk_fail_yeet] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_try] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_ret] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_yeet] compile-flags: -Zcontract-checks=yes +//! This test ensures that ensures clauses are checked for different return points of a function. + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] +#![feature(yeet_expr)] + +/// This ensures will fail in different return points depending on the input. +#[core::contracts::ensures(|ret: &Option| ret.is_some())] +fn try_sum(x: u32, y: u32, z: u32) -> Option { + // Use Yeet to return early. + if x == u32::MAX && (y > 0 || z > 0) { do yeet } + + // Use `?` to early return. + let partial = x.checked_add(y)?; + + // Explicitly use `return` clause. + if u32::MAX - partial < z { + return None; + } + + Some(partial + z) +} + +fn main() { + // This should always succeed + assert_eq!(try_sum(0, 1, 2), Some(3)); + + #[cfg(any(unchk_pass, chk_fail_yeet))] + assert_eq!(try_sum(u32::MAX, 1, 1), None); + + #[cfg(any(unchk_pass, chk_fail_try))] + assert_eq!(try_sum(u32::MAX - 10, 12, 0), None); + + #[cfg(any(unchk_pass, chk_fail_ret))] + assert_eq!(try_sum(u32::MAX - 10, 2, 100), None); +} diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.unchk_pass.stderr b/tests/ui/contracts/contracts-ensures-early-fn-exit.unchk_pass.stderr new file mode 100644 index 0000000000000..d693fad446a4d --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.unchk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-early-fn-exit.rs:16:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/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..f01a852fbff34 --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs @@ -0,0 +1,15 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +#[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-ensures-is-not-inherited-when-nesting.stderr b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.stderr new file mode 100644 index 0000000000000..49a372b53c7d8 --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-ensures-is-not-inherited-when-nesting.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/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..2c2a4a6978550 --- /dev/null +++ b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs @@ -0,0 +1,17 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] + +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/contracts-requires-is-not-inherited-when-nesting.stderr b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.stderr new file mode 100644 index 0000000000000..48898c4434ad5 --- /dev/null +++ b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contracts-requires-is-not-inherited-when-nesting.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs new file mode 100644 index 0000000000000..69be906782a67 --- /dev/null +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs @@ -0,0 +1,53 @@ +//! Checks for compilation errors related to adding contracts to non-function items. + +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] +#![allow(dead_code)] + +#[core::contracts::requires(true)] +//~^ ERROR contract annotations can only be used on functions +struct Dummy(usize); + +#[core::contracts::ensures(|v| v == 100)] +//~^ ERROR contract annotations can only be used on functions +const MAX_VAL: usize = 100; + +// FIXME: Improve the error message here. The macro thinks this is a function. +#[core::contracts::ensures(|v| v == 100)] +//~^ ERROR contract annotations is only supported in functions with bodies +type NewDummy = fn(usize) -> Dummy; + +#[core::contracts::ensures(|v| v == 100)] +//~^ ERROR contract annotations is only supported in functions with bodies +const NEW_DUMMY_FN : fn(usize) -> Dummy = || { Dummy(0) }; + +#[core::contracts::requires(true)] +//~^ ERROR contract annotations can only be used on functions +impl Dummy { + + // This should work + #[core::contracts::ensures(|ret| ret.0 == v)] + fn new(v: usize) -> Dummy { + Dummy(v) + } +} + +#[core::contracts::ensures(|dummy| dummy.0 > 0)] +//~^ ERROR contract annotations can only be used on functions +impl From for Dummy { + // This should work. + #[core::contracts::ensures(|ret| ret.0 == v)] + fn from(value: usize) -> Self { + Dummy::new(value) + } +} + +/// You should not be able to annotate a trait either. +#[core::contracts::requires(true)] +//~^ ERROR contract annotations can only be used on functions +pub trait DummyBuilder { + fn build() -> Dummy; +} + +fn main() { +} diff --git a/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr new file mode 100644 index 0000000000000..0a7fff8183e09 --- /dev/null +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr @@ -0,0 +1,53 @@ +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:7:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:11:1 + | +LL | #[core::contracts::ensures(|v| v == 100)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations is only supported in functions with bodies + --> $DIR/disallow-contract-annotation-on-non-fn.rs:16:1 + | +LL | #[core::contracts::ensures(|v| v == 100)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations is only supported in functions with bodies + --> $DIR/disallow-contract-annotation-on-non-fn.rs:20:1 + | +LL | #[core::contracts::ensures(|v| v == 100)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:24:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:35:1 + | +LL | #[core::contracts::ensures(|dummy| dummy.0 > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:46:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/disallow-contract-annotation-on-non-fn.rs:3:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: aborting due to 7 previous errors; 1 warning emitted + 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..6d8cd3949eedb --- /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(contracts_internals)] + +fn nest(x: Baz) -> i32 + contract_requires(|| x.baz > 0) + 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..07ec26f921b80 --- /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(contracts_internals)] + +fn tail(x: Baz) -> i32 + contract_requires(|| x.baz > 0) + 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..ae692afd146fe --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -0,0 +1,36 @@ +//@ revisions: default unchk_pass chk_pass chk_fail_ensures chk_fail_requires +// +//@ [default] run-pass +//@ [unchk_pass] run-pass +//@ [chk_pass] run-pass +//@ [chk_fail_requires] run-fail +//@ [chk_fail_ensures] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_requires] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_ensures] compile-flags: -Zcontract-checks=yes +#![feature(cfg_contract_checks, contracts_internals, core_intrinsics)] + +fn main() { + #[cfg(any(default, unchk_pass))] // default: disabled + assert_eq!(core::intrinsics::contract_checks(), false); + + #[cfg(chk_pass)] // explicitly enabled + assert_eq!(core::intrinsics::contract_checks(), true); + + // always pass + core::intrinsics::contract_check_requires(|| true); + + // fail if enabled + #[cfg(any(default, unchk_pass, chk_fail_requires))] + core::intrinsics::contract_check_requires(|| false); + + let doubles_to_two = { let old = 2; move |ret| ret + ret == old }; + // Always pass + core::intrinsics::contract_check_ensures(&1, doubles_to_two); + + // Fail if enabled + #[cfg(any(default, unchk_pass, chk_fail_ensures))] + core::intrinsics::contract_check_ensures(&2, doubles_to_two); +} diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr new file mode 100644 index 0000000000000..a60ce1602659b --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-lang-items.rs:15:12 + | +LL | #![feature(contracts)] // to access core::contracts + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr new file mode 100644 index 0000000000000..a60ce1602659b --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-lang-items.rs:15:12 + | +LL | #![feature(contracts)] // to access core::contracts + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + 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..e91bbed294d12 --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -0,0 +1,39 @@ +//@ revisions: unchk_pass unchk_fail_post chk_pass chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(contracts)] // to access core::contracts +//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features] +#![feature(contracts_internals)] // to access check_requires lang item + +fn foo(x: Baz) -> i32 { + 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 }; + +fn main() { + assert_eq!(foo(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_post, chk_fail_post))] + foo(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr new file mode 100644 index 0000000000000..a60ce1602659b --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-lang-items.rs:15:12 + | +LL | #![feature(contracts)] // to access core::contracts + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr new file mode 100644 index 0000000000000..a60ce1602659b --- /dev/null +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr @@ -0,0 +1,11 @@ +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/contract-lang-items.rs:15:12 + | +LL | #![feature(contracts)] // to access core::contracts + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +warning: 1 warning emitted + 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..960ccaed3588a --- /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(contracts_internals)] + +fn outer() -> i32 + 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..bee703de16a0b --- /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(contracts_internals)] + +struct Outer { outer: std::cell::Cell } + +fn outer(x: Outer) + 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..1b76eef6780fe --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -0,0 +1,20 @@ +// gate-test-contracts_internals + +fn main() { + // intrinsics are guarded by contracts_internals feature gate. + core::intrinsics::contract_checks(); + //~^ ERROR use of unstable library feature `contracts_internals` + core::intrinsics::contract_check_requires(|| true); + //~^ ERROR use of unstable library feature `contracts_internals` + core::intrinsics::contract_check_ensures(&1, |_|true); + //~^ ERROR use of unstable library feature `contracts_internals` + + core::contracts::build_check_ensures(|_: &()| true); + //~^ ERROR use of unstable library feature `contracts_internals` + + // ast extensions are guarded by contracts_internals feature gate + fn identity_1() -> i32 contract_requires(|| true) { 10 } + //~^ ERROR contract internal machinery is for internal use only + fn identity_2() -> i32 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..c0e1522f54c61 --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -0,0 +1,63 @@ +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:16:45 + | +LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:18:44 + | +LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } + | ^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `contracts_internals` + --> $DIR/internal-feature-gating.rs:5:5 + | +LL | core::intrinsics::contract_checks(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `contracts_internals` + --> $DIR/internal-feature-gating.rs:7:5 + | +LL | core::intrinsics::contract_check_requires(|| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `contracts_internals` + --> $DIR/internal-feature-gating.rs:9:5 + | +LL | core::intrinsics::contract_check_ensures(&1, |_|true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `contracts_internals` + --> $DIR/internal-feature-gating.rs:12:5 + | +LL | core::contracts::build_check_ensures(|_: &()| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 6 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..89c6d077f97eb --- /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 #128044 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-contracts.rs b/tests/ui/feature-gates/feature-gate-contracts.rs new file mode 100644 index 0000000000000..5544f1d82ee4c --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-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 `contracts` +//~^^^ ERROR contracts are incomplete + +#[core::contracts::ensures(|ret| *ret > 0)] +pub fn ensures_needs_it() -> i32 { 10 } +//~^^ ERROR use of unstable library feature `contracts` +//~^^^ ERROR contracts are incomplete diff --git a/tests/ui/feature-gates/feature-gate-contracts.stderr b/tests/ui/feature-gates/feature-gate-contracts.stderr new file mode 100644 index 0000000000000..4403e7df50b49 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-contracts.stderr @@ -0,0 +1,43 @@ +error[E0658]: use of unstable library feature `contracts` + --> $DIR/feature-gate-contracts.rs:3:3 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(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 `contracts` + --> $DIR/feature-gate-contracts.rs:8:3 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(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 incomplete + --> $DIR/feature-gate-contracts.rs:3:1 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(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 incomplete + --> $DIR/feature-gate-contracts.rs:8:1 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #128044 for more information + = help: add `#![feature(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`.