From bcb8565f301b579dee60fffe87d5a329cc69fefa Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Mon, 2 Dec 2024 20:35:13 +0000 Subject: [PATCH 01/10] Contracts core intrinsics. These are hooks to: 1. control whether contract checks are run 2. allow 3rd party tools to intercept and reintepret the results of running contracts. --- compiler/rustc_borrowck/src/type_check/mod.rs | 1 + compiler/rustc_codegen_cranelift/src/base.rs | 9 ++++++ compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 4 +++ .../src/check_consts/check.rs | 6 +++- .../rustc_const_eval/src/interpret/machine.rs | 10 ++++++ .../src/interpret/operator.rs | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 ++ .../rustc_hir_analysis/src/check/intrinsic.rs | 20 ++++++++++++ compiler/rustc_middle/src/mir/pretty.rs | 1 + compiler/rustc_middle/src/mir/syntax.rs | 3 ++ compiler/rustc_middle/src/mir/tcx.rs | 3 +- .../src/move_paths/builder.rs | 6 +++- compiler/rustc_mir_transform/src/gvn.rs | 1 + .../src/known_panics_lint.rs | 1 + .../src/lower_intrinsics.rs | 11 +++++++ .../rustc_mir_transform/src/promote_consts.rs | 1 + compiler/rustc_mir_transform/src/validate.rs | 5 ++- compiler/rustc_session/src/config/cfg.rs | 7 ++++ compiler/rustc_session/src/options.rs | 2 ++ compiler/rustc_session/src/session.rs | 4 +++ .../rustc_smir/src/rustc_smir/convert/mir.rs | 1 + compiler/rustc_span/src/symbol.rs | 4 +++ compiler/stable_mir/src/mir/body.rs | 5 ++- library/core/src/intrinsics/mod.rs | 32 +++++++++++++++++++ .../clippy_utils/src/qualify_min_const_fn.rs | 2 +- src/tools/miri/src/machine.rs | 5 +++ tests/ui/contracts/contract-intrinsics.rs | 23 +++++++++++++ .../feature-gate-cfg-contract-checks.rs | 5 +++ .../feature-gate-cfg-contract-checks.stderr | 13 ++++++++ 30 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 tests/ui/contracts/contract-intrinsics.rs create mode 100644 tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs create mode 100644 tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 92492bfdb8d31..b727616ebceb9 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1650,6 +1650,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::SizedBound, ); } + &Rvalue::NullaryOp(NullOp::ContractChecks, _) => {} &Rvalue::NullaryOp(NullOp::UbChecks, _) => {} Rvalue::ShallowInitBox(operand, ty) => { diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 7a40d236b9288..de2ce1768fa18 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -874,6 +874,15 @@ fn codegen_stmt<'tcx>( lval.write_cvalue(fx, val); return; } + NullOp::ContractChecks => { + let val = fx.tcx.sess.contract_checks(); + let val = CValue::by_val( + fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()), + fx.layout_of(fx.tcx.types.bool), + ); + lval.write_cvalue(fx, val); + return; + } }; let val = CValue::by_val( fx.bcx.ins().iconst(fx.pointer_type, i64::try_from(val).unwrap()), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 85de3238b3e77..27cb7883b9a6c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -741,6 +741,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let val = bx.tcx().sess.ub_checks(); bx.cx().const_bool(val) } + mir::NullOp::ContractChecks => { + let val = bx.tcx().sess.contract_checks(); + bx.cx().const_bool(val) + } }; let tcx = self.cx.tcx(); OperandRef { diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index e8052a3c83a1f..d0ce027ec2b71 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -675,7 +675,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, + NullOp::SizeOf + | NullOp::AlignOf + | NullOp::OffsetOf(_) + | NullOp::UbChecks + | NullOp::ContractChecks, _, ) => {} Rvalue::ShallowInitBox(_, _) => {} diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 8f6b15b8df012..1a799f5dea5b5 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -293,6 +293,9 @@ pub trait Machine<'tcx>: Sized { /// Determines the result of a `NullaryOp::UbChecks` invocation. fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Determines the result of a `NullaryOp::ContractChecks` invocation. + fn contract_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction. /// You can use this to detect long or endlessly running programs. #[inline] @@ -679,6 +682,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) { interp_ok(true) } + #[inline(always)] + fn contract_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> { + // We can't look at `tcx.sess` here as that can differ across crates, which can lead to + // unsound differences in evaluating the same constant at different instantiation sites. + interp_ok(true) + } + #[inline(always)] fn adjust_global_allocation<'b>( _ecx: &InterpCx<$tcx, Self>, diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index 5fa632fc57aaf..899670aeb62da 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ImmTy::from_uint(val, usize_layout()) } UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx), + ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx), }) } } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index e0543977e98d8..67eb96e4d56ad 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -19,6 +19,7 @@ const GATED_CFGS: &[GatedCfg] = &[ // (name in cfg, feature, function to check if the feature is enabled) (sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks), (sym::ub_checks, sym::cfg_ub_checks, Features::cfg_ub_checks), + (sym::contract_checks, sym::cfg_contract_checks, Features::cfg_contract_checks), (sym::target_thread_local, sym::cfg_target_thread_local, Features::cfg_target_thread_local), ( sym::target_has_atomic_equal_alignment, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 1a216ebf117c6..08a5e22db3a54 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -403,6 +403,8 @@ declare_features! ( (unstable, c_variadic, "1.34.0", Some(44930)), /// Allows the use of `#[cfg()]`. (unstable, cfg_boolean_literals, "1.83.0", Some(131204)), + /// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled. + (unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(133866)), /// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour. (unstable, cfg_overflow_checks, "1.71.0", Some(111466)), /// Provides the relocation model information as cfg entry diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index cf3d489730425..6c0cebccefd95 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,18 @@ pub fn check_intrinsic_type( } }; (n_tps, 0, 0, inputs, output, hir::Safety::Unsafe) + } else if intrinsic_name == sym::contract_check_ensures { + // contract_check_ensures::<'a, Ret, C>(&'a Ret, C) -> bool + // where C: impl Fn(&'a Ret) -> bool, + // + // so: two type params, one lifetime param, 0 const params, two inputs, returns boolean + + let p = generics.param_at(0, tcx); + let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data()); + let ref_ret = Ty::new_imm_ref(tcx, r, param(1)); + // let br = ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BrAnon }; + // let ref_ret = Ty::new_imm_ref(tcx, ty::Region::new_bound(tcx, ty::INNERMOST, br), param(0)); + (2, 1, 0, vec![ref_ret, param(2)], tcx.types.bool, hir::Safety::Safe) } else { let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let (n_tps, n_cts, inputs, output) = match intrinsic_name { @@ -610,6 +625,11 @@ pub fn check_intrinsic_type( sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))), + // contract_checks() -> bool + sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), + // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool + sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.bool), + sym::simd_eq | sym::simd_ne | sym::simd_lt diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 09d7e60e19941..f53b4f3def259 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1103,6 +1103,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { NullOp::AlignOf => write!(fmt, "AlignOf({t})"), NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), NullOp::UbChecks => write!(fmt, "UbChecks()"), + NullOp::ContractChecks => write!(fmt, "ContractChecks()"), } } ThreadLocalRef(did) => ty::tls::with(|tcx| { diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 2da25f480c65f..9cec8d832dd14 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1591,6 +1591,9 @@ pub enum NullOp<'tcx> { /// Returns whether we should perform some UB-checking at runtime. /// See the `ub_checks` intrinsic docs for details. UbChecks, + /// Returns whether we should perform contract-checking at runtime. + /// See the `contract_checks` intrinsic docs for details. + ContractChecks, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 49449426fa408..af23c8b2ea76d 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -230,7 +230,8 @@ impl<'tcx> Rvalue<'tcx> { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { tcx.types.usize } - Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool, + Rvalue::NullaryOp(NullOp::ContractChecks, _) + | Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool, Rvalue::Aggregate(ref ak, ref ops) => match **ak { AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64), AggregateKind::Tuple => { diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 6e00e427a46c7..b6c259aa4e0ab 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -417,7 +417,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { | Rvalue::Discriminant(..) | Rvalue::Len(..) | Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks, + NullOp::SizeOf + | NullOp::AlignOf + | NullOp::OffsetOf(..) + | NullOp::UbChecks + | NullOp::ContractChecks, _, ) => {} } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index c261e25100d38..d2ffd26f0a06d 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -545,6 +545,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { .offset_of_subfield(self.typing_env(), layout, fields.iter()) .bytes(), NullOp::UbChecks => return None, + NullOp::ContractChecks => return None, }; let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap(); let imm = ImmTy::from_uint(val, usize_layout); diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 2864cc0b9fe0c..e43254ba089e5 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -629,6 +629,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { .offset_of_subfield(self.typing_env, op_layout, fields.iter()) .bytes(), NullOp::UbChecks => return None, + NullOp::ContractChecks => return None, }; ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into() } diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 9a9f66ed4fd7a..9c21bcfc0d26a 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -34,6 +34,17 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { }); terminator.kind = TerminatorKind::Goto { target }; } + sym::contract_checks => { + let target = target.unwrap(); + block.statements.push(Statement { + source_info: terminator.source_info, + kind: StatementKind::Assign(Box::new(( + *destination, + Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool), + ))), + }); + terminator.kind = TerminatorKind::Goto { target }; + } sym::forget => { let target = target.unwrap(); block.statements.push(Statement { diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 9101c9bfc9aeb..4dbbcae1756b7 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -457,6 +457,7 @@ impl<'tcx> Validator<'_, 'tcx> { NullOp::AlignOf => {} NullOp::OffsetOf(_) => {} NullOp::UbChecks => {} + NullOp::ContractChecks => {} }, Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index e282eaf761c10..b7a3770fc6b1d 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1379,7 +1379,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { Rvalue::Repeat(_, _) | Rvalue::ThreadLocalRef(_) | Rvalue::RawPtr(_, _) - | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _) + | Rvalue::NullaryOp( + NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks, + _, + ) | Rvalue::Discriminant(_) => {} Rvalue::WrapUnsafeBinder(op, ty) => { diff --git a/compiler/rustc_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/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/symbol.rs b/compiler/rustc_span/src/symbol.rs index b23cc90991154..3138efdfd9b0d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -566,6 +566,7 @@ symbols! { cfg_attr, cfg_attr_multi, cfg_boolean_literals, + cfg_contract_checks, cfg_doctest, cfg_emscripten_wasm_eh, cfg_eval, @@ -675,6 +676,9 @@ symbols! { const_ty_placeholder: "", constant, constructor, + contract_check_ensures, + contract_check_requires, + contract_checks, 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/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index bf07632d9928b..beea0996775a4 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4044,6 +4044,38 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) // Runtime NOP } +/// Returns whether we should perform contract-checking at runtime. +/// +/// This is meant to be similar to the ub_checks intrinsic, in terms +/// of not prematurely commiting at compile-time to whether contract +/// checking is turned on, so that we can specify contracts in libstd +/// and let an end user opt into turning them on. +#[cfg(not(bootstrap))] +#[rustc_const_unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[inline(always)] +#[rustc_intrinsic] +pub const fn contract_checks() -> bool { + // FIXME: should this be `false` or `cfg!(contract_checks)`? + + // cfg!(contract_checks) + false +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_requires bool>(c: C) -> bool { + c() +} + +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_intrinsic] +pub fn contract_check_ensures<'a, Ret, C: FnOnce(&'a Ret) -> bool>(ret: &'a Ret, c: C) -> bool { + c(ret) +} + /// The intrinsic will return the size stored in that vtable. /// /// # Safety diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 0aaef91e48a6d..5a3a3d0cedc42 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -179,7 +179,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _) + Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 4735db48e81f8..6bd1076a8a848 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1150,6 +1150,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { interp_ok(ecx.tcx.sess.ub_checks()) } + #[inline(always)] + fn contract_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> { + interp_ok(ecx.tcx.sess.contract_checks()) + } + #[inline(always)] fn thread_local_static_pointer( ecx: &mut MiriInterpCx<'tcx>, diff --git a/tests/ui/contracts/contract-intrinsics.rs b/tests/ui/contracts/contract-intrinsics.rs new file mode 100644 index 0000000000000..6e3565baf7ac5 --- /dev/null +++ b/tests/ui/contracts/contract-intrinsics.rs @@ -0,0 +1,23 @@ +//@ run-pass +//@ revisions: yes no none +//@ [yes] compile-flags: -Zcontract-checks=yes +//@ [no] compile-flags: -Zcontract-checks=no +#![feature(cfg_contract_checks, rustc_contracts, core_intrinsics)] + +fn main() { + #[cfg(none)] // default: disabled + assert_eq!(core::intrinsics::contract_checks(), false); + + #[cfg(yes)] // explicitly enabled + assert_eq!(core::intrinsics::contract_checks(), true); + + #[cfg(no)] // explicitly disabled + assert_eq!(core::intrinsics::contract_checks(), false); + + assert_eq!(core::intrinsics::contract_check_requires(|| true), true); + assert_eq!(core::intrinsics::contract_check_requires(|| false), false); + + let doubles_to_two = { let old = 2; move |ret| ret + ret == old }; + assert_eq!(core::intrinsics::contract_check_ensures(&1, doubles_to_two), true); + assert_eq!(core::intrinsics::contract_check_ensures(&2, doubles_to_two), false); +} diff --git a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs new file mode 100644 index 0000000000000..cd9bf12b5e773 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.rs @@ -0,0 +1,5 @@ +#![crate_type = "lib"] + +pub fn contract_checks_are_enabled() -> bool { + cfg!(contract_checks) //~ ERROR `cfg(contract_checks)` is experimental +} diff --git a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr new file mode 100644 index 0000000000000..af4e605e57098 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr @@ -0,0 +1,13 @@ +error[E0658]: `cfg(contract_checks)` is experimental and subject to change + --> $DIR/feature-gate-cfg-contract-checks.rs:4:10 + | +LL | cfg!(contract_checks) + | ^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(cfg_contract_checks)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. From 777def87d58ee067e1df2e94a99fc099bcb15189 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Mon, 2 Dec 2024 21:16:35 +0000 Subject: [PATCH 02/10] contracts: added lang items that act as hooks for rustc-injected code to invoke. see test for an example of the kind of injected code that is anticipated here. --- compiler/rustc_hir/src/lang_items.rs | 4 ++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/contracts.rs | 33 ++++++++++++++++ library/core/src/lib.rs | 5 +++ tests/ui/contracts/contract-lang-items.rs | 47 +++++++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 library/core/src/contracts.rs create mode 100644 tests/ui/contracts/contract-lang-items.rs 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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3138efdfd9b0d..df424b3312a0f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -676,6 +676,7 @@ symbols! { const_ty_placeholder: "", constant, constructor, + contract_build_check_ensures, contract_check_ensures, contract_check_requires, contract_checks, diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs new file mode 100644 index 0000000000000..c13565d59d58f --- /dev/null +++ b/library/core/src/contracts.rs @@ -0,0 +1,33 @@ +//! Unstable module containing the unstable contracts lang items and attribute macros. + +/// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }` +/// into: `fn foo(x: X) { check_requires(|| PRED) ... }` +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[lang = "contract_check_requires"] +#[track_caller] +pub fn check_requires bool>(c: C) { + if core::intrinsics::contract_checks() { + assert!(core::intrinsics::contract_check_requires(c), "failed requires check"); + } +} + +/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }` +/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }` +/// (including the implicit return of the tail expression, if any). +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[lang = "contract_build_check_ensures"] +#[track_caller] +pub fn build_check_ensures(c: C) -> impl (FnOnce(Ret) -> Ret) + Copy +where + C: for<'a> FnOnce(&'a Ret) -> bool + Copy + 'static, +{ + #[track_caller] + move |ret| { + if core::intrinsics::contract_checks() { + assert!(core::intrinsics::contract_check_ensures(&ret, c), "failed ensures check"); + } + ret + } +} diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index c18e0405f7293..fd283fe92fa36 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -114,6 +114,7 @@ #![feature(bstr)] #![feature(bstr_internals)] #![feature(const_carrying_mul_add)] +#![feature(closure_track_caller)] #![feature(const_eval_select)] #![feature(core_intrinsics)] #![feature(coverage_attribute)] @@ -246,6 +247,10 @@ pub mod autodiff { pub use crate::macros::builtin::autodiff; } +#[cfg(not(bootstrap))] +#[unstable(feature = "rustc_contracts", issue = "none")] +pub mod contracts; + #[unstable(feature = "cfg_match", issue = "115585")] pub use crate::macros::cfg_match; diff --git a/tests/ui/contracts/contract-lang-items.rs b/tests/ui/contracts/contract-lang-items.rs new file mode 100644 index 0000000000000..1dbf71722fd70 --- /dev/null +++ b/tests/ui/contracts/contract-lang-items.rs @@ -0,0 +1,47 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +fn foo(x: Baz) -> i32 { + core::contracts::check_requires(|| x.baz > 0); + + let injected_checker = { + core::contracts::build_check_ensures(|ret| *ret > 100) + }; + + let ret = x.baz + 50; + injected_checker(ret) +} + +struct Baz { baz: i32 } + + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(foo(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + foo(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + foo(BAZ_FAIL_POST); +} From 38eff16d0aa029706a0b5845961f9b5ccddfd999 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 8 Jan 2025 16:38:25 -0800 Subject: [PATCH 03/10] Express contracts as part of function header and lower it to the contract lang items includes post-developed commit: do not suggest internal-only keywords as corrections to parse failures. includes post-developed commit: removed tabs that creeped in into rustfmt tool source code. includes post-developed commit, placating rustfmt self dogfooding. includes post-developed commit: add backquotes to prevent markdown checking from trying to treat an attr as a markdown hyperlink/ includes post-developed commit: fix lowering to keep contracts from being erroneously inherited by nested bodies (like closures). Rebase Conflicts: - compiler/rustc_parse/src/parser/diagnostics.rs - compiler/rustc_parse/src/parser/item.rs - compiler/rustc_span/src/hygiene.rs Remove contracts keywords from diagnostic messages --- compiler/rustc_ast/src/ast.rs | 9 +- compiler/rustc_ast/src/mut_visit.rs | 19 ++- compiler/rustc_ast/src/visit.rs | 17 ++- compiler/rustc_ast_lowering/src/expr.rs | 15 ++- compiler/rustc_ast_lowering/src/item.rs | 112 +++++++++++++++++- compiler/rustc_ast_lowering/src/lib.rs | 20 ++++ .../rustc_ast_passes/src/ast_validation.rs | 2 +- .../rustc_ast_pretty/src/pprust/state/item.rs | 21 +++- .../src/alloc_error_handler.rs | 1 + .../src/deriving/generic/mod.rs | 1 + .../src/global_allocator.rs | 1 + .../rustc_builtin_macros/src/test_harness.rs | 1 + compiler/rustc_hir/src/hir.rs | 2 + compiler/rustc_hir_typeck/src/expr.rs | 2 + compiler/rustc_parse/src/parser/generics.rs | 22 ++++ compiler/rustc_parse/src/parser/item.rs | 13 +- compiler/rustc_parse/src/parser/token_type.rs | 8 ++ compiler/rustc_resolve/src/def_collector.rs | 5 +- compiler/rustc_resolve/src/late.rs | 6 +- compiler/rustc_span/src/hygiene.rs | 3 + compiler/rustc_span/src/symbol.rs | 2 + library/core/src/lib.rs | 2 +- .../clippy/clippy_utils/src/ast_utils/mod.rs | 20 ++++ .../contracts/contract-ast-extensions-nest.rs | 44 +++++++ .../contracts/contract-ast-extensions-tail.rs | 42 +++++++ ...g-ensures-is-not-inherited-when-nesting.rs | 15 +++ ...-requires-is-not-inherited-when-nesting.rs | 17 +++ 27 files changed, 405 insertions(+), 17 deletions(-) create mode 100644 tests/ui/contracts/contract-ast-extensions-nest.rs create mode 100644 tests/ui/contracts/contract-ast-extensions-tail.rs create mode 100644 tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs create mode 100644 tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs 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..19433047595a0 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -314,7 +314,20 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label)) } ExprKind::Ret(e) => { - let e = e.as_ref().map(|x| self.lower_expr(x)); + let mut e = e.as_ref().map(|x| self.lower_expr(x)); + if let Some(Some((span, fresh_ident))) = self + .contract + .as_ref() + .map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident))) + { + let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2); + let args = if let Some(e) = e { + std::slice::from_ref(e) + } else { + std::slice::from_ref(self.expr_unit(span)) + }; + e = Some(self.expr_call(span, checker_fn, args)); + } hir::ExprKind::Ret(e) } ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 74870d741504c..4679ccdddbbe1 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -207,9 +207,42 @@ impl<'hir> LoweringContext<'_, 'hir> { sig: FnSig { decl, header, span: fn_sig_span }, generics, body, + contract, .. }) => { self.with_new_scopes(*fn_sig_span, |this| { + assert!(this.contract.is_none()); + if let Some(contract) = contract { + let requires = contract.requires.clone(); + let ensures = contract.ensures.clone(); + let ensures = if let Some(ens) = ensures { + // FIXME: this needs to be a fresh (or illegal) identifier to prevent + // accidental capture of a parameter or global variable. + let checker_ident: Ident = + Ident::from_str_and_span("__ensures_checker", ens.span); + let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut( + ens.span, + checker_ident, + hir::BindingMode::NONE, + ); + + Some(crate::FnContractLoweringEnsures { + expr: ens, + fresh_ident: (checker_ident, checker_pat, checker_hir_id), + }) + } else { + None + }; + + // Note: `with_new_scopes` will reinstall the outer + // item's contract (if any) after its callback finishes. + this.contract.replace(crate::FnContractLoweringInfo { + span, + requires, + ensures, + }); + } + // Note: we don't need to change the return type from `T` to // `impl Future` here because lower_body // only cares about the input argument patterns in the function @@ -1054,10 +1087,81 @@ 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 } + // ==> + // { rustc_contract_requires(PRECOND); { body } } + let result: hir::Expr<'hir> = if let Some(contract) = opt_contract { + let lit_unit = |this: &mut LoweringContext<'_, 'hir>| { + this.expr(contract.span, hir::ExprKind::Tup(&[])) + }; + + let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires { + let lowered_req = this.lower_expr_mut(&req); + let precond = this.expr_call_lang_item_fn_mut( + req.span, + hir::LangItem::ContractCheckRequires, + &*arena_vec![this; lowered_req], + ); + this.stmt_expr(req.span, precond) + } else { + let u = lit_unit(this); + this.stmt_expr(contract.span, u) + }; + + let (postcond_checker, 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, + ), + { + let checker_fn = + this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2); + let span = this.mark_span_with_reason( + DesugaringKind::Contract, + ens.span, + None, + ); + this.expr_call_mut( + span, + checker_fn, + std::slice::from_ref(this.arena.alloc(result)), + ) + }, + ) + } else { + let u = lit_unit(this); + (this.stmt_expr(contract.span, u), result) + }; + + let block = this.block_all( + contract.span, + arena_vec![this; precond, postcond_checker], + Some(this.arena.alloc(result)), + ); + this.expr_block(block) + } else { + result + }; + + (params, result) }) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 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_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_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/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/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_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_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_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index b1b84b0b7011e..d5ac8d1588d7a 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -297,6 +297,28 @@ impl<'a> Parser<'a> { }) } + /// Parses a rustc-internal fn contract + /// (`rustc_contract_requires(WWW) rustc_contract_ensures(ZZZ)`) + pub(super) fn parse_contract( + &mut self, + ) -> PResult<'a, Option>> { + let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) { + Some(self.parse_expr()?) + } else { + None + }; + let ensures = if self.eat_keyword_noexpect(exp!(RustcContractEnsures).kw) { + Some(self.parse_expr()?) + } else { + None + }; + if requires.is_none() && ensures.is_none() { + Ok(None) + } else { + Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures }))) + } + } + /// Parses an optional where-clause. /// /// ```ignore (only-for-syntax-highlight) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index f3e56be9f6e8b..dbdc31f06a29d 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -213,9 +213,12 @@ impl<'a> Parser<'a> { self.parse_use_item()? } else if self.check_fn_front_matter(check_pub, case) { // FUNCTION ITEM - let (ident, sig, generics, body) = + let (ident, sig, generics, contract, body) = self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?; - (ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body }))) + ( + ident, + ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, contract, body })), + ) } else if self.eat_keyword(exp!(Extern)) { if self.eat_keyword(exp!(Crate)) { // EXTERN CRATE @@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> { sig_lo: Span, vis: &Visibility, case: Case, - ) -> PResult<'a, (Ident, FnSig, Generics, Option>)> { + ) -> PResult<'a, (Ident, FnSig, Generics, Option>, Option>)> { let fn_span = self.token.span; let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn` let ident = self.parse_ident()?; // `foo` @@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> { // inside `parse_fn_body()`. let fn_params_end = self.prev_token.span.shrink_to_hi(); + let contract = self.parse_contract()?; + generics.where_clause = self.parse_where_clause()?; // `where T: Ord` // `fn_params_end` is needed only when it's followed by a where clause. @@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> { let body = self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?; let fn_sig_span = sig_lo.to(sig_hi); - Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, body)) + Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body)) } /// Provide diagnostics when function body is not found diff --git a/compiler/rustc_parse/src/parser/token_type.rs b/compiler/rustc_parse/src/parser/token_type.rs index 73f3ac001c8ae..50f03e72f82dc 100644 --- a/compiler/rustc_parse/src/parser/token_type.rs +++ b/compiler/rustc_parse/src/parser/token_type.rs @@ -108,6 +108,8 @@ pub enum TokenType { KwRef, KwReturn, KwReuse, + KwRustcContractEnsures, + KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -242,6 +244,8 @@ impl TokenType { KwRef, KwReturn, KwReuse, + KwRustcContractEnsures, + KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -314,6 +318,8 @@ impl TokenType { TokenType::KwRef => Some(kw::Ref), TokenType::KwReturn => Some(kw::Return), TokenType::KwReuse => Some(kw::Reuse), + TokenType::KwRustcContractEnsures => Some(kw::RustcContractEnsures), + TokenType::KwRustcContractRequires => Some(kw::RustcContractRequires), TokenType::KwSafe => Some(kw::Safe), TokenType::KwSelfUpper => Some(kw::SelfUpper), TokenType::KwStatic => Some(kw::Static), @@ -544,6 +550,8 @@ macro_rules! exp { (Ref) => { exp!(@kw, Ref, KwRef) }; (Return) => { exp!(@kw, Return, KwReturn) }; (Reuse) => { exp!(@kw, Reuse, KwReuse) }; + (RustcContractEnsures) => { exp!(@kw, RustcContractEnsures, KwRustcContractEnsures) }; + (RustcContractRequires) => { exp!(@kw, RustcContractRequires, KwRustcContractRequires) }; (Safe) => { exp!(@kw, Safe, KwSafe) }; (SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) }; (Static) => { exp!(@kw, Static, KwStatic) }; diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 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_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 df424b3312a0f..1c15ca7d11aa8 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -118,6 +118,8 @@ symbols! { MacroRules: "macro_rules", Raw: "raw", Reuse: "reuse", + RustcContractEnsures: "rustc_contract_ensures", + RustcContractRequires: "rustc_contract_requires", Safe: "safe", Union: "union", Yeet: "yeet", diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index fd283fe92fa36..a31cca5d425c9 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -113,8 +113,8 @@ #![feature(bigint_helper_methods)] #![feature(bstr)] #![feature(bstr_internals)] -#![feature(const_carrying_mul_add)] #![feature(closure_track_caller)] +#![feature(const_carrying_mul_add)] #![feature(const_eval_select)] #![feature(core_intrinsics)] #![feature(coverage_attribute)] 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/tests/ui/contracts/contract-ast-extensions-nest.rs b/tests/ui/contracts/contract-ast-extensions-nest.rs new file mode 100644 index 0000000000000..ed137c4a98671 --- /dev/null +++ b/tests/ui/contracts/contract-ast-extensions-nest.rs @@ -0,0 +1,44 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +fn nest(x: Baz) -> i32 + rustc_contract_requires(|| x.baz > 0) + rustc_contract_ensures(|ret| *ret > 100) +{ + loop { + return x.baz + 50; + } +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(nest(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + nest(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + nest(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contract-ast-extensions-tail.rs b/tests/ui/contracts/contract-ast-extensions-tail.rs new file mode 100644 index 0000000000000..b501c3faaed69 --- /dev/null +++ b/tests/ui/contracts/contract-ast-extensions-tail.rs @@ -0,0 +1,42 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +fn tail(x: Baz) -> i32 + rustc_contract_requires(|| x.baz > 0) + rustc_contract_ensures(|ret| *ret > 100) +{ + x.baz + 50 +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(tail(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + tail(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + tail(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..069f26e6796be --- /dev/null +++ b/tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs @@ -0,0 +1,15 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +fn outer() -> i32 + rustc_contract_ensures(|ret| *ret > 0) +{ + let inner_closure = || -> i32 { 0 }; + inner_closure(); + 10 +} + +fn main() { + outer(); +} diff --git a/tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..fbee8277a968e --- /dev/null +++ b/tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs @@ -0,0 +1,17 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +struct Outer { outer: std::cell::Cell } + +fn outer(x: Outer) + rustc_contract_requires(|| x.outer.get() > 0) +{ + let inner_closure = || { }; + x.outer.set(0); + inner_closure(); +} + +fn main() { + outer(Outer { outer: 1.into() }); +} From ae7eff0be5c4abae63c06851af14cfdf7fec3981 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 3 Dec 2024 02:52:29 +0000 Subject: [PATCH 04/10] Desugars contract into the internal AST extensions Check ensures on early return due to Try / Yeet Expand these two expressions to include a call to contract checking --- compiler/rustc_ast_lowering/src/expr.rs | 48 +++-- compiler/rustc_ast_lowering/src/item.rs | 115 +++++------- .../rustc_builtin_macros/src/contracts.rs | 172 ++++++++++++++++++ compiler/rustc_builtin_macros/src/lib.rs | 5 + compiler/rustc_span/src/symbol.rs | 2 + library/core/src/contracts.rs | 5 + library/core/src/macros/mod.rs | 26 +++ .../ui/contracts/contract-attributes-nest.rs | 44 +++++ .../ui/contracts/contract-attributes-tail.rs | 42 +++++ .../contracts-ensures-early-fn-exit.rs | 48 +++++ ...s-ensures-is-not-inherited-when-nesting.rs | 14 ++ ...-requires-is-not-inherited-when-nesting.rs | 16 ++ 12 files changed, 453 insertions(+), 84 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/contracts.rs create mode 100644 tests/ui/contracts/contract-attributes-nest.rs create mode 100644 tests/ui/contracts/contract-attributes-tail.rs create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.rs create mode 100644 tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs create mode 100644 tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 19433047595a0..8125da361b814 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -314,21 +314,8 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label)) } ExprKind::Ret(e) => { - let mut e = e.as_ref().map(|x| self.lower_expr(x)); - if let Some(Some((span, fresh_ident))) = self - .contract - .as_ref() - .map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident))) - { - let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2); - let args = if let Some(e) = e { - std::slice::from_ref(e) - } else { - std::slice::from_ref(self.expr_unit(span)) - }; - e = Some(self.expr_call(span, checker_fn, args)); - } - hir::ExprKind::Ret(e) + 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) => { @@ -395,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); @@ -1983,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); @@ -2032,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 4679ccdddbbe1..c96110fee619b 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -215,7 +215,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if let Some(contract) = contract { let requires = contract.requires.clone(); let ensures = contract.ensures.clone(); - let ensures = if let Some(ens) = ensures { + 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 = @@ -226,13 +226,11 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::BindingMode::NONE, ); - Some(crate::FnContractLoweringEnsures { + crate::FnContractLoweringEnsures { expr: ens, fresh_ident: (checker_ident, checker_pat, checker_hir_id), - }) - } else { - None - }; + } + }); // Note: `with_new_scopes` will reinstall the outer // item's contract (if any) after its callback finishes. @@ -1095,73 +1093,56 @@ impl<'hir> LoweringContext<'_, 'hir> { // { body } // ==> - // { rustc_contract_requires(PRECOND); { body } } - let result: hir::Expr<'hir> = if let Some(contract) = opt_contract { - let lit_unit = |this: &mut LoweringContext<'_, 'hir>| { - this.expr(contract.span, hir::ExprKind::Tup(&[])) - }; - - let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires { - let lowered_req = this.lower_expr_mut(&req); - let precond = this.expr_call_lang_item_fn_mut( - req.span, - hir::LangItem::ContractCheckRequires, - &*arena_vec![this; lowered_req], - ); - this.stmt_expr(req.span, precond) - } else { - let u = lit_unit(this); - this.stmt_expr(contract.span, u) - }; + // { 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 (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, - ), - { - let checker_fn = - this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2); - let span = this.mark_span_with_reason( - DesugaringKind::Contract, - ens.span, - None, - ); - this.expr_call_mut( - span, - checker_fn, - std::slice::from_ref(this.arena.alloc(result)), - ) - }, - ) - } else { - let u = lit_unit(this); - (this.stmt_expr(contract.span, u), result) - }; + 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 block = this.block_all( - contract.span, - arena_vec![this; precond, postcond_checker], - Some(this.arena.alloc(result)), + 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], ); - this.expr_block(block) + 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 { - result + let u = lit_unit(this); + (this.stmt_expr(contract.span, u), &*result_ref) }; - (params, result) + 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_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs new file mode 100644 index 0000000000000..d269287b555b3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -0,0 +1,172 @@ +#![allow(unused_imports, unused_variables)] + +use rustc_ast::token; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_errors::ErrorGuaranteed; +use rustc_expand::base::{AttrProcMacro, ExtCtxt}; +use rustc_span::Span; +use rustc_span::symbol::{Ident, Symbol, kw, sym}; + +pub(crate) struct ExpandRequires; + +pub(crate) struct ExpandEnsures; + +impl AttrProcMacro for ExpandRequires { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result { + expand_requires_tts(ecx, span, annotation, annotated) + } +} + +impl AttrProcMacro for ExpandEnsures { + fn expand<'cx>( + &self, + ecx: &'cx mut ExtCtxt<'_>, + span: Span, + annotation: TokenStream, + annotated: TokenStream, + ) -> Result { + expand_ensures_tts(ecx, span, annotation, annotated) + } +} + +fn expand_injecting_circa_where_clause( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotated: TokenStream, + inject: impl FnOnce(&mut Vec) -> Result<(), ErrorGuaranteed>, +) -> Result { + let mut new_tts = Vec::with_capacity(annotated.len()); + let mut cursor = annotated.into_trees(); + + // Find the `fn name(x:X,...)` and inject the AST contract forms right after + // the formal parameters (and return type if any). + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + if let TokenTree::Token(tok, _) = tt + && tok.is_ident_named(kw::Fn) + { + break; + } + } + + // Found the `fn` keyword, now find the formal parameters. + // + // FIXME: can this fail if you have parentheticals in a generics list, like `fn foo Y>` ? + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + + if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt { + break; + } + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt { + panic!("contract attribute applied to fn without parameter list."); + } + } + + // There *might* be a return type declaration (and figuring out where that ends would require + // parsing an arbitrary type expression, e.g. `-> Foo` + // + // Instead of trying to figure that out, scan ahead and look for the first occurence of a + // `where`, a `{ ... }`, or a `;`. + // + // FIXME: this might still fall into a trap for something like `-> Ctor`. I + // *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form + // prefixed by e.g. `const`, so we should still be able to filter them out without having to + // parse the type expression itself. But rather than try to fix things with hacks like that, + // time might be better spent extending the attribute expander to suport tt-annotation atop + // ast-annotated, which would be an elegant way to sidestep all of this. + let mut opt_next_tt = cursor.next_ref(); + while let Some(next_tt) = opt_next_tt { + if let TokenTree::Token(tok, _) = next_tt + && tok.is_ident_named(kw::Where) + { + break; + } + if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt { + break; + } + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt { + break; + } + + // for anything else, transcribe the tt and keep looking. + new_tts.push(next_tt.clone()); + opt_next_tt = cursor.next_ref(); + continue; + } + + // At this point, we've transcribed everything from the `fn` through the formal parameter list + // and return type declaration, (if any), but `tt` itself has *not* been transcribed. + // + // Now inject the AST contract form. + // + // FIXME: this kind of manual token tree munging does not have significant precedent among + // rustc builtin macros, probably because most builtin macros use direct AST manipulation to + // accomplish similar goals. But since our attributes need to take arbitrary expressions, and + // our attribute infrastructure does not yet support mixing a token-tree annotation with an AST + // annotated, we end up doing token tree manipulation. + inject(&mut new_tts)?; + + // Above we injected the internal AST requires/ensures contruct. Now copy over all the other + // token trees. + if let Some(tt) = opt_next_tt { + new_tts.push(tt.clone()); + } + while let Some(tt) = cursor.next_ref() { + new_tts.push(tt.clone()); + } + + Ok(TokenStream::new(new_tts)) +} + +fn expand_requires_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result { + expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push(TokenTree::Token( + token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)), + Spacing::Joint, + )); + new_tts.push(TokenTree::Token( + token::Token::new(token::TokenKind::OrOr, attr_span), + Spacing::Alone, + )); + new_tts.push(TokenTree::Delimited( + DelimSpan::from_single(attr_span), + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), + token::Delimiter::Parenthesis, + annotation, + )); + Ok(()) + }) +} + +fn expand_ensures_tts( + _ecx: &mut ExtCtxt<'_>, + attr_span: Span, + annotation: TokenStream, + annotated: TokenStream, +) -> Result { + expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push(TokenTree::Token( + token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)), + Spacing::Joint, + )); + new_tts.push(TokenTree::Delimited( + DelimSpan::from_single(attr_span), + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), + token::Delimiter::Parenthesis, + annotation, + )); + Ok(()) + }) +} diff --git a/compiler/rustc_builtin_macros/src/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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 1c15ca7d11aa8..43bc69e6e7cfe 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -682,6 +682,8 @@ symbols! { contract_check_ensures, contract_check_requires, contract_checks, + contracts_ensures, + contracts_requires, convert_identity, copy, copy_closures, diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs index c13565d59d58f..3efd2df0a3832 100644 --- a/library/core/src/contracts.rs +++ b/library/core/src/contracts.rs @@ -1,5 +1,10 @@ //! Unstable module containing the unstable contracts lang items and attribute macros. +#[cfg(not(bootstrap))] +pub use crate::macros::builtin::contracts_ensures as ensures; +#[cfg(not(bootstrap))] +pub use crate::macros::builtin::contracts_requires as requires; + /// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }` /// into: `fn foo(x: X) { check_requires(|| PRED) ... }` #[cfg(not(bootstrap))] diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 3669051ce82d3..1a5d2973dfc3f 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 = "rustc_contracts", issue = "none")] + #[allow_internal_unstable(core_intrinsics)] + #[rustc_builtin_macro] + pub macro contracts_ensures($item:item) { + /* compiler built-in */ + } + + /// Attribute macro applied to a function to give it a precondition. + /// + /// The attribute carries an argument token-tree which is + /// eventually parsed as an boolean expression with access to the + /// function's formal parameters + #[cfg(not(bootstrap))] + #[unstable(feature = "rustc_contracts", issue = "none")] + #[allow_internal_unstable(core_intrinsics)] + #[rustc_builtin_macro] + pub macro contracts_requires($item:item) { + /* compiler built-in */ + } + /// Attribute macro applied to a function to register it as a handler for allocation failure. /// /// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html). diff --git a/tests/ui/contracts/contract-attributes-nest.rs b/tests/ui/contracts/contract-attributes-nest.rs new file mode 100644 index 0000000000000..1cda21f10d7ec --- /dev/null +++ b/tests/ui/contracts/contract-attributes-nest.rs @@ -0,0 +1,44 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures(|ret| *ret > 100)] +fn nest(x: Baz) -> i32 +{ + loop { + return x.baz + 50; + } +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(nest(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + nest(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + nest(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contract-attributes-tail.rs b/tests/ui/contracts/contract-attributes-tail.rs new file mode 100644 index 0000000000000..26855bfa82ac4 --- /dev/null +++ b/tests/ui/contracts/contract-attributes-tail.rs @@ -0,0 +1,42 @@ +//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +// +//@ [unchk_pass] run-pass +//@ [unchk_fail_pre] run-pass +//@ [unchk_fail_post] run-pass +//@ [chk_pass] run-pass +// +//@ [chk_fail_pre] run-fail +//@ [chk_fail_post] run-fail +// +//@ [unchk_pass] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no +//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no +// +//@ [chk_pass] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes +//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures(|ret| *ret > 100)] +fn tail(x: Baz) -> i32 +{ + x.baz + 50 +} + +struct Baz { baz: i32 } + +const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; +#[cfg(any(unchk_fail_post, chk_fail_post))] +const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; +#[cfg(any(unchk_fail_pre, chk_fail_pre))] +const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; + +fn main() { + assert_eq!(tail(BAZ_PASS_PRE_POST), 150); + #[cfg(any(unchk_fail_pre, chk_fail_pre))] + tail(BAZ_FAIL_PRE); + #[cfg(any(unchk_fail_post, chk_fail_post))] + tail(BAZ_FAIL_POST); +} diff --git a/tests/ui/contracts/contracts-ensures-early-fn-exit.rs b/tests/ui/contracts/contracts-ensures-early-fn-exit.rs new file mode 100644 index 0000000000000..faf97473a90f2 --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.rs @@ -0,0 +1,48 @@ +//@ 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(rustc_contracts)] +#![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-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..9872fdb1a1852 --- /dev/null +++ b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +#[core::contracts::ensures(|ret| *ret > 0)] +fn outer() -> i32 { + let inner_closure = || -> i32 { 0 }; + inner_closure(); + 10 +} + +fn main() { + outer(); +} diff --git a/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs new file mode 100644 index 0000000000000..75124259b0d72 --- /dev/null +++ b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs @@ -0,0 +1,16 @@ +//@ run-pass +//@ compile-flags: -Zcontract-checks=yes +#![feature(rustc_contracts)] + +struct Outer { outer: std::cell::Cell } + +#[core::contracts::requires(x.outer.get() > 0)] +fn outer(x: Outer) { + let inner_closure = || { }; + x.outer.set(0); + inner_closure(); +} + +fn main() { + outer(Outer { outer: 1.into() }); +} From b279ff9dcfadcdb6976097d58044d151af81cf51 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 3 Dec 2024 14:29:27 +0000 Subject: [PATCH 05/10] demonstrate how to capture state at precondition time and feed into postcondition predicate. --- .../contract-captures-via-closure-copy.rs | 25 +++++++++++++++++ .../contract-captures-via-closure-noncopy.rs | 22 +++++++++++++++ ...ntract-captures-via-closure-noncopy.stderr | 27 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 tests/ui/contracts/contract-captures-via-closure-copy.rs create mode 100644 tests/ui/contracts/contract-captures-via-closure-noncopy.rs create mode 100644 tests/ui/contracts/contract-captures-via-closure-noncopy.stderr diff --git a/tests/ui/contracts/contract-captures-via-closure-copy.rs b/tests/ui/contracts/contract-captures-via-closure-copy.rs new file mode 100644 index 0000000000000..742895ab0ad80 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-copy.rs @@ -0,0 +1,25 @@ +//@ run-fail +//@ compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +struct Baz { + baz: i32 +} + +#[track_caller] +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures({let old = x.baz; move |ret:&Baz| ret.baz == old*2 })] +// Relevant thing is this: ^^^^^^^^^^^^^^^ +// because we are capturing state that is Copy +fn doubler(x: Baz) -> Baz { + Baz { baz: x.baz + 10 } +} + +fn main() { + assert_eq!(doubler(Baz { baz: 10 }).baz, 20); + assert_eq!(doubler(Baz { baz: 100 }).baz, 200); + // This is a *run-fail* test because it is still exercising the + // contract machinery, specifically because this second invocation + // of `doubler` shows how the code does not meet its contract. +} diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.rs b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs new file mode 100644 index 0000000000000..8d7f2fd200e2c --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs @@ -0,0 +1,22 @@ +//@ compile-flags: -Zcontract-checks=yes + +#![feature(rustc_contracts)] + +struct Baz { + baz: i32 +} + +#[track_caller] +#[core::contracts::requires(x.baz > 0)] +#[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] +// Relevant thing is this: ^^^^^^^^^^^ +// because we are capturing state that is non-Copy. +//~^^^ ERROR trait bound `Baz: std::marker::Copy` is not satisfied +fn doubler(x: Baz) -> Baz { + Baz { baz: x.baz + 10 } +} + +fn main() { + assert_eq!(doubler(Baz { baz: 10 }).baz, 20); + assert_eq!(doubler(Baz { baz: 100 }).baz, 200); +} diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr new file mode 100644 index 0000000000000..b53809827f987 --- /dev/null +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}` + --> $DIR/contract-captures-via-closure-noncopy.rs:11:1 + | +LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------------------------------------^^^^ + | | | + | | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}` + | | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:11:42}` + | unsatisfied trait bound + | + = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}`, the trait `std::marker::Copy` is not implemented for `Baz` +note: required because it's used within this closure + --> $DIR/contract-captures-via-closure-noncopy.rs:11:42 + | +LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] + | ^^^^^^^^^^^^^^^ +note: required by a bound in `build_check_ensures` + --> $SRC_DIR/core/src/contracts.rs:LL:COL +help: consider annotating `Baz` with `#[derive(Copy)]` + | +LL + #[derive(Copy)] +LL | struct Baz { + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 6a6c6b891bb0350b3f16abd3e84ff12dbd1b4c5b Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 3 Dec 2024 16:13:00 +0000 Subject: [PATCH 06/10] Separate contract feature gates for the internal machinery The extended syntax for function signature that includes contract clauses should never be user exposed versus the interface we want to ship externally eventually. --- compiler/rustc_ast_passes/src/feature_gate.rs | 2 + .../rustc_builtin_macros/src/contracts.rs | 20 +++-- compiler/rustc_feature/src/unstable.rs | 4 + compiler/rustc_parse/src/parser/generics.rs | 20 ++++- compiler/rustc_session/src/parse.rs | 5 ++ compiler/rustc_span/src/symbol.rs | 2 + library/core/src/contracts.rs | 4 +- library/core/src/intrinsics/mod.rs | 8 +- library/core/src/lib.rs | 2 +- library/core/src/macros/mod.rs | 8 +- .../contract-ast-extensions-nest.rs | 2 +- .../contract-ast-extensions-tail.rs | 2 +- .../contract-intrinsics.rs | 2 +- .../contract-lang-items.rs | 3 +- ...g-ensures-is-not-inherited-when-nesting.rs | 2 +- ...-requires-is-not-inherited-when-nesting.rs | 2 +- .../internal-feature-gating.rs | 23 ++++++ .../internal-feature-gating.stderr | 73 +++++++++++++++++++ .../feature-gate-rustc-contracts.rs | 11 +++ .../feature-gate-rustc-contracts.stderr | 43 +++++++++++ 20 files changed, 210 insertions(+), 28 deletions(-) rename tests/ui/contracts/{ => internal_machinery}/contract-ast-extensions-nest.rs (96%) rename tests/ui/contracts/{ => internal_machinery}/contract-ast-extensions-tail.rs (96%) rename tests/ui/contracts/{ => internal_machinery}/contract-intrinsics.rs (91%) rename tests/ui/contracts/{ => internal_machinery}/contract-lang-items.rs (90%) rename tests/ui/contracts/{ => internal_machinery}/contracts-lowering-ensures-is-not-inherited-when-nesting.rs (84%) rename tests/ui/contracts/{ => internal_machinery}/contracts-lowering-requires-is-not-inherited-when-nesting.rs (88%) create mode 100644 tests/ui/contracts/internal_machinery/internal-feature-gating.rs create mode 100644 tests/ui/contracts/internal_machinery/internal-feature-gating.stderr create mode 100644 tests/ui/feature-gates/feature-gate-rustc-contracts.rs create mode 100644 tests/ui/feature-gates/feature-gate-rustc-contracts.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 80b99f9448567..a3af942a10f17 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!(rustc_contracts, "contracts are experimental"); + gate_all!(rustc_contracts_internals, "contract internal machinery is for internal use only"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index d269287b555b3..be7f276cdaa93 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -36,17 +36,17 @@ impl AttrProcMacro for ExpandEnsures { } fn expand_injecting_circa_where_clause( - _ecx: &mut ExtCtxt<'_>, + ecx: &mut ExtCtxt<'_>, attr_span: Span, annotated: TokenStream, inject: impl FnOnce(&mut Vec) -> Result<(), ErrorGuaranteed>, ) -> Result { let mut new_tts = Vec::with_capacity(annotated.len()); - let mut cursor = annotated.into_trees(); + let mut cursor = annotated.iter(); // Find the `fn name(x:X,...)` and inject the AST contract forms right after // the formal parameters (and return type if any). - while let Some(tt) = cursor.next_ref() { + while let Some(tt) = cursor.next() { new_tts.push(tt.clone()); if let TokenTree::Token(tok, _) = tt && tok.is_ident_named(kw::Fn) @@ -58,7 +58,7 @@ fn expand_injecting_circa_where_clause( // Found the `fn` keyword, now find the formal parameters. // // FIXME: can this fail if you have parentheticals in a generics list, like `fn foo Y>` ? - while let Some(tt) = cursor.next_ref() { + while let Some(tt) = cursor.next() { new_tts.push(tt.clone()); if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt { @@ -81,7 +81,7 @@ fn expand_injecting_circa_where_clause( // parse the type expression itself. But rather than try to fix things with hacks like that, // time might be better spent extending the attribute expander to suport tt-annotation atop // ast-annotated, which would be an elegant way to sidestep all of this. - let mut opt_next_tt = cursor.next_ref(); + let mut opt_next_tt = cursor.next(); while let Some(next_tt) = opt_next_tt { if let TokenTree::Token(tok, _) = next_tt && tok.is_ident_named(kw::Where) @@ -97,8 +97,7 @@ fn expand_injecting_circa_where_clause( // for anything else, transcribe the tt and keep looking. new_tts.push(next_tt.clone()); - opt_next_tt = cursor.next_ref(); - continue; + opt_next_tt = cursor.next(); } // At this point, we've transcribed everything from the `fn` through the formal parameter list @@ -118,10 +117,15 @@ fn expand_injecting_circa_where_clause( if let Some(tt) = opt_next_tt { new_tts.push(tt.clone()); } - while let Some(tt) = cursor.next_ref() { + while let Some(tt) = cursor.next() { new_tts.push(tt.clone()); } + // Record the span as a contract attribute expansion. + // This is used later to stop users from using the extended syntax directly + // which is gated via `rustc_contracts_internals`. + ecx.psess().contract_attribute_spans.push(attr_span); + Ok(TokenStream::new(new_tts)) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 08a5e22db3a54..57bcd8c5eca22 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -608,6 +608,10 @@ declare_features! ( (unstable, return_type_notation, "1.70.0", Some(109417)), /// Allows `extern "rust-cold"`. (unstable, rust_cold_cc, "1.63.0", Some(97544)), + /// Allows use of contracts attributes. + (unstable, rustc_contracts, "CURRENT_RUSTC_VERSION", Some(133866)), + /// Allows access to internal machinery used to implement contracts. + (unstable, rustc_contracts_internals, "CURRENT_RUSTC_VERSION", Some(133866)), /// Allows use of x86 SHA512, SM3 and SM4 target-features and intrinsics (unstable, sha512_sm_x86, "1.82.0", Some(126624)), /// Allows the use of SIMD types in functions declared in `extern` blocks. diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index d5ac8d1588d7a..14b949dbc3d57 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -4,7 +4,7 @@ use rustc_ast::{ WhereClause, token, }; use rustc_errors::{Applicability, PResult}; -use rustc_span::{Ident, Span, kw}; +use rustc_span::{Ident, Span, kw, sym}; use thin_vec::ThinVec; use super::{ForceCollect, Parser, Trailing, UsePreAttrPos}; @@ -302,13 +302,27 @@ impl<'a> Parser<'a> { pub(super) fn parse_contract( &mut self, ) -> PResult<'a, Option>> { + let gate = |span| { + if self.psess.contract_attribute_spans.contains(span) { + // span was generated via a builtin contracts attribute, so gate as end-user visible + self.psess.gated_spans.gate(sym::rustc_contracts, span); + } else { + // span was not generated via a builtin contracts attribute, so gate as internal machinery + self.psess.gated_spans.gate(sym::rustc_contracts_internals, span); + } + }; + let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) { - Some(self.parse_expr()?) + let precond = self.parse_expr()?; + gate(precond.span); + Some(precond) } else { None }; let ensures = if self.eat_keyword_noexpect(exp!(RustcContractEnsures).kw) { - Some(self.parse_expr()?) + let postcond = self.parse_expr()?; + gate(postcond.span); + Some(postcond) } else { None }; 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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 43bc69e6e7cfe..ea2ce5475c264 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1716,6 +1716,8 @@ symbols! { rustc_const_stable, rustc_const_stable_indirect, rustc_const_unstable, + rustc_contracts, + rustc_contracts_internals, rustc_conversion_suggestion, rustc_deallocator, rustc_def_path, diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs index 3efd2df0a3832..b155dbc213ee8 100644 --- a/library/core/src/contracts.rs +++ b/library/core/src/contracts.rs @@ -8,7 +8,7 @@ pub use crate::macros::builtin::contracts_requires as requires; /// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }` /// into: `fn foo(x: X) { check_requires(|| PRED) ... }` #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[lang = "contract_check_requires"] #[track_caller] pub fn check_requires bool>(c: C) { @@ -21,7 +21,7 @@ pub fn check_requires bool>(c: C) { /// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }` /// (including the implicit return of the tail expression, if any). #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[lang = "contract_build_check_ensures"] #[track_caller] pub fn build_check_ensures(c: C) -> impl (FnOnce(Ret) -> Ret) + Copy diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index beea0996775a4..a7f0f09f0c6b3 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4051,8 +4051,8 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) /// checking is turned on, so that we can specify contracts in libstd /// and let an end user opt into turning them on. #[cfg(not(bootstrap))] -#[rustc_const_unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] -#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[rustc_const_unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[inline(always)] #[rustc_intrinsic] pub const fn contract_checks() -> bool { @@ -4063,14 +4063,14 @@ pub const fn contract_checks() -> bool { } #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[rustc_intrinsic] pub fn contract_check_requires bool>(c: C) -> bool { c() } #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "none" /* compiler-team#759 */)] +#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[rustc_intrinsic] pub fn contract_check_ensures<'a, Ret, C: FnOnce(&'a Ret) -> bool>(ret: &'a Ret, c: C) -> bool { c(ret) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a31cca5d425c9..6a0051244f062 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -248,7 +248,7 @@ pub mod autodiff { } #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "none")] +#[unstable(feature = "rustc_contracts", issue = "133866")] pub mod contracts; #[unstable(feature = "cfg_match", issue = "115585")] diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 1a5d2973dfc3f..cb37530e90db8 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1783,8 +1783,8 @@ pub(crate) mod builtin { /// eventually parsed as a unary closure expression that is /// invoked on a reference to the return value. #[cfg(not(bootstrap))] - #[unstable(feature = "rustc_contracts", issue = "none")] - #[allow_internal_unstable(core_intrinsics)] + #[unstable(feature = "rustc_contracts", issue = "133866")] + #[allow_internal_unstable(rustc_contracts_internals)] #[rustc_builtin_macro] pub macro contracts_ensures($item:item) { /* compiler built-in */ @@ -1796,8 +1796,8 @@ pub(crate) mod builtin { /// eventually parsed as an boolean expression with access to the /// function's formal parameters #[cfg(not(bootstrap))] - #[unstable(feature = "rustc_contracts", issue = "none")] - #[allow_internal_unstable(core_intrinsics)] + #[unstable(feature = "rustc_contracts", issue = "133866")] + #[allow_internal_unstable(rustc_contracts_internals)] #[rustc_builtin_macro] pub macro contracts_requires($item:item) { /* compiler built-in */ diff --git a/tests/ui/contracts/contract-ast-extensions-nest.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs similarity index 96% rename from tests/ui/contracts/contract-ast-extensions-nest.rs rename to tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs index ed137c4a98671..d95ccd4f7b9b6 100644 --- a/tests/ui/contracts/contract-ast-extensions-nest.rs +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs @@ -16,7 +16,7 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![feature(rustc_contracts_internals)] fn nest(x: Baz) -> i32 rustc_contract_requires(|| x.baz > 0) diff --git a/tests/ui/contracts/contract-ast-extensions-tail.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs similarity index 96% rename from tests/ui/contracts/contract-ast-extensions-tail.rs rename to tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs index b501c3faaed69..636a595e06adb 100644 --- a/tests/ui/contracts/contract-ast-extensions-tail.rs +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs @@ -16,7 +16,7 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![feature(rustc_contracts_internals)] fn tail(x: Baz) -> i32 rustc_contract_requires(|| x.baz > 0) diff --git a/tests/ui/contracts/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs similarity index 91% rename from tests/ui/contracts/contract-intrinsics.rs rename to tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 6e3565baf7ac5..2e1be01e7cabc 100644 --- a/tests/ui/contracts/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -2,7 +2,7 @@ //@ revisions: yes no none //@ [yes] compile-flags: -Zcontract-checks=yes //@ [no] compile-flags: -Zcontract-checks=no -#![feature(cfg_contract_checks, rustc_contracts, core_intrinsics)] +#![feature(cfg_contract_checks, rustc_contracts_internals, core_intrinsics)] fn main() { #[cfg(none)] // default: disabled diff --git a/tests/ui/contracts/contract-lang-items.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs similarity index 90% rename from tests/ui/contracts/contract-lang-items.rs rename to tests/ui/contracts/internal_machinery/contract-lang-items.rs index 1dbf71722fd70..5af467485b10e 100644 --- a/tests/ui/contracts/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -16,7 +16,8 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![feature(rustc_contracts)] // to access core::contracts +#![feature(rustc_contracts_internals)] // to access check_requires lang item fn foo(x: Baz) -> i32 { core::contracts::check_requires(|| x.baz > 0); diff --git a/tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs b/tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs similarity index 84% rename from tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs rename to tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs index 069f26e6796be..0b0151c6df742 100644 --- a/tests/ui/contracts/contracts-lowering-ensures-is-not-inherited-when-nesting.rs +++ b/tests/ui/contracts/internal_machinery/contracts-lowering-ensures-is-not-inherited-when-nesting.rs @@ -1,6 +1,6 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![feature(rustc_contracts_internals)] fn outer() -> i32 rustc_contract_ensures(|ret| *ret > 0) diff --git a/tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs b/tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs similarity index 88% rename from tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs rename to tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs index fbee8277a968e..79c50a18f7056 100644 --- a/tests/ui/contracts/contracts-lowering-requires-is-not-inherited-when-nesting.rs +++ b/tests/ui/contracts/internal_machinery/contracts-lowering-requires-is-not-inherited-when-nesting.rs @@ -1,6 +1,6 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![feature(rustc_contracts_internals)] struct Outer { outer: std::cell::Cell } diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs new file mode 100644 index 0000000000000..7b5f17679421f --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -0,0 +1,23 @@ +// gate-test-rustc_contracts_internals + +fn main() { + // intrinsics are guarded by rustc_contracts_internals feature gate. + core::intrinsics::contract_checks(); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::intrinsics::contract_check_requires(|| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::intrinsics::contract_check_ensures(&1, |_|true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + + // lang items are guarded by rustc_contracts_internals feature gate. + core::contracts::check_requires(|| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + core::contracts::build_check_ensures(|_: &()| true); + //~^ ERROR use of unstable library feature `rustc_contracts_internals` + + // ast extensions are guarded by rustc_contracts_internals feature gate + fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } + //~^ ERROR contract internal machinery is for internal use only + fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } + //~^ ERROR contract internal machinery is for internal use only +} diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr new file mode 100644 index 0000000000000..2acd03b9a358f --- /dev/null +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -0,0 +1,73 @@ +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:19:51 + | +LL | fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } + | ^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contract internal machinery is for internal use only + --> $DIR/internal-feature-gating.rs:21:50 + | +LL | fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } + | ^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:5:5 + | +LL | core::intrinsics::contract_checks(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:7:5 + | +LL | core::intrinsics::contract_check_requires(|| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:9:5 + | +LL | core::intrinsics::contract_check_ensures(&1, |_|true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:13:5 + | +LL | core::contracts::check_requires(|| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts_internals` + --> $DIR/internal-feature-gating.rs:15:5 + | +LL | core::contracts::build_check_ensures(|_: &()| true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-rustc-contracts.rs b/tests/ui/feature-gates/feature-gate-rustc-contracts.rs new file mode 100644 index 0000000000000..d4249c252cda3 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-rustc-contracts.rs @@ -0,0 +1,11 @@ +#![crate_type = "lib"] + +#[core::contracts::requires(x > 0)] +pub fn requires_needs_it(x: i32) { } +//~^^ ERROR use of unstable library feature `rustc_contracts` +//~^^^ ERROR contracts are experimental + +#[core::contracts::ensures(|ret| *ret > 0)] +pub fn ensures_needs_it() -> i32 { 10 } +//~^^ ERROR use of unstable library feature `rustc_contracts` +//~^^^ ERROR contracts are experimental diff --git a/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr b/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr new file mode 100644 index 0000000000000..eb7777a4a5179 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr @@ -0,0 +1,43 @@ +error[E0658]: use of unstable library feature `rustc_contracts` + --> $DIR/feature-gate-rustc-contracts.rs:3:3 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `rustc_contracts` + --> $DIR/feature-gate-rustc-contracts.rs:8:3 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contracts are experimental + --> $DIR/feature-gate-rustc-contracts.rs:3:1 + | +LL | #[core::contracts::requires(x > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: contracts are experimental + --> $DIR/feature-gate-rustc-contracts.rs:8:1 + | +LL | #[core::contracts::ensures(|ret| *ret > 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #133866 for more information + = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0658`. From 804cce47d96d7b30f3798b51a1377c6697011c54 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 15 Jan 2025 13:54:04 -0800 Subject: [PATCH 07/10] Refactor contract builtin macro + error handling Instead of parsing the different components of a function signature, eagerly look for either the `where` keyword or the function body. - Also address feedback to use `From` instead of `TryFrom` in cranelift contract and ubcheck codegen. --- .../rustc_builtin_macros/src/contracts.rs | 142 +++++++++--------- compiler/rustc_codegen_cranelift/src/base.rs | 4 +- .../contract-annotation-limitations.rs | 27 ++++ .../contract-annotation-limitations.stderr | 14 ++ .../contracts/contract-attributes-generics.rs | 70 +++++++++ .../disallow-contract-annotation-on-non-fn.rs | 52 +++++++ ...allow-contract-annotation-on-non-fn.stderr | 44 ++++++ 7 files changed, 280 insertions(+), 73 deletions(-) create mode 100644 tests/ui/contracts/contract-annotation-limitations.rs create mode 100644 tests/ui/contracts/contract-annotation-limitations.stderr create mode 100644 tests/ui/contracts/contract-attributes-generics.rs create mode 100644 tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs create mode 100644 tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr diff --git a/compiler/rustc_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index be7f276cdaa93..fbdd8af99542d 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -35,90 +35,90 @@ impl AttrProcMacro for ExpandEnsures { } } -fn expand_injecting_circa_where_clause( +/// 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 Vec) -> Result<(), ErrorGuaranteed>, + inject: impl FnOnce(&mut TokenStream) -> Result<(), ErrorGuaranteed>, ) -> Result { - let mut new_tts = Vec::with_capacity(annotated.len()); + let mut new_tts = TokenStream::default(); let mut cursor = annotated.iter(); - // Find the `fn name(x:X,...)` and inject the AST contract forms right after - // the formal parameters (and return type if any). - while let Some(tt) = cursor.next() { - new_tts.push(tt.clone()); - if let TokenTree::Token(tok, _) = tt - && tok.is_ident_named(kw::Fn) - { - break; - } + 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 the formal parameters. - // - // FIXME: can this fail if you have parentheticals in a generics list, like `fn foo Y>` ? - while let Some(tt) = cursor.next() { - new_tts.push(tt.clone()); - - if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt { - break; - } - if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt { - panic!("contract attribute applied to fn without parameter list."); + // 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", + )); + } } - } - // There *might* be a return type declaration (and figuring out where that ends would require - // parsing an arbitrary type expression, e.g. `-> Foo` - // - // Instead of trying to figure that out, scan ahead and look for the first occurence of a - // `where`, a `{ ... }`, or a `;`. - // - // FIXME: this might still fall into a trap for something like `-> Ctor`. I - // *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form - // prefixed by e.g. `const`, so we should still be able to filter them out without having to - // parse the type expression itself. But rather than try to fix things with hacks like that, - // time might be better spent extending the attribute expander to suport tt-annotation atop - // ast-annotated, which would be an elegant way to sidestep all of this. - let mut opt_next_tt = cursor.next(); - while let Some(next_tt) = opt_next_tt { - if let TokenTree::Token(tok, _) = next_tt - && tok.is_ident_named(kw::Where) - { - break; - } - if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt { - break; - } - if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt { - break; + if is_kw(tt, kw::Where) { + break tt; } - - // for anything else, transcribe the tt and keep looking. - new_tts.push(next_tt.clone()); - opt_next_tt = cursor.next(); - } + 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. // - // FIXME: this kind of manual token tree munging does not have significant precedent among - // rustc builtin macros, probably because most builtin macros use direct AST manipulation to - // accomplish similar goals. But since our attributes need to take arbitrary expressions, and - // our attribute infrastructure does not yet support mixing a token-tree annotation with an AST - // annotated, we end up doing token tree manipulation. inject(&mut new_tts)?; - // Above we injected the internal AST requires/ensures contruct. Now copy over all the other + // Above we injected the internal AST requires/ensures construct. Now copy over all the other // token trees. - if let Some(tt) = opt_next_tt { - new_tts.push(tt.clone()); - } + new_tts.push_tree(next_tt.clone()); while let Some(tt) = cursor.next() { - new_tts.push(tt.clone()); + 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. @@ -126,7 +126,7 @@ fn expand_injecting_circa_where_clause( // which is gated via `rustc_contracts_internals`. ecx.psess().contract_attribute_spans.push(attr_span); - Ok(TokenStream::new(new_tts)) + Ok(new_tts) } fn expand_requires_tts( @@ -135,16 +135,16 @@ fn expand_requires_tts( annotation: TokenStream, annotated: TokenStream, ) -> Result { - expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { - new_tts.push(TokenTree::Token( + expand_contract_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push_tree(TokenTree::Token( token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)), Spacing::Joint, )); - new_tts.push(TokenTree::Token( + new_tts.push_tree(TokenTree::Token( token::Token::new(token::TokenKind::OrOr, attr_span), Spacing::Alone, )); - new_tts.push(TokenTree::Delimited( + new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), token::Delimiter::Parenthesis, @@ -160,12 +160,12 @@ fn expand_ensures_tts( annotation: TokenStream, annotated: TokenStream, ) -> Result { - expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { - new_tts.push(TokenTree::Token( + expand_contract_clause(_ecx, attr_span, annotated, |new_tts| { + new_tts.push_tree(TokenTree::Token( token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)), Spacing::Joint, )); - new_tts.push(TokenTree::Delimited( + new_tts.push_tree(TokenTree::Delimited( DelimSpan::from_single(attr_span), DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), token::Delimiter::Parenthesis, diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index de2ce1768fa18..a2b9e5712e50b 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -868,7 +868,7 @@ 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); @@ -877,7 +877,7 @@ fn codegen_stmt<'tcx>( NullOp::ContractChecks => { let val = fx.tcx.sess.contract_checks(); let val = CValue::by_val( - fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()), + fx.bcx.ins().iconst(types::I8, i64::from(val)), fx.layout_of(fx.tcx.types.bool), ); lval.write_cvalue(fx, val); diff --git a/tests/ui/contracts/contract-annotation-limitations.rs b/tests/ui/contracts/contract-annotation-limitations.rs new file mode 100644 index 0000000000000..f01d526e3f70e --- /dev/null +++ b/tests/ui/contracts/contract-annotation-limitations.rs @@ -0,0 +1,27 @@ +//! Test for some of the existing limitations and the current error messages. +//! Some of these limitations may be removed in the future. + +#![feature(rustc_contracts)] +#![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..25b01744aac8e --- /dev/null +++ b/tests/ui/contracts/contract-annotation-limitations.stderr @@ -0,0 +1,14 @@ +error: contract annotations is only supported in functions with bodies + --> $DIR/contract-annotation-limitations.rs:17: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:21:5 + | +LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/contracts/contract-attributes-generics.rs b/tests/ui/contracts/contract-attributes-generics.rs new file mode 100644 index 0000000000000..87088ce9de2ce --- /dev/null +++ b/tests/ui/contracts/contract-attributes-generics.rs @@ -0,0 +1,70 @@ +//! 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(rustc_contracts)] + +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/disallow-contract-annotation-on-non-fn.rs b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs new file mode 100644 index 0000000000000..76ed30e856462 --- /dev/null +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs @@ -0,0 +1,52 @@ +//! Checks for compilation errors related to adding contracts to non-function items. + +#![feature(rustc_contracts)] +#![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..4d6d23340ac0e --- /dev/null +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr @@ -0,0 +1,44 @@ +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:6:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:10: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:15: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:19: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:23:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/disallow-contract-annotation-on-non-fn.rs:34: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:45:1 + | +LL | #[core::contracts::requires(true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + From 2bb1464cb6b46d175f92943cb0f9ab534e6cc6eb Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 17 Jan 2025 14:49:10 -0800 Subject: [PATCH 08/10] Improve contracts intrisics and remove wrapper function 1. Document the new intrinsics. 2. Make the intrinsics actually check the contract if enabled, and remove `contract::check_requires` function. 3. Use panic with no unwind in case contract is using to check for safety, we probably don't want to unwind. Following the same reasoning as UB checks. --- .../rustc_hir_analysis/src/check/intrinsic.rs | 10 ++--- library/core/src/contracts.rs | 27 +++----------- library/core/src/intrinsics/mod.rs | 22 +++++++++-- .../internal_machinery/contract-intrinsics.rs | 37 +++++++++++++------ .../internal_machinery/contract-lang-items.rs | 12 +----- .../internal-feature-gating.rs | 3 -- .../internal-feature-gating.stderr | 18 ++------- 7 files changed, 57 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 6c0cebccefd95..e641fb0fb62e6 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -223,17 +223,15 @@ pub fn check_intrinsic_type( }; (n_tps, 0, 0, inputs, output, hir::Safety::Unsafe) } else if intrinsic_name == sym::contract_check_ensures { - // contract_check_ensures::<'a, Ret, C>(&'a Ret, C) -> bool + // 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, returns boolean + // 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)); - // let br = ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BrAnon }; - // let ref_ret = Ty::new_imm_ref(tcx, ty::Region::new_bound(tcx, ty::INNERMOST, br), param(0)); - (2, 1, 0, vec![ref_ret, param(2)], tcx.types.bool, hir::Safety::Safe) + (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 { @@ -628,7 +626,7 @@ pub fn check_intrinsic_type( // contract_checks() -> bool sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool), // contract_check_requires::(C) -> bool, where C: impl Fn() -> bool - sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.bool), + sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit), sym::simd_eq | sym::simd_ne diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs index b155dbc213ee8..0668cacb92c60 100644 --- a/library/core/src/contracts.rs +++ b/library/core/src/contracts.rs @@ -1,38 +1,21 @@ //! Unstable module containing the unstable contracts lang items and attribute macros. +#![cfg(not(bootstrap))] -#[cfg(not(bootstrap))] -pub use crate::macros::builtin::contracts_ensures as ensures; -#[cfg(not(bootstrap))] -pub use crate::macros::builtin::contracts_requires as requires; - -/// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }` -/// into: `fn foo(x: X) { check_requires(|| PRED) ... }` -#[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] -#[lang = "contract_check_requires"] -#[track_caller] -pub fn check_requires bool>(c: C) { - if core::intrinsics::contract_checks() { - assert!(core::intrinsics::contract_check_requires(c), "failed requires check"); - } -} +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). -#[cfg(not(bootstrap))] #[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[lang = "contract_build_check_ensures"] #[track_caller] -pub fn build_check_ensures(c: C) -> impl (FnOnce(Ret) -> Ret) + Copy +pub fn build_check_ensures(cond: C) -> impl (Fn(Ret) -> Ret) + Copy where - C: for<'a> FnOnce(&'a Ret) -> bool + Copy + 'static, + C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static, { #[track_caller] move |ret| { - if core::intrinsics::contract_checks() { - assert!(core::intrinsics::contract_check_ensures(&ret, c), "failed ensures check"); - } + crate::intrinsics::contract_check_ensures(&ret, cond); ret } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index a7f0f09f0c6b3..14f8645d6f1e5 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4062,18 +4062,32 @@ pub const fn contract_checks() -> bool { 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 = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[lang = "contract_check_requires"] #[rustc_intrinsic] -pub fn contract_check_requires bool>(c: C) -> bool { - c() +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 = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] #[rustc_intrinsic] -pub fn contract_check_ensures<'a, Ret, C: FnOnce(&'a Ret) -> bool>(ret: &'a Ret, c: C) -> bool { - c(ret) +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. diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 2e1be01e7cabc..8c70c1a85f6d3 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -1,23 +1,36 @@ -//@ run-pass -//@ revisions: yes no none -//@ [yes] compile-flags: -Zcontract-checks=yes -//@ [no] compile-flags: -Zcontract-checks=no +//@ 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, rustc_contracts_internals, core_intrinsics)] fn main() { - #[cfg(none)] // default: disabled + #[cfg(any(default, unchk_pass))] // default: disabled assert_eq!(core::intrinsics::contract_checks(), false); - #[cfg(yes)] // explicitly enabled + #[cfg(chk_pass)] // explicitly enabled assert_eq!(core::intrinsics::contract_checks(), true); - #[cfg(no)] // explicitly disabled - assert_eq!(core::intrinsics::contract_checks(), false); + // always pass + core::intrinsics::contract_check_requires(|| true); - assert_eq!(core::intrinsics::contract_check_requires(|| true), true); - assert_eq!(core::intrinsics::contract_check_requires(|| false), false); + // 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 }; - assert_eq!(core::intrinsics::contract_check_ensures(&1, doubles_to_two), true); - assert_eq!(core::intrinsics::contract_check_ensures(&2, doubles_to_two), false); + // 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.rs b/tests/ui/contracts/internal_machinery/contract-lang-items.rs index 5af467485b10e..ff569e011f206 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -1,27 +1,21 @@ -//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post +//@ revisions: unchk_pass unchk_fail_post chk_pass chk_fail_post // //@ [unchk_pass] run-pass -//@ [unchk_fail_pre] run-pass //@ [unchk_fail_post] run-pass //@ [chk_pass] run-pass // -//@ [chk_fail_pre] run-fail //@ [chk_fail_post] run-fail // //@ [unchk_pass] compile-flags: -Zcontract-checks=no -//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no //@ [unchk_fail_post] compile-flags: -Zcontract-checks=no // //@ [chk_pass] compile-flags: -Zcontract-checks=yes -//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes #![feature(rustc_contracts)] // to access core::contracts #![feature(rustc_contracts_internals)] // to access check_requires lang item fn foo(x: Baz) -> i32 { - core::contracts::check_requires(|| x.baz > 0); - let injected_checker = { core::contracts::build_check_ensures(|ret| *ret > 100) }; @@ -36,13 +30,9 @@ struct Baz { baz: i32 } const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 }; #[cfg(any(unchk_fail_post, chk_fail_post))] const BAZ_FAIL_POST: Baz = Baz { baz: 10 }; -#[cfg(any(unchk_fail_pre, chk_fail_pre))] -const BAZ_FAIL_PRE: Baz = Baz { baz: -10 }; fn main() { assert_eq!(foo(BAZ_PASS_PRE_POST), 150); - #[cfg(any(unchk_fail_pre, chk_fail_pre))] - foo(BAZ_FAIL_PRE); #[cfg(any(unchk_fail_post, chk_fail_post))] foo(BAZ_FAIL_POST); } diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index 7b5f17679421f..ee9edf3ebb0c2 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -9,9 +9,6 @@ fn main() { core::intrinsics::contract_check_ensures(&1, |_|true); //~^ ERROR use of unstable library feature `rustc_contracts_internals` - // lang items are guarded by rustc_contracts_internals feature gate. - core::contracts::check_requires(|| true); - //~^ ERROR use of unstable library feature `rustc_contracts_internals` core::contracts::build_check_ensures(|_: &()| true); //~^ ERROR use of unstable library feature `rustc_contracts_internals` diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr index 2acd03b9a358f..5f9263e03e85a 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -1,5 +1,5 @@ error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:19:51 + --> $DIR/internal-feature-gating.rs:16:51 | LL | fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } | ^^^^^^^^^ @@ -9,7 +9,7 @@ LL | fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:21:50 + --> $DIR/internal-feature-gating.rs:18:50 | LL | fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } | ^^^^^^^^^^ @@ -49,17 +49,7 @@ LL | core::intrinsics::contract_check_ensures(&1, |_|true); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `rustc_contracts_internals` - --> $DIR/internal-feature-gating.rs:13:5 - | -LL | core::contracts::check_requires(|| true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: use of unstable library feature `rustc_contracts_internals` - --> $DIR/internal-feature-gating.rs:15:5 + --> $DIR/internal-feature-gating.rs:12:5 | LL | core::contracts::build_check_ensures(|_: &()| true); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -68,6 +58,6 @@ LL | core::contracts::build_check_ensures(|_: &()| true); = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 7 previous errors +error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0658`. From 2c4923e6bc9608557f0bc59f975006e590fd337d Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 30 Jan 2025 13:09:14 -0800 Subject: [PATCH 09/10] Update test output to include check_contracts cfg This is now a valid expected value. --- tests/ui/check-cfg/cargo-build-script.stderr | 2 +- tests/ui/check-cfg/cargo-feature.none.stderr | 2 +- tests/ui/check-cfg/cargo-feature.some.stderr | 2 +- tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr | 2 +- tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr | 2 +- tests/ui/check-cfg/exhaustive-names-values.feature.stderr | 2 +- tests/ui/check-cfg/exhaustive-names-values.full.stderr | 2 +- tests/ui/check-cfg/mix.stderr | 2 +- tests/ui/check-cfg/raw-keywords.edition2015.stderr | 2 +- tests/ui/check-cfg/raw-keywords.edition2021.stderr | 2 +- tests/ui/check-cfg/report-in-external-macros.cargo.stderr | 2 +- tests/ui/check-cfg/report-in-external-macros.rustc.stderr | 2 +- tests/ui/check-cfg/well-known-names.stderr | 1 + 13 files changed, 13 insertions(+), 12 deletions(-) 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` From ddbf54b67d9befcf1fb90613d2a6f7f6aa03141e Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 30 Jan 2025 17:06:09 -0800 Subject: [PATCH 10/10] Rename rustc_contract to contract This has now been approved as a language feature and no longer needs a `rustc_` prefix. Also change the `contracts` feature to be marked as incomplete and `contracts_internals` as internal. --- compiler/rustc_ast_passes/src/feature_gate.rs | 4 +- .../rustc_builtin_macros/src/contracts.rs | 6 +-- compiler/rustc_feature/src/unstable.rs | 10 ++--- compiler/rustc_parse/src/parser/generics.rs | 12 ++--- compiler/rustc_parse/src/parser/token_type.rs | 16 +++---- compiler/rustc_span/src/symbol.rs | 8 ++-- library/core/src/contracts.rs | 2 +- library/core/src/intrinsics/mod.rs | 8 ++-- library/core/src/lib.rs | 2 +- library/core/src/macros/mod.rs | 8 ++-- .../contract-annotation-limitations.rs | 3 +- .../contract-annotation-limitations.stderr | 15 +++++-- ...-attributes-generics.chk_const_fail.stderr | 11 +++++ ...t-attributes-generics.chk_fail_post.stderr | 11 +++++ ...ct-attributes-generics.chk_fail_pre.stderr | 11 +++++ ...ntract-attributes-generics.chk_pass.stderr | 11 +++++ .../contracts/contract-attributes-generics.rs | 3 +- ...ract-attributes-generics.unchk_pass.stderr | 11 +++++ ...tract-attributes-nest.chk_fail_post.stderr | 11 +++++ ...ntract-attributes-nest.chk_fail_pre.stderr | 11 +++++ .../contract-attributes-nest.chk_pass.stderr | 11 +++++ .../ui/contracts/contract-attributes-nest.rs | 3 +- ...act-attributes-nest.unchk_fail_post.stderr | 11 +++++ ...ract-attributes-nest.unchk_fail_pre.stderr | 11 +++++ ...contract-attributes-nest.unchk_pass.stderr | 11 +++++ ...tract-attributes-tail.chk_fail_post.stderr | 11 +++++ ...ntract-attributes-tail.chk_fail_pre.stderr | 11 +++++ .../contract-attributes-tail.chk_pass.stderr | 11 +++++ .../ui/contracts/contract-attributes-tail.rs | 3 +- ...act-attributes-tail.unchk_fail_post.stderr | 11 +++++ ...ract-attributes-tail.unchk_fail_pre.stderr | 11 +++++ ...contract-attributes-tail.unchk_pass.stderr | 11 +++++ .../contract-captures-via-closure-copy.rs | 3 +- .../contract-captures-via-closure-copy.stderr | 11 +++++ .../contract-captures-via-closure-noncopy.rs | 3 +- ...ntract-captures-via-closure-noncopy.stderr | 23 +++++++--- ...-ensures-early-fn-exit.chk_fail_ret.stderr | 11 +++++ ...-ensures-early-fn-exit.chk_fail_try.stderr | 11 +++++ ...ensures-early-fn-exit.chk_fail_yeet.stderr | 11 +++++ ...acts-ensures-early-fn-exit.chk_pass.stderr | 11 +++++ .../contracts-ensures-early-fn-exit.rs | 3 +- ...ts-ensures-early-fn-exit.unchk_pass.stderr | 11 +++++ ...s-ensures-is-not-inherited-when-nesting.rs | 3 +- ...sures-is-not-inherited-when-nesting.stderr | 11 +++++ ...-requires-is-not-inherited-when-nesting.rs | 3 +- ...uires-is-not-inherited-when-nesting.stderr | 11 +++++ .../disallow-contract-annotation-on-non-fn.rs | 3 +- ...allow-contract-annotation-on-non-fn.stderr | 25 +++++++---- .../contract-ast-extensions-nest.rs | 6 +-- .../contract-ast-extensions-tail.rs | 6 +-- .../internal_machinery/contract-intrinsics.rs | 2 +- .../contract-lang-items.chk_fail_post.stderr | 11 +++++ .../contract-lang-items.chk_pass.stderr | 11 +++++ .../internal_machinery/contract-lang-items.rs | 5 ++- ...contract-lang-items.unchk_fail_post.stderr | 11 +++++ .../contract-lang-items.unchk_pass.stderr | 11 +++++ ...g-ensures-is-not-inherited-when-nesting.rs | 4 +- ...-requires-is-not-inherited-when-nesting.rs | 4 +- .../internal-feature-gating.rs | 18 ++++---- .../internal-feature-gating.stderr | 44 +++++++++---------- .../feature-gate-cfg-contract-checks.stderr | 2 +- .../feature-gates/feature-gate-contracts.rs | 11 +++++ .../feature-gate-contracts.stderr | 43 ++++++++++++++++++ .../feature-gate-rustc-contracts.rs | 11 ----- .../feature-gate-rustc-contracts.stderr | 43 ------------------ 65 files changed, 522 insertions(+), 165 deletions(-) create mode 100644 tests/ui/contracts/contract-attributes-generics.chk_const_fail.stderr create mode 100644 tests/ui/contracts/contract-attributes-generics.chk_fail_post.stderr create mode 100644 tests/ui/contracts/contract-attributes-generics.chk_fail_pre.stderr create mode 100644 tests/ui/contracts/contract-attributes-generics.chk_pass.stderr create mode 100644 tests/ui/contracts/contract-attributes-generics.unchk_pass.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.chk_fail_post.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.chk_fail_pre.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.chk_pass.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.unchk_fail_post.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.unchk_fail_pre.stderr create mode 100644 tests/ui/contracts/contract-attributes-nest.unchk_pass.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.chk_fail_post.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.chk_fail_pre.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.chk_pass.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.unchk_fail_post.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.unchk_fail_pre.stderr create mode 100644 tests/ui/contracts/contract-attributes-tail.unchk_pass.stderr create mode 100644 tests/ui/contracts/contract-captures-via-closure-copy.stderr create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_ret.stderr create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_try.stderr create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.chk_fail_yeet.stderr create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.chk_pass.stderr create mode 100644 tests/ui/contracts/contracts-ensures-early-fn-exit.unchk_pass.stderr create mode 100644 tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.stderr create mode 100644 tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.stderr create mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.chk_fail_post.stderr create mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.chk_pass.stderr create mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.unchk_fail_post.stderr create mode 100644 tests/ui/contracts/internal_machinery/contract-lang-items.unchk_pass.stderr create mode 100644 tests/ui/feature-gates/feature-gate-contracts.rs create mode 100644 tests/ui/feature-gates/feature-gate-contracts.stderr delete mode 100644 tests/ui/feature-gates/feature-gate-rustc-contracts.rs delete mode 100644 tests/ui/feature-gates/feature-gate-rustc-contracts.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index a3af942a10f17..62e451fa8764f 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -548,8 +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!(rustc_contracts, "contracts are experimental"); - gate_all!(rustc_contracts_internals, "contract internal machinery is for internal use only"); + 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_builtin_macros/src/contracts.rs b/compiler/rustc_builtin_macros/src/contracts.rs index fbdd8af99542d..85a30f7bdc9b4 100644 --- a/compiler/rustc_builtin_macros/src/contracts.rs +++ b/compiler/rustc_builtin_macros/src/contracts.rs @@ -123,7 +123,7 @@ fn expand_contract_clause( // Record the span as a contract attribute expansion. // This is used later to stop users from using the extended syntax directly - // which is gated via `rustc_contracts_internals`. + // which is gated via `contracts_internals`. ecx.psess().contract_attribute_spans.push(attr_span); Ok(new_tts) @@ -137,7 +137,7 @@ fn expand_requires_tts( ) -> Result { expand_contract_clause(_ecx, attr_span, annotated, |new_tts| { new_tts.push_tree(TokenTree::Token( - token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)), + token::Token::from_ast_ident(Ident::new(kw::ContractRequires, attr_span)), Spacing::Joint, )); new_tts.push_tree(TokenTree::Token( @@ -162,7 +162,7 @@ fn expand_ensures_tts( ) -> Result { expand_contract_clause(_ecx, attr_span, annotated, |new_tts| { new_tts.push_tree(TokenTree::Token( - token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)), + token::Token::from_ast_ident(Ident::new(kw::ContractEnsures, attr_span)), Spacing::Joint, )); new_tts.push_tree(TokenTree::Delimited( diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 57bcd8c5eca22..5699d4ce3b9b6 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -404,7 +404,7 @@ declare_features! ( /// Allows the use of `#[cfg()]`. (unstable, cfg_boolean_literals, "1.83.0", Some(131204)), /// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled. - (unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(133866)), + (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 @@ -447,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. @@ -608,10 +612,6 @@ declare_features! ( (unstable, return_type_notation, "1.70.0", Some(109417)), /// Allows `extern "rust-cold"`. (unstable, rust_cold_cc, "1.63.0", Some(97544)), - /// Allows use of contracts attributes. - (unstable, rustc_contracts, "CURRENT_RUSTC_VERSION", Some(133866)), - /// Allows access to internal machinery used to implement contracts. - (unstable, rustc_contracts_internals, "CURRENT_RUSTC_VERSION", Some(133866)), /// Allows use of x86 SHA512, SM3 and SM4 target-features and intrinsics (unstable, sha512_sm_x86, "1.82.0", Some(126624)), /// Allows the use of SIMD types in functions declared in `extern` blocks. diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 14b949dbc3d57..86816819be275 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -297,29 +297,29 @@ impl<'a> Parser<'a> { }) } - /// Parses a rustc-internal fn contract - /// (`rustc_contract_requires(WWW) rustc_contract_ensures(ZZZ)`) + /// 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::rustc_contracts, span); + 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::rustc_contracts_internals, span); + self.psess.gated_spans.gate(sym::contracts_internals, span); } }; - let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) { + 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!(RustcContractEnsures).kw) { + let ensures = if self.eat_keyword_noexpect(exp!(ContractEnsures).kw) { let postcond = self.parse_expr()?; gate(postcond.span); Some(postcond) diff --git a/compiler/rustc_parse/src/parser/token_type.rs b/compiler/rustc_parse/src/parser/token_type.rs index 50f03e72f82dc..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, @@ -108,8 +110,6 @@ pub enum TokenType { KwRef, KwReturn, KwReuse, - KwRustcContractEnsures, - KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -219,6 +219,8 @@ impl TokenType { KwCatch, KwConst, KwContinue, + KwContractEnsures, + KwContractRequires, KwCrate, KwDefault, KwDyn, @@ -244,8 +246,6 @@ impl TokenType { KwRef, KwReturn, KwReuse, - KwRustcContractEnsures, - KwRustcContractRequires, KwSafe, KwSelfUpper, KwStatic, @@ -293,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), @@ -318,8 +320,6 @@ impl TokenType { TokenType::KwRef => Some(kw::Ref), TokenType::KwReturn => Some(kw::Return), TokenType::KwReuse => Some(kw::Reuse), - TokenType::KwRustcContractEnsures => Some(kw::RustcContractEnsures), - TokenType::KwRustcContractRequires => Some(kw::RustcContractRequires), TokenType::KwSafe => Some(kw::Safe), TokenType::KwSelfUpper => Some(kw::SelfUpper), TokenType::KwStatic => Some(kw::Static), @@ -525,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) }; @@ -550,8 +552,6 @@ macro_rules! exp { (Ref) => { exp!(@kw, Ref, KwRef) }; (Return) => { exp!(@kw, Return, KwReturn) }; (Reuse) => { exp!(@kw, Reuse, KwReuse) }; - (RustcContractEnsures) => { exp!(@kw, RustcContractEnsures, KwRustcContractEnsures) }; - (RustcContractRequires) => { exp!(@kw, RustcContractRequires, KwRustcContractRequires) }; (Safe) => { exp!(@kw, Safe, KwSafe) }; (SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) }; (Static) => { exp!(@kw, Static, KwStatic) }; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ea2ce5475c264..529dfc6ff7a03 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -118,8 +118,8 @@ symbols! { MacroRules: "macro_rules", Raw: "raw", Reuse: "reuse", - RustcContractEnsures: "rustc_contract_ensures", - RustcContractRequires: "rustc_contract_requires", + ContractEnsures: "contract_ensures", + ContractRequires: "contract_requires", Safe: "safe", Union: "union", Yeet: "yeet", @@ -682,7 +682,9 @@ symbols! { contract_check_ensures, contract_check_requires, contract_checks, + contracts, contracts_ensures, + contracts_internals, contracts_requires, convert_identity, copy, @@ -1716,8 +1718,6 @@ symbols! { rustc_const_stable, rustc_const_stable_indirect, rustc_const_unstable, - rustc_contracts, - rustc_contracts_internals, rustc_conversion_suggestion, rustc_deallocator, rustc_def_path, diff --git a/library/core/src/contracts.rs b/library/core/src/contracts.rs index 0668cacb92c60..c769e219e4d49 100644 --- a/library/core/src/contracts.rs +++ b/library/core/src/contracts.rs @@ -6,7 +6,7 @@ pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_require /// 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 = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[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 diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 14f8645d6f1e5..1e4dc12f9b617 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -4051,8 +4051,8 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) /// checking is turned on, so that we can specify contracts in libstd /// and let an end user opt into turning them on. #[cfg(not(bootstrap))] -#[rustc_const_unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] -#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[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 { @@ -4067,7 +4067,7 @@ pub const fn contract_checks() -> bool { /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition /// returns false. #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)] #[lang = "contract_check_requires"] #[rustc_intrinsic] pub fn contract_check_requires bool>(cond: C) { @@ -4082,7 +4082,7 @@ pub fn contract_check_requires bool>(cond: C) { /// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition /// returns false. #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts_internals", issue = "133866" /* compiler-team#759 */)] +#[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) { diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 6a0051244f062..de8e85f7b36ed 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -248,7 +248,7 @@ pub mod autodiff { } #[cfg(not(bootstrap))] -#[unstable(feature = "rustc_contracts", issue = "133866")] +#[unstable(feature = "contracts", issue = "128044")] pub mod contracts; #[unstable(feature = "cfg_match", issue = "115585")] diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index cb37530e90db8..4c6fd196bd31c 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1783,8 +1783,8 @@ pub(crate) mod builtin { /// eventually parsed as a unary closure expression that is /// invoked on a reference to the return value. #[cfg(not(bootstrap))] - #[unstable(feature = "rustc_contracts", issue = "133866")] - #[allow_internal_unstable(rustc_contracts_internals)] + #[unstable(feature = "contracts", issue = "128044")] + #[allow_internal_unstable(contracts_internals)] #[rustc_builtin_macro] pub macro contracts_ensures($item:item) { /* compiler built-in */ @@ -1796,8 +1796,8 @@ pub(crate) mod builtin { /// eventually parsed as an boolean expression with access to the /// function's formal parameters #[cfg(not(bootstrap))] - #[unstable(feature = "rustc_contracts", issue = "133866")] - #[allow_internal_unstable(rustc_contracts_internals)] + #[unstable(feature = "contracts", issue = "128044")] + #[allow_internal_unstable(contracts_internals)] #[rustc_builtin_macro] pub macro contracts_requires($item:item) { /* compiler built-in */ diff --git a/tests/ui/contracts/contract-annotation-limitations.rs b/tests/ui/contracts/contract-annotation-limitations.rs index f01d526e3f70e..10b3bacab5cfa 100644 --- a/tests/ui/contracts/contract-annotation-limitations.rs +++ b/tests/ui/contracts/contract-annotation-limitations.rs @@ -1,7 +1,8 @@ //! Test for some of the existing limitations and the current error messages. //! Some of these limitations may be removed in the future. -#![feature(rustc_contracts)] +#![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. diff --git a/tests/ui/contracts/contract-annotation-limitations.stderr b/tests/ui/contracts/contract-annotation-limitations.stderr index 25b01744aac8e..14338cf4b8687 100644 --- a/tests/ui/contracts/contract-annotation-limitations.stderr +++ b/tests/ui/contracts/contract-annotation-limitations.stderr @@ -1,14 +1,23 @@ error: contract annotations is only supported in functions with bodies - --> $DIR/contract-annotation-limitations.rs:17:5 + --> $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:21:5 + --> $DIR/contract-annotation-limitations.rs:22:5 | LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +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 index 87088ce9de2ce..fd79c6abedd85 100644 --- a/tests/ui/contracts/contract-attributes-generics.rs +++ b/tests/ui/contracts/contract-attributes-generics.rs @@ -16,7 +16,8 @@ //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes //@ [chk_const_fail] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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; 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 index 1cda21f10d7ec..e1e61b88f282e 100644 --- a/tests/ui/contracts/contract-attributes-nest.rs +++ b/tests/ui/contracts/contract-attributes-nest.rs @@ -16,7 +16,8 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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)] 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 index 26855bfa82ac4..ce4a6be5b82f7 100644 --- a/tests/ui/contracts/contract-attributes-tail.rs +++ b/tests/ui/contracts/contract-attributes-tail.rs @@ -16,7 +16,8 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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)] 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 index 742895ab0ad80..32c6d2bf4fe17 100644 --- a/tests/ui/contracts/contract-captures-via-closure-copy.rs +++ b/tests/ui/contracts/contract-captures-via-closure-copy.rs @@ -1,7 +1,8 @@ //@ run-fail //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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 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 index 8d7f2fd200e2c..976f64c7fd911 100644 --- a/tests/ui/contracts/contract-captures-via-closure-noncopy.rs +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.rs @@ -1,6 +1,7 @@ //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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 diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr index b53809827f987..4a47671fee191 100644 --- a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -1,16 +1,25 @@ -error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:11:42: 11:57}` - --> $DIR/contract-captures-via-closure-noncopy.rs:11:1 +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:11:42: 11:57}` - | | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:11:42}` + | | 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:11:42: 11:57}`, the trait `std::marker::Copy` is not implemented for `Baz` + = 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:11:42 + --> $DIR/contract-captures-via-closure-noncopy.rs:12:42 | LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] | ^^^^^^^^^^^^^^^ @@ -22,6 +31,6 @@ LL + #[derive(Copy)] LL | struct Baz { | -error: aborting due to 1 previous error +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 index faf97473a90f2..034cead3b4e9f 100644 --- a/tests/ui/contracts/contracts-ensures-early-fn-exit.rs +++ b/tests/ui/contracts/contracts-ensures-early-fn-exit.rs @@ -13,7 +13,8 @@ //@ [chk_fail_yeet] compile-flags: -Zcontract-checks=yes //! This test ensures that ensures clauses are checked for different return points of a function. -#![feature(rustc_contracts)] +#![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. 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 index 9872fdb1a1852..f01a852fbff34 100644 --- a/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs +++ b/tests/ui/contracts/contracts-ensures-is-not-inherited-when-nesting.rs @@ -1,6 +1,7 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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 { 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 index 75124259b0d72..2c2a4a6978550 100644 --- a/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs +++ b/tests/ui/contracts/contracts-requires-is-not-inherited-when-nesting.rs @@ -1,6 +1,7 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] +#![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 } 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 index 76ed30e856462..69be906782a67 100644 --- a/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs @@ -1,6 +1,7 @@ //! Checks for compilation errors related to adding contracts to non-function items. -#![feature(rustc_contracts)] +#![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)] diff --git a/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr index 4d6d23340ac0e..0a7fff8183e09 100644 --- a/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr +++ b/tests/ui/contracts/disallow-contract-annotation-on-non-fn.stderr @@ -1,44 +1,53 @@ error: contract annotations can only be used on functions - --> $DIR/disallow-contract-annotation-on-non-fn.rs:6:1 + --> $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:10:1 + --> $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:15:1 + --> $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:19:1 + --> $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:23:1 + --> $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:34:1 + --> $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:45:1 + --> $DIR/disallow-contract-annotation-on-non-fn.rs:46:1 | LL | #[core::contracts::requires(true)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +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 index d95ccd4f7b9b6..6d8cd3949eedb 100644 --- a/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs @@ -16,11 +16,11 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts_internals)] +#![feature(contracts_internals)] fn nest(x: Baz) -> i32 - rustc_contract_requires(|| x.baz > 0) - rustc_contract_ensures(|ret| *ret > 100) + contract_requires(|| x.baz > 0) + contract_ensures(|ret| *ret > 100) { loop { return x.baz + 50; diff --git a/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs index 636a595e06adb..07ec26f921b80 100644 --- a/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs +++ b/tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs @@ -16,11 +16,11 @@ //@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts_internals)] +#![feature(contracts_internals)] fn tail(x: Baz) -> i32 - rustc_contract_requires(|| x.baz > 0) - rustc_contract_ensures(|ret| *ret > 100) + contract_requires(|| x.baz > 0) + contract_ensures(|ret| *ret > 100) { x.baz + 50 } diff --git a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs index 8c70c1a85f6d3..ae692afd146fe 100644 --- a/tests/ui/contracts/internal_machinery/contract-intrinsics.rs +++ b/tests/ui/contracts/internal_machinery/contract-intrinsics.rs @@ -10,7 +10,7 @@ //@ [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, rustc_contracts_internals, core_intrinsics)] +#![feature(cfg_contract_checks, contracts_internals, core_intrinsics)] fn main() { #[cfg(any(default, unchk_pass))] // default: disabled 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 index ff569e011f206..e91bbed294d12 100644 --- a/tests/ui/contracts/internal_machinery/contract-lang-items.rs +++ b/tests/ui/contracts/internal_machinery/contract-lang-items.rs @@ -12,8 +12,9 @@ //@ [chk_pass] compile-flags: -Zcontract-checks=yes //@ [chk_fail_post] compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts)] // to access core::contracts -#![feature(rustc_contracts_internals)] // to access check_requires lang item +#![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 = { 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 index 0b0151c6df742..960ccaed3588a 100644 --- 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 @@ -1,9 +1,9 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts_internals)] +#![feature(contracts_internals)] fn outer() -> i32 - rustc_contract_ensures(|ret| *ret > 0) + contract_ensures(|ret| *ret > 0) { let inner_closure = || -> i32 { 0 }; inner_closure(); 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 index 79c50a18f7056..bee703de16a0b 100644 --- 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 @@ -1,11 +1,11 @@ //@ run-pass //@ compile-flags: -Zcontract-checks=yes -#![feature(rustc_contracts_internals)] +#![feature(contracts_internals)] struct Outer { outer: std::cell::Cell } fn outer(x: Outer) - rustc_contract_requires(|| x.outer.get() > 0) + contract_requires(|| x.outer.get() > 0) { let inner_closure = || { }; x.outer.set(0); diff --git a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs index ee9edf3ebb0c2..1b76eef6780fe 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.rs +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.rs @@ -1,20 +1,20 @@ -// gate-test-rustc_contracts_internals +// gate-test-contracts_internals fn main() { - // intrinsics are guarded by rustc_contracts_internals feature gate. + // intrinsics are guarded by contracts_internals feature gate. core::intrinsics::contract_checks(); - //~^ ERROR use of unstable library feature `rustc_contracts_internals` + //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_requires(|| true); - //~^ ERROR use of unstable library feature `rustc_contracts_internals` + //~^ ERROR use of unstable library feature `contracts_internals` core::intrinsics::contract_check_ensures(&1, |_|true); - //~^ ERROR use of unstable library feature `rustc_contracts_internals` + //~^ ERROR use of unstable library feature `contracts_internals` core::contracts::build_check_ensures(|_: &()| true); - //~^ ERROR use of unstable library feature `rustc_contracts_internals` + //~^ ERROR use of unstable library feature `contracts_internals` - // ast extensions are guarded by rustc_contracts_internals feature gate - fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } + // 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 rustc_contract_ensures(|_| true) { 10 } + 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 index 5f9263e03e85a..c0e1522f54c61 100644 --- a/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr +++ b/tests/ui/contracts/internal_machinery/internal-feature-gating.stderr @@ -1,61 +1,61 @@ error[E0658]: contract internal machinery is for internal use only - --> $DIR/internal-feature-gating.rs:16:51 + --> $DIR/internal-feature-gating.rs:16:45 | -LL | fn identity_1() -> i32 rustc_contract_requires(|| true) { 10 } - | ^^^^^^^^^ +LL | fn identity_1() -> i32 contract_requires(|| true) { 10 } + | ^^^^^^^^^ | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = 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:50 + --> $DIR/internal-feature-gating.rs:18:44 | -LL | fn identity_2() -> i32 rustc_contract_ensures(|_| true) { 10 } - | ^^^^^^^^^^ +LL | fn identity_2() -> i32 contract_ensures(|_| true) { 10 } + | ^^^^^^^^^^ | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = 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 `rustc_contracts_internals` +error[E0658]: use of unstable library feature `contracts_internals` --> $DIR/internal-feature-gating.rs:5:5 | LL | core::intrinsics::contract_checks(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = note: 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 `rustc_contracts_internals` +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 #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = 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 `rustc_contracts_internals` +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 #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = 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 `rustc_contracts_internals` +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 #133866 for more information - = help: add `#![feature(rustc_contracts_internals)]` to the crate attributes to enable + = 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 diff --git a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr index af4e605e57098..89c6d077f97eb 100644 --- a/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr +++ b/tests/ui/feature-gates/feature-gate-cfg-contract-checks.stderr @@ -4,7 +4,7 @@ error[E0658]: `cfg(contract_checks)` is experimental and subject to change LL | cfg!(contract_checks) | ^^^^^^^^^^^^^^^ | - = note: see issue #133866 for more information + = 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 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/feature-gates/feature-gate-rustc-contracts.rs b/tests/ui/feature-gates/feature-gate-rustc-contracts.rs deleted file mode 100644 index d4249c252cda3..0000000000000 --- a/tests/ui/feature-gates/feature-gate-rustc-contracts.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![crate_type = "lib"] - -#[core::contracts::requires(x > 0)] -pub fn requires_needs_it(x: i32) { } -//~^^ ERROR use of unstable library feature `rustc_contracts` -//~^^^ ERROR contracts are experimental - -#[core::contracts::ensures(|ret| *ret > 0)] -pub fn ensures_needs_it() -> i32 { 10 } -//~^^ ERROR use of unstable library feature `rustc_contracts` -//~^^^ ERROR contracts are experimental diff --git a/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr b/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr deleted file mode 100644 index eb7777a4a5179..0000000000000 --- a/tests/ui/feature-gates/feature-gate-rustc-contracts.stderr +++ /dev/null @@ -1,43 +0,0 @@ -error[E0658]: use of unstable library feature `rustc_contracts` - --> $DIR/feature-gate-rustc-contracts.rs:3:3 - | -LL | #[core::contracts::requires(x > 0)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: use of unstable library feature `rustc_contracts` - --> $DIR/feature-gate-rustc-contracts.rs:8:3 - | -LL | #[core::contracts::ensures(|ret| *ret > 0)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: contracts are experimental - --> $DIR/feature-gate-rustc-contracts.rs:3:1 - | -LL | #[core::contracts::requires(x > 0)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: contracts are experimental - --> $DIR/feature-gate-rustc-contracts.rs:8:1 - | -LL | #[core::contracts::ensures(|ret| *ret > 0)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #133866 for more information - = help: add `#![feature(rustc_contracts)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 4 previous errors - -For more information about this error, try `rustc --explain E0658`.