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 78aa38e9e48f4..002f6e36cd8b3 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1653,6 +1653,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_llvm/src/debuginfo/create_scope_map.rs b/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs index 11eb9651af6c5..f52991b369797 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs @@ -127,7 +127,7 @@ fn make_mir_scope<'ll, 'tcx>( }) } None => unsafe { - llvm::LLVMRustDIBuilderCreateLexicalBlock( + llvm::LLVMDIBuilderCreateLexicalBlock( DIB(cx), parent_scope.dbg_scope, file_metadata, diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 3a0c7f007bdd2..f497ba95661df 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -931,7 +931,7 @@ pub(crate) fn build_compile_unit_di_node<'ll, 'tcx>( unsafe { let compile_unit_file = llvm::LLVMRustDIBuilderCreateFile( - debug_context.builder, + debug_context.builder.as_ref(), name_in_debuginfo.as_c_char_ptr(), name_in_debuginfo.len(), work_dir.as_c_char_ptr(), @@ -944,7 +944,7 @@ pub(crate) fn build_compile_unit_di_node<'ll, 'tcx>( ); let unit_metadata = llvm::LLVMRustDIBuilderCreateCompileUnit( - debug_context.builder, + debug_context.builder.as_ref(), dwarf_const::DW_LANG_Rust, compile_unit_file, producer.as_c_char_ptr(), @@ -1641,7 +1641,14 @@ pub(crate) fn extend_scope_to_file<'ll>( file: &SourceFile, ) -> &'ll DILexicalBlock { let file_metadata = file_metadata(cx, file); - unsafe { llvm::LLVMRustDIBuilderCreateLexicalBlockFile(DIB(cx), scope_metadata, file_metadata) } + unsafe { + llvm::LLVMDIBuilderCreateLexicalBlockFile( + DIB(cx), + scope_metadata, + file_metadata, + /* Discriminator (default) */ 0u32, + ) + } } fn tuple_field_name(field_index: usize) -> Cow<'static, str> { diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index b1ce52667bd6d..496178c6b1d94 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -34,7 +34,7 @@ use crate::builder::Builder; use crate::common::{AsCCharPtr, CodegenCx}; use crate::llvm; use crate::llvm::debuginfo::{ - DIArray, DIBuilder, DIFile, DIFlags, DILexicalBlock, DILocation, DISPFlags, DIScope, DIType, + DIArray, DIBuilderBox, DIFile, DIFlags, DILexicalBlock, DILocation, DISPFlags, DIScope, DIType, DIVariable, }; use crate::value::Value; @@ -61,7 +61,7 @@ const DW_TAG_arg_variable: c_uint = 0x101; /// A context object for maintaining all state needed by the debuginfo module. pub(crate) struct CodegenUnitDebugContext<'ll, 'tcx> { llmod: &'ll llvm::Module, - builder: &'ll mut DIBuilder<'ll>, + builder: DIBuilderBox<'ll>, created_files: RefCell, &'ll DIFile>>, type_map: metadata::TypeMap<'ll, 'tcx>, @@ -69,18 +69,10 @@ pub(crate) struct CodegenUnitDebugContext<'ll, 'tcx> { recursion_marker_type: OnceCell<&'ll DIType>, } -impl Drop for CodegenUnitDebugContext<'_, '_> { - fn drop(&mut self) { - unsafe { - llvm::LLVMRustDIBuilderDispose(&mut *(self.builder as *mut _)); - } - } -} - impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> { pub(crate) fn new(llmod: &'ll llvm::Module) -> Self { debug!("CodegenUnitDebugContext::new"); - let builder = unsafe { llvm::LLVMRustDIBuilderCreate(llmod) }; + let builder = DIBuilderBox::new(llmod); // DIBuilder inherits context from the module, so we'd better use the same one CodegenUnitDebugContext { llmod, @@ -93,7 +85,7 @@ impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> { } pub(crate) fn finalize(&self, sess: &Session) { - unsafe { llvm::LLVMRustDIBuilderFinalize(self.builder) }; + unsafe { llvm::LLVMDIBuilderFinalize(self.builder.as_ref()) }; match sess.target.debuginfo_kind { DebuginfoKind::Dwarf | DebuginfoKind::DwarfDsym => { @@ -582,7 +574,7 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { (line, col) }; - unsafe { llvm::LLVMRustDIBuilderCreateDebugLocation(line, col, scope, inlined_at) } + unsafe { llvm::LLVMDIBuilderCreateDebugLocation(self.llcx, line, col, scope, inlined_at) } } fn create_vtable_debuginfo( diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs b/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs index 33d9bc238903e..b4d639368b005 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs @@ -5,7 +5,7 @@ use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, Instance}; use super::utils::{DIB, debug_context}; -use crate::common::{AsCCharPtr, CodegenCx}; +use crate::common::CodegenCx; use crate::llvm; use crate::llvm::debuginfo::DIScope; @@ -33,12 +33,12 @@ pub(crate) fn item_namespace<'ll>(cx: &CodegenCx<'ll, '_>, def_id: DefId) -> &'l }; let scope = unsafe { - llvm::LLVMRustDIBuilderCreateNameSpace( + llvm::LLVMDIBuilderCreateNameSpace( DIB(cx), parent_scope, - namespace_name_string.as_c_char_ptr(), + namespace_name_string.as_ptr(), namespace_name_string.len(), - false, // ExportSymbols (only relevant for C++ anonymous namespaces) + llvm::False, // ExportSymbols (only relevant for C++ anonymous namespaces) ) }; diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs b/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs index 6e84129347787..cc1d504b43017 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs @@ -41,7 +41,7 @@ pub(crate) fn debug_context<'a, 'll, 'tcx>( #[inline] #[allow(non_snake_case)] pub(crate) fn DIB<'a, 'll>(cx: &'a CodegenCx<'ll, '_>) -> &'a DIBuilder<'ll> { - cx.dbg_cx.as_ref().unwrap().builder + cx.dbg_cx.as_ref().unwrap().builder.as_ref() } pub(crate) fn get_namespace_for_item<'ll>(cx: &CodegenCx<'ll, '_>, def_id: DefId) -> &'ll DIScope { diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index cc7c5231aca58..5c53a419a6ccc 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1,3 +1,15 @@ +//! Bindings to the LLVM-C API (`LLVM*`), and to our own `extern "C"` wrapper +//! functions around the unstable LLVM C++ API (`LLVMRust*`). +//! +//! ## Passing pointer/length strings as `*const c_uchar` +//! +//! Normally it's a good idea for Rust-side bindings to match the corresponding +//! C-side function declarations as closely as possible. But when passing `&str` +//! or `&[u8]` data as a pointer/length pair, it's more convenient to declare +//! the Rust-side pointer as `*const c_uchar` instead of `*const c_char`. +//! Both pointer types have the same ABI, and using `*const c_uchar` avoids +//! the need for an extra cast from `*const u8` on the Rust side. + #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] @@ -5,17 +17,18 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::ptr; -use libc::{c_char, c_int, c_uint, c_ulonglong, c_void, size_t}; +use bitflags::bitflags; +use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_void, size_t}; use rustc_macros::TryFromU32; use rustc_target::spec::SymbolVisibility; use super::RustString; use super::debuginfo::{ DIArray, DIBasicType, DIBuilder, DICompositeType, DIDerivedType, DIDescriptor, DIEnumerator, - DIFile, DIFlags, DIGlobalVariableExpression, DILexicalBlock, DILocation, DINameSpace, - DISPFlags, DIScope, DISubprogram, DISubrange, DITemplateTypeParameter, DIType, DIVariable, - DebugEmissionKind, DebugNameTableKind, + DIFile, DIFlags, DIGlobalVariableExpression, DILocation, DISPFlags, DIScope, DISubprogram, + DISubrange, DITemplateTypeParameter, DIType, DIVariable, DebugEmissionKind, DebugNameTableKind, }; +use crate::llvm; /// In the LLVM-C API, boolean values are passed as `typedef int LLVMBool`, /// which has a different ABI from Rust or C++ `bool`. @@ -789,12 +802,50 @@ pub type DiagnosticHandlerTy = unsafe extern "C" fn(&DiagnosticInfo, *mut c_void pub type InlineAsmDiagHandlerTy = unsafe extern "C" fn(&SMDiagnostic, *const c_void, c_uint); pub mod debuginfo { + use std::ptr; + use bitflags::bitflags; use super::{InvariantOpaque, Metadata}; + use crate::llvm::{self, Module}; + /// Opaque target type for references to an LLVM debuginfo builder. + /// + /// `&'_ DIBuilder<'ll>` corresponds to `LLVMDIBuilderRef`, which is the + /// LLVM-C wrapper for `DIBuilder *`. + /// + /// Debuginfo builders are created and destroyed during codegen, so the + /// builder reference typically has a shorter lifetime than the LLVM + /// session (`'ll`) that it participates in. #[repr(C)] - pub struct DIBuilder<'a>(InvariantOpaque<'a>); + pub struct DIBuilder<'ll>(InvariantOpaque<'ll>); + + /// Owning pointer to a `DIBuilder<'ll>` that will dispose of the builder + /// when dropped. Use `.as_ref()` to get the underlying `&DIBuilder` + /// needed for debuginfo FFI calls. + pub(crate) struct DIBuilderBox<'ll> { + raw: ptr::NonNull>, + } + + impl<'ll> DIBuilderBox<'ll> { + pub(crate) fn new(llmod: &'ll Module) -> Self { + let raw = unsafe { llvm::LLVMCreateDIBuilder(llmod) }; + let raw = ptr::NonNull::new(raw).unwrap(); + Self { raw } + } + + pub(crate) fn as_ref(&self) -> &DIBuilder<'ll> { + // SAFETY: This is an owning pointer, so `&DIBuilder` is valid + // for as long as `&self` is. + unsafe { self.raw.as_ref() } + } + } + + impl<'ll> Drop for DIBuilderBox<'ll> { + fn drop(&mut self) { + unsafe { llvm::LLVMDisposeDIBuilder(self.raw) }; + } + } pub type DIDescriptor = Metadata; pub type DILocation = Metadata; @@ -914,7 +965,6 @@ pub mod debuginfo { } } -use bitflags::bitflags; // These values **must** match with LLVMRustAllocKindFlags bitflags! { #[repr(transparent)] @@ -1672,6 +1722,50 @@ unsafe extern "C" { ) -> &'a Value; } +// FFI bindings for `DIBuilder` functions in the LLVM-C API. +// Try to keep these in the same order as in `llvm/include/llvm-c/DebugInfo.h`. +// +// FIXME(#134001): Audit all `Option` parameters, especially in lists, to check +// that they really are nullable on the C/C++ side. LLVM doesn't appear to +// actually document which ones are nullable. +unsafe extern "C" { + pub(crate) fn LLVMCreateDIBuilder<'ll>(M: &'ll Module) -> *mut DIBuilder<'ll>; + pub(crate) fn LLVMDisposeDIBuilder<'ll>(Builder: ptr::NonNull>); + + pub(crate) fn LLVMDIBuilderFinalize<'ll>(Builder: &DIBuilder<'ll>); + + pub(crate) fn LLVMDIBuilderCreateNameSpace<'ll>( + Builder: &DIBuilder<'ll>, + ParentScope: Option<&'ll Metadata>, + Name: *const c_uchar, + NameLen: size_t, + ExportSymbols: llvm::Bool, + ) -> &'ll Metadata; + + pub(crate) fn LLVMDIBuilderCreateLexicalBlock<'ll>( + Builder: &DIBuilder<'ll>, + Scope: &'ll Metadata, + File: &'ll Metadata, + Line: c_uint, + Column: c_uint, + ) -> &'ll Metadata; + + pub(crate) fn LLVMDIBuilderCreateLexicalBlockFile<'ll>( + Builder: &DIBuilder<'ll>, + Scope: &'ll Metadata, + File: &'ll Metadata, + Discriminator: c_uint, // (optional "DWARF path discriminator"; default is 0) + ) -> &'ll Metadata; + + pub(crate) fn LLVMDIBuilderCreateDebugLocation<'ll>( + Ctx: &'ll Context, + Line: c_uint, + Column: c_uint, + Scope: &'ll Metadata, + InlinedAt: Option<&'ll Metadata>, + ) -> &'ll Metadata; +} + #[link(name = "llvm-wrapper", kind = "static")] unsafe extern "C" { pub fn LLVMRustInstallErrorHandlers(); @@ -1939,12 +2033,6 @@ unsafe extern "C" { ValueLen: size_t, ); - pub fn LLVMRustDIBuilderCreate(M: &Module) -> &mut DIBuilder<'_>; - - pub fn LLVMRustDIBuilderDispose<'a>(Builder: &'a mut DIBuilder<'a>); - - pub fn LLVMRustDIBuilderFinalize(Builder: &DIBuilder<'_>); - pub fn LLVMRustDIBuilderCreateCompileUnit<'a>( Builder: &DIBuilder<'a>, Lang: c_uint, @@ -2107,20 +2195,6 @@ unsafe extern "C" { Type: &'a DIType, ) -> &'a DIDerivedType; - pub fn LLVMRustDIBuilderCreateLexicalBlock<'a>( - Builder: &DIBuilder<'a>, - Scope: &'a DIScope, - File: &'a DIFile, - Line: c_uint, - Col: c_uint, - ) -> &'a DILexicalBlock; - - pub fn LLVMRustDIBuilderCreateLexicalBlockFile<'a>( - Builder: &DIBuilder<'a>, - Scope: &'a DIScope, - File: &'a DIFile, - ) -> &'a DILexicalBlock; - pub fn LLVMRustDIBuilderCreateStaticVariable<'a>( Builder: &DIBuilder<'a>, Context: Option<&'a DIScope>, @@ -2245,14 +2319,6 @@ unsafe extern "C" { Ty: &'a DIType, ) -> &'a DITemplateTypeParameter; - pub fn LLVMRustDIBuilderCreateNameSpace<'a>( - Builder: &DIBuilder<'a>, - Scope: Option<&'a DIScope>, - Name: *const c_char, - NameLen: size_t, - ExportSymbols: bool, - ) -> &'a DINameSpace; - pub fn LLVMRustDICompositeTypeReplaceArrays<'a>( Builder: &DIBuilder<'a>, CompositeType: &'a DIType, @@ -2260,12 +2326,6 @@ unsafe extern "C" { Params: Option<&'a DIArray>, ); - pub fn LLVMRustDIBuilderCreateDebugLocation<'a>( - Line: c_uint, - Column: c_uint, - Scope: &'a DIScope, - InlinedAt: Option<&'a DILocation>, - ) -> &'a DILocation; pub fn LLVMRustDILocationCloneWithBaseDiscriminator<'a>( Location: &'a DILocation, BD: c_uint, 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 f5761da60f2c2..aff5514907389 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_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index bbe653714962e..e90474cabb420 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -13,7 +13,7 @@ use rustc_hir::{ExprKind, HirId, Node, QPath}; use rustc_hir_analysis::check::intrinsicck::InlineAsmCtxt; use rustc_hir_analysis::check::potentially_plural_count; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; -use rustc_index::IndexVec; +use rustc_index::{Idx, IndexVec}; use rustc_infer::infer::{DefineOpaqueTypes, InferOk, TypeTrace}; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::TypeError; @@ -21,7 +21,7 @@ use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_session::Session; -use rustc_span::{DUMMY_SP, Ident, Span, kw, sym}; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use rustc_trait_selection::error_reporting::infer::{FailureCode, ObligationCauseExt}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt, SelectionContext}; @@ -126,7 +126,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Err(guar) => Err(guar), }; if let Err(guar) = has_error { - let err_inputs = self.err_args(args_no_rcvr.len(), guar); + let err_inputs = self.err_args( + method.map_or(args_no_rcvr.len(), |method| method.sig.inputs().len() - 1), + guar, + ); let err_output = Ty::new_error(self.tcx, guar); let err_inputs = match tuple_arguments { @@ -2374,11 +2377,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let check_for_matched_generics = || { if matched_inputs.iter().any(|x| x.is_some()) - && params_with_generics.iter().any(|x| x.1.is_some()) + && params_with_generics.iter().any(|(x, _)| x.is_some()) { - for &(idx, generic, _) in ¶ms_with_generics { + for (idx, (generic, _)) in params_with_generics.iter_enumerated() { // Param has to have a generic and be matched to be relevant - if matched_inputs[idx.into()].is_none() { + if matched_inputs[idx].is_none() { continue; } @@ -2386,10 +2389,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { continue; }; - for unmatching_idx in idx + 1..params_with_generics.len() { - if matched_inputs[unmatching_idx.into()].is_none() + for unmatching_idx in + idx.plus(1)..ExpectedIdx::from_usize(params_with_generics.len()) + { + if matched_inputs[unmatching_idx].is_none() && let Some(unmatched_idx_param_generic) = - params_with_generics[unmatching_idx].1 + params_with_generics[unmatching_idx].0 && unmatched_idx_param_generic.name.ident() == generic.name.ident() { @@ -2404,61 +2409,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let check_for_matched_generics = check_for_matched_generics(); - for &(idx, generic_param, param) in - params_with_generics.iter().filter(|&(idx, _, _)| { + for (idx, &(generic_param, param)) in + params_with_generics.iter_enumerated().filter(|&(idx, _)| { check_for_matched_generics - || expected_idx.is_none_or(|expected_idx| expected_idx == *idx) + || expected_idx + .is_none_or(|expected_idx| expected_idx == idx.as_usize()) }) { let Some(generic_param) = generic_param else { - spans.push_span_label(param.span, ""); + spans.push_span_label(param.span(), ""); continue; }; - let other_params_matched: Vec<(usize, &hir::Param<'_>)> = params_with_generics - .iter() - .filter(|(other_idx, other_generic_param, _)| { - if *other_idx == idx { - return false; - } - let Some(other_generic_param) = other_generic_param else { - return false; - }; - if matched_inputs[idx.into()].is_none() - && matched_inputs[(*other_idx).into()].is_none() - { - return false; - } - if matched_inputs[idx.into()].is_some() - && matched_inputs[(*other_idx).into()].is_some() - { - return false; - } - other_generic_param.name.ident() == generic_param.name.ident() - }) - .map(|&(other_idx, _, other_param)| (other_idx, other_param)) - .collect(); + let other_params_matched: Vec<(ExpectedIdx, FnParam<'_>)> = + params_with_generics + .iter_enumerated() + .filter(|&(other_idx, &(other_generic_param, _))| { + if other_idx == idx { + return false; + } + let Some(other_generic_param) = other_generic_param else { + return false; + }; + if matched_inputs[idx].is_none() + && matched_inputs[other_idx].is_none() + { + return false; + } + if matched_inputs[idx].is_some() + && matched_inputs[other_idx].is_some() + { + return false; + } + other_generic_param.name.ident() == generic_param.name.ident() + }) + .map(|(other_idx, &(_, other_param))| (other_idx, other_param)) + .collect(); if !other_params_matched.is_empty() { let other_param_matched_names: Vec = other_params_matched .iter() .map(|(idx, other_param)| { - if let hir::PatKind::Binding(_, _, ident, _) = other_param.pat.kind - { - format!("`{ident}`") + if let Some(name) = other_param.name() { + format!("`{name}`") } else { - format!("parameter #{}", idx + 1) + format!("parameter #{}", idx.as_u32() + 1) } }) .collect(); let matched_ty = self - .resolve_vars_if_possible(formal_and_expected_inputs[idx.into()].1) + .resolve_vars_if_possible(formal_and_expected_inputs[idx].1) .sort_string(self.tcx); - if matched_inputs[idx.into()].is_some() { + if matched_inputs[idx].is_some() { spans.push_span_label( - param.span, + param.span(), format!( "{} need{} to match the {} type of this parameter", listify(&other_param_matched_names, |n| n.to_string()) @@ -2473,7 +2479,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } else { spans.push_span_label( - param.span, + param.span(), format!( "this parameter needs to match the {} type of {}", matched_ty, @@ -2484,7 +2490,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } generics_with_unmatched_params.push(generic_param); } else { - spans.push_span_label(param.span, ""); + spans.push_span_label(param.span(), ""); } } @@ -2502,19 +2508,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) { let param_idents_matching: Vec = params_with_generics - .iter() - .filter(|(_, generic, _)| { + .iter_enumerated() + .filter(|&(_, &(generic, _))| { if let Some(generic) = generic { generic.name.ident() == generic_param.name.ident() } else { false } }) - .map(|(idx, _, param)| { - if let hir::PatKind::Binding(_, _, ident, _) = param.pat.kind { - format!("`{ident}`") + .map(|(idx, &(_, param))| { + if let Some(name) = param.name() { + format!("`{name}`") } else { - format!("parameter #{}", idx + 1) + format!("parameter #{}", idx.as_u32() + 1) } }) .collect(); @@ -2607,12 +2613,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(params_with_generics) = self.get_hir_params_with_generics(def_id, is_method) { debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); - for &(idx, generic_param, _) in ¶ms_with_generics { - if matched_inputs[idx.into()].is_none() { + for (idx, (generic_param, _)) in params_with_generics.iter_enumerated() { + if matched_inputs[idx].is_none() { continue; } - let Some((_, matched_arg_span)) = provided_arg_tys.get(idx.into()) else { + let Some((_, matched_arg_span)) = provided_arg_tys.get(idx.to_provided_idx()) + else { continue; }; @@ -2620,32 +2627,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { continue; }; - let mut idxs_matched: Vec = vec![]; - for &(other_idx, _, _) in - params_with_generics.iter().filter(|&&(other_idx, other_generic_param, _)| { + let idxs_matched = params_with_generics + .iter_enumerated() + .filter(|&(other_idx, (other_generic_param, _))| { if other_idx == idx { return false; } let Some(other_generic_param) = other_generic_param else { return false; }; - if matched_inputs[other_idx.into()].is_some() { + if matched_inputs[other_idx].is_some() { return false; } other_generic_param.name.ident() == generic_param.name.ident() }) - { - idxs_matched.push(other_idx); - } + .count(); - if idxs_matched.is_empty() { + if idxs_matched == 0 { continue; } let expected_display_type = self .resolve_vars_if_possible(formal_and_expected_inputs[idx.into()].1) .sort_string(self.tcx); - let label = if idxs_matched.len() == params_with_generics.len() - 1 { + let label = if idxs_matched == params_with_generics.len() - 1 { format!( "expected all arguments to be this {} type because they need to match the type of this parameter", expected_display_type @@ -2664,62 +2669,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// Returns the parameters of a function, with their generic parameters if those are the full - /// type of that parameter. Returns `None` if the function body is unavailable (eg is an instrinsic). + /// type of that parameter. Returns `None` if the function has no generics or the body is + /// unavailable (eg is an instrinsic). fn get_hir_params_with_generics( &self, def_id: DefId, is_method: bool, - ) -> Option>, &hir::Param<'_>)>> { - let fn_node = self.tcx.hir().get_if_local(def_id)?; - let fn_decl = fn_node.fn_decl()?; - - let generic_params: Vec>> = fn_decl - .inputs - .into_iter() - .skip(if is_method { 1 } else { 0 }) - .map(|param| { - if let hir::TyKind::Path(QPath::Resolved( - _, - hir::Path { res: Res::Def(_, res_def_id), .. }, - )) = param.kind - { - fn_node - .generics() - .into_iter() - .flat_map(|generics| generics.params) - .find(|param| ¶m.def_id.to_def_id() == res_def_id) - } else { - None - } + ) -> Option>, FnParam<'_>)>> { + let (sig, generics, body_id, param_names) = match self.tcx.hir().get_if_local(def_id)? { + hir::Node::TraitItem(&hir::TraitItem { + generics, + kind: hir::TraitItemKind::Fn(sig, trait_fn), + .. + }) => match trait_fn { + hir::TraitFn::Required(params) => (sig, generics, None, Some(params)), + hir::TraitFn::Provided(body) => (sig, generics, Some(body), None), + }, + hir::Node::ImplItem(&hir::ImplItem { + generics, + kind: hir::ImplItemKind::Fn(sig, body), + .. }) - .collect(); - - let mut params: Vec<&hir::Param<'_>> = self - .tcx - .hir() - .body(fn_node.body_id()?) - .params - .into_iter() - .skip(if is_method { 1 } else { 0 }) - .collect(); - - // The surrounding code expects variadic functions to not have a parameter representing - // the "..." parameter. This is already true of the FnDecl but not of the body params, so - // we drop it if it exists. + | hir::Node::Item(&hir::Item { + kind: hir::ItemKind::Fn { sig, generics, body, .. }, + .. + }) => (sig, generics, Some(body), None), + _ => return None, + }; - if fn_decl.c_variadic { - params.pop(); + // Make sure to remove both the receiver and variadic argument. Both are removed + // when matching parameter types. + let fn_inputs = sig.decl.inputs.get(is_method as usize..)?.iter().map(|param| { + if let hir::TyKind::Path(QPath::Resolved( + _, + &hir::Path { res: Res::Def(_, res_def_id), .. }, + )) = param.kind + { + generics.params.iter().find(|param| param.def_id.to_def_id() == res_def_id) + } else { + None + } + }); + match (body_id, param_names) { + (Some(_), Some(_)) | (None, None) => unreachable!(), + (Some(body), None) => { + let params = self.tcx.hir().body(body).params; + let params = + params.get(is_method as usize..params.len() - sig.decl.c_variadic as usize)?; + debug_assert_eq!(params.len(), fn_inputs.len()); + Some(fn_inputs.zip(params.iter().map(|param| FnParam::Param(param))).collect()) + } + (None, Some(params)) => { + let params = params.get(is_method as usize..)?; + debug_assert_eq!(params.len(), fn_inputs.len()); + Some(fn_inputs.zip(params.iter().map(|param| FnParam::Name(param))).collect()) + } } - - debug_assert_eq!(params.len(), generic_params.len()); - Some( - generic_params - .into_iter() - .zip(params) - .enumerate() - .map(|(a, (b, c))| (a, b, c)) - .collect(), - ) } } @@ -2742,3 +2747,27 @@ impl<'tcx> Visitor<'tcx> for FindClosureArg<'tcx> { hir::intravisit::walk_expr(self, ex); } } + +#[derive(Clone, Copy)] +enum FnParam<'hir> { + Param(&'hir hir::Param<'hir>), + Name(&'hir Ident), +} +impl FnParam<'_> { + fn span(&self) -> Span { + match self { + Self::Param(x) => x.span, + Self::Name(x) => x.span, + } + } + + fn name(&self) -> Option { + match self { + Self::Param(x) if let hir::PatKind::Binding(_, _, ident, _) = x.pat.kind => { + Some(ident.name) + } + Self::Name(x) if x.name != kw::Empty => Some(x.name), + _ => None, + } + } +} diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 7ff316ba83a61..76ad7fc890911 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1003,10 +1003,6 @@ extern "C" void LLVMRustDIBuilderDispose(LLVMDIBuilderRef Builder) { delete unwrap(Builder); } -extern "C" void LLVMRustDIBuilderFinalize(LLVMDIBuilderRef Builder) { - unwrap(Builder)->finalize(); -} - extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateCompileUnit( LLVMDIBuilderRef Builder, unsigned Lang, LLVMMetadataRef FileRef, const char *Producer, size_t ProducerLen, bool isOptimized, @@ -1183,20 +1179,6 @@ LLVMRustDIBuilderCreateQualifiedType(LLVMDIBuilderRef Builder, unsigned Tag, unwrap(Builder)->createQualifiedType(Tag, unwrapDI(Type))); } -extern "C" LLVMMetadataRef -LLVMRustDIBuilderCreateLexicalBlock(LLVMDIBuilderRef Builder, - LLVMMetadataRef Scope, LLVMMetadataRef File, - unsigned Line, unsigned Col) { - return wrap(unwrap(Builder)->createLexicalBlock( - unwrapDI(Scope), unwrapDI(File), Line, Col)); -} - -extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateLexicalBlockFile( - LLVMDIBuilderRef Builder, LLVMMetadataRef Scope, LLVMMetadataRef File) { - return wrap(unwrap(Builder)->createLexicalBlockFile( - unwrapDI(Scope), unwrapDI(File))); -} - extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateStaticVariable( LLVMDIBuilderRef Builder, LLVMMetadataRef Context, const char *Name, size_t NameLen, const char *LinkageName, size_t LinkageNameLen, @@ -1325,14 +1307,6 @@ extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateTemplateTypeParameter( unwrapDI(Ty), IsDefault)); } -extern "C" LLVMMetadataRef -LLVMRustDIBuilderCreateNameSpace(LLVMDIBuilderRef Builder, - LLVMMetadataRef Scope, const char *Name, - size_t NameLen, bool ExportSymbols) { - return wrap(unwrap(Builder)->createNameSpace( - unwrapDI(Scope), StringRef(Name, NameLen), ExportSymbols)); -} - extern "C" void LLVMRustDICompositeTypeReplaceArrays( LLVMDIBuilderRef Builder, LLVMMetadataRef CompositeTy, LLVMMetadataRef Elements, LLVMMetadataRef Params) { @@ -1341,16 +1315,6 @@ extern "C" void LLVMRustDICompositeTypeReplaceArrays( DINodeArray(unwrap(Params))); } -extern "C" LLVMMetadataRef -LLVMRustDIBuilderCreateDebugLocation(unsigned Line, unsigned Column, - LLVMMetadataRef ScopeRef, - LLVMMetadataRef InlinedAt) { - MDNode *Scope = unwrapDIPtr(ScopeRef); - DILocation *Loc = DILocation::get(Scope->getContext(), Line, Column, Scope, - unwrapDIPtr(InlinedAt)); - return wrap(Loc); -} - extern "C" LLVMMetadataRef LLVMRustDILocationCloneWithBaseDiscriminator(LLVMMetadataRef Location, unsigned BD) { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index aa8ec3aff040a..3007b78749683 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1104,6 +1104,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_build/src/thir/cx/block.rs b/compiler/rustc_mir_build/src/thir/cx/block.rs index c9df027687ab9..e858b629ab14b 100644 --- a/compiler/rustc_mir_build/src/thir/cx/block.rs +++ b/compiler/rustc_mir_build/src/thir/cx/block.rs @@ -6,9 +6,9 @@ use rustc_middle::ty; use rustc_middle::ty::CanonicalUserTypeAnnotation; use tracing::debug; -use crate::thir::cx::Cx; +use crate::thir::cx::ThirBuildCx; -impl<'tcx> Cx<'tcx> { +impl<'tcx> ThirBuildCx<'tcx> { pub(crate) fn mirror_block(&mut self, block: &'tcx hir::Block<'tcx>) -> BlockId { // We have to eagerly lower the "spine" of the statements // in order to get the lexical scoping correctly. diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 795ac6b4bea54..54da6924db444 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -21,10 +21,9 @@ use rustc_middle::{bug, span_bug}; use rustc_span::{Span, sym}; use tracing::{debug, info, instrument, trace}; -use crate::thir::cx::Cx; -use crate::thir::util::UserAnnotatedTyHelpers; +use crate::thir::cx::ThirBuildCx; -impl<'tcx> Cx<'tcx> { +impl<'tcx> ThirBuildCx<'tcx> { /// Create a THIR expression for the given HIR expression. This expands all /// adjustments and directly adds the type information from the /// `typeck_results`. See the [dev-guide] for more details. @@ -142,9 +141,9 @@ impl<'tcx> Cx<'tcx> { Adjust::Deref(Some(deref)) => { // We don't need to do call adjust_span here since // deref coercions always start with a built-in deref. - let call_def_id = deref.method_call(self.tcx()); + let call_def_id = deref.method_call(self.tcx); let overloaded_callee = - Ty::new_fn_def(self.tcx(), call_def_id, self.tcx().mk_args(&[expr.ty.into()])); + Ty::new_fn_def(self.tcx, call_def_id, self.tcx.mk_args(&[expr.ty.into()])); expr = Expr { temp_lifetime, @@ -253,10 +252,10 @@ impl<'tcx> Cx<'tcx> { // Check to see if this cast is a "coercion cast", where the cast is actually done // using a coercion (or is a no-op). - if self.typeck_results().is_coercion_cast(source.hir_id) { + if self.typeck_results.is_coercion_cast(source.hir_id) { // Convert the lexpr to a vexpr. ExprKind::Use { source: self.mirror_expr(source) } - } else if self.typeck_results().expr_ty(source).is_ref() { + } else if self.typeck_results.expr_ty(source).is_ref() { // Special cased so that we can type check that the element // type of the source matches the pointed to type of the // destination. @@ -266,8 +265,8 @@ impl<'tcx> Cx<'tcx> { is_from_as_cast: true, } } else if let hir::ExprKind::Path(ref qpath) = source.kind - && let res = self.typeck_results().qpath_res(qpath, source.hir_id) - && let ty = self.typeck_results().node_type(source.hir_id) + && let res = self.typeck_results.qpath_res(qpath, source.hir_id) + && let ty = self.typeck_results.node_type(source.hir_id) && let ty::Adt(adt_def, args) = ty.kind() && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res { @@ -330,7 +329,7 @@ impl<'tcx> Cx<'tcx> { #[instrument(level = "debug", skip(self), ret)] fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; - let expr_ty = self.typeck_results().expr_ty(expr); + let expr_ty = self.typeck_results.expr_ty(expr); let (temp_lifetime, backwards_incompatible) = self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); @@ -354,7 +353,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Call(fun, ref args) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { // The callee is something implementing Fn, FnMut, or FnOnce. // Find the actual method implementation being called and // build the appropriate UFCS call expression with the @@ -364,7 +363,7 @@ impl<'tcx> Cx<'tcx> { let method = self.method_callee(expr, fun.span, None); - let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e)); + let arg_tys = args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, arg_tys), temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, @@ -380,7 +379,7 @@ impl<'tcx> Cx<'tcx> { from_hir_call: true, fn_span: expr.span, } - } else if let ty::FnDef(def_id, _) = self.typeck_results().expr_ty(fun).kind() + } else if let ty::FnDef(def_id, _) = self.typeck_results.expr_ty(fun).kind() && let Some(intrinsic) = self.tcx.intrinsic(def_id) && intrinsic.name == sym::box_new { @@ -413,7 +412,7 @@ impl<'tcx> Cx<'tcx> { }, hir::QPath::TypeRelative(_ty, _) => { if let Some((DefKind::Ctor(_, CtorKind::Fn), ctor_id)) = - self.typeck_results().type_dependent_def(fun.hir_id) + self.typeck_results.type_dependent_def(fun.hir_id) { Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id))) } else { @@ -426,8 +425,8 @@ impl<'tcx> Cx<'tcx> { None }; if let Some((adt_def, index)) = adt_data { - let node_args = self.typeck_results().node_args(fun.hir_id); - let user_provided_types = self.typeck_results().user_provided_types(); + let node_args = self.typeck_results.node_args(fun.hir_id); + let user_provided_types = self.typeck_results.user_provided_types(); let user_ty = user_provided_types.get(fun.hir_id).copied().map(|mut u_ty| { if let ty::UserTypeKind::TypeOf(ref mut did, _) = @@ -457,7 +456,7 @@ impl<'tcx> Cx<'tcx> { })) } else { ExprKind::Call { - ty: self.typeck_results().node_type(fun.hir_id), + ty: self.typeck_results.node_type(fun.hir_id), fun: self.mirror_expr(fun), args: self.mirror_exprs(args), from_hir_call: true, @@ -482,7 +481,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::AssignOp(op, lhs, rhs) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let lhs = self.mirror_expr(lhs); let rhs = self.mirror_expr(rhs); self.overloaded_operator(expr, Box::new([lhs, rhs])) @@ -498,7 +497,7 @@ impl<'tcx> Cx<'tcx> { hir::ExprKind::Lit(lit) => ExprKind::Literal { lit, neg: false }, hir::ExprKind::Binary(op, lhs, rhs) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let lhs = self.mirror_expr(lhs); let rhs = self.mirror_expr(rhs); self.overloaded_operator(expr, Box::new([lhs, rhs])) @@ -527,7 +526,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Index(lhs, index, brackets_span) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let lhs = self.mirror_expr(lhs); let index = self.mirror_expr(index); self.overloaded_place( @@ -543,7 +542,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Unary(hir::UnOp::Deref, arg) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let arg = self.mirror_expr(arg); self.overloaded_place(expr, expr_ty, None, Box::new([arg]), expr.span) } else { @@ -552,7 +551,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Unary(hir::UnOp::Not, arg) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let arg = self.mirror_expr(arg); self.overloaded_operator(expr, Box::new([arg])) } else { @@ -561,7 +560,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Unary(hir::UnOp::Neg, arg) => { - if self.typeck_results().is_method_call(expr) { + if self.typeck_results.is_method_call(expr) { let arg = self.mirror_expr(arg); self.overloaded_operator(expr, Box::new([arg])) } else if let hir::ExprKind::Lit(lit) = arg.kind { @@ -574,7 +573,7 @@ impl<'tcx> Cx<'tcx> { hir::ExprKind::Struct(qpath, fields, ref base) => match expr_ty.kind() { ty::Adt(adt, args) => match adt.adt_kind() { AdtKind::Struct | AdtKind::Union => { - let user_provided_types = self.typeck_results().user_provided_types(); + let user_provided_types = self.typeck_results.user_provided_types(); let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new); debug!("make_mirror_unadjusted: (struct/union) user_ty={:?}", user_ty); ExprKind::Adt(Box::new(AdtExpr { @@ -586,15 +585,14 @@ impl<'tcx> Cx<'tcx> { base: match base { hir::StructTailExpr::Base(base) => AdtExprBase::Base(FruInfo { base: self.mirror_expr(base), - field_types: self.typeck_results().fru_field_types() - [expr.hir_id] + field_types: self.typeck_results.fru_field_types()[expr.hir_id] .iter() .copied() .collect(), }), hir::StructTailExpr::DefaultFields(_) => { AdtExprBase::DefaultFields( - self.typeck_results().fru_field_types()[expr.hir_id] + self.typeck_results.fru_field_types()[expr.hir_id] .iter() .copied() .collect(), @@ -605,7 +603,7 @@ impl<'tcx> Cx<'tcx> { })) } AdtKind::Enum => { - let res = self.typeck_results().qpath_res(qpath, expr.hir_id); + let res = self.typeck_results.qpath_res(qpath, expr.hir_id); match res { Res::Def(DefKind::Variant, variant_id) => { assert!(matches!( @@ -615,8 +613,7 @@ impl<'tcx> Cx<'tcx> { )); let index = adt.variant_index_with_id(variant_id); - let user_provided_types = - self.typeck_results().user_provided_types(); + let user_provided_types = self.typeck_results.user_provided_types(); let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new); debug!("make_mirror_unadjusted: (variant) user_ty={:?}", user_ty); @@ -629,8 +626,7 @@ impl<'tcx> Cx<'tcx> { base: match base { hir::StructTailExpr::DefaultFields(_) => { AdtExprBase::DefaultFields( - self.typeck_results().fru_field_types() - [expr.hir_id] + self.typeck_results.fru_field_types()[expr.hir_id] .iter() .copied() .collect(), @@ -655,7 +651,7 @@ impl<'tcx> Cx<'tcx> { }, hir::ExprKind::Closure { .. } => { - let closure_ty = self.typeck_results().expr_ty(expr); + let closure_ty = self.typeck_results.expr_ty(expr); let (def_id, args, movability) = match *closure_ty.kind() { ty::Closure(def_id, args) => (def_id, UpvarArgs::Closure(args), None), ty::Coroutine(def_id, args) => { @@ -703,7 +699,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Path(ref qpath) => { - let res = self.typeck_results().qpath_res(qpath, expr.hir_id); + let res = self.typeck_results.qpath_res(qpath, expr.hir_id); self.convert_path_expr(expr, res) } @@ -772,7 +768,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::ConstBlock(ref anon_const) => { - let ty = self.typeck_results().node_type(anon_const.hir_id); + let ty = self.typeck_results.node_type(anon_const.hir_id); let did = anon_const.def_id.to_def_id(); let typeck_root_def_id = tcx.typeck_root_def_id(did); let parent_args = @@ -783,7 +779,7 @@ impl<'tcx> Cx<'tcx> { } // Now comes the rote stuff: hir::ExprKind::Repeat(v, _) => { - let ty = self.typeck_results().expr_ty(expr); + let ty = self.typeck_results.expr_ty(expr); let ty::Array(_, count) = ty.kind() else { span_bug!(expr.span, "unexpected repeat expr ty: {:?}", ty); }; @@ -837,7 +833,7 @@ impl<'tcx> Cx<'tcx> { match_source, }, hir::ExprKind::Loop(body, ..) => { - let block_ty = self.typeck_results().node_type(body.hir_id); + let block_ty = self.typeck_results.node_type(body.hir_id); let (temp_lifetime, backwards_incompatible) = self .rvalue_scopes .temporary_scope(self.region_scope_tree, body.hir_id.local_id); @@ -957,7 +953,7 @@ impl<'tcx> Cx<'tcx> { | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) | Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => { - self.typeck_results().user_provided_types().get(hir_id).copied().map(Box::new) + self.typeck_results.user_provided_types().get(hir_id).copied().map(Box::new) } // A unit struct/variant which is used as a value (e.g., @@ -989,17 +985,13 @@ impl<'tcx> Cx<'tcx> { Some(fn_def) => (fn_def, None), None => { let (kind, def_id) = - self.typeck_results().type_dependent_def(expr.hir_id).unwrap_or_else(|| { + self.typeck_results.type_dependent_def(expr.hir_id).unwrap_or_else(|| { span_bug!(expr.span, "no type-dependent def for method callee") }); let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(kind, def_id)); debug!("method_callee: user_ty={:?}", user_ty); ( - Ty::new_fn_def( - self.tcx(), - def_id, - self.typeck_results().node_args(expr.hir_id), - ), + Ty::new_fn_def(self.tcx, def_id, self.typeck_results.node_args(expr.hir_id)), user_ty, ) } @@ -1025,7 +1017,7 @@ impl<'tcx> Cx<'tcx> { } fn convert_path_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, res: Res) -> ExprKind<'tcx> { - let args = self.typeck_results().node_args(expr.hir_id); + let args = self.typeck_results.node_args(expr.hir_id); match res { // A regular function, constructor function or a constant. Res::Def(DefKind::Fn, _) @@ -1060,7 +1052,7 @@ impl<'tcx> Cx<'tcx> { let user_provided_types = self.typeck_results.user_provided_types(); let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new); debug!("convert_path_expr: user_ty={:?}", user_ty); - let ty = self.typeck_results().node_type(expr.hir_id); + let ty = self.typeck_results.node_type(expr.hir_id); match ty.kind() { // A unit struct/variant which is used as a value. // We return a completely different ExprKind here to account for this special case. diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index a98c2bb61f666..9d114e6755931 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -16,15 +16,15 @@ use rustc_middle::ty::{self, RvalueScopes, TyCtxt}; use tracing::instrument; use crate::thir::pattern::pat_from_hir; -use crate::thir::util::UserAnnotatedTyHelpers; +/// Query implementation for [`TyCtxt::thir_body`]. pub(crate) fn thir_body( tcx: TyCtxt<'_>, owner_def: LocalDefId, ) -> Result<(&Steal>, ExprId), ErrorGuaranteed> { let hir = tcx.hir(); let body = hir.body_owned_by(owner_def); - let mut cx = Cx::new(tcx, owner_def); + let mut cx = ThirBuildCx::new(tcx, owner_def); if let Some(reported) = cx.typeck_results.tainted_by_errors { return Err(reported); } @@ -52,8 +52,10 @@ pub(crate) fn thir_body( Ok((tcx.alloc_steal_thir(cx.thir), expr)) } -struct Cx<'tcx> { +/// Context for lowering HIR to THIR for a single function body (or other kind of body). +struct ThirBuildCx<'tcx> { tcx: TyCtxt<'tcx>, + /// The THIR data that this context is building. thir: Thir<'tcx>, typing_env: ty::TypingEnv<'tcx>, @@ -69,8 +71,8 @@ struct Cx<'tcx> { body_owner: DefId, } -impl<'tcx> Cx<'tcx> { - fn new(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Cx<'tcx> { +impl<'tcx> ThirBuildCx<'tcx> { + fn new(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Self { let typeck_results = tcx.typeck(def); let hir = tcx.hir(); let hir_id = tcx.local_def_id_to_hir_id(def); @@ -94,7 +96,7 @@ impl<'tcx> Cx<'tcx> { BodyTy::Const(typeck_results.node_type(hir_id)) }; - Cx { + Self { tcx, thir: Thir::new(body_type), // FIXME(#132279): We're in a body, we should use a typing @@ -113,7 +115,7 @@ impl<'tcx> Cx<'tcx> { #[instrument(level = "debug", skip(self))] fn pattern_from_hir(&mut self, p: &'tcx hir::Pat<'tcx>) -> Box> { - pat_from_hir(self.tcx, self.typing_env, self.typeck_results(), p) + pat_from_hir(self.tcx, self.typing_env, self.typeck_results, p) } fn closure_env_param(&self, owner_def: LocalDefId, expr_id: HirId) -> Option> { @@ -197,15 +199,12 @@ impl<'tcx> Cx<'tcx> { Param { pat: Some(pat), ty, ty_span, self_kind, hir_id: Some(param.hir_id) } }) } -} - -impl<'tcx> UserAnnotatedTyHelpers<'tcx> for Cx<'tcx> { - fn tcx(&self) -> TyCtxt<'tcx> { - self.tcx - } - fn typeck_results(&self) -> &ty::TypeckResults<'tcx> { - self.typeck_results + fn user_args_applied_to_ty_of_hir_id( + &self, + hir_id: HirId, + ) -> Option> { + crate::thir::util::user_args_applied_to_ty_of_hir_id(self.typeck_results, hir_id) } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 0aa61152330a7..8ecdbecf1651a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -27,7 +27,6 @@ use tracing::{debug, instrument}; pub(crate) use self::check_match::check_match; use crate::errors::*; use crate::fluent_generated as fluent; -use crate::thir::util::UserAnnotatedTyHelpers; struct PatCtxt<'a, 'tcx> { tcx: TyCtxt<'tcx>, @@ -540,16 +539,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { _ => { let e = match res { Res::Def(DefKind::ConstParam, def_id) => { - self.tcx.dcx().emit_err(ConstParamInPattern { - span, - const_span: self.tcx().def_span(def_id), - }) + let const_span = self.tcx.def_span(def_id); + self.tcx.dcx().emit_err(ConstParamInPattern { span, const_span }) } Res::Def(DefKind::Static { .. }, def_id) => { - self.tcx.dcx().emit_err(StaticInPattern { - span, - static_span: self.tcx().def_span(def_id), - }) + let static_span = self.tcx.def_span(def_id); + self.tcx.dcx().emit_err(StaticInPattern { span, static_span }) } _ => self.tcx.dcx().emit_err(NonConstPath { span }), }; @@ -573,6 +568,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { kind } + fn user_args_applied_to_ty_of_hir_id( + &self, + hir_id: hir::HirId, + ) -> Option> { + crate::thir::util::user_args_applied_to_ty_of_hir_id(self.typeck_results, hir_id) + } + /// Takes a HIR Path. If the path is a constant, evaluates it and feeds /// it to `const_to_pat`. Any other path (like enum variants without fields) /// is converted to the corresponding pattern via `lower_variant_or_leaf`. @@ -603,12 +605,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { return pattern; } - let user_provided_types = self.typeck_results().user_provided_types(); + let user_provided_types = self.typeck_results.user_provided_types(); if let Some(&user_ty) = user_provided_types.get(id) { let annotation = CanonicalUserTypeAnnotation { user_ty: Box::new(user_ty), span, - inferred_ty: self.typeck_results().node_type(id), + inferred_ty: self.typeck_results.node_type(id), }; Box::new(Pat { span, @@ -672,13 +674,3 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { self.const_to_pat(constant, ct_ty, expr.hir_id, lit.span).kind } } - -impl<'tcx> UserAnnotatedTyHelpers<'tcx> for PatCtxt<'_, 'tcx> { - fn tcx(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn typeck_results(&self) -> &ty::TypeckResults<'tcx> { - self.typeck_results - } -} diff --git a/compiler/rustc_mir_build/src/thir/util.rs b/compiler/rustc_mir_build/src/thir/util.rs index ed7c7e4099304..4dff093afd0d9 100644 --- a/compiler/rustc_mir_build/src/thir/util.rs +++ b/compiler/rustc_mir_build/src/thir/util.rs @@ -1,33 +1,27 @@ use rustc_hir as hir; use rustc_middle::bug; -use rustc_middle::ty::{self, CanonicalUserType, TyCtxt}; +use rustc_middle::ty::{self, CanonicalUserType}; use tracing::debug; -pub(crate) trait UserAnnotatedTyHelpers<'tcx> { - fn tcx(&self) -> TyCtxt<'tcx>; - - fn typeck_results(&self) -> &ty::TypeckResults<'tcx>; - - /// Looks up the type associated with this hir-id and applies the - /// user-given generic parameters; the hir-id must map to a suitable - /// type. - fn user_args_applied_to_ty_of_hir_id( - &self, - hir_id: hir::HirId, - ) -> Option> { - let user_provided_types = self.typeck_results().user_provided_types(); - let mut user_ty = *user_provided_types.get(hir_id)?; - debug!("user_subts_applied_to_ty_of_hir_id: user_ty={:?}", user_ty); - let ty = self.typeck_results().node_type(hir_id); - match ty.kind() { - ty::Adt(adt_def, ..) => { - if let ty::UserTypeKind::TypeOf(ref mut did, _) = &mut user_ty.value.kind { - *did = adt_def.did(); - } - Some(user_ty) +/// Looks up the type associated with this hir-id and applies the +/// user-given generic parameters; the hir-id must map to a suitable +/// type. +pub(crate) fn user_args_applied_to_ty_of_hir_id<'tcx>( + typeck_results: &ty::TypeckResults<'tcx>, + hir_id: hir::HirId, +) -> Option> { + let user_provided_types = typeck_results.user_provided_types(); + let mut user_ty = *user_provided_types.get(hir_id)?; + debug!("user_subts_applied_to_ty_of_hir_id: user_ty={:?}", user_ty); + let ty = typeck_results.node_type(hir_id); + match ty.kind() { + ty::Adt(adt_def, ..) => { + if let ty::UserTypeKind::TypeOf(ref mut did, _) = &mut user_ty.value.kind { + *did = adt_def.did(); } - ty::FnDef(..) => Some(user_ty), - _ => bug!("ty: {:?} should not have user provided type {:?} recorded ", ty, user_ty), + Some(user_ty) } + ty::FnDef(..) => Some(user_ty), + _ => bug!("ty: {:?} should not have user provided type {:?} recorded ", ty, user_ty), } } 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 382b12638f43f..e0cb68323d00b 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/fmt/builders.rs b/library/core/src/fmt/builders.rs index 1862be0e86c5d..665b05b12ec07 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -1228,6 +1228,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// assert_eq!(format!("{:?}", wrapped), "'a'"); /// ``` #[unstable(feature = "debug_closure_helpers", issue = "117729")] +#[must_use = "returns a type implementing Debug and Display, which do not have any effects unless they are used"] pub fn from_fn) -> fmt::Result>(f: F) -> FromFn { FromFn(f) } 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/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 5fcf7eda8df79..441674936c666 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -11,12 +11,12 @@ use std::str::FromStr; use std::{env, process}; use bootstrap::{ - Build, CONFIG_CHANGE_HISTORY, Config, Flags, Subcommand, find_recent_config_change_ids, + Build, CONFIG_CHANGE_HISTORY, Config, Flags, Subcommand, debug, find_recent_config_change_ids, human_readable_changes, t, }; use build_helper::ci::CiEnv; #[cfg(feature = "tracing")] -use tracing::{debug, instrument}; +use tracing::instrument; #[cfg_attr(feature = "tracing", instrument(level = "trace", name = "main"))] fn main() { @@ -29,10 +29,8 @@ fn main() { return; } - #[cfg(feature = "tracing")] debug!("parsing flags"); let flags = Flags::parse(&args); - #[cfg(feature = "tracing")] debug!("parsing config based on flags"); let config = Config::parse(flags); @@ -95,7 +93,6 @@ fn main() { let dump_bootstrap_shims = config.dump_bootstrap_shims; let out_dir = config.out.clone(); - #[cfg(feature = "tracing")] debug!("creating new build based on config"); Build::new(config).build(); @@ -207,8 +204,9 @@ fn check_version(config: &Config) -> Option { // Due to the conditional compilation via the `tracing` cargo feature, this means that `tracing` // usages in bootstrap need to be also gated behind the `tracing` feature: // -// - `tracing` macros (like `trace!`) and anything from `tracing`, `tracing_subscriber` and -// `tracing-tree` will need to be gated by `#[cfg(feature = "tracing")]`. +// - `tracing` macros with log levels (`trace!`, `debug!`, `warn!`, `info`, `error`) should not be +// used *directly*. You should use the wrapped `tracing` macros which gate the actual invocations +// behind `feature = "tracing"`. // - `tracing`'s `#[instrument(..)]` macro will need to be gated like `#![cfg_attr(feature = // "tracing", instrument(..))]`. #[cfg(feature = "tracing")] diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index d56f35f866cb8..2dd83d5938e9d 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -28,8 +28,6 @@ use std::{env, fs, io, str}; use build_helper::ci::gha; use build_helper::exit; use termcolor::{ColorChoice, StandardStream, WriteColor}; -#[cfg(feature = "tracing")] -use tracing::{debug, instrument, span, trace}; use utils::build_stamp::BuildStamp; use utils::channel::GitInfo; @@ -46,6 +44,8 @@ pub use core::builder::PathSet; pub use core::config::Config; pub use core::config::flags::{Flags, Subcommand}; +#[cfg(feature = "tracing")] +use tracing::{instrument, span}; pub use utils::change_tracker::{ CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes, }; @@ -541,72 +541,71 @@ impl Build { /// Executes the entire build, as configured by the flags and configuration. #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Build::build", skip_all))] pub fn build(&mut self) { - #[cfg(feature = "tracing")] trace!("setting up job management"); unsafe { crate::utils::job::setup(self); } - #[cfg(feature = "tracing")] - trace!("downloading rustfmt early"); - // Download rustfmt early so that it can be used in rust-analyzer configs. + trace!("downloading rustfmt early"); let _ = &builder::Builder::new(self).initial_rustfmt(); - #[cfg(feature = "tracing")] - let hardcoded_span = - span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Suggest, Perf)") - .entered(); - - // hardcoded subcommands - match &self.config.cmd { - Subcommand::Format { check, all } => { - return core::build_steps::format::format( - &builder::Builder::new(self), - *check, - *all, - &self.config.paths, - ); - } - Subcommand::Suggest { run } => { - return core::build_steps::suggest::suggest(&builder::Builder::new(self), *run); - } - Subcommand::Perf { .. } => { - return core::build_steps::perf::perf(&builder::Builder::new(self)); - } - _cmd => { - #[cfg(feature = "tracing")] - debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling"); + // Handle hard-coded subcommands. + { + #[cfg(feature = "tracing")] + let _hardcoded_span = span!( + tracing::Level::DEBUG, + "handling hardcoded subcommands (Format, Suggest, Perf)" + ) + .entered(); + + match &self.config.cmd { + Subcommand::Format { check, all } => { + return core::build_steps::format::format( + &builder::Builder::new(self), + *check, + *all, + &self.config.paths, + ); + } + Subcommand::Suggest { run } => { + return core::build_steps::suggest::suggest(&builder::Builder::new(self), *run); + } + Subcommand::Perf { .. } => { + return core::build_steps::perf::perf(&builder::Builder::new(self)); + } + _cmd => { + debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling"); + } } - } - #[cfg(feature = "tracing")] - drop(hardcoded_span); - #[cfg(feature = "tracing")] - debug!("handling subcommand normally"); + debug!("handling subcommand normally"); + } if !self.config.dry_run() { #[cfg(feature = "tracing")] let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered(); + // We first do a dry-run. This is a sanity-check to ensure that + // steps don't do anything expensive in the dry-run. { #[cfg(feature = "tracing")] let _sanity_check_span = span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered(); - - // We first do a dry-run. This is a sanity-check to ensure that - // steps don't do anything expensive in the dry-run. self.config.dry_run = DryRun::SelfCheck; let builder = builder::Builder::new(self); builder.execute_cli(); } - #[cfg(feature = "tracing")] - let _actual_run_span = - span!(tracing::Level::DEBUG, "(2) executing actual run").entered(); - self.config.dry_run = DryRun::Disabled; - let builder = builder::Builder::new(self); - builder.execute_cli(); + // Actual run. + { + #[cfg(feature = "tracing")] + let _actual_run_span = + span!(tracing::Level::DEBUG, "(2) executing actual run").entered(); + self.config.dry_run = DryRun::Disabled; + let builder = builder::Builder::new(self); + builder.execute_cli(); + } } else { #[cfg(feature = "tracing")] let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered(); diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index ea56932b40437..caef8ce3088a7 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -14,6 +14,8 @@ pub(crate) mod render_tests; pub(crate) mod shared_helpers; pub(crate) mod tarball; +pub(crate) mod tracing; + #[cfg(feature = "build-metrics")] pub(crate) mod metrics; diff --git a/src/bootstrap/src/utils/tracing.rs b/src/bootstrap/src/utils/tracing.rs new file mode 100644 index 0000000000000..e89decf9e5586 --- /dev/null +++ b/src/bootstrap/src/utils/tracing.rs @@ -0,0 +1,49 @@ +//! Wrapper macros for `tracing` macros to avoid having to write `cfg(feature = "tracing")`-gated +//! `debug!`/`trace!` everytime, e.g. +//! +//! ```rust,ignore (example) +//! #[cfg(feature = "tracing")] +//! trace!("..."); +//! ``` +//! +//! When `feature = "tracing"` is inactive, these macros expand to nothing. + +#[macro_export] +macro_rules! trace { + ($($tokens:tt)*) => { + #[cfg(feature = "tracing")] + ::tracing::trace!($($tokens)*) + } +} + +#[macro_export] +macro_rules! debug { + ($($tokens:tt)*) => { + #[cfg(feature = "tracing")] + ::tracing::debug!($($tokens)*) + } +} + +#[macro_export] +macro_rules! warn { + ($($tokens:tt)*) => { + #[cfg(feature = "tracing")] + ::tracing::warn!($($tokens)*) + } +} + +#[macro_export] +macro_rules! info { + ($($tokens:tt)*) => { + #[cfg(feature = "tracing")] + ::tracing::info!($($tokens)*) + } +} + +#[macro_export] +macro_rules! error { + ($($tokens:tt)*) => { + #[cfg(feature = "tracing")] + ::tracing::error!($($tokens)*) + } +} diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 7eb1b8df2333d..3abd538d372c4 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -330,7 +330,7 @@ the source. ## `--show-type-layout`: add a section to each type's docs describing its memory layout -* Tracking issue: [#113248](https://github.com/rust-lang/rust/issues/113248) + * Tracking issue: [#113248](https://github.com/rust-lang/rust/issues/113248) Using this flag looks like this: @@ -526,9 +526,10 @@ use `-o -`. ### json + * Tracking Issue: [#76578](https://github.com/rust-lang/rust/issues/76578) + `--output-format json` emits documentation in the experimental -[JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types/). `--output-format html` has no effect, -and is also accepted on stable toolchains. +[JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types/). JSON Output for toolchain crates (`std`, `alloc`, `core`, `test`, and `proc_macro`) is available via the `rust-docs-json` rustup component. @@ -546,11 +547,11 @@ information. ### doctest + * Tracking issue: [#134529](https://github.com/rust-lang/rust/issues/134529) + `--output-format doctest` emits JSON on stdout which gives you information about doctests in the provided crate. -Tracking issue: [#134529](https://github.com/rust-lang/rust/issues/134529) - You can use this option like this: ```bash @@ -606,6 +607,11 @@ The generated output (formatted) will look like this: * `doctest_code` is the code modified by rustdoc that will be run. If there is a fatal syntax error, this field will not be present. * `name` is the name generated by rustdoc which represents this doctest. +### html + +`--output-format html` has no effect, as the default output is HTML. This is +accepted on stable, even though the other options for this flag aren't. + ## `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests * Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index bfd5cb7764fd4..a348c6c5678b9 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -196,15 +196,13 @@ function switchDisplayedElement(elemToDisplay) { removeClass(el, "hidden"); const mainHeading = elemToDisplay.querySelector(".main-heading"); - // @ts-expect-error - if (mainHeading && searchState.rustdocToolbar) { - // @ts-expect-error - if (searchState.rustdocToolbar.parentElement) { - // @ts-expect-error - searchState.rustdocToolbar.parentElement.removeChild(searchState.rustdocToolbar); + if (mainHeading && window.searchState.rustdocToolbar) { + if (window.searchState.rustdocToolbar.parentElement) { + window.searchState.rustdocToolbar.parentElement.removeChild( + window.searchState.rustdocToolbar, + ); } - // @ts-expect-error - mainHeading.appendChild(searchState.rustdocToolbar); + mainHeading.appendChild(window.searchState.rustdocToolbar); } } @@ -212,7 +210,12 @@ function browserSupportsHistoryApi() { return window.history && typeof window.history.pushState === "function"; } -// @ts-expect-error +/** + * Download CSS from the web without making it the active stylesheet. + * We use this in the settings popover so that you don't get FOUC when switching. + * + * @param {string} cssUrl + */ function preLoadCss(cssUrl) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload const link = document.createElement("link"); @@ -225,7 +228,11 @@ function preLoadCss(cssUrl) { (function() { const isHelpPage = window.location.pathname.endsWith("/help.html"); - // @ts-expect-error + /** + * Run a JavaScript file asynchronously. + * @param {string} url + * @param {function(): any} errorCallback + */ function loadScript(url, errorCallback) { const script = document.createElement("script"); script.src = url; @@ -235,13 +242,12 @@ function preLoadCss(cssUrl) { document.head.append(script); } - if (getSettingsButton()) { - // @ts-expect-error - getSettingsButton().onclick = event => { + const settingsButton = getSettingsButton(); + if (settingsButton) { + settingsButton.onclick = event => { if (event.ctrlKey || event.altKey || event.metaKey) { return; } - // @ts-expect-error window.hideAllModals(false); addClass(getSettingsButton(), "rotate"); event.preventDefault(); @@ -470,7 +476,6 @@ function preLoadCss(cssUrl) { } return onEachLazy(implElem.parentElement.parentElement.querySelectorAll( `[id^="${assocId}"]`), - // @ts-expect-error item => { const numbered = /^(.+?)-([0-9]+)$/.exec(item.id); if (item.id === assocId || (numbered && numbered[1] === assocId)) { @@ -522,7 +527,6 @@ function preLoadCss(cssUrl) { ev.preventDefault(); // @ts-expect-error searchState.defocus(); - // @ts-expect-error window.hideAllModals(true); // true = reset focus for tooltips } @@ -687,7 +691,6 @@ function preLoadCss(cssUrl) { // // By the way, this is only used by and useful for traits implemented automatically // (like "Send" and "Sync"). - // @ts-expect-error onEachLazy(synthetic_implementors.getElementsByClassName("impl"), el => { const aliases = el.getAttribute("data-aliases"); if (!aliases) { @@ -740,7 +743,6 @@ function preLoadCss(cssUrl) { code.innerHTML = struct[TEXT_IDX]; addClass(code, "code-header"); - // @ts-expect-error onEachLazy(code.getElementsByTagName("a"), elem => { const href = elem.getAttribute("href"); @@ -886,7 +888,6 @@ function preLoadCss(cssUrl) { const template = document.createElement("template"); template.innerHTML = text; - // @ts-expect-error onEachLazy(template.content.querySelectorAll("a"), elem => { const href = elem.getAttribute("href"); @@ -894,7 +895,6 @@ function preLoadCss(cssUrl) { elem.setAttribute("href", window.rootPath + href); } }); - // @ts-expect-error onEachLazy(template.content.querySelectorAll("[id]"), el => { let i = 0; if (idMap.has(el.id)) { @@ -912,7 +912,6 @@ function preLoadCss(cssUrl) { const oldHref = `#${el.id}`; const newHref = `#${el.id}-${i}`; el.id = `${el.id}-${i}`; - // @ts-expect-error onEachLazy(template.content.querySelectorAll("a[href]"), link => { if (link.getAttribute("href") === oldHref) { link.href = newHref; @@ -933,7 +932,6 @@ function preLoadCss(cssUrl) { // @ts-expect-error sidebarTraitList.append(li); } else { - // @ts-expect-error onEachLazy(templateAssocItems, item => { let block = hasClass(item, "associatedtype") ? associatedTypes : ( hasClass(item, "associatedconstant") ? associatedConstants : ( @@ -1040,7 +1038,6 @@ function preLoadCss(cssUrl) { function expandAllDocs() { const innerToggle = document.getElementById(toggleAllDocsId); removeClass(innerToggle, "will-expand"); - // @ts-expect-error onEachLazy(document.getElementsByClassName("toggle"), e => { if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) { e.open = true; @@ -1053,7 +1050,6 @@ function preLoadCss(cssUrl) { function collapseAllDocs() { const innerToggle = document.getElementById(toggleAllDocsId); addClass(innerToggle, "will-expand"); - // @ts-expect-error onEachLazy(document.getElementsByClassName("toggle"), e => { if (e.parentNode.id !== "implementations-list" || (!hasClass(e, "implementors-toggle") && @@ -1092,7 +1088,6 @@ function preLoadCss(cssUrl) { function setImplementorsTogglesOpen(id, open) { const list = document.getElementById(id); if (list !== null) { - // @ts-expect-error onEachLazy(list.getElementsByClassName("implementors-toggle"), e => { e.open = open; }); @@ -1104,7 +1099,6 @@ function preLoadCss(cssUrl) { setImplementorsTogglesOpen("blanket-implementations-list", false); } - // @ts-expect-error onEachLazy(document.getElementsByClassName("toggle"), e => { if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) { e.open = true; @@ -1124,7 +1118,6 @@ function preLoadCss(cssUrl) { } onEachLazy(document.querySelectorAll( ":not(.scraped-example) > .example-wrap > pre:not(.example-line-numbers)", - // @ts-expect-error ), x => { const parent = x.parentNode; const line_numbers = parent.querySelectorAll(".example-line-numbers"); @@ -1145,7 +1138,6 @@ function preLoadCss(cssUrl) { // @ts-expect-error window.rustdoc_remove_line_numbers_from_examples = () => { - // @ts-expect-error onEachLazy(document.querySelectorAll(".example-wrap > .example-line-numbers"), x => { x.parentNode.removeChild(x); }); @@ -1157,16 +1149,13 @@ function preLoadCss(cssUrl) { } function showSidebar() { - // @ts-expect-error window.hideAllModals(false); const sidebar = document.getElementsByClassName("sidebar")[0]; - // @ts-expect-error addClass(sidebar, "shown"); } function hideSidebar() { const sidebar = document.getElementsByClassName("sidebar")[0]; - // @ts-expect-error removeClass(sidebar, "shown"); } @@ -1193,7 +1182,6 @@ function preLoadCss(cssUrl) { mainElem.addEventListener("click", hideSidebar); } - // @ts-expect-error onEachLazy(document.querySelectorAll("a[href^='#']"), el => { // For clicks on internal links ( tags with a hash property), we expand the section we're // jumping to *before* jumping there. We can't do this in onHashChange, because it changes @@ -1204,7 +1192,6 @@ function preLoadCss(cssUrl) { }); }); - // @ts-expect-error onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => { // @ts-expect-error el.addEventListener("click", e => { @@ -1241,7 +1228,6 @@ function preLoadCss(cssUrl) { clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT); return; } - // @ts-expect-error window.hideAllModals(false); const wrapper = document.createElement("div"); if (notable_ty) { @@ -1422,7 +1408,6 @@ function preLoadCss(cssUrl) { } } - // @ts-expect-error onEachLazy(document.getElementsByClassName("tooltip"), e => { e.onclick = () => { e.TOOLTIP_FORCE_VISIBLE = e.TOOLTIP_FORCE_VISIBLE ? false : true; @@ -1527,7 +1512,6 @@ function preLoadCss(cssUrl) { // @ts-expect-error !getSettingsButton().contains(event.relatedTarget) ) { - // @ts-expect-error window.hidePopoverMenus(); } } @@ -1626,10 +1610,8 @@ function preLoadCss(cssUrl) { * * Pass "true" to reset focus for tooltip popovers. */ - // @ts-expect-error window.hideAllModals = switchFocus => { hideSidebar(); - // @ts-expect-error window.hidePopoverMenus(); hideTooltip(switchFocus); }; @@ -1637,9 +1619,7 @@ function preLoadCss(cssUrl) { /** * Hide all the popover menus. */ - // @ts-expect-error window.hidePopoverMenus = () => { - // @ts-expect-error onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"), elem => { elem.style.display = "none"; }); @@ -1708,7 +1688,6 @@ function preLoadCss(cssUrl) { if (shouldShowHelp) { showHelp(); } else { - // @ts-expect-error window.hidePopoverMenus(); } }); @@ -1780,30 +1759,42 @@ function preLoadCss(cssUrl) { }); } - // Pointer capture. - // - // Resizing is a single-pointer gesture. Any secondary pointer is ignored - // @ts-expect-error + /** + * Pointer capture. + * + * Resizing is a single-pointer gesture. Any secondary pointer is ignored + * + * @type {null|number} + */ let currentPointerId = null; - // "Desired" sidebar size. - // - // This is stashed here for window resizing. If the sidebar gets - // shrunk to maintain BODY_MIN, and then the user grows the window again, - // it gets the sidebar to restore its size. - // @ts-expect-error + /** + * "Desired" sidebar size. + * + * This is stashed here for window resizing. If the sidebar gets + * shrunk to maintain BODY_MIN, and then the user grows the window again, + * it gets the sidebar to restore its size. + * + * @type {null|number} + */ let desiredSidebarSize = null; - // Sidebar resize debouncer. - // - // The sidebar itself is resized instantly, but the body HTML can be too - // big for that, causing reflow jank. To reduce this, we queue up a separate - // animation frame and throttle it. + /** + * Sidebar resize debouncer. + * + * The sidebar itself is resized instantly, but the body HTML can be too + * big for that, causing reflow jank. To reduce this, we queue up a separate + * animation frame and throttle it. + * + * @type {false|ReturnType} + */ let pendingSidebarResizingFrame = false; - // If this page has no sidebar at all, bail out. + /** @type {HTMLElement|null} */ const resizer = document.querySelector(".sidebar-resizer"); + /** @type {HTMLElement|null} */ const sidebar = document.querySelector(".sidebar"); + // If this page has no sidebar at all, bail out. if (!resizer || !sidebar) { return; } @@ -1820,11 +1811,9 @@ function preLoadCss(cssUrl) { // from settings.js, which uses a separate function. It's done here because // the minimum sidebar size is rather uncomfortable, and it must pass // through that size when using the shrink-to-nothing gesture. - function hideSidebar() { + const hideSidebar = function() { if (isSrcPage) { - // @ts-expect-error window.rustdocCloseSourceSidebar(); - // @ts-expect-error updateLocalStorage("src-sidebar-width", null); // [RUSTDOCIMPL] CSS variable fast path // @@ -1837,22 +1826,17 @@ function preLoadCss(cssUrl) { // // So, to clear it, we need to clear all three. document.documentElement.style.removeProperty("--src-sidebar-width"); - // @ts-expect-error sidebar.style.removeProperty("--src-sidebar-width"); - // @ts-expect-error resizer.style.removeProperty("--src-sidebar-width"); } else { addClass(document.documentElement, "hide-sidebar"); updateLocalStorage("hide-sidebar", "true"); - // @ts-expect-error updateLocalStorage("desktop-sidebar-width", null); document.documentElement.style.removeProperty("--desktop-sidebar-width"); - // @ts-expect-error sidebar.style.removeProperty("--desktop-sidebar-width"); - // @ts-expect-error resizer.style.removeProperty("--desktop-sidebar-width"); } - } + }; // Call this function to show the sidebar from the resize handle. // On docs pages, this can only happen if the user has grabbed the resize @@ -1860,15 +1844,14 @@ function preLoadCss(cssUrl) { // the visible range without releasing it. You can, however, grab the // resize handle on a source page with the sidebar closed, because it // remains visible all the time on there. - function showSidebar() { + const showSidebar = function() { if (isSrcPage) { - // @ts-expect-error window.rustdocShowSourceSidebar(); } else { removeClass(document.documentElement, "hide-sidebar"); updateLocalStorage("hide-sidebar", "false"); } - } + }; /** * Call this to set the correct CSS variable and setting. @@ -1876,44 +1859,40 @@ function preLoadCss(cssUrl) { * * @param {number} size - CSS px width of the sidebar. */ - function changeSidebarSize(size) { + const changeSidebarSize = function(size) { if (isSrcPage) { - // @ts-expect-error - updateLocalStorage("src-sidebar-width", size); + updateLocalStorage("src-sidebar-width", size.toString()); // [RUSTDOCIMPL] CSS variable fast path // // While this property is set on the HTML element at load time, // because the sidebar isn't actually loaded yet, // we scope this update to the sidebar to avoid hitting a slow // path in WebKit. - // @ts-expect-error sidebar.style.setProperty("--src-sidebar-width", size + "px"); - // @ts-expect-error resizer.style.setProperty("--src-sidebar-width", size + "px"); } else { - // @ts-expect-error - updateLocalStorage("desktop-sidebar-width", size); - // @ts-expect-error + updateLocalStorage("desktop-sidebar-width", size.toString()); sidebar.style.setProperty("--desktop-sidebar-width", size + "px"); - // @ts-expect-error resizer.style.setProperty("--desktop-sidebar-width", size + "px"); } - } + }; // Check if the sidebar is hidden. Since src pages and doc pages have // different settings, this function has to check that. - function isSidebarHidden() { + const isSidebarHidden = function() { return isSrcPage ? !hasClass(document.documentElement, "src-sidebar-expanded") : hasClass(document.documentElement, "hide-sidebar"); - } + }; - // Respond to the resize handle event. - // This function enforces size constraints, and implements the - // shrink-to-nothing gesture based on thresholds defined above. - // @ts-expect-error - function resize(e) { - // @ts-expect-error + /** + * Respond to the resize handle event. + * This function enforces size constraints, and implements the + * shrink-to-nothing gesture based on thresholds defined above. + * + * @param {PointerEvent} e + */ + const resize = function(e) { if (currentPointerId === null || currentPointerId !== e.pointerId) { return; } @@ -1931,97 +1910,83 @@ function preLoadCss(cssUrl) { changeSidebarSize(constrainedPos); desiredSidebarSize = constrainedPos; if (pendingSidebarResizingFrame !== false) { - // @ts-expect-error clearTimeout(pendingSidebarResizingFrame); } - // @ts-expect-error pendingSidebarResizingFrame = setTimeout(() => { - // @ts-expect-error if (currentPointerId === null || pendingSidebarResizingFrame === false) { return; } pendingSidebarResizingFrame = false; document.documentElement.style.setProperty( "--resizing-sidebar-width", - // @ts-expect-error desiredSidebarSize + "px", ); }, 100); } - } + }; // Respond to the window resize event. window.addEventListener("resize", () => { if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { return; } stopResize(); - // @ts-expect-error - if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) { + if (desiredSidebarSize !== null && desiredSidebarSize >= (window.innerWidth - BODY_MIN)) { changeSidebarSize(window.innerWidth - BODY_MIN); - // @ts-expect-error } else if (desiredSidebarSize !== null && desiredSidebarSize > SIDEBAR_MIN) { - // @ts-expect-error changeSidebarSize(desiredSidebarSize); } }); - // @ts-expect-error - function stopResize(e) { - // @ts-expect-error + + /** + * @param {PointerEvent=} e + */ + const stopResize = function(e) { if (currentPointerId === null) { return; } if (e) { e.preventDefault(); } - // @ts-expect-error desiredSidebarSize = sidebar.getBoundingClientRect().width; - // @ts-expect-error removeClass(resizer, "active"); window.removeEventListener("pointermove", resize, false); window.removeEventListener("pointerup", stopResize, false); removeClass(document.documentElement, "sidebar-resizing"); document.documentElement.style.removeProperty( "--resizing-sidebar-width"); - // @ts-expect-error if (resizer.releasePointerCapture) { - // @ts-expect-error resizer.releasePointerCapture(currentPointerId); currentPointerId = null; } - } - // @ts-expect-error - function initResize(e) { - // @ts-expect-error + }; + + /** + * @param {PointerEvent} e + */ + const initResize = function(e) { if (currentPointerId !== null || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { return; } - // @ts-expect-error if (resizer.setPointerCapture) { - // @ts-expect-error resizer.setPointerCapture(e.pointerId); - // @ts-expect-error if (!resizer.hasPointerCapture(e.pointerId)) { // unable to capture pointer; something else has it // on iOS, this usually means you long-clicked a link instead - // @ts-expect-error resizer.releasePointerCapture(e.pointerId); return; } currentPointerId = e.pointerId; } - // @ts-expect-error window.hideAllModals(false); e.preventDefault(); window.addEventListener("pointermove", resize, false); window.addEventListener("pointercancel", stopResize, false); window.addEventListener("pointerup", stopResize, false); - // @ts-expect-error addClass(resizer, "active"); addClass(document.documentElement, "sidebar-resizing"); - // @ts-expect-error const pos = e.clientX - sidebar.offsetLeft - 3; document.documentElement.style.setProperty( "--resizing-sidebar-width", pos + "px"); desiredSidebarSize = null; - } + }; resizer.addEventListener("pointerdown", initResize, false); }()); @@ -2029,8 +1994,13 @@ function preLoadCss(cssUrl) { // and the copy buttons on the code examples. (function() { // Common functions to copy buttons. - // @ts-expect-error + /** + * @param {string|null} content + */ function copyContentToClipboard(content) { + if (content === null) { + return; + } const el = document.createElement("textarea"); el.value = content; el.setAttribute("readonly", ""); @@ -2044,15 +2014,17 @@ function preLoadCss(cssUrl) { document.body.removeChild(el); } - // @ts-expect-error + /** + * @param {HTMLElement & {reset_button_timeout?: ReturnType}} button + */ function copyButtonAnimation(button) { button.classList.add("clicked"); if (button.reset_button_timeout !== undefined) { - window.clearTimeout(button.reset_button_timeout); + clearTimeout(button.reset_button_timeout); } - button.reset_button_timeout = window.setTimeout(() => { + button.reset_button_timeout = setTimeout(() => { button.reset_button_timeout = undefined; button.classList.remove("clicked"); }, 1000); @@ -2067,9 +2039,7 @@ function preLoadCss(cssUrl) { // Most page titles are ' in - Rust', except // modules (which don't have the first part) and keywords/primitives // (which don't have a module path) - // @ts-expect-error - const title = document.querySelector("title").textContent.replace(" - Rust", ""); - const [item, module] = title.split(" in "); + const [item, module] = document.title.split(" in "); const path = [item]; if (module !== undefined) { path.unshift(module); @@ -2079,8 +2049,10 @@ function preLoadCss(cssUrl) { copyButtonAnimation(but); }; - // Copy buttons on code examples. - // @ts-expect-error + /** + * Copy buttons on code examples. + * @param {HTMLElement|null} codeElem + */ function copyCode(codeElem) { if (!codeElem) { // Should never happen, but the world is a dark and dangerous place. @@ -2089,23 +2061,34 @@ function preLoadCss(cssUrl) { copyContentToClipboard(codeElem.textContent); } - // @ts-expect-error + /** + * @param {UIEvent} event + * @returns {HTMLElement|null} + */ function getExampleWrap(event) { - let elem = event.target; - while (!hasClass(elem, "example-wrap")) { - if (elem === document.body || - elem.tagName === "A" || - elem.tagName === "BUTTON" || - hasClass(elem, "docblock") - ) { - return null; + const target = event.target; + if (target instanceof HTMLElement) { + /** @type {HTMLElement|null} */ + let elem = target; + while (elem !== null && !hasClass(elem, "example-wrap")) { + if (elem === document.body || + elem.tagName === "A" || + elem.tagName === "BUTTON" || + hasClass(elem, "docblock") + ) { + return null; + } + elem = elem.parentElement; } - elem = elem.parentElement; + return elem; + } else { + return null; } - return elem; } - // @ts-expect-error + /** + * @param {UIEvent} event + */ function addCopyButton(event) { const elem = getExampleWrap(event); if (elem === null) { @@ -2132,15 +2115,17 @@ function preLoadCss(cssUrl) { }); parent.appendChild(copyButton); - if (!elem.parentElement.classList.contains("scraped-example")) { + if (!elem.parentElement || !elem.parentElement.classList.contains("scraped-example") || + !window.updateScrapedExample) { return; } const scrapedWrapped = elem.parentElement; - // @ts-expect-error window.updateScrapedExample(scrapedWrapped, parent); } - // @ts-expect-error + /** + * @param {UIEvent} event + */ function showHideCodeExampleButtons(event) { const elem = getExampleWrap(event); if (elem === null) { @@ -2159,7 +2144,6 @@ function preLoadCss(cssUrl) { buttons.classList.toggle("keep-visible"); } - // @ts-expect-error onEachLazy(document.querySelectorAll(".docblock .example-wrap"), elem => { elem.addEventListener("mouseover", addCopyButton); elem.addEventListener("click", showHideCodeExampleButtons); diff --git a/src/librustdoc/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts index 18a3e22113b8b..acea7828e86a9 100644 --- a/src/librustdoc/html/static/js/rustdoc.d.ts +++ b/src/librustdoc/html/static/js/rustdoc.d.ts @@ -20,6 +20,28 @@ declare global { * As a multi-page application, we know this never changes once set. */ currentCrate: string|null; + /** + * Hide popovers, tooltips, or the mobile sidebar. + */ + hideAllModals: function(boolean), + /** + * Hide popovers, but leave other modals alone. + */ + hidePopoverMenus: function(), + /** + * Hide the source page sidebar. If it's already closed, + * or if this is a docs page, this function does nothing. + */ + rustdocCloseSourceSidebar: function(), + /** + * Show the source page sidebar. If it's already opened, + * or if this is a docs page, this function does nothing. + */ + rustdocShowSourceSidebar: function(), + /** + * Set up event listeners for a scraped source example. + */ + updateScrapedExample?: function(HTMLElement, HTMLElement), } interface HTMLElement { /** Used by the popover tooltip code. */ diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 662e951069973..121a43e3d92e2 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -4732,10 +4732,8 @@ function printTab(nb) { // Corrections only kick in on type-based searches. const correctionsElem = document.getElementsByClassName("search-corrections"); if (isTypeSearch) { - // @ts-expect-error removeClass(correctionsElem[0], "hidden"); } else { - // @ts-expect-error addClass(correctionsElem[0], "hidden"); } } else if (nb !== 0) { diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 10369e77320fe..3042373fb096f 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -59,7 +59,7 @@ function hasClass(elem, className) { * Add a class to a DOM Element. If `elem` is null, * does nothing. This function is idempotent. * - * @param {HTMLElement|null} elem + * @param {Element|null} elem * @param {string} className */ function addClass(elem, className) { @@ -72,7 +72,7 @@ function addClass(elem, className) { * Remove a class from a DOM Element. If `elem` is null, * does nothing. This function is idempotent. * - * @param {HTMLElement|null} elem + * @param {Element|null} elem * @param {string} className */ // eslint-disable-next-line no-unused-vars @@ -85,7 +85,7 @@ function removeClass(elem, className) { /** * Run a callback for every element of an Array. * @param {Array} arr - The array to iterate over - * @param {function(?): boolean|undefined} func - The callback + * @param {function(?): boolean|void} func - The callback */ function onEach(arr, func) { for (const elem of arr) { @@ -103,7 +103,7 @@ function onEach(arr, func) { * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection * https://developer.mozilla.org/en-US/docs/Web/API/NodeList * @param {NodeList|HTMLCollection} lazyArray - An array to iterate over - * @param {function(?): boolean} func - The callback + * @param {function(?): boolean|void} func - The callback */ // eslint-disable-next-line no-unused-vars function onEachLazy(lazyArray, func) { @@ -119,11 +119,15 @@ function onEachLazy(lazyArray, func) { * If localStorage is disabled, this function does nothing. * * @param {string} name - * @param {string} value + * @param {string|null} value */ function updateLocalStorage(name, value) { try { - window.localStorage.setItem("rustdoc-" + name, value); + if (value === null) { + window.localStorage.removeItem("rustdoc-" + name); + } else { + window.localStorage.setItem("rustdoc-" + name, value); + } } catch (e) { // localStorage is not accessible, do nothing } 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/crashes/135124.rs b/tests/crashes/135124.rs deleted file mode 100644 index d6655cb46fac2..0000000000000 --- a/tests/crashes/135124.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@ known-bug: #135124 -trait A { - fn y(&self) - { - fn call() -> impl Sized {} - self.fold(call()); - } - fn fold(&self, &self._) {} -} 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/editions/auxiliary/nested_macro_rules_dep_2021.rs b/tests/ui/editions/auxiliary/nested_macro_rules_dep_2021.rs new file mode 100644 index 0000000000000..36d3e712dc0e9 --- /dev/null +++ b/tests/ui/editions/auxiliary/nested_macro_rules_dep_2021.rs @@ -0,0 +1,23 @@ +//@ edition: 2021 + +#[macro_export] +macro_rules! make_macro_with_input { + ($i:ident) => { + macro_rules! macro_inner_input { + () => { + pub fn $i() {} + }; + } + }; +} + +#[macro_export] +macro_rules! make_macro { + () => { + macro_rules! macro_inner { + () => { + pub fn gen() {} + }; + } + }; +} diff --git a/tests/ui/editions/auxiliary/nested_macro_rules_dep_2024.rs b/tests/ui/editions/auxiliary/nested_macro_rules_dep_2024.rs new file mode 100644 index 0000000000000..4012398fe6622 --- /dev/null +++ b/tests/ui/editions/auxiliary/nested_macro_rules_dep_2024.rs @@ -0,0 +1,23 @@ +//@ edition: 2024 + +#[macro_export] +macro_rules! make_macro_with_input { + ($i:ident) => { + macro_rules! macro_inner_input { + () => { + pub fn $i() {} + }; + } + }; +} + +#[macro_export] +macro_rules! make_macro { + () => { + macro_rules! macro_inner { + () => { + pub fn gen() {} + }; + } + }; +} diff --git a/tests/ui/editions/nested-macro-rules-edition.e2021.stderr b/tests/ui/editions/nested-macro-rules-edition.e2021.stderr new file mode 100644 index 0000000000000..eac80262364ba --- /dev/null +++ b/tests/ui/editions/nested-macro-rules-edition.e2021.stderr @@ -0,0 +1,27 @@ +error: expected identifier, found reserved keyword `gen` + --> $DIR/nested-macro-rules-edition.rs:30:5 + | +LL | macro_inner_input!{} + | ^^^^^^^^^^^^^^^^^^^^ expected identifier, found reserved keyword + | + = note: this error originates in the macro `macro_inner_input` (in Nightly builds, run with -Z macro-backtrace for more info) +help: escape `gen` to use it as an identifier + | +LL | nested_macro_rules_dep::make_macro_with_input!{r#gen} + | ++ + +error: expected identifier, found reserved keyword `gen` + --> $DIR/nested-macro-rules-edition.rs:35:5 + | +LL | macro_inner!{} + | ^^^^^^^^^^^^^^ expected identifier, found reserved keyword + | + = note: this error originates in the macro `macro_inner` (in Nightly builds, run with -Z macro-backtrace for more info) +help: escape `gen` to use it as an identifier + --> $DIR/auxiliary/nested_macro_rules_dep_2024.rs:19:24 + | +LL | pub fn r#gen() {} + | ++ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/editions/nested-macro-rules-edition.rs b/tests/ui/editions/nested-macro-rules-edition.rs new file mode 100644 index 0000000000000..28d568f77501b --- /dev/null +++ b/tests/ui/editions/nested-macro-rules-edition.rs @@ -0,0 +1,39 @@ +// This checks the behavior of how nested macro_rules definitions are handled +// with regards to edition spans. Prior to https://github.com/rust-lang/rust/pull/133274, +// the compiler would compile the inner macro with the edition of the local crate. +// Afterwards, it uses the edition where the macro was *defined*. +// +// Unfortunately macro_rules compiler discards the edition of any *input* that +// was used to generate the macro. This is possibly not the behavior that we +// want. If we want to keep with the philosophy that code should follow the +// edition rules of the crate where it is written, then presumably we would +// want the input tokens to retain the edition of where they were written. +// +// See https://github.com/rust-lang/rust/issues/135669 for more. +// +// This has two revisions, one where local=2021 and the dep=2024. The other +// revision is vice-versa. + +//@ revisions: e2021 e2024 +//@[e2021] edition:2021 +//@[e2024] edition:2024 +//@[e2021] aux-crate: nested_macro_rules_dep=nested_macro_rules_dep_2024.rs +//@[e2024] aux-crate: nested_macro_rules_dep=nested_macro_rules_dep_2021.rs +//@[e2024] check-pass + +mod with_input { + // If we change the macro_rules input behavior, then this should pass when + // local edition is 2021 because `gen` is written in a context with 2021 + // behavior. For local edition 2024, the reverse would be true and this + // should fail. + nested_macro_rules_dep::make_macro_with_input!{gen} + macro_inner_input!{} + //[e2021]~^ ERROR found reserved keyword +} +mod no_input { + nested_macro_rules_dep::make_macro!{} + macro_inner!{} + //[e2021]~^ ERROR found reserved keyword +} + +fn main() {} 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`. diff --git a/tests/ui/fn/error-recovery-mismatch.rs b/tests/ui/fn/error-recovery-mismatch.rs new file mode 100644 index 0000000000000..a50a30c8c78a6 --- /dev/null +++ b/tests/ui/fn/error-recovery-mismatch.rs @@ -0,0 +1,20 @@ +// Used to ICE due to a size mismatch between the actual fake signature of `fold` and the +// generated signature used reporting the parameter mismatch at the call site. +// See issue #135124 + +trait A { + fn y(&self) + { + fn call() -> impl Sized {} + self.fold(call(), call()); + } + fn fold(&self, _: T, &self._) {} + //~^ ERROR unexpected `self` parameter in function + //~| ERROR expected one of `)` or `,`, found `.` + //~| ERROR identifier `self` is bound more than once in this parameter list + //~| WARNING anonymous parameters are deprecated + //~| WARNING this is accepted in the current edition + //~| ERROR the placeholder `_` is not allowed within types +} + +fn main() {} diff --git a/tests/ui/fn/error-recovery-mismatch.stderr b/tests/ui/fn/error-recovery-mismatch.stderr new file mode 100644 index 0000000000000..ad4652c11c12f --- /dev/null +++ b/tests/ui/fn/error-recovery-mismatch.stderr @@ -0,0 +1,46 @@ +error: unexpected `self` parameter in function + --> $DIR/error-recovery-mismatch.rs:11:29 + | +LL | fn fold(&self, _: T, &self._) {} + | ^^^^^ must be the first parameter of an associated function + +error: expected one of `)` or `,`, found `.` + --> $DIR/error-recovery-mismatch.rs:11:34 + | +LL | fn fold(&self, _: T, &self._) {} + | ^ + | | + | expected one of `)` or `,` + | help: missing `,` + +error[E0415]: identifier `self` is bound more than once in this parameter list + --> $DIR/error-recovery-mismatch.rs:11:30 + | +LL | fn fold(&self, _: T, &self._) {} + | ^^^^ used as parameter more than once + +warning: anonymous parameters are deprecated and will be removed in the next edition + --> $DIR/error-recovery-mismatch.rs:11:35 + | +LL | fn fold(&self, _: T, &self._) {} + | ^ help: try naming the parameter or explicitly ignoring it: `_: _` + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2018! + = note: for more information, see issue #41686 + = note: `#[warn(anonymous_parameters)]` on by default + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for functions + --> $DIR/error-recovery-mismatch.rs:11:35 + | +LL | fn fold(&self, _: T, &self._) {} + | ^ not allowed in type signatures + | +help: use type parameters instead + | +LL | fn fold(&self, _: T, &self.U) {} + | +++ ~ + +error: aborting due to 4 previous errors; 1 warning emitted + +Some errors have detailed explanations: E0121, E0415. +For more information about an error, try `rustc --explain E0121`. diff --git a/tests/ui/fn/param-mismatch-trait-fn.rs b/tests/ui/fn/param-mismatch-trait-fn.rs new file mode 100644 index 0000000000000..69ded6a9068d7 --- /dev/null +++ b/tests/ui/fn/param-mismatch-trait-fn.rs @@ -0,0 +1,10 @@ +trait Foo { + fn same_type(_: T, _: T); +} + +fn f(x: X, y: Y) { + T::same_type([x], Some(y)); + //~^ ERROR mismatched types +} + +fn main() {} diff --git a/tests/ui/fn/param-mismatch-trait-fn.stderr b/tests/ui/fn/param-mismatch-trait-fn.stderr new file mode 100644 index 0000000000000..28e1bcaaf49dc --- /dev/null +++ b/tests/ui/fn/param-mismatch-trait-fn.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/param-mismatch-trait-fn.rs:6:23 + | +LL | T::same_type([x], Some(y)); + | ------------ --- ^^^^^^^ expected `[X; 1]`, found `Option` + | | | + | | expected all arguments to be this `[X; 1]` type because they need to match the type of this parameter + | arguments to this function are incorrect + | + = note: expected array `[X; 1]` + found enum `Option` +note: associated function defined here + --> $DIR/param-mismatch-trait-fn.rs:2:8 + | +LL | fn same_type(_: T, _: T); + | ^^^^^^^^^ - - - this parameter needs to match the `[X; 1]` type of parameter #1 + | | | + | | parameter #2 needs to match the `[X; 1]` type of this parameter + | parameter #1 and parameter #2 both reference this parameter `T` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/methods/issues/issue-61525.stderr b/tests/ui/methods/issues/issue-61525.stderr index 35001ae22a6c7..7ac3d3dc0cff0 100644 --- a/tests/ui/methods/issues/issue-61525.stderr +++ b/tests/ui/methods/issues/issue-61525.stderr @@ -32,7 +32,7 @@ note: method defined here --> $DIR/issue-61525.rs:2:8 | LL | fn query(self, q: Q); - | ^^^^^ + | ^^^^^ - error: aborting due to 2 previous errors diff --git a/tests/ui/suggestions/trait-with-missing-associated-type-restriction.stderr b/tests/ui/suggestions/trait-with-missing-associated-type-restriction.stderr index 980c2455c8e41..df59a28c4b97b 100644 --- a/tests/ui/suggestions/trait-with-missing-associated-type-restriction.stderr +++ b/tests/ui/suggestions/trait-with-missing-associated-type-restriction.stderr @@ -94,7 +94,7 @@ note: method defined here --> $DIR/trait-with-missing-associated-type-restriction.rs:9:8 | LL | fn funk(&self, _: Self::A); - | ^^^^ + | ^^^^ - help: consider constraining the associated type `>::A` to `{integer}` | LL | fn bar2>(x: T) { diff --git a/tests/ui/traits/issue-52893.stderr b/tests/ui/traits/issue-52893.stderr index c37dde90e336f..3c5df82fcdc87 100644 --- a/tests/ui/traits/issue-52893.stderr +++ b/tests/ui/traits/issue-52893.stderr @@ -22,7 +22,7 @@ note: method defined here --> $DIR/issue-52893.rs:11:8 | LL | fn push(self, other: T) -> Self::PushRes; - | ^^^^ + | ^^^^ ----- error: aborting due to 1 previous error